All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/20] drm: sun4i: Add support for the HDMI controller
@ 2017-05-03 11:59 ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

Hi,

Here is an attempt at getting the HDMI controller running.

This HDMI controller is found on a number of old Allwinner SoCs (A10, A10s,
A20, A31).

This driver only supports for now the A10s because it was an easy target,
being very close to the A13 that is already supported by our DRM driver.

There's nothing out of the extraordinary there, except maybe the clock
setup. All the internal clocks (TMDS, DDC) have been modeled using the
common clock framework, the TMDS clock being the parent of the DDC one.

While this might sound overkill, other SoC have a different, external
source for the DDC clock, which will be easier to support through the clock
framework.

The IP also supports audio (through an already supported i2s controller,
and some missing configuration in the HDMI controller) and CEC. Both will
come eventually.

Let me know what you think!
Maxime

Changes from v1:
  - Fixed typos in the CCU header and the HDMI code
  - Reintroduced the comment for the backporch timings
  - Renamed the hdmi node to hdmi, instead of hdmi0
  - Added support for hdmi-connector
  - Added a separate Kconfig option for the HDMI support
  - Changed the TCON muxing configuration for an explicit call in the
    TCON's "clients"
  - Fixed the initialisation sequence that was clearing the clocks bits
  - Constified the HDMI's structures and removed whitespaces errors
  - Fixed an issue in the sunxi-ng code that was not reporting the proper
    parent clock rate if it was modified
  - Removed unused headers
  - Removed CLK_SET_RATE_PARENT for the DDC clock
  - Used the DDC address defines
  - Removed the interlace flag that wasn't supported at the moment
  - Moved most of the HDMI encoder init to the bind function like we do for
    the other encoders
  - Switched to drm_of_find_possible_crtcs
  - Removed the extra printk that were still in my code
  - Rebased on top of linux-next
  - Removed the patch changing the divider_round_rate prototype to
    introduce a new function instead that takes the parent clock to
    evaluate
  - Added a clk_set_rate for the hdmi module clock
  - Fixed the V_TOTAL TCON ch0 calculation to be consistent with ch1's
  - Defined all registers, and remove the TODOs
  - Fixed the EDID issues by increasing the timeout.
  - Added an atomic_check to prevent the DBLCLK modes to be used, as it is
    not supported yet
  - Updated the binding to add the interrupts and DMA channels

Maxime Ripard (20):
  clk: divider: Make divider_round_rate take the parent clock
  clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
  clk: sunxi-ng: div: Switch to divider_round_rate
  clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
  clk: sunxi-ng: mux: split out the pre-divider computation code
  clk: sunxi-ng: mux: Change pre-divider application function prototype
  clk: sunxi-ng: mux: Re-adjust parent rate
  clk: sunxi-ng: sun5i: Export video PLLs
  drm/sun4i: tcon: Add channel debug
  drm/sun4i: tcon: Move the muxing out of the mode set function
  drm/sun4i: tcon: Switch mux on only for composite
  drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
  drm/sun4i: tcon: Change vertical total size computation inconsistency
  drm/sun4i: tcon: multiply the vtotal when not in interlace
  drm/sun4i: Ignore the generic connectors for components
  dt-bindings: display: sun4i: Add HDMI display bindings
  dt-bindings: display: sun4i: Add allwinner,tcon-channel property
  drm/sun4i: Add HDMI support
  ARM: sun5i: a10s: Add the HDMI controller node
  ARM: sun5i: a10s-olinuxino: Enable HDMI

 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt |  90 +-
 arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts              |  29 +-
 arch/arm/boot/dts/sun5i-a10s.dtsi                             |  50 +-
 arch/arm/boot/dts/sun5i.dtsi                                  |   1 +-
 drivers/clk/clk-divider.c                                     |  26 +-
 drivers/clk/sunxi-ng/ccu-sun5i.h                              |   6 +-
 drivers/clk/sunxi-ng/ccu_div.c                                |  38 +-
 drivers/clk/sunxi-ng/ccu_mp.c                                 |  15 +-
 drivers/clk/sunxi-ng/ccu_mult.c                               |  19 +-
 drivers/clk/sunxi-ng/ccu_mux.c                                |  90 +-
 drivers/clk/sunxi-ng/ccu_mux.h                                |  11 +-
 drivers/clk/sunxi-ng/ccu_nkm.c                                |   7 +-
 drivers/gpu/drm/sun4i/Kconfig                                 |   9 +-
 drivers/gpu/drm/sun4i/Makefile                                |   6 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c                             |   8 +-
 drivers/gpu/drm/sun4i/sun4i_hdmi.h                            | 157 ++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c                    | 127 ++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c                        | 493 +++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c                   | 225 +++-
 drivers/gpu/drm/sun4i/sun4i_rgb.c                             |   1 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.c                            |  43 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.h                            |   4 +-
 drivers/gpu/drm/sun4i/sun4i_tv.c                              |   1 +-
 include/dt-bindings/clock/sun5i-ccu.h                         |   3 +-
 include/linux/clk-provider.h                                  |   4 +-
 25 files changed, 1347 insertions(+), 116 deletions(-)
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c

base-commit: ee91aaf669ac4fbb09283958f69d57dcad5c4963
-- 
git-series 0.8.11

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

* [PATCH v2 0/20] drm: sun4i: Add support for the HDMI controller
@ 2017-05-03 11:59 ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

Hi,

Here is an attempt at getting the HDMI controller running.

This HDMI controller is found on a number of old Allwinner SoCs (A10, A10s,
A20, A31).

This driver only supports for now the A10s because it was an easy target,
being very close to the A13 that is already supported by our DRM driver.

There's nothing out of the extraordinary there, except maybe the clock
setup. All the internal clocks (TMDS, DDC) have been modeled using the
common clock framework, the TMDS clock being the parent of the DDC one.

While this might sound overkill, other SoC have a different, external
source for the DDC clock, which will be easier to support through the clock
framework.

The IP also supports audio (through an already supported i2s controller,
and some missing configuration in the HDMI controller) and CEC. Both will
come eventually.

Let me know what you think!
Maxime

Changes from v1:
  - Fixed typos in the CCU header and the HDMI code
  - Reintroduced the comment for the backporch timings
  - Renamed the hdmi node to hdmi, instead of hdmi0
  - Added support for hdmi-connector
  - Added a separate Kconfig option for the HDMI support
  - Changed the TCON muxing configuration for an explicit call in the
    TCON's "clients"
  - Fixed the initialisation sequence that was clearing the clocks bits
  - Constified the HDMI's structures and removed whitespaces errors
  - Fixed an issue in the sunxi-ng code that was not reporting the proper
    parent clock rate if it was modified
  - Removed unused headers
  - Removed CLK_SET_RATE_PARENT for the DDC clock
  - Used the DDC address defines
  - Removed the interlace flag that wasn't supported at the moment
  - Moved most of the HDMI encoder init to the bind function like we do for
    the other encoders
  - Switched to drm_of_find_possible_crtcs
  - Removed the extra printk that were still in my code
  - Rebased on top of linux-next
  - Removed the patch changing the divider_round_rate prototype to
    introduce a new function instead that takes the parent clock to
    evaluate
  - Added a clk_set_rate for the hdmi module clock
  - Fixed the V_TOTAL TCON ch0 calculation to be consistent with ch1's
  - Defined all registers, and remove the TODOs
  - Fixed the EDID issues by increasing the timeout.
  - Added an atomic_check to prevent the DBLCLK modes to be used, as it is
    not supported yet
  - Updated the binding to add the interrupts and DMA channels

Maxime Ripard (20):
  clk: divider: Make divider_round_rate take the parent clock
  clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
  clk: sunxi-ng: div: Switch to divider_round_rate
  clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
  clk: sunxi-ng: mux: split out the pre-divider computation code
  clk: sunxi-ng: mux: Change pre-divider application function prototype
  clk: sunxi-ng: mux: Re-adjust parent rate
  clk: sunxi-ng: sun5i: Export video PLLs
  drm/sun4i: tcon: Add channel debug
  drm/sun4i: tcon: Move the muxing out of the mode set function
  drm/sun4i: tcon: Switch mux on only for composite
  drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
  drm/sun4i: tcon: Change vertical total size computation inconsistency
  drm/sun4i: tcon: multiply the vtotal when not in interlace
  drm/sun4i: Ignore the generic connectors for components
  dt-bindings: display: sun4i: Add HDMI display bindings
  dt-bindings: display: sun4i: Add allwinner,tcon-channel property
  drm/sun4i: Add HDMI support
  ARM: sun5i: a10s: Add the HDMI controller node
  ARM: sun5i: a10s-olinuxino: Enable HDMI

 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt |  90 +-
 arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts              |  29 +-
 arch/arm/boot/dts/sun5i-a10s.dtsi                             |  50 +-
 arch/arm/boot/dts/sun5i.dtsi                                  |   1 +-
 drivers/clk/clk-divider.c                                     |  26 +-
 drivers/clk/sunxi-ng/ccu-sun5i.h                              |   6 +-
 drivers/clk/sunxi-ng/ccu_div.c                                |  38 +-
 drivers/clk/sunxi-ng/ccu_mp.c                                 |  15 +-
 drivers/clk/sunxi-ng/ccu_mult.c                               |  19 +-
 drivers/clk/sunxi-ng/ccu_mux.c                                |  90 +-
 drivers/clk/sunxi-ng/ccu_mux.h                                |  11 +-
 drivers/clk/sunxi-ng/ccu_nkm.c                                |   7 +-
 drivers/gpu/drm/sun4i/Kconfig                                 |   9 +-
 drivers/gpu/drm/sun4i/Makefile                                |   6 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c                             |   8 +-
 drivers/gpu/drm/sun4i/sun4i_hdmi.h                            | 157 ++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c                    | 127 ++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c                        | 493 +++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c                   | 225 +++-
 drivers/gpu/drm/sun4i/sun4i_rgb.c                             |   1 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.c                            |  43 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.h                            |   4 +-
 drivers/gpu/drm/sun4i/sun4i_tv.c                              |   1 +-
 include/dt-bindings/clock/sun5i-ccu.h                         |   3 +-
 include/linux/clk-provider.h                                  |   4 +-
 25 files changed, 1347 insertions(+), 116 deletions(-)
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c

base-commit: ee91aaf669ac4fbb09283958f69d57dcad5c4963
-- 
git-series 0.8.11

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

* [PATCH v2 0/20] drm: sun4i: Add support for the HDMI controller
@ 2017-05-03 11:59 ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

Here is an attempt at getting the HDMI controller running.

This HDMI controller is found on a number of old Allwinner SoCs (A10, A10s,
A20, A31).

This driver only supports for now the A10s because it was an easy target,
being very close to the A13 that is already supported by our DRM driver.

There's nothing out of the extraordinary there, except maybe the clock
setup. All the internal clocks (TMDS, DDC) have been modeled using the
common clock framework, the TMDS clock being the parent of the DDC one.

While this might sound overkill, other SoC have a different, external
source for the DDC clock, which will be easier to support through the clock
framework.

The IP also supports audio (through an already supported i2s controller,
and some missing configuration in the HDMI controller) and CEC. Both will
come eventually.

Let me know what you think!
Maxime

Changes from v1:
  - Fixed typos in the CCU header and the HDMI code
  - Reintroduced the comment for the backporch timings
  - Renamed the hdmi node to hdmi, instead of hdmi0
  - Added support for hdmi-connector
  - Added a separate Kconfig option for the HDMI support
  - Changed the TCON muxing configuration for an explicit call in the
    TCON's "clients"
  - Fixed the initialisation sequence that was clearing the clocks bits
  - Constified the HDMI's structures and removed whitespaces errors
  - Fixed an issue in the sunxi-ng code that was not reporting the proper
    parent clock rate if it was modified
  - Removed unused headers
  - Removed CLK_SET_RATE_PARENT for the DDC clock
  - Used the DDC address defines
  - Removed the interlace flag that wasn't supported at the moment
  - Moved most of the HDMI encoder init to the bind function like we do for
    the other encoders
  - Switched to drm_of_find_possible_crtcs
  - Removed the extra printk that were still in my code
  - Rebased on top of linux-next
  - Removed the patch changing the divider_round_rate prototype to
    introduce a new function instead that takes the parent clock to
    evaluate
  - Added a clk_set_rate for the hdmi module clock
  - Fixed the V_TOTAL TCON ch0 calculation to be consistent with ch1's
  - Defined all registers, and remove the TODOs
  - Fixed the EDID issues by increasing the timeout.
  - Added an atomic_check to prevent the DBLCLK modes to be used, as it is
    not supported yet
  - Updated the binding to add the interrupts and DMA channels

Maxime Ripard (20):
  clk: divider: Make divider_round_rate take the parent clock
  clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
  clk: sunxi-ng: div: Switch to divider_round_rate
  clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
  clk: sunxi-ng: mux: split out the pre-divider computation code
  clk: sunxi-ng: mux: Change pre-divider application function prototype
  clk: sunxi-ng: mux: Re-adjust parent rate
  clk: sunxi-ng: sun5i: Export video PLLs
  drm/sun4i: tcon: Add channel debug
  drm/sun4i: tcon: Move the muxing out of the mode set function
  drm/sun4i: tcon: Switch mux on only for composite
  drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
  drm/sun4i: tcon: Change vertical total size computation inconsistency
  drm/sun4i: tcon: multiply the vtotal when not in interlace
  drm/sun4i: Ignore the generic connectors for components
  dt-bindings: display: sun4i: Add HDMI display bindings
  dt-bindings: display: sun4i: Add allwinner,tcon-channel property
  drm/sun4i: Add HDMI support
  ARM: sun5i: a10s: Add the HDMI controller node
  ARM: sun5i: a10s-olinuxino: Enable HDMI

 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt |  90 +-
 arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts              |  29 +-
 arch/arm/boot/dts/sun5i-a10s.dtsi                             |  50 +-
 arch/arm/boot/dts/sun5i.dtsi                                  |   1 +-
 drivers/clk/clk-divider.c                                     |  26 +-
 drivers/clk/sunxi-ng/ccu-sun5i.h                              |   6 +-
 drivers/clk/sunxi-ng/ccu_div.c                                |  38 +-
 drivers/clk/sunxi-ng/ccu_mp.c                                 |  15 +-
 drivers/clk/sunxi-ng/ccu_mult.c                               |  19 +-
 drivers/clk/sunxi-ng/ccu_mux.c                                |  90 +-
 drivers/clk/sunxi-ng/ccu_mux.h                                |  11 +-
 drivers/clk/sunxi-ng/ccu_nkm.c                                |   7 +-
 drivers/gpu/drm/sun4i/Kconfig                                 |   9 +-
 drivers/gpu/drm/sun4i/Makefile                                |   6 +-
 drivers/gpu/drm/sun4i/sun4i_drv.c                             |   8 +-
 drivers/gpu/drm/sun4i/sun4i_hdmi.h                            | 157 ++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c                    | 127 ++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c                        | 493 +++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c                   | 225 +++-
 drivers/gpu/drm/sun4i/sun4i_rgb.c                             |   1 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.c                            |  43 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.h                            |   4 +-
 drivers/gpu/drm/sun4i/sun4i_tv.c                              |   1 +-
 include/dt-bindings/clock/sun5i-ccu.h                         |   3 +-
 include/linux/clk-provider.h                                  |   4 +-
 25 files changed, 1347 insertions(+), 116 deletions(-)
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c

base-commit: ee91aaf669ac4fbb09283958f69d57dcad5c4963
-- 
git-series 0.8.11

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

* [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock
  2017-05-03 11:59 ` Maxime Ripard
  (?)
@ 2017-05-03 11:59   ` Maxime Ripard
  -1 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

So far, divider_round_rate only considers the parent clock returned by
clk_hw_get_parent.

This works fine on clocks that have a single parents, this doesn't work on
muxes, since we will only consider the first parent, while other parents
may totally be able to provide a better combination.

Clocks in that case cannot use divider_round_rate, so would have to come up
with a very similar logic to work around it. Instead of having to do
something like this, and duplicate that logic everywhere, create a
divider_round_rate parent to allow caller to give an additional parameter
for the parent clock to consider.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/clk-divider.c    | 26 ++++++++++++++++++--------
 include/linux/clk-provider.h |  4 ++++
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 96386ffc8483..48750439b1cd 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
 	return div;
 }
 
-static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
+static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate,
 			       unsigned long *best_parent_rate,
 			       const struct clk_div_table *table, u8 width,
 			       unsigned long flags)
@@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
 			*best_parent_rate = parent_rate_saved;
 			return i;
 		}
-		parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
-					       rate * i);
+		parent_rate = clk_hw_round_rate(parent, rate * i);
 		now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
 		if (_is_best_div(rate, now, best, flags)) {
 			bestdiv = i;
@@ -326,22 +326,32 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
 
 	if (!bestdiv) {
 		bestdiv = _get_maxdiv(table, width, flags);
-		*best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
+		*best_parent_rate = clk_hw_round_rate(parent, 1);
 	}
 
 	return bestdiv;
 }
 
-long divider_round_rate(struct clk_hw *hw, unsigned long rate,
-			unsigned long *prate, const struct clk_div_table *table,
-			u8 width, unsigned long flags)
+long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate, unsigned long *prate,
+			       const struct clk_div_table *table,
+			       u8 width, unsigned long flags)
 {
 	int div;
 
-	div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
+	div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
 
 	return DIV_ROUND_UP_ULL((u64)*prate, div);
 }
+EXPORT_SYMBOL_GPL(divider_round_rate_parent);
+
+long divider_round_rate(struct clk_hw *hw, unsigned long rate,
+			unsigned long *prate, const struct clk_div_table *table,
+			u8 width, unsigned long flags)
+{
+	return divider_round_rate_parent(hw, clk_hw_get_parent(hw), rate, prate,
+					 table, width, flags);
+}
 EXPORT_SYMBOL_GPL(divider_round_rate);
 
 static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index a428aec36ace..14102f783f64 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -412,6 +412,10 @@ extern const struct clk_ops clk_divider_ro_ops;
 unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
 		unsigned int val, const struct clk_div_table *table,
 		unsigned long flags);
+long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate, unsigned long *prate,
+			       const struct clk_div_table *table,
+			       u8 width, unsigned long flags);
 long divider_round_rate(struct clk_hw *hw, unsigned long rate,
 		unsigned long *prate, const struct clk_div_table *table,
 		u8 width, unsigned long flags);
-- 
git-series 0.8.11

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

* [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Mark Rutland, devicetree, linux-kernel, dri-devel, linux-sunxi,
	Rob Herring, Daniel Vetter, linux-clk, linux-arm-kernel

So far, divider_round_rate only considers the parent clock returned by
clk_hw_get_parent.

This works fine on clocks that have a single parents, this doesn't work on
muxes, since we will only consider the first parent, while other parents
may totally be able to provide a better combination.

Clocks in that case cannot use divider_round_rate, so would have to come up
with a very similar logic to work around it. Instead of having to do
something like this, and duplicate that logic everywhere, create a
divider_round_rate parent to allow caller to give an additional parameter
for the parent clock to consider.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/clk-divider.c    | 26 ++++++++++++++++++--------
 include/linux/clk-provider.h |  4 ++++
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 96386ffc8483..48750439b1cd 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
 	return div;
 }
 
-static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
+static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate,
 			       unsigned long *best_parent_rate,
 			       const struct clk_div_table *table, u8 width,
 			       unsigned long flags)
@@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
 			*best_parent_rate = parent_rate_saved;
 			return i;
 		}
-		parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
-					       rate * i);
+		parent_rate = clk_hw_round_rate(parent, rate * i);
 		now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
 		if (_is_best_div(rate, now, best, flags)) {
 			bestdiv = i;
@@ -326,22 +326,32 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
 
 	if (!bestdiv) {
 		bestdiv = _get_maxdiv(table, width, flags);
-		*best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
+		*best_parent_rate = clk_hw_round_rate(parent, 1);
 	}
 
 	return bestdiv;
 }
 
-long divider_round_rate(struct clk_hw *hw, unsigned long rate,
-			unsigned long *prate, const struct clk_div_table *table,
-			u8 width, unsigned long flags)
+long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate, unsigned long *prate,
+			       const struct clk_div_table *table,
+			       u8 width, unsigned long flags)
 {
 	int div;
 
-	div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
+	div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
 
 	return DIV_ROUND_UP_ULL((u64)*prate, div);
 }
+EXPORT_SYMBOL_GPL(divider_round_rate_parent);
+
+long divider_round_rate(struct clk_hw *hw, unsigned long rate,
+			unsigned long *prate, const struct clk_div_table *table,
+			u8 width, unsigned long flags)
+{
+	return divider_round_rate_parent(hw, clk_hw_get_parent(hw), rate, prate,
+					 table, width, flags);
+}
 EXPORT_SYMBOL_GPL(divider_round_rate);
 
 static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index a428aec36ace..14102f783f64 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -412,6 +412,10 @@ extern const struct clk_ops clk_divider_ro_ops;
 unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
 		unsigned int val, const struct clk_div_table *table,
 		unsigned long flags);
+long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate, unsigned long *prate,
+			       const struct clk_div_table *table,
+			       u8 width, unsigned long flags);
 long divider_round_rate(struct clk_hw *hw, unsigned long rate,
 		unsigned long *prate, const struct clk_div_table *table,
 		u8 width, unsigned long flags);
-- 
git-series 0.8.11
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

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

* [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

So far, divider_round_rate only considers the parent clock returned by
clk_hw_get_parent.

This works fine on clocks that have a single parents, this doesn't work on
muxes, since we will only consider the first parent, while other parents
may totally be able to provide a better combination.

Clocks in that case cannot use divider_round_rate, so would have to come up
with a very similar logic to work around it. Instead of having to do
something like this, and duplicate that logic everywhere, create a
divider_round_rate parent to allow caller to give an additional parameter
for the parent clock to consider.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/clk-divider.c    | 26 ++++++++++++++++++--------
 include/linux/clk-provider.h |  4 ++++
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
index 96386ffc8483..48750439b1cd 100644
--- a/drivers/clk/clk-divider.c
+++ b/drivers/clk/clk-divider.c
@@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
 	return div;
 }
 
-static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
+static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate,
 			       unsigned long *best_parent_rate,
 			       const struct clk_div_table *table, u8 width,
 			       unsigned long flags)
@@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
 			*best_parent_rate = parent_rate_saved;
 			return i;
 		}
-		parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
-					       rate * i);
+		parent_rate = clk_hw_round_rate(parent, rate * i);
 		now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
 		if (_is_best_div(rate, now, best, flags)) {
 			bestdiv = i;
@@ -326,22 +326,32 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
 
 	if (!bestdiv) {
 		bestdiv = _get_maxdiv(table, width, flags);
-		*best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
+		*best_parent_rate = clk_hw_round_rate(parent, 1);
 	}
 
 	return bestdiv;
 }
 
-long divider_round_rate(struct clk_hw *hw, unsigned long rate,
-			unsigned long *prate, const struct clk_div_table *table,
-			u8 width, unsigned long flags)
+long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate, unsigned long *prate,
+			       const struct clk_div_table *table,
+			       u8 width, unsigned long flags)
 {
 	int div;
 
-	div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
+	div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
 
 	return DIV_ROUND_UP_ULL((u64)*prate, div);
 }
+EXPORT_SYMBOL_GPL(divider_round_rate_parent);
+
+long divider_round_rate(struct clk_hw *hw, unsigned long rate,
+			unsigned long *prate, const struct clk_div_table *table,
+			u8 width, unsigned long flags)
+{
+	return divider_round_rate_parent(hw, clk_hw_get_parent(hw), rate, prate,
+					 table, width, flags);
+}
 EXPORT_SYMBOL_GPL(divider_round_rate);
 
 static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index a428aec36ace..14102f783f64 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -412,6 +412,10 @@ extern const struct clk_ops clk_divider_ro_ops;
 unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
 		unsigned int val, const struct clk_div_table *table,
 		unsigned long flags);
+long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
+			       unsigned long rate, unsigned long *prate,
+			       const struct clk_div_table *table,
+			       u8 width, unsigned long flags);
 long divider_round_rate(struct clk_hw *hw, unsigned long rate,
 		unsigned long *prate, const struct clk_div_table *table,
 		u8 width, unsigned long flags);
-- 
git-series 0.8.11

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

* [PATCH v2 2/20] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The clocks might need to modify their parent clocks. In order to make that
possible, give them access to the parent clock being evaluated, and to a
pointer to the parent rate so that they can modify it if needed.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_div.c  |  7 ++++---
 drivers/clk/sunxi-ng/ccu_mp.c   |  7 ++++---
 drivers/clk/sunxi-ng/ccu_mult.c | 11 ++++++-----
 drivers/clk/sunxi-ng/ccu_mux.c  |  8 +++++---
 drivers/clk/sunxi-ng/ccu_mux.h  |  3 ++-
 drivers/clk/sunxi-ng/ccu_nkm.c  |  7 ++++---
 6 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 4057e6021aa9..a489f18a3c01 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -14,7 +14,8 @@
 #include "ccu_div.h"
 
 static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
+					struct clk_hw *parent,
+					unsigned long *parent_rate,
 					unsigned long rate,
 					void *data)
 {
@@ -26,10 +27,10 @@ static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
 	 * several parents, while we might be called to evaluate
 	 * several different parents.
 	 */
-	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
+	val = divider_get_val(rate, *parent_rate, cd->div.table, cd->div.width,
 			      cd->div.flags);
 
-	return divider_recalc_rate(&cd->common.hw, parent_rate, val,
+	return divider_recalc_rate(&cd->common.hw, *parent_rate, val,
 				   cd->div.table, cd->div.flags);
 }
 
diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c
index b583f186a804..de02e6c386d8 100644
--- a/drivers/clk/sunxi-ng/ccu_mp.c
+++ b/drivers/clk/sunxi-ng/ccu_mp.c
@@ -41,7 +41,8 @@ static void ccu_mp_find_best(unsigned long parent, unsigned long rate,
 }
 
 static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
-				       unsigned long parent_rate,
+				       struct clk_hw *hw,
+				       unsigned long *parent_rate,
 				       unsigned long rate,
 				       void *data)
 {
@@ -52,9 +53,9 @@ static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
 	max_m = cmp->m.max ?: 1 << cmp->m.width;
 	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
 
-	ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
+	ccu_mp_find_best(*parent_rate, rate, max_m, max_p, &m, &p);
 
-	return parent_rate / p / m;
+	return *parent_rate / p / m;
 }
 
 static void ccu_mp_disable(struct clk_hw *hw)
diff --git a/drivers/clk/sunxi-ng/ccu_mult.c b/drivers/clk/sunxi-ng/ccu_mult.c
index 671141359895..6ee7ba0738fb 100644
--- a/drivers/clk/sunxi-ng/ccu_mult.c
+++ b/drivers/clk/sunxi-ng/ccu_mult.c
@@ -33,9 +33,10 @@ static void ccu_mult_find_best(unsigned long parent, unsigned long rate,
 }
 
 static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
-					unsigned long rate,
-					void *data)
+					 struct clk_hw *parent,
+					 unsigned long *parent_rate,
+					 unsigned long rate,
+					 void *data)
 {
 	struct ccu_mult *cm = data;
 	struct _ccu_mult _cm;
@@ -47,9 +48,9 @@ static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
 	else
 		_cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
 
-	ccu_mult_find_best(parent_rate, rate, &_cm);
+	ccu_mult_find_best(*parent_rate, rate, &_cm);
 
-	return parent_rate * _cm.mult;
+	return *parent_rate * _cm.mult;
 }
 
 static void ccu_mult_disable(struct clk_hw *hw)
diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index c6bb1f523232..bae735e252b6 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -61,7 +61,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
 				  unsigned long (*round)(struct ccu_mux_internal *,
-							 unsigned long,
+							 struct clk_hw *,
+							 unsigned long *,
 							 unsigned long,
 							 void *),
 				  void *data)
@@ -80,7 +81,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
 							&adj_parent_rate);
 
-		best_rate = round(cm, adj_parent_rate, req->rate, data);
+		best_rate = round(cm, best_parent, &adj_parent_rate,
+				  req->rate, data);
 
 		goto out;
 	}
@@ -109,7 +111,7 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
 							&adj_parent_rate);
 
-		tmp_rate = round(cm, adj_parent_rate, req->rate, data);
+		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
 		if (tmp_rate == req->rate) {
 			best_parent = parent;
 			best_parent_rate = parent_rate;
diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h
index 47aba3a48245..4be56eee2bfd 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.h
+++ b/drivers/clk/sunxi-ng/ccu_mux.h
@@ -86,7 +86,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
 				  unsigned long (*round)(struct ccu_mux_internal *,
-							 unsigned long,
+							 struct clk_hw *,
+							 unsigned long *,
 							 unsigned long,
 							 void *),
 				  void *data);
diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c
index cba84afe1cf1..44b16dc8fea6 100644
--- a/drivers/clk/sunxi-ng/ccu_nkm.c
+++ b/drivers/clk/sunxi-ng/ccu_nkm.c
@@ -102,7 +102,8 @@ static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw,
 }
 
 static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
+					struct clk_hw *hw,
+					unsigned long *parent_rate,
 					unsigned long rate,
 					void *data)
 {
@@ -116,9 +117,9 @@ static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
 	_nkm.min_m = 1;
 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
 
-	ccu_nkm_find_best(parent_rate, rate, &_nkm);
+	ccu_nkm_find_best(*parent_rate, rate, &_nkm);
 
-	return parent_rate * _nkm.n * _nkm.k / _nkm.m;
+	return *parent_rate * _nkm.n * _nkm.k / _nkm.m;
 }
 
 static int ccu_nkm_determine_rate(struct clk_hw *hw,
-- 
git-series 0.8.11

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

* [PATCH v2 2/20] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The clocks might need to modify their parent clocks. In order to make that
possible, give them access to the parent clock being evaluated, and to a
pointer to the parent rate so that they can modify it if needed.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu_div.c  |  7 ++++---
 drivers/clk/sunxi-ng/ccu_mp.c   |  7 ++++---
 drivers/clk/sunxi-ng/ccu_mult.c | 11 ++++++-----
 drivers/clk/sunxi-ng/ccu_mux.c  |  8 +++++---
 drivers/clk/sunxi-ng/ccu_mux.h  |  3 ++-
 drivers/clk/sunxi-ng/ccu_nkm.c  |  7 ++++---
 6 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 4057e6021aa9..a489f18a3c01 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -14,7 +14,8 @@
 #include "ccu_div.h"
 
 static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
+					struct clk_hw *parent,
+					unsigned long *parent_rate,
 					unsigned long rate,
 					void *data)
 {
@@ -26,10 +27,10 @@ static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
 	 * several parents, while we might be called to evaluate
 	 * several different parents.
 	 */
-	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
+	val = divider_get_val(rate, *parent_rate, cd->div.table, cd->div.width,
 			      cd->div.flags);
 
-	return divider_recalc_rate(&cd->common.hw, parent_rate, val,
+	return divider_recalc_rate(&cd->common.hw, *parent_rate, val,
 				   cd->div.table, cd->div.flags);
 }
 
diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c
index b583f186a804..de02e6c386d8 100644
--- a/drivers/clk/sunxi-ng/ccu_mp.c
+++ b/drivers/clk/sunxi-ng/ccu_mp.c
@@ -41,7 +41,8 @@ static void ccu_mp_find_best(unsigned long parent, unsigned long rate,
 }
 
 static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
-				       unsigned long parent_rate,
+				       struct clk_hw *hw,
+				       unsigned long *parent_rate,
 				       unsigned long rate,
 				       void *data)
 {
@@ -52,9 +53,9 @@ static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
 	max_m = cmp->m.max ?: 1 << cmp->m.width;
 	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
 
-	ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
+	ccu_mp_find_best(*parent_rate, rate, max_m, max_p, &m, &p);
 
-	return parent_rate / p / m;
+	return *parent_rate / p / m;
 }
 
 static void ccu_mp_disable(struct clk_hw *hw)
diff --git a/drivers/clk/sunxi-ng/ccu_mult.c b/drivers/clk/sunxi-ng/ccu_mult.c
index 671141359895..6ee7ba0738fb 100644
--- a/drivers/clk/sunxi-ng/ccu_mult.c
+++ b/drivers/clk/sunxi-ng/ccu_mult.c
@@ -33,9 +33,10 @@ static void ccu_mult_find_best(unsigned long parent, unsigned long rate,
 }
 
 static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
-					unsigned long rate,
-					void *data)
+					 struct clk_hw *parent,
+					 unsigned long *parent_rate,
+					 unsigned long rate,
+					 void *data)
 {
 	struct ccu_mult *cm = data;
 	struct _ccu_mult _cm;
@@ -47,9 +48,9 @@ static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
 	else
 		_cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
 
-	ccu_mult_find_best(parent_rate, rate, &_cm);
+	ccu_mult_find_best(*parent_rate, rate, &_cm);
 
-	return parent_rate * _cm.mult;
+	return *parent_rate * _cm.mult;
 }
 
 static void ccu_mult_disable(struct clk_hw *hw)
diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index c6bb1f523232..bae735e252b6 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -61,7 +61,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
 				  unsigned long (*round)(struct ccu_mux_internal *,
-							 unsigned long,
+							 struct clk_hw *,
+							 unsigned long *,
 							 unsigned long,
 							 void *),
 				  void *data)
@@ -80,7 +81,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
 							&adj_parent_rate);
 
-		best_rate = round(cm, adj_parent_rate, req->rate, data);
+		best_rate = round(cm, best_parent, &adj_parent_rate,
+				  req->rate, data);
 
 		goto out;
 	}
@@ -109,7 +111,7 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
 							&adj_parent_rate);
 
-		tmp_rate = round(cm, adj_parent_rate, req->rate, data);
+		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
 		if (tmp_rate == req->rate) {
 			best_parent = parent;
 			best_parent_rate = parent_rate;
diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h
index 47aba3a48245..4be56eee2bfd 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.h
+++ b/drivers/clk/sunxi-ng/ccu_mux.h
@@ -86,7 +86,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
 				  unsigned long (*round)(struct ccu_mux_internal *,
-							 unsigned long,
+							 struct clk_hw *,
+							 unsigned long *,
 							 unsigned long,
 							 void *),
 				  void *data);
diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c
index cba84afe1cf1..44b16dc8fea6 100644
--- a/drivers/clk/sunxi-ng/ccu_nkm.c
+++ b/drivers/clk/sunxi-ng/ccu_nkm.c
@@ -102,7 +102,8 @@ static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw,
 }
 
 static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
+					struct clk_hw *hw,
+					unsigned long *parent_rate,
 					unsigned long rate,
 					void *data)
 {
@@ -116,9 +117,9 @@ static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
 	_nkm.min_m = 1;
 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
 
-	ccu_nkm_find_best(parent_rate, rate, &_nkm);
+	ccu_nkm_find_best(*parent_rate, rate, &_nkm);
 
-	return parent_rate * _nkm.n * _nkm.k / _nkm.m;
+	return *parent_rate * _nkm.n * _nkm.k / _nkm.m;
 }
 
 static int ccu_nkm_determine_rate(struct clk_hw *hw,
-- 
git-series 0.8.11

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

* [PATCH v2 2/20] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The clocks might need to modify their parent clocks. In order to make that
possible, give them access to the parent clock being evaluated, and to a
pointer to the parent rate so that they can modify it if needed.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_div.c  |  7 ++++---
 drivers/clk/sunxi-ng/ccu_mp.c   |  7 ++++---
 drivers/clk/sunxi-ng/ccu_mult.c | 11 ++++++-----
 drivers/clk/sunxi-ng/ccu_mux.c  |  8 +++++---
 drivers/clk/sunxi-ng/ccu_mux.h  |  3 ++-
 drivers/clk/sunxi-ng/ccu_nkm.c  |  7 ++++---
 6 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 4057e6021aa9..a489f18a3c01 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -14,7 +14,8 @@
 #include "ccu_div.h"
 
 static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
+					struct clk_hw *parent,
+					unsigned long *parent_rate,
 					unsigned long rate,
 					void *data)
 {
@@ -26,10 +27,10 @@ static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
 	 * several parents, while we might be called to evaluate
 	 * several different parents.
 	 */
-	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
+	val = divider_get_val(rate, *parent_rate, cd->div.table, cd->div.width,
 			      cd->div.flags);
 
-	return divider_recalc_rate(&cd->common.hw, parent_rate, val,
+	return divider_recalc_rate(&cd->common.hw, *parent_rate, val,
 				   cd->div.table, cd->div.flags);
 }
 
diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c
index b583f186a804..de02e6c386d8 100644
--- a/drivers/clk/sunxi-ng/ccu_mp.c
+++ b/drivers/clk/sunxi-ng/ccu_mp.c
@@ -41,7 +41,8 @@ static void ccu_mp_find_best(unsigned long parent, unsigned long rate,
 }
 
 static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
-				       unsigned long parent_rate,
+				       struct clk_hw *hw,
+				       unsigned long *parent_rate,
 				       unsigned long rate,
 				       void *data)
 {
@@ -52,9 +53,9 @@ static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
 	max_m = cmp->m.max ?: 1 << cmp->m.width;
 	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
 
-	ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
+	ccu_mp_find_best(*parent_rate, rate, max_m, max_p, &m, &p);
 
-	return parent_rate / p / m;
+	return *parent_rate / p / m;
 }
 
 static void ccu_mp_disable(struct clk_hw *hw)
diff --git a/drivers/clk/sunxi-ng/ccu_mult.c b/drivers/clk/sunxi-ng/ccu_mult.c
index 671141359895..6ee7ba0738fb 100644
--- a/drivers/clk/sunxi-ng/ccu_mult.c
+++ b/drivers/clk/sunxi-ng/ccu_mult.c
@@ -33,9 +33,10 @@ static void ccu_mult_find_best(unsigned long parent, unsigned long rate,
 }
 
 static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
-					unsigned long rate,
-					void *data)
+					 struct clk_hw *parent,
+					 unsigned long *parent_rate,
+					 unsigned long rate,
+					 void *data)
 {
 	struct ccu_mult *cm = data;
 	struct _ccu_mult _cm;
@@ -47,9 +48,9 @@ static unsigned long ccu_mult_round_rate(struct ccu_mux_internal *mux,
 	else
 		_cm.max = (1 << cm->mult.width) + cm->mult.offset - 1;
 
-	ccu_mult_find_best(parent_rate, rate, &_cm);
+	ccu_mult_find_best(*parent_rate, rate, &_cm);
 
-	return parent_rate * _cm.mult;
+	return *parent_rate * _cm.mult;
 }
 
 static void ccu_mult_disable(struct clk_hw *hw)
diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index c6bb1f523232..bae735e252b6 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -61,7 +61,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
 				  unsigned long (*round)(struct ccu_mux_internal *,
-							 unsigned long,
+							 struct clk_hw *,
+							 unsigned long *,
 							 unsigned long,
 							 void *),
 				  void *data)
@@ -80,7 +81,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
 							&adj_parent_rate);
 
-		best_rate = round(cm, adj_parent_rate, req->rate, data);
+		best_rate = round(cm, best_parent, &adj_parent_rate,
+				  req->rate, data);
 
 		goto out;
 	}
@@ -109,7 +111,7 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
 							&adj_parent_rate);
 
-		tmp_rate = round(cm, adj_parent_rate, req->rate, data);
+		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
 		if (tmp_rate == req->rate) {
 			best_parent = parent;
 			best_parent_rate = parent_rate;
diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h
index 47aba3a48245..4be56eee2bfd 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.h
+++ b/drivers/clk/sunxi-ng/ccu_mux.h
@@ -86,7 +86,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
 				  unsigned long (*round)(struct ccu_mux_internal *,
-							 unsigned long,
+							 struct clk_hw *,
+							 unsigned long *,
 							 unsigned long,
 							 void *),
 				  void *data);
diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c
index cba84afe1cf1..44b16dc8fea6 100644
--- a/drivers/clk/sunxi-ng/ccu_nkm.c
+++ b/drivers/clk/sunxi-ng/ccu_nkm.c
@@ -102,7 +102,8 @@ static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw,
 }
 
 static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
-					unsigned long parent_rate,
+					struct clk_hw *hw,
+					unsigned long *parent_rate,
 					unsigned long rate,
 					void *data)
 {
@@ -116,9 +117,9 @@ static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
 	_nkm.min_m = 1;
 	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
 
-	ccu_nkm_find_best(parent_rate, rate, &_nkm);
+	ccu_nkm_find_best(*parent_rate, rate, &_nkm);
 
-	return parent_rate * _nkm.n * _nkm.k / _nkm.m;
+	return *parent_rate * _nkm.n * _nkm.k / _nkm.m;
 }
 
 static int ccu_nkm_determine_rate(struct clk_hw *hw,
-- 
git-series 0.8.11

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

* [PATCH v2 3/20] clk: sunxi-ng: div: Switch to divider_round_rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

divider_round_rate already evaluates changing the parent rate if
CLK_SET_RATE_PARENT is set. Now that we can do that on muxes too, let's
just use it.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_div.c | 27 ++++-----------------------
 1 file changed, 4 insertions(+), 23 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index a489f18a3c01..419463375bc1 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -20,18 +20,11 @@ static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
 					void *data)
 {
 	struct ccu_div *cd = data;
-	unsigned long val;
-
-	/*
-	 * We can't use divider_round_rate that assumes that there's
-	 * several parents, while we might be called to evaluate
-	 * several different parents.
-	 */
-	val = divider_get_val(rate, *parent_rate, cd->div.table, cd->div.width,
-			      cd->div.flags);
 
-	return divider_recalc_rate(&cd->common.hw, *parent_rate, val,
-				   cd->div.table, cd->div.flags);
+	return divider_round_rate_parent(&cd->common.hw, parent,
+					 rate, parent_rate,
+					 cd->div.table, cd->div.width,
+					 cd->div.flags);
 }
 
 static void ccu_div_disable(struct clk_hw *hw)
@@ -78,18 +71,6 @@ static int ccu_div_determine_rate(struct clk_hw *hw,
 {
 	struct ccu_div *cd = hw_to_ccu_div(hw);
 
-	if (clk_hw_get_num_parents(hw) == 1) {
-		req->rate = divider_round_rate(hw, req->rate,
-					       &req->best_parent_rate,
-					       cd->div.table,
-					       cd->div.width,
-					       cd->div.flags);
-
-		req->best_parent_hw = clk_hw_get_parent(hw);
-
-		return 0;
-	}
-
 	return ccu_mux_helper_determine_rate(&cd->common, &cd->mux,
 					     req, ccu_div_round_rate, cd);
 }
-- 
git-series 0.8.11

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

* [PATCH v2 3/20] clk: sunxi-ng: div: Switch to divider_round_rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

divider_round_rate already evaluates changing the parent rate if
CLK_SET_RATE_PARENT is set. Now that we can do that on muxes too, let's
just use it.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu_div.c | 27 ++++-----------------------
 1 file changed, 4 insertions(+), 23 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index a489f18a3c01..419463375bc1 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -20,18 +20,11 @@ static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
 					void *data)
 {
 	struct ccu_div *cd = data;
-	unsigned long val;
-
-	/*
-	 * We can't use divider_round_rate that assumes that there's
-	 * several parents, while we might be called to evaluate
-	 * several different parents.
-	 */
-	val = divider_get_val(rate, *parent_rate, cd->div.table, cd->div.width,
-			      cd->div.flags);
 
-	return divider_recalc_rate(&cd->common.hw, *parent_rate, val,
-				   cd->div.table, cd->div.flags);
+	return divider_round_rate_parent(&cd->common.hw, parent,
+					 rate, parent_rate,
+					 cd->div.table, cd->div.width,
+					 cd->div.flags);
 }
 
 static void ccu_div_disable(struct clk_hw *hw)
@@ -78,18 +71,6 @@ static int ccu_div_determine_rate(struct clk_hw *hw,
 {
 	struct ccu_div *cd = hw_to_ccu_div(hw);
 
-	if (clk_hw_get_num_parents(hw) == 1) {
-		req->rate = divider_round_rate(hw, req->rate,
-					       &req->best_parent_rate,
-					       cd->div.table,
-					       cd->div.width,
-					       cd->div.flags);
-
-		req->best_parent_hw = clk_hw_get_parent(hw);
-
-		return 0;
-	}
-
 	return ccu_mux_helper_determine_rate(&cd->common, &cd->mux,
 					     req, ccu_div_round_rate, cd);
 }
-- 
git-series 0.8.11

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

* [PATCH v2 3/20] clk: sunxi-ng: div: Switch to divider_round_rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

divider_round_rate already evaluates changing the parent rate if
CLK_SET_RATE_PARENT is set. Now that we can do that on muxes too, let's
just use it.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_div.c | 27 ++++-----------------------
 1 file changed, 4 insertions(+), 23 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index a489f18a3c01..419463375bc1 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -20,18 +20,11 @@ static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
 					void *data)
 {
 	struct ccu_div *cd = data;
-	unsigned long val;
-
-	/*
-	 * We can't use divider_round_rate that assumes that there's
-	 * several parents, while we might be called to evaluate
-	 * several different parents.
-	 */
-	val = divider_get_val(rate, *parent_rate, cd->div.table, cd->div.width,
-			      cd->div.flags);
 
-	return divider_recalc_rate(&cd->common.hw, *parent_rate, val,
-				   cd->div.table, cd->div.flags);
+	return divider_round_rate_parent(&cd->common.hw, parent,
+					 rate, parent_rate,
+					 cd->div.table, cd->div.width,
+					 cd->div.flags);
 }
 
 static void ccu_div_disable(struct clk_hw *hw)
@@ -78,18 +71,6 @@ static int ccu_div_determine_rate(struct clk_hw *hw,
 {
 	struct ccu_div *cd = hw_to_ccu_div(hw);
 
-	if (clk_hw_get_num_parents(hw) == 1) {
-		req->rate = divider_round_rate(hw, req->rate,
-					       &req->best_parent_rate,
-					       cd->div.table,
-					       cd->div.width,
-					       cd->div.flags);
-
-		req->best_parent_hw = clk_hw_get_parent(hw);
-
-		return 0;
-	}
-
 	return ccu_mux_helper_determine_rate(&cd->common, &cd->mux,
 					     req, ccu_div_round_rate, cd);
 }
-- 
git-series 0.8.11

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

* [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The current code only rely on the parent to change its rate in the case
where CLK_SET_RATE_PARENT is set.

However, some clock rates might be obtained only through a modification of
the parent and the clock divider. Just rely on the round rate of the clocks
to give us the best computation that might be achieved for a given rate.

round_rate functions now need to honor CLK_SET_RATE_PARENT, but either the
functions already do that if they modify the parent, or don't modify the
praents at all.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index bae735e252b6..58b6e349a0ed 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -95,19 +95,7 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		if (!parent)
 			continue;
 
-		if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
-			struct clk_rate_request parent_req = *req;
-			int ret = __clk_determine_rate(parent, &parent_req);
-
-			if (ret)
-				continue;
-
-			parent_rate = parent_req.rate;
-		} else {
-			parent_rate = clk_hw_get_rate(parent);
-		}
-
-		adj_parent_rate = parent_rate;
+		adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
 							&adj_parent_rate);
 
-- 
git-series 0.8.11

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

* [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The current code only rely on the parent to change its rate in the case
where CLK_SET_RATE_PARENT is set.

However, some clock rates might be obtained only through a modification of
the parent and the clock divider. Just rely on the round rate of the clocks
to give us the best computation that might be achieved for a given rate.

round_rate functions now need to honor CLK_SET_RATE_PARENT, but either the
functions already do that if they modify the parent, or don't modify the
praents at all.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index bae735e252b6..58b6e349a0ed 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -95,19 +95,7 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		if (!parent)
 			continue;
 
-		if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
-			struct clk_rate_request parent_req = *req;
-			int ret = __clk_determine_rate(parent, &parent_req);
-
-			if (ret)
-				continue;
-
-			parent_rate = parent_req.rate;
-		} else {
-			parent_rate = clk_hw_get_rate(parent);
-		}
-
-		adj_parent_rate = parent_rate;
+		adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
 							&adj_parent_rate);
 
-- 
git-series 0.8.11

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

* [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The current code only rely on the parent to change its rate in the case
where CLK_SET_RATE_PARENT is set.

However, some clock rates might be obtained only through a modification of
the parent and the clock divider. Just rely on the round rate of the clocks
to give us the best computation that might be achieved for a given rate.

round_rate functions now need to honor CLK_SET_RATE_PARENT, but either the
functions already do that if they modify the parent, or don't modify the
praents at all.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index bae735e252b6..58b6e349a0ed 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -95,19 +95,7 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		if (!parent)
 			continue;
 
-		if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
-			struct clk_rate_request parent_req = *req;
-			int ret = __clk_determine_rate(parent, &parent_req);
-
-			if (ret)
-				continue;
-
-			parent_rate = parent_req.rate;
-		} else {
-			parent_rate = clk_hw_get_rate(parent);
-		}
-
-		adj_parent_rate = parent_rate;
+		adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
 		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
 							&adj_parent_rate);
 
-- 
git-series 0.8.11

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

* [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The pre-divider retrieval code was merged into the function to apply the
current pre-divider onto the parent clock rate so that we can use that
adjusted value to do our factors computation.

However, since we'll need to do the reverse operation, we need to split out
that code into a function that will be shared.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 32 ++++++++++++++++++++------------
 1 file changed, 20 insertions(+), 12 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index 58b6e349a0ed..3eb23d4e6534 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -15,24 +15,20 @@
 #include "ccu_gate.h"
 #include "ccu_mux.h"
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate)
+static u16 ccu_mux_get_prediv(struct ccu_common *common,
+			      struct ccu_mux_internal *cm,
+			      int parent_index)
 {
 	u16 prediv = 1;
 	u32 reg;
-	int i;
 
 	if (!((common->features & CCU_FEATURE_FIXED_PREDIV) ||
 	      (common->features & CCU_FEATURE_VARIABLE_PREDIV) ||
 	      (common->features & CCU_FEATURE_ALL_PREDIV)))
-		return;
+		return 1;
 
-	if (common->features & CCU_FEATURE_ALL_PREDIV) {
-		*parent_rate = *parent_rate / common->prediv;
-		return;
-	}
+	if (common->features & CCU_FEATURE_ALL_PREDIV)
+		return common->prediv;
 
 	reg = readl(common->base + common->reg);
 	if (parent_index < 0) {
@@ -40,10 +36,13 @@ void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
 		parent_index &= (1 << cm->width) - 1;
 	}
 
-	if (common->features & CCU_FEATURE_FIXED_PREDIV)
+	if (common->features & CCU_FEATURE_FIXED_PREDIV) {
+		int i;
+
 		for (i = 0; i < cm->n_predivs; i++)
 			if (parent_index == cm->fixed_predivs[i].index)
 				prediv = cm->fixed_predivs[i].div;
+	}
 
 	if (common->features & CCU_FEATURE_VARIABLE_PREDIV)
 		if (parent_index == cm->variable_prediv.index) {
@@ -54,7 +53,16 @@ void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
 			prediv = div + 1;
 		}
 
-	*parent_rate = *parent_rate / prediv;
+	return prediv;
+}
+
+void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
+					     struct ccu_mux_internal *cm,
+					     int parent_index,
+					     unsigned long *parent_rate)
+{
+	*parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
+							 parent_index);
 }
 
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
-- 
git-series 0.8.11

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

* [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The pre-divider retrieval code was merged into the function to apply the
current pre-divider onto the parent clock rate so that we can use that
adjusted value to do our factors computation.

However, since we'll need to do the reverse operation, we need to split out
that code into a function that will be shared.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 32 ++++++++++++++++++++------------
 1 file changed, 20 insertions(+), 12 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index 58b6e349a0ed..3eb23d4e6534 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -15,24 +15,20 @@
 #include "ccu_gate.h"
 #include "ccu_mux.h"
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate)
+static u16 ccu_mux_get_prediv(struct ccu_common *common,
+			      struct ccu_mux_internal *cm,
+			      int parent_index)
 {
 	u16 prediv = 1;
 	u32 reg;
-	int i;
 
 	if (!((common->features & CCU_FEATURE_FIXED_PREDIV) ||
 	      (common->features & CCU_FEATURE_VARIABLE_PREDIV) ||
 	      (common->features & CCU_FEATURE_ALL_PREDIV)))
-		return;
+		return 1;
 
-	if (common->features & CCU_FEATURE_ALL_PREDIV) {
-		*parent_rate = *parent_rate / common->prediv;
-		return;
-	}
+	if (common->features & CCU_FEATURE_ALL_PREDIV)
+		return common->prediv;
 
 	reg = readl(common->base + common->reg);
 	if (parent_index < 0) {
@@ -40,10 +36,13 @@ void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
 		parent_index &= (1 << cm->width) - 1;
 	}
 
-	if (common->features & CCU_FEATURE_FIXED_PREDIV)
+	if (common->features & CCU_FEATURE_FIXED_PREDIV) {
+		int i;
+
 		for (i = 0; i < cm->n_predivs; i++)
 			if (parent_index == cm->fixed_predivs[i].index)
 				prediv = cm->fixed_predivs[i].div;
+	}
 
 	if (common->features & CCU_FEATURE_VARIABLE_PREDIV)
 		if (parent_index == cm->variable_prediv.index) {
@@ -54,7 +53,16 @@ void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
 			prediv = div + 1;
 		}
 
-	*parent_rate = *parent_rate / prediv;
+	return prediv;
+}
+
+void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
+					     struct ccu_mux_internal *cm,
+					     int parent_index,
+					     unsigned long *parent_rate)
+{
+	*parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
+							 parent_index);
 }
 
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
-- 
git-series 0.8.11

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

* [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The pre-divider retrieval code was merged into the function to apply the
current pre-divider onto the parent clock rate so that we can use that
adjusted value to do our factors computation.

However, since we'll need to do the reverse operation, we need to split out
that code into a function that will be shared.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 32 ++++++++++++++++++++------------
 1 file changed, 20 insertions(+), 12 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index 58b6e349a0ed..3eb23d4e6534 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -15,24 +15,20 @@
 #include "ccu_gate.h"
 #include "ccu_mux.h"
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate)
+static u16 ccu_mux_get_prediv(struct ccu_common *common,
+			      struct ccu_mux_internal *cm,
+			      int parent_index)
 {
 	u16 prediv = 1;
 	u32 reg;
-	int i;
 
 	if (!((common->features & CCU_FEATURE_FIXED_PREDIV) ||
 	      (common->features & CCU_FEATURE_VARIABLE_PREDIV) ||
 	      (common->features & CCU_FEATURE_ALL_PREDIV)))
-		return;
+		return 1;
 
-	if (common->features & CCU_FEATURE_ALL_PREDIV) {
-		*parent_rate = *parent_rate / common->prediv;
-		return;
-	}
+	if (common->features & CCU_FEATURE_ALL_PREDIV)
+		return common->prediv;
 
 	reg = readl(common->base + common->reg);
 	if (parent_index < 0) {
@@ -40,10 +36,13 @@ void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
 		parent_index &= (1 << cm->width) - 1;
 	}
 
-	if (common->features & CCU_FEATURE_FIXED_PREDIV)
+	if (common->features & CCU_FEATURE_FIXED_PREDIV) {
+		int i;
+
 		for (i = 0; i < cm->n_predivs; i++)
 			if (parent_index == cm->fixed_predivs[i].index)
 				prediv = cm->fixed_predivs[i].div;
+	}
 
 	if (common->features & CCU_FEATURE_VARIABLE_PREDIV)
 		if (parent_index == cm->variable_prediv.index) {
@@ -54,7 +53,16 @@ void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
 			prediv = div + 1;
 		}
 
-	*parent_rate = *parent_rate / prediv;
+	return prediv;
+}
+
+void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
+					     struct ccu_mux_internal *cm,
+					     int parent_index,
+					     unsigned long *parent_rate)
+{
+	*parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
+							 parent_index);
 }
 
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
-- 
git-series 0.8.11

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

* [PATCH v2 6/20] clk: sunxi-ng: mux: Change pre-divider application function prototype
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The current function name is a bit confusing, and doesn't really allow to
create an explicit function to reverse the operation.

We also for now change the parent rate through a pointer, while we don't
return anything.

In order to be less confusing, and easier to use for downstream users,
change the function name to something hopefully clearer, and return the
adjusted rate instead of changing the pointer.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_div.c  |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mp.c   |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mult.c |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mux.c  | 29 ++++++++++++-----------------
 drivers/clk/sunxi-ng/ccu_mux.h  |  8 ++++----
 5 files changed, 28 insertions(+), 33 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 419463375bc1..c0e5c10d0091 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -59,8 +59,8 @@ static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
 	val = reg >> cd->div.shift;
 	val &= (1 << cd->div.width) - 1;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
+						  parent_rate);
 
 	return divider_recalc_rate(hw, parent_rate, val, cd->div.table,
 				   cd->div.flags);
@@ -83,8 +83,8 @@ static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate,
 	unsigned long val;
 	u32 reg;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
+						  parent_rate);
 
 	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
 			      cd->div.flags);
diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c
index de02e6c386d8..b917ad7a386c 100644
--- a/drivers/clk/sunxi-ng/ccu_mp.c
+++ b/drivers/clk/sunxi-ng/ccu_mp.c
@@ -87,8 +87,8 @@ static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
 	u32 reg;
 
 	/* Adjust parent_rate according to pre-dividers */
-	ccu_mux_helper_adjust_parent_for_prediv(&cmp->common, &cmp->mux,
-						-1, &parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
+						  parent_rate);
 
 	reg = readl(cmp->common.base + cmp->common.reg);
 
@@ -123,8 +123,8 @@ static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate,
 	u32 reg;
 
 	/* Adjust parent_rate according to pre-dividers */
-	ccu_mux_helper_adjust_parent_for_prediv(&cmp->common, &cmp->mux,
-						-1, &parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
+						  parent_rate);
 
 	max_m = cmp->m.max ?: 1 << cmp->m.width;
 	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
diff --git a/drivers/clk/sunxi-ng/ccu_mult.c b/drivers/clk/sunxi-ng/ccu_mult.c
index 6ee7ba0738fb..20d0300867f2 100644
--- a/drivers/clk/sunxi-ng/ccu_mult.c
+++ b/drivers/clk/sunxi-ng/ccu_mult.c
@@ -88,8 +88,8 @@ static unsigned long ccu_mult_recalc_rate(struct clk_hw *hw,
 	val = reg >> cm->mult.shift;
 	val &= (1 << cm->mult.width) - 1;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+						  parent_rate);
 
 	return parent_rate * (val + cm->mult.offset);
 }
@@ -116,8 +116,8 @@ static int ccu_mult_set_rate(struct clk_hw *hw, unsigned long rate,
 	else
 		ccu_frac_helper_disable(&cm->common, &cm->frac);
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+						  parent_rate);
 
 	_cm.min = cm->mult.min;
 
diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index 3eb23d4e6534..c33210972581 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -56,13 +56,12 @@ static u16 ccu_mux_get_prediv(struct ccu_common *common,
 	return prediv;
 }
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate)
+unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
+					  struct ccu_mux_internal *cm,
+					  int parent_index,
+					  unsigned long parent_rate)
 {
-	*parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
-							 parent_index);
+	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
 }
 
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
@@ -84,10 +83,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 
 		best_parent = clk_hw_get_parent(hw);
 		best_parent_rate = clk_hw_get_rate(best_parent);
-
-		adj_parent_rate = best_parent_rate;
-		ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
-							&adj_parent_rate);
+		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1,
+							      best_parent_rate);
 
 		best_rate = round(cm, best_parent, &adj_parent_rate,
 				  req->rate, data);
@@ -103,9 +100,9 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		if (!parent)
 			continue;
 
-		adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
-		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
-							&adj_parent_rate);
+		parent_rate = clk_hw_get_rate(parent);
+		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
+							      parent_rate);
 
 		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
 		if (tmp_rate == req->rate) {
@@ -215,10 +212,8 @@ static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw,
 {
 	struct ccu_mux *cm = hw_to_ccu_mux(hw);
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
-
-	return parent_rate;
+	return ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+					   parent_rate);
 }
 
 const struct clk_ops ccu_mux_ops = {
diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h
index 4be56eee2bfd..dba12c76cf54 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.h
+++ b/drivers/clk/sunxi-ng/ccu_mux.h
@@ -78,10 +78,10 @@ static inline struct ccu_mux *hw_to_ccu_mux(struct clk_hw *hw)
 
 extern const struct clk_ops ccu_mux_ops;
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate);
+unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
+					  struct ccu_mux_internal *cm,
+					  int parent_index,
+					  unsigned long parent_rate);
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
-- 
git-series 0.8.11

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

* [PATCH v2 6/20] clk: sunxi-ng: mux: Change pre-divider application function prototype
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The current function name is a bit confusing, and doesn't really allow to
create an explicit function to reverse the operation.

We also for now change the parent rate through a pointer, while we don't
return anything.

In order to be less confusing, and easier to use for downstream users,
change the function name to something hopefully clearer, and return the
adjusted rate instead of changing the pointer.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu_div.c  |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mp.c   |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mult.c |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mux.c  | 29 ++++++++++++-----------------
 drivers/clk/sunxi-ng/ccu_mux.h  |  8 ++++----
 5 files changed, 28 insertions(+), 33 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 419463375bc1..c0e5c10d0091 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -59,8 +59,8 @@ static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
 	val = reg >> cd->div.shift;
 	val &= (1 << cd->div.width) - 1;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
+						  parent_rate);
 
 	return divider_recalc_rate(hw, parent_rate, val, cd->div.table,
 				   cd->div.flags);
@@ -83,8 +83,8 @@ static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate,
 	unsigned long val;
 	u32 reg;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
+						  parent_rate);
 
 	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
 			      cd->div.flags);
diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c
index de02e6c386d8..b917ad7a386c 100644
--- a/drivers/clk/sunxi-ng/ccu_mp.c
+++ b/drivers/clk/sunxi-ng/ccu_mp.c
@@ -87,8 +87,8 @@ static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
 	u32 reg;
 
 	/* Adjust parent_rate according to pre-dividers */
-	ccu_mux_helper_adjust_parent_for_prediv(&cmp->common, &cmp->mux,
-						-1, &parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
+						  parent_rate);
 
 	reg = readl(cmp->common.base + cmp->common.reg);
 
@@ -123,8 +123,8 @@ static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate,
 	u32 reg;
 
 	/* Adjust parent_rate according to pre-dividers */
-	ccu_mux_helper_adjust_parent_for_prediv(&cmp->common, &cmp->mux,
-						-1, &parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
+						  parent_rate);
 
 	max_m = cmp->m.max ?: 1 << cmp->m.width;
 	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
diff --git a/drivers/clk/sunxi-ng/ccu_mult.c b/drivers/clk/sunxi-ng/ccu_mult.c
index 6ee7ba0738fb..20d0300867f2 100644
--- a/drivers/clk/sunxi-ng/ccu_mult.c
+++ b/drivers/clk/sunxi-ng/ccu_mult.c
@@ -88,8 +88,8 @@ static unsigned long ccu_mult_recalc_rate(struct clk_hw *hw,
 	val = reg >> cm->mult.shift;
 	val &= (1 << cm->mult.width) - 1;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+						  parent_rate);
 
 	return parent_rate * (val + cm->mult.offset);
 }
@@ -116,8 +116,8 @@ static int ccu_mult_set_rate(struct clk_hw *hw, unsigned long rate,
 	else
 		ccu_frac_helper_disable(&cm->common, &cm->frac);
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+						  parent_rate);
 
 	_cm.min = cm->mult.min;
 
diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index 3eb23d4e6534..c33210972581 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -56,13 +56,12 @@ static u16 ccu_mux_get_prediv(struct ccu_common *common,
 	return prediv;
 }
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate)
+unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
+					  struct ccu_mux_internal *cm,
+					  int parent_index,
+					  unsigned long parent_rate)
 {
-	*parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
-							 parent_index);
+	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
 }
 
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
@@ -84,10 +83,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 
 		best_parent = clk_hw_get_parent(hw);
 		best_parent_rate = clk_hw_get_rate(best_parent);
-
-		adj_parent_rate = best_parent_rate;
-		ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
-							&adj_parent_rate);
+		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1,
+							      best_parent_rate);
 
 		best_rate = round(cm, best_parent, &adj_parent_rate,
 				  req->rate, data);
@@ -103,9 +100,9 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		if (!parent)
 			continue;
 
-		adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
-		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
-							&adj_parent_rate);
+		parent_rate = clk_hw_get_rate(parent);
+		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
+							      parent_rate);
 
 		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
 		if (tmp_rate == req->rate) {
@@ -215,10 +212,8 @@ static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw,
 {
 	struct ccu_mux *cm = hw_to_ccu_mux(hw);
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
-
-	return parent_rate;
+	return ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+					   parent_rate);
 }
 
 const struct clk_ops ccu_mux_ops = {
diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h
index 4be56eee2bfd..dba12c76cf54 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.h
+++ b/drivers/clk/sunxi-ng/ccu_mux.h
@@ -78,10 +78,10 @@ static inline struct ccu_mux *hw_to_ccu_mux(struct clk_hw *hw)
 
 extern const struct clk_ops ccu_mux_ops;
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate);
+unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
+					  struct ccu_mux_internal *cm,
+					  int parent_index,
+					  unsigned long parent_rate);
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
-- 
git-series 0.8.11

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

* [PATCH v2 6/20] clk: sunxi-ng: mux: Change pre-divider application function prototype
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The current function name is a bit confusing, and doesn't really allow to
create an explicit function to reverse the operation.

We also for now change the parent rate through a pointer, while we don't
return anything.

In order to be less confusing, and easier to use for downstream users,
change the function name to something hopefully clearer, and return the
adjusted rate instead of changing the pointer.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_div.c  |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mp.c   |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mult.c |  8 ++++----
 drivers/clk/sunxi-ng/ccu_mux.c  | 29 ++++++++++++-----------------
 drivers/clk/sunxi-ng/ccu_mux.h  |  8 ++++----
 5 files changed, 28 insertions(+), 33 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c
index 419463375bc1..c0e5c10d0091 100644
--- a/drivers/clk/sunxi-ng/ccu_div.c
+++ b/drivers/clk/sunxi-ng/ccu_div.c
@@ -59,8 +59,8 @@ static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
 	val = reg >> cd->div.shift;
 	val &= (1 << cd->div.width) - 1;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
+						  parent_rate);
 
 	return divider_recalc_rate(hw, parent_rate, val, cd->div.table,
 				   cd->div.flags);
@@ -83,8 +83,8 @@ static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate,
 	unsigned long val;
 	u32 reg;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cd->common, &cd->mux, -1,
+						  parent_rate);
 
 	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
 			      cd->div.flags);
diff --git a/drivers/clk/sunxi-ng/ccu_mp.c b/drivers/clk/sunxi-ng/ccu_mp.c
index de02e6c386d8..b917ad7a386c 100644
--- a/drivers/clk/sunxi-ng/ccu_mp.c
+++ b/drivers/clk/sunxi-ng/ccu_mp.c
@@ -87,8 +87,8 @@ static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
 	u32 reg;
 
 	/* Adjust parent_rate according to pre-dividers */
-	ccu_mux_helper_adjust_parent_for_prediv(&cmp->common, &cmp->mux,
-						-1, &parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
+						  parent_rate);
 
 	reg = readl(cmp->common.base + cmp->common.reg);
 
@@ -123,8 +123,8 @@ static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate,
 	u32 reg;
 
 	/* Adjust parent_rate according to pre-dividers */
-	ccu_mux_helper_adjust_parent_for_prediv(&cmp->common, &cmp->mux,
-						-1, &parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cmp->common, &cmp->mux, -1,
+						  parent_rate);
 
 	max_m = cmp->m.max ?: 1 << cmp->m.width;
 	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
diff --git a/drivers/clk/sunxi-ng/ccu_mult.c b/drivers/clk/sunxi-ng/ccu_mult.c
index 6ee7ba0738fb..20d0300867f2 100644
--- a/drivers/clk/sunxi-ng/ccu_mult.c
+++ b/drivers/clk/sunxi-ng/ccu_mult.c
@@ -88,8 +88,8 @@ static unsigned long ccu_mult_recalc_rate(struct clk_hw *hw,
 	val = reg >> cm->mult.shift;
 	val &= (1 << cm->mult.width) - 1;
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+						  parent_rate);
 
 	return parent_rate * (val + cm->mult.offset);
 }
@@ -116,8 +116,8 @@ static int ccu_mult_set_rate(struct clk_hw *hw, unsigned long rate,
 	else
 		ccu_frac_helper_disable(&cm->common, &cm->frac);
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
+	parent_rate = ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+						  parent_rate);
 
 	_cm.min = cm->mult.min;
 
diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index 3eb23d4e6534..c33210972581 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -56,13 +56,12 @@ static u16 ccu_mux_get_prediv(struct ccu_common *common,
 	return prediv;
 }
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate)
+unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
+					  struct ccu_mux_internal *cm,
+					  int parent_index,
+					  unsigned long parent_rate)
 {
-	*parent_rate = *parent_rate / ccu_mux_get_prediv(common, cm,
-							 parent_index);
+	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
 }
 
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
@@ -84,10 +83,8 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 
 		best_parent = clk_hw_get_parent(hw);
 		best_parent_rate = clk_hw_get_rate(best_parent);
-
-		adj_parent_rate = best_parent_rate;
-		ccu_mux_helper_adjust_parent_for_prediv(common, cm, -1,
-							&adj_parent_rate);
+		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, -1,
+							      best_parent_rate);
 
 		best_rate = round(cm, best_parent, &adj_parent_rate,
 				  req->rate, data);
@@ -103,9 +100,9 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		if (!parent)
 			continue;
 
-		adj_parent_rate = parent_rate = clk_hw_get_rate(parent);
-		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
-							&adj_parent_rate);
+		parent_rate = clk_hw_get_rate(parent);
+		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
+							      parent_rate);
 
 		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
 		if (tmp_rate == req->rate) {
@@ -215,10 +212,8 @@ static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw,
 {
 	struct ccu_mux *cm = hw_to_ccu_mux(hw);
 
-	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
-						&parent_rate);
-
-	return parent_rate;
+	return ccu_mux_helper_apply_prediv(&cm->common, &cm->mux, -1,
+					   parent_rate);
 }
 
 const struct clk_ops ccu_mux_ops = {
diff --git a/drivers/clk/sunxi-ng/ccu_mux.h b/drivers/clk/sunxi-ng/ccu_mux.h
index 4be56eee2bfd..dba12c76cf54 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.h
+++ b/drivers/clk/sunxi-ng/ccu_mux.h
@@ -78,10 +78,10 @@ static inline struct ccu_mux *hw_to_ccu_mux(struct clk_hw *hw)
 
 extern const struct clk_ops ccu_mux_ops;
 
-void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
-					     struct ccu_mux_internal *cm,
-					     int parent_index,
-					     unsigned long *parent_rate);
+unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
+					  struct ccu_mux_internal *cm,
+					  int parent_index,
+					  unsigned long parent_rate);
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
-- 
git-series 0.8.11

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

* [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

Currently, the parent rate given back to the clock framework in our
request is the original parent rate we calculated before trying to round
the rate of our clock.

This works fine unless our clock also changes its parent rate, in which
case we will simply ignore that change and still use the previous parent
rate.

Create a new function to re-adjust the parent rate to take the pre-dividers
into account, and give that back to the clock framework.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 33 ++++++++++++++++++++++++++++-----
 1 file changed, 28 insertions(+), 5 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index c33210972581..1ce62cc23f8a 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -64,6 +64,14 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
 	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
 }
 
+unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
+					    struct ccu_mux_internal *cm,
+					    int parent_index,
+					    unsigned long parent_rate)
+{
+	return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
+}
+
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
@@ -89,22 +97,37 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		best_rate = round(cm, best_parent, &adj_parent_rate,
 				  req->rate, data);
 
+		/*
+		 * parent_rate might have been modified by our clock.
+		 * Re-apply the pre-divider if there's one, and give
+		 * the actual frequency the parent needs to run at.
+		 */
+		best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
+								 adj_parent_rate);
+
 		goto out;
 	}
 
 	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
-		unsigned long tmp_rate, parent_rate, adj_parent_rate;
+		unsigned long tmp_rate, parent_rate;
 		struct clk_hw *parent;
 
 		parent = clk_hw_get_parent_by_index(hw, i);
 		if (!parent)
 			continue;
 
-		parent_rate = clk_hw_get_rate(parent);
-		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
-							      parent_rate);
+		parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
+							  clk_hw_get_rate(parent));
+
+		tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
 
-		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
+		/*
+		 * parent_rate might have been modified by our clock.
+		 * Re-apply the pre-divider if there's one, and give
+		 * the actual frequency the parent needs to run at.
+		 */
+		parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
+							    parent_rate);
 		if (tmp_rate == req->rate) {
 			best_parent = parent;
 			best_parent_rate = parent_rate;
-- 
git-series 0.8.11

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

* [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

Currently, the parent rate given back to the clock framework in our
request is the original parent rate we calculated before trying to round
the rate of our clock.

This works fine unless our clock also changes its parent rate, in which
case we will simply ignore that change and still use the previous parent
rate.

Create a new function to re-adjust the parent rate to take the pre-dividers
into account, and give that back to the clock framework.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 33 ++++++++++++++++++++++++++++-----
 1 file changed, 28 insertions(+), 5 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index c33210972581..1ce62cc23f8a 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -64,6 +64,14 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
 	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
 }
 
+unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
+					    struct ccu_mux_internal *cm,
+					    int parent_index,
+					    unsigned long parent_rate)
+{
+	return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
+}
+
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
@@ -89,22 +97,37 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		best_rate = round(cm, best_parent, &adj_parent_rate,
 				  req->rate, data);
 
+		/*
+		 * parent_rate might have been modified by our clock.
+		 * Re-apply the pre-divider if there's one, and give
+		 * the actual frequency the parent needs to run at.
+		 */
+		best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
+								 adj_parent_rate);
+
 		goto out;
 	}
 
 	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
-		unsigned long tmp_rate, parent_rate, adj_parent_rate;
+		unsigned long tmp_rate, parent_rate;
 		struct clk_hw *parent;
 
 		parent = clk_hw_get_parent_by_index(hw, i);
 		if (!parent)
 			continue;
 
-		parent_rate = clk_hw_get_rate(parent);
-		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
-							      parent_rate);
+		parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
+							  clk_hw_get_rate(parent));
+
+		tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
 
-		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
+		/*
+		 * parent_rate might have been modified by our clock.
+		 * Re-apply the pre-divider if there's one, and give
+		 * the actual frequency the parent needs to run at.
+		 */
+		parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
+							    parent_rate);
 		if (tmp_rate == req->rate) {
 			best_parent = parent;
 			best_parent_rate = parent_rate;
-- 
git-series 0.8.11

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

* [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

Currently, the parent rate given back to the clock framework in our
request is the original parent rate we calculated before trying to round
the rate of our clock.

This works fine unless our clock also changes its parent rate, in which
case we will simply ignore that change and still use the previous parent
rate.

Create a new function to re-adjust the parent rate to take the pre-dividers
into account, and give that back to the clock framework.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu_mux.c | 33 ++++++++++++++++++++++++++++-----
 1 file changed, 28 insertions(+), 5 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
index c33210972581..1ce62cc23f8a 100644
--- a/drivers/clk/sunxi-ng/ccu_mux.c
+++ b/drivers/clk/sunxi-ng/ccu_mux.c
@@ -64,6 +64,14 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
 	return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
 }
 
+unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
+					    struct ccu_mux_internal *cm,
+					    int parent_index,
+					    unsigned long parent_rate)
+{
+	return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
+}
+
 int ccu_mux_helper_determine_rate(struct ccu_common *common,
 				  struct ccu_mux_internal *cm,
 				  struct clk_rate_request *req,
@@ -89,22 +97,37 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
 		best_rate = round(cm, best_parent, &adj_parent_rate,
 				  req->rate, data);
 
+		/*
+		 * parent_rate might have been modified by our clock.
+		 * Re-apply the pre-divider if there's one, and give
+		 * the actual frequency the parent needs to run at.
+		 */
+		best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
+								 adj_parent_rate);
+
 		goto out;
 	}
 
 	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
-		unsigned long tmp_rate, parent_rate, adj_parent_rate;
+		unsigned long tmp_rate, parent_rate;
 		struct clk_hw *parent;
 
 		parent = clk_hw_get_parent_by_index(hw, i);
 		if (!parent)
 			continue;
 
-		parent_rate = clk_hw_get_rate(parent);
-		adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
-							      parent_rate);
+		parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
+							  clk_hw_get_rate(parent));
+
+		tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
 
-		tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
+		/*
+		 * parent_rate might have been modified by our clock.
+		 * Re-apply the pre-divider if there's one, and give
+		 * the actual frequency the parent needs to run at.
+		 */
+		parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
+							    parent_rate);
 		if (tmp_rate == req->rate) {
 			best_parent = parent;
 			best_parent_rate = parent_rate;
-- 
git-series 0.8.11

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

* [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The video PLLs are used directly by the HDMI controller. Export them so
that we can use them in our DT node.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu-sun5i.h      | 6 ++++--
 include/dt-bindings/clock/sun5i-ccu.h | 3 +++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu-sun5i.h b/drivers/clk/sunxi-ng/ccu-sun5i.h
index 8144487eb7ca..93a275fbd9a9 100644
--- a/drivers/clk/sunxi-ng/ccu-sun5i.h
+++ b/drivers/clk/sunxi-ng/ccu-sun5i.h
@@ -28,15 +28,17 @@
 #define CLK_PLL_AUDIO_4X	6
 #define CLK_PLL_AUDIO_8X	7
 #define CLK_PLL_VIDEO0		8
-#define CLK_PLL_VIDEO0_2X	9
+
+/* The PLL_VIDEO0_2X is exported for HDMI */
+
 #define CLK_PLL_VE		10
 #define CLK_PLL_DDR_BASE	11
 #define CLK_PLL_DDR		12
 #define CLK_PLL_DDR_OTHER	13
 #define CLK_PLL_PERIPH		14
 #define CLK_PLL_VIDEO1		15
-#define CLK_PLL_VIDEO1_2X	16
 
+/* The PLL_VIDEO1_2X is exported for HDMI */
 /* The CPU clock is exported */
 
 #define CLK_AXI			18
diff --git a/include/dt-bindings/clock/sun5i-ccu.h b/include/dt-bindings/clock/sun5i-ccu.h
index aeb2e2f781fb..81f34d477aeb 100644
--- a/include/dt-bindings/clock/sun5i-ccu.h
+++ b/include/dt-bindings/clock/sun5i-ccu.h
@@ -19,6 +19,9 @@
 
 #define CLK_HOSC		1
 
+#define CLK_PLL_VIDEO0_2X	9
+
+#define CLK_PLL_VIDEO1_2X	16
 #define CLK_CPU			17
 
 #define CLK_AHB_OTG		23
-- 
git-series 0.8.11

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

* [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The video PLLs are used directly by the HDMI controller. Export them so
that we can use them in our DT node.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/clk/sunxi-ng/ccu-sun5i.h      | 6 ++++--
 include/dt-bindings/clock/sun5i-ccu.h | 3 +++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu-sun5i.h b/drivers/clk/sunxi-ng/ccu-sun5i.h
index 8144487eb7ca..93a275fbd9a9 100644
--- a/drivers/clk/sunxi-ng/ccu-sun5i.h
+++ b/drivers/clk/sunxi-ng/ccu-sun5i.h
@@ -28,15 +28,17 @@
 #define CLK_PLL_AUDIO_4X	6
 #define CLK_PLL_AUDIO_8X	7
 #define CLK_PLL_VIDEO0		8
-#define CLK_PLL_VIDEO0_2X	9
+
+/* The PLL_VIDEO0_2X is exported for HDMI */
+
 #define CLK_PLL_VE		10
 #define CLK_PLL_DDR_BASE	11
 #define CLK_PLL_DDR		12
 #define CLK_PLL_DDR_OTHER	13
 #define CLK_PLL_PERIPH		14
 #define CLK_PLL_VIDEO1		15
-#define CLK_PLL_VIDEO1_2X	16
 
+/* The PLL_VIDEO1_2X is exported for HDMI */
 /* The CPU clock is exported */
 
 #define CLK_AXI			18
diff --git a/include/dt-bindings/clock/sun5i-ccu.h b/include/dt-bindings/clock/sun5i-ccu.h
index aeb2e2f781fb..81f34d477aeb 100644
--- a/include/dt-bindings/clock/sun5i-ccu.h
+++ b/include/dt-bindings/clock/sun5i-ccu.h
@@ -19,6 +19,9 @@
 
 #define CLK_HOSC		1
 
+#define CLK_PLL_VIDEO0_2X	9
+
+#define CLK_PLL_VIDEO1_2X	16
 #define CLK_CPU			17
 
 #define CLK_AHB_OTG		23
-- 
git-series 0.8.11

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

* [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The video PLLs are used directly by the HDMI controller. Export them so
that we can use them in our DT node.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/clk/sunxi-ng/ccu-sun5i.h      | 6 ++++--
 include/dt-bindings/clock/sun5i-ccu.h | 3 +++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu-sun5i.h b/drivers/clk/sunxi-ng/ccu-sun5i.h
index 8144487eb7ca..93a275fbd9a9 100644
--- a/drivers/clk/sunxi-ng/ccu-sun5i.h
+++ b/drivers/clk/sunxi-ng/ccu-sun5i.h
@@ -28,15 +28,17 @@
 #define CLK_PLL_AUDIO_4X	6
 #define CLK_PLL_AUDIO_8X	7
 #define CLK_PLL_VIDEO0		8
-#define CLK_PLL_VIDEO0_2X	9
+
+/* The PLL_VIDEO0_2X is exported for HDMI */
+
 #define CLK_PLL_VE		10
 #define CLK_PLL_DDR_BASE	11
 #define CLK_PLL_DDR		12
 #define CLK_PLL_DDR_OTHER	13
 #define CLK_PLL_PERIPH		14
 #define CLK_PLL_VIDEO1		15
-#define CLK_PLL_VIDEO1_2X	16
 
+/* The PLL_VIDEO1_2X is exported for HDMI */
 /* The CPU clock is exported */
 
 #define CLK_AXI			18
diff --git a/include/dt-bindings/clock/sun5i-ccu.h b/include/dt-bindings/clock/sun5i-ccu.h
index aeb2e2f781fb..81f34d477aeb 100644
--- a/include/dt-bindings/clock/sun5i-ccu.h
+++ b/include/dt-bindings/clock/sun5i-ccu.h
@@ -19,6 +19,9 @@
 
 #define CLK_HOSC		1
 
+#define CLK_PLL_VIDEO0_2X	9
+
+#define CLK_PLL_VIDEO1_2X	16
 #define CLK_CPU			17
 
 #define CLK_AHB_OTG		23
-- 
git-series 0.8.11

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

* [PATCH v2 9/20] drm/sun4i: tcon: Add channel debug
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

While all functions have debug logs, the channel enable and disable are not
logged. Make sure this is the case.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++++
 1 file changed, 4 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 9a83a85529ac..66a5bb9b85e9 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -54,6 +54,8 @@ EXPORT_SYMBOL(sun4i_tcon_enable);
 
 void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
 {
+	DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel);
+
 	/* Disable the TCON's channel */
 	if (channel == 0) {
 		regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@@ -71,6 +73,8 @@ EXPORT_SYMBOL(sun4i_tcon_channel_disable);
 
 void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
 {
+	DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel);
+
 	/* Enable the TCON's channel */
 	if (channel == 0) {
 		regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 9/20] drm/sun4i: tcon: Add channel debug
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

While all functions have debug logs, the channel enable and disable are not
logged. Make sure this is the case.

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++++
 1 file changed, 4 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 9a83a85529ac..66a5bb9b85e9 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -54,6 +54,8 @@ EXPORT_SYMBOL(sun4i_tcon_enable);
 
 void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
 {
+	DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel);
+
 	/* Disable the TCON's channel */
 	if (channel == 0) {
 		regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@@ -71,6 +73,8 @@ EXPORT_SYMBOL(sun4i_tcon_channel_disable);
 
 void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
 {
+	DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel);
+
 	/* Enable the TCON's channel */
 	if (channel == 0) {
 		regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 9/20] drm/sun4i: tcon: Add channel debug
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

While all functions have debug logs, the channel enable and disable are not
logged. Make sure this is the case.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++++
 1 file changed, 4 insertions(+), 0 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 9a83a85529ac..66a5bb9b85e9 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -54,6 +54,8 @@ EXPORT_SYMBOL(sun4i_tcon_enable);
 
 void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel)
 {
+	DRM_DEBUG_DRIVER("Disabling TCON channel %d\n", channel);
+
 	/* Disable the TCON's channel */
 	if (channel == 0) {
 		regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
@@ -71,6 +73,8 @@ EXPORT_SYMBOL(sun4i_tcon_channel_disable);
 
 void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel)
 {
+	DRM_DEBUG_DRIVER("Enabling TCON channel %d\n", channel);
+
 	/* Enable the TCON's channel */
 	if (channel == 0) {
 		regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The muxing can actually happen on both channels on some SoCs, so it makes
more sense to just move it out of the sun4i_tcon1_mode_set function and
create a separate function that needs to be called by the encoders.

Let's do that and convert the existing drivers.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_rgb.c  |  1 +
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 22 ++++++++++++++++------
 drivers/gpu/drm/sun4i/sun4i_tcon.h |  2 ++
 drivers/gpu/drm/sun4i/sun4i_tv.c   |  1 +
 4 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
index 67f0b91a99de..3003d290c635 100644
--- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
+++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
@@ -175,6 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
 	struct sun4i_tcon *tcon = rgb->tcon;
 
 	sun4i_tcon0_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 0, encoder);
 
 	clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 66a5bb9b85e9..0204d9fadb66 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -108,6 +108,22 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
 }
 EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
 
+void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
+			struct drm_encoder *encoder)
+{
+	if (!tcon->quirks->has_unknown_mux)
+		return;
+
+	if (channel != 1)
+		return;
+
+	/*
+	 * FIXME: Undocumented bits
+	 */
+	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+}
+EXPORT_SYMBOL(sun4i_tcon_set_mux);
+
 static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
 				    int channel)
 {
@@ -266,12 +282,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 	regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
 			   SUN4I_TCON_GCTL_IOMAP_MASK,
 			   SUN4I_TCON_GCTL_IOMAP_TCON1);
-
-	/*
-	 * FIXME: Undocumented bits
-	 */
-	if (tcon->quirks->has_unknown_mux)
-		regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
 }
 EXPORT_SYMBOL(sun4i_tcon1_mode_set);
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index f636343a935d..0350936b413c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -190,6 +190,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
 /* Mode Related Controls */
 void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
 				 bool enable);
+void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
+			struct drm_encoder *encoder);
 void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
 			  struct drm_display_mode *mode);
 void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
index 49c49431a053..03c494b8159c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
@@ -393,6 +393,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
 	const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
 
 	sun4i_tcon1_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 1, encoder);
 
 	/* Enable and map the DAC to the output */
 	regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The muxing can actually happen on both channels on some SoCs, so it makes
more sense to just move it out of the sun4i_tcon1_mode_set function and
create a separate function that needs to be called by the encoders.

Let's do that and convert the existing drivers.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_rgb.c  |  1 +
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 22 ++++++++++++++++------
 drivers/gpu/drm/sun4i/sun4i_tcon.h |  2 ++
 drivers/gpu/drm/sun4i/sun4i_tv.c   |  1 +
 4 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
index 67f0b91a99de..3003d290c635 100644
--- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
+++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
@@ -175,6 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
 	struct sun4i_tcon *tcon = rgb->tcon;
 
 	sun4i_tcon0_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 0, encoder);
 
 	clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 66a5bb9b85e9..0204d9fadb66 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -108,6 +108,22 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
 }
 EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
 
+void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
+			struct drm_encoder *encoder)
+{
+	if (!tcon->quirks->has_unknown_mux)
+		return;
+
+	if (channel != 1)
+		return;
+
+	/*
+	 * FIXME: Undocumented bits
+	 */
+	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+}
+EXPORT_SYMBOL(sun4i_tcon_set_mux);
+
 static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
 				    int channel)
 {
@@ -266,12 +282,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 	regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
 			   SUN4I_TCON_GCTL_IOMAP_MASK,
 			   SUN4I_TCON_GCTL_IOMAP_TCON1);
-
-	/*
-	 * FIXME: Undocumented bits
-	 */
-	if (tcon->quirks->has_unknown_mux)
-		regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
 }
 EXPORT_SYMBOL(sun4i_tcon1_mode_set);
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index f636343a935d..0350936b413c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -190,6 +190,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
 /* Mode Related Controls */
 void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
 				 bool enable);
+void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
+			struct drm_encoder *encoder);
 void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
 			  struct drm_display_mode *mode);
 void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
index 49c49431a053..03c494b8159c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
@@ -393,6 +393,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
 	const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
 
 	sun4i_tcon1_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 1, encoder);
 
 	/* Enable and map the DAC to the output */
 	regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The muxing can actually happen on both channels on some SoCs, so it makes
more sense to just move it out of the sun4i_tcon1_mode_set function and
create a separate function that needs to be called by the encoders.

Let's do that and convert the existing drivers.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_rgb.c  |  1 +
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 22 ++++++++++++++++------
 drivers/gpu/drm/sun4i/sun4i_tcon.h |  2 ++
 drivers/gpu/drm/sun4i/sun4i_tv.c   |  1 +
 4 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
index 67f0b91a99de..3003d290c635 100644
--- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
+++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
@@ -175,6 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
 	struct sun4i_tcon *tcon = rgb->tcon;
 
 	sun4i_tcon0_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 0, encoder);
 
 	clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 66a5bb9b85e9..0204d9fadb66 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -108,6 +108,22 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
 }
 EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
 
+void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
+			struct drm_encoder *encoder)
+{
+	if (!tcon->quirks->has_unknown_mux)
+		return;
+
+	if (channel != 1)
+		return;
+
+	/*
+	 * FIXME: Undocumented bits
+	 */
+	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+}
+EXPORT_SYMBOL(sun4i_tcon_set_mux);
+
 static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
 				    int channel)
 {
@@ -266,12 +282,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 	regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
 			   SUN4I_TCON_GCTL_IOMAP_MASK,
 			   SUN4I_TCON_GCTL_IOMAP_TCON1);
-
-	/*
-	 * FIXME: Undocumented bits
-	 */
-	if (tcon->quirks->has_unknown_mux)
-		regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
 }
 EXPORT_SYMBOL(sun4i_tcon1_mode_set);
 
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index f636343a935d..0350936b413c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -190,6 +190,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
 /* Mode Related Controls */
 void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
 				 bool enable);
+void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
+			struct drm_encoder *encoder);
 void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
 			  struct drm_display_mode *mode);
 void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
index 49c49431a053..03c494b8159c 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
@@ -393,6 +393,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
 	const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
 
 	sun4i_tcon1_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 1, encoder);
 
 	/* Enable and map the DAC to the output */
 	regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

Even though that mux is undocumented, it seems like it needs to be set to 1
when using composite, and 0 when using HDMI.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c |  9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0204d9fadb66..1c609d808b86 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -111,16 +111,23 @@ EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
 void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
 			struct drm_encoder *encoder)
 {
+	u32 val;
+
 	if (!tcon->quirks->has_unknown_mux)
 		return;
 
 	if (channel != 1)
 		return;
 
+	if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
+		val = 1;
+	else
+		val = 0;
+
 	/*
 	 * FIXME: Undocumented bits
 	 */
-	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
 }
 EXPORT_SYMBOL(sun4i_tcon_set_mux);
 
-- 
git-series 0.8.11

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

* [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

Even though that mux is undocumented, it seems like it needs to be set to 1
when using composite, and 0 when using HDMI.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c |  9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0204d9fadb66..1c609d808b86 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -111,16 +111,23 @@ EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
 void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
 			struct drm_encoder *encoder)
 {
+	u32 val;
+
 	if (!tcon->quirks->has_unknown_mux)
 		return;
 
 	if (channel != 1)
 		return;
 
+	if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
+		val = 1;
+	else
+		val = 0;
+
 	/*
 	 * FIXME: Undocumented bits
 	 */
-	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
 }
 EXPORT_SYMBOL(sun4i_tcon_set_mux);
 
-- 
git-series 0.8.11

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

* [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

Even though that mux is undocumented, it seems like it needs to be set to 1
when using composite, and 0 when using HDMI.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c |  9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0204d9fadb66..1c609d808b86 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -111,16 +111,23 @@ EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
 void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
 			struct drm_encoder *encoder)
 {
+	u32 val;
+
 	if (!tcon->quirks->has_unknown_mux)
 		return;
 
 	if (channel != 1)
 		return;
 
+	if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
+		val = 1;
+	else
+		val = 0;
+
 	/*
 	 * FIXME: Undocumented bits
 	 */
-	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
+	regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
 }
 EXPORT_SYMBOL(sun4i_tcon_set_mux);
 
-- 
git-series 0.8.11

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

* [PATCH v2 12/20] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

It seems like what's called a backporch in the datasheet is actually the
backporch plus the sync period. Fix that in our driver.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 1c609d808b86..86a806fae9fb 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -262,7 +262,7 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 		     SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay));
 
 	/* Set horizontal display timings */
-	bp = mode->crtc_htotal - mode->crtc_hsync_end;
+	bp = mode->crtc_htotal - mode->crtc_hsync_start;
 	DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
 			 mode->htotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG,
@@ -270,7 +270,7 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 		     SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
 
 	/* Set vertical display timings */
-	bp = mode->crtc_vtotal - mode->crtc_vsync_end;
+	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
 	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
 			 mode->vtotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 12/20] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

It seems like what's called a backporch in the datasheet is actually the
backporch plus the sync period. Fix that in our driver.

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 1c609d808b86..86a806fae9fb 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -262,7 +262,7 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 		     SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay));
 
 	/* Set horizontal display timings */
-	bp = mode->crtc_htotal - mode->crtc_hsync_end;
+	bp = mode->crtc_htotal - mode->crtc_hsync_start;
 	DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
 			 mode->htotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG,
@@ -270,7 +270,7 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 		     SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
 
 	/* Set vertical display timings */
-	bp = mode->crtc_vtotal - mode->crtc_vsync_end;
+	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
 	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
 			 mode->vtotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 12/20] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

It seems like what's called a backporch in the datasheet is actually the
backporch plus the sync period. Fix that in our driver.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 1c609d808b86..86a806fae9fb 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -262,7 +262,7 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 		     SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay));
 
 	/* Set horizontal display timings */
-	bp = mode->crtc_htotal - mode->crtc_hsync_end;
+	bp = mode->crtc_htotal - mode->crtc_hsync_start;
 	DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n",
 			 mode->htotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG,
@@ -270,7 +270,7 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 		     SUN4I_TCON1_BASIC3_H_BACKPORCH(bp));
 
 	/* Set vertical display timings */
-	bp = mode->crtc_vtotal - mode->crtc_vsync_end;
+	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
 	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
 			 mode->vtotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
-- 
git-series 0.8.11

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

* [PATCH v2 13/20] drm/sun4i: tcon: Change vertical total size computation inconsistency
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

Both TCON channels need to have the resolution doubled, since the size the
hardware is going to use is whatever we put in the register divided by two.

However, we handle it differently for the two channels: in the channel 0,
our register access macro does the multiplication of the value passed as
paremeter, while in the channel 1, the macro doesn't do this, and we need
to do it before calling it.

Make this consistent by aligning the channel 0 with the channel 1
behaviour.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 86a806fae9fb..0f91ec8a4b26 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -190,7 +190,7 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
 
 	/* Set vertical display timings */
 	regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
-		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) |
+		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
 		     SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
 
 	/* Set Hsync and Vsync length */
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index 0350936b413c..3517122ee679 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -51,7 +51,7 @@
 #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)		(((bp) - 1) & 0xfff)
 
 #define SUN4I_TCON0_BASIC2_REG			0x50
-#define SUN4I_TCON0_BASIC2_V_TOTAL(total)		((((total) * 2) & 0x1fff) << 16)
+#define SUN4I_TCON0_BASIC2_V_TOTAL(total)		(((total) & 0x1fff) << 16)
 #define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)		(((bp) - 1) & 0xfff)
 
 #define SUN4I_TCON0_BASIC3_REG			0x54
-- 
git-series 0.8.11

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

* [PATCH v2 13/20] drm/sun4i: tcon: Change vertical total size computation inconsistency
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

Both TCON channels need to have the resolution doubled, since the size the
hardware is going to use is whatever we put in the register divided by two.

However, we handle it differently for the two channels: in the channel 0,
our register access macro does the multiplication of the value passed as
paremeter, while in the channel 1, the macro doesn't do this, and we need
to do it before calling it.

Make this consistent by aligning the channel 0 with the channel 1
behaviour.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 86a806fae9fb..0f91ec8a4b26 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -190,7 +190,7 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
 
 	/* Set vertical display timings */
 	regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
-		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) |
+		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
 		     SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
 
 	/* Set Hsync and Vsync length */
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index 0350936b413c..3517122ee679 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -51,7 +51,7 @@
 #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)		(((bp) - 1) & 0xfff)
 
 #define SUN4I_TCON0_BASIC2_REG			0x50
-#define SUN4I_TCON0_BASIC2_V_TOTAL(total)		((((total) * 2) & 0x1fff) << 16)
+#define SUN4I_TCON0_BASIC2_V_TOTAL(total)		(((total) & 0x1fff) << 16)
 #define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)		(((bp) - 1) & 0xfff)
 
 #define SUN4I_TCON0_BASIC3_REG			0x54
-- 
git-series 0.8.11

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

* [PATCH v2 13/20] drm/sun4i: tcon: Change vertical total size computation inconsistency
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

Both TCON channels need to have the resolution doubled, since the size the
hardware is going to use is whatever we put in the register divided by two.

However, we handle it differently for the two channels: in the channel 0,
our register access macro does the multiplication of the value passed as
paremeter, while in the channel 1, the macro doesn't do this, and we need
to do it before calling it.

Make this consistent by aligning the channel 0 with the channel 1
behaviour.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 2 +-
 drivers/gpu/drm/sun4i/sun4i_tcon.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 86a806fae9fb..0f91ec8a4b26 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -190,7 +190,7 @@ void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
 
 	/* Set vertical display timings */
 	regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG,
-		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) |
+		     SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal * 2) |
 		     SUN4I_TCON0_BASIC2_V_BACKPORCH(bp));
 
 	/* Set Hsync and Vsync length */
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index 0350936b413c..3517122ee679 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -51,7 +51,7 @@
 #define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)		(((bp) - 1) & 0xfff)
 
 #define SUN4I_TCON0_BASIC2_REG			0x50
-#define SUN4I_TCON0_BASIC2_V_TOTAL(total)		((((total) * 2) & 0x1fff) << 16)
+#define SUN4I_TCON0_BASIC2_V_TOTAL(total)		(((total) & 0x1fff) << 16)
 #define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)		(((bp) - 1) & 0xfff)
 
 #define SUN4I_TCON0_BASIC3_REG			0x54
-- 
git-series 0.8.11

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

* [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

It appears that the total vertical resolution needs to be doubled when
we're not in interlaced. Make sure that is the case.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0f91ec8a4b26..efa079c1a3f5 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 	/* Set vertical display timings */
 	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
 	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
-			 mode->vtotal, bp);
+			 mode->crtc_vtotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
-		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
+		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |
 		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
 
 	/* Set Hsync and Vsync length */
-- 
git-series 0.8.11

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

* [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

It appears that the total vertical resolution needs to be doubled when
we're not in interlaced. Make sure that is the case.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0f91ec8a4b26..efa079c1a3f5 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 	/* Set vertical display timings */
 	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
 	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
-			 mode->vtotal, bp);
+			 mode->crtc_vtotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
-		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
+		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |
 		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
 
 	/* Set Hsync and Vsync length */
-- 
git-series 0.8.11

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

* [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

It appears that the total vertical resolution needs to be doubled when
we're not in interlaced. Make sure that is the case.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 0f91ec8a4b26..efa079c1a3f5 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
 	/* Set vertical display timings */
 	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
 	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
-			 mode->vtotal, bp);
+			 mode->crtc_vtotal, bp);
 	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
-		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
+		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |
 		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
 
 	/* Set Hsync and Vsync length */
-- 
git-series 0.8.11

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

* [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The generic connectors such as hdmi-connector doesn't have any driver in,
so if they are added to the component list, we will be waiting forever for
a non-existing driver to probe.

Add a list of the connectors we want to ignore when building our component
list.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_drv.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
index 8ddd72cd5873..8c9d2e36be55 100644
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
@@ -164,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
 	.unbind	= sun4i_drv_unbind,
 };
 
+static bool sun4i_drv_node_is_connector(struct device_node *node)
+{
+	return of_device_is_compatible(node, "hdmi-connector");
+}
+
 static bool sun4i_drv_node_is_frontend(struct device_node *node)
 {
 	return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
@@ -204,7 +209,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
 	    !of_device_is_available(node))
 		return 0;
 
-	if (!sun4i_drv_node_is_frontend(node)) {
+	if (!sun4i_drv_node_is_connector(node) &&
+	    !sun4i_drv_node_is_frontend(node)) {
 		/* Add current component */
 		DRM_DEBUG_DRIVER("Adding component %s\n",
 				 of_node_full_name(node));
-- 
git-series 0.8.11

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

* [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The generic connectors such as hdmi-connector doesn't have any driver in,
so if they are added to the component list, we will be waiting forever for
a non-existing driver to probe.

Add a list of the connectors we want to ignore when building our component
list.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/sun4i_drv.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
index 8ddd72cd5873..8c9d2e36be55 100644
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
@@ -164,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
 	.unbind	= sun4i_drv_unbind,
 };
 
+static bool sun4i_drv_node_is_connector(struct device_node *node)
+{
+	return of_device_is_compatible(node, "hdmi-connector");
+}
+
 static bool sun4i_drv_node_is_frontend(struct device_node *node)
 {
 	return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
@@ -204,7 +209,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
 	    !of_device_is_available(node))
 		return 0;
 
-	if (!sun4i_drv_node_is_frontend(node)) {
+	if (!sun4i_drv_node_is_connector(node) &&
+	    !sun4i_drv_node_is_frontend(node)) {
 		/* Add current component */
 		DRM_DEBUG_DRIVER("Adding component %s\n",
 				 of_node_full_name(node));
-- 
git-series 0.8.11

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

* [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The generic connectors such as hdmi-connector doesn't have any driver in,
so if they are added to the component list, we will be waiting forever for
a non-existing driver to probe.

Add a list of the connectors we want to ignore when building our component
list.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/sun4i_drv.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
index 8ddd72cd5873..8c9d2e36be55 100644
--- a/drivers/gpu/drm/sun4i/sun4i_drv.c
+++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
@@ -164,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
 	.unbind	= sun4i_drv_unbind,
 };
 
+static bool sun4i_drv_node_is_connector(struct device_node *node)
+{
+	return of_device_is_compatible(node, "hdmi-connector");
+}
+
 static bool sun4i_drv_node_is_frontend(struct device_node *node)
 {
 	return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
@@ -204,7 +209,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
 	    !of_device_is_available(node))
 		return 0;
 
-	if (!sun4i_drv_node_is_frontend(node)) {
+	if (!sun4i_drv_node_is_connector(node) &&
+	    !sun4i_drv_node_is_frontend(node)) {
 		/* Add current component */
 		DRM_DEBUG_DRIVER("Adding component %s\n",
 				 of_node_full_name(node));
-- 
git-series 0.8.11

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

* [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

One of the possible output of the display pipeline, on the SoCs that have
it, is the HDMI controller.

Add a binding for it.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 79 +++++++-
 1 file changed, 79 insertions(+), 0 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 57a8d0610062..91ce2c920750 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -4,6 +4,34 @@ Allwinner A10 Display Pipeline
 The Allwinner A10 Display pipeline is composed of several components
 that are going to be documented below:
 
+HDMI Encoder
+------------
+
+The HDMI Encoder supports the HDMI video and audio outputs, and does
+CEC. It is one end of the pipeline.
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun5i-a10s-hdmi
+  - reg: base address and size of memory-mapped region
+  - interrupts: interrupt associated to this IP
+  - clocks: phandles to the clocks feeding the HDMI encoder
+    * ahb: the HDMI interface clock
+    * mod: the HDMI module clock
+    * pll-0: the first video PLL
+    * pll-1: the second video PLL
+  - clock-names: the clock names mentioned above
+  - dmas: phandles to the DMA channels used by the HDMI encoder
+    * ddc-tx: The channel for DDC transmission
+    * ddc-rx: The channel for DDC reception
+    * audio-tx: The channel used for audio transmission
+  - dma-names: the channel names mentioned above
+
+  - ports: A ports node with endpoint definitions as defined in
+    Documentation/devicetree/bindings/media/video-interfaces.txt. The
+    first port should be the input endpoint. The second should be the
+    output, usually to an HDMI connector.
+
 TV Encoder
 ----------
 
@@ -173,6 +201,57 @@ panel: panel {
 	};
 };
 
+connector {
+	compatible = "hdmi-connector";
+	type = "a";
+
+	port {
+		hdmi_con_in: endpoint {
+			remote-endpoint = <&hdmi_out_con>;
+		};
+	};
+};
+
+hdmi: hdmi@01c16000 {
+	compatible = "allwinner,sun5i-a10s-hdmi";
+	reg = <0x01c16000 0x1000>;
+	interrupts = <58>;
+	clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
+		 <&ccu CLK_PLL_VIDEO0_2X>,
+		 <&ccu CLK_PLL_VIDEO1_2X>;
+	clock-names = "ahb", "mod", "pll-0", "pll-1";
+	dmas = <&dma SUN4I_DMA_NORMAL 16>,
+	       <&dma SUN4I_DMA_NORMAL 16>,
+	       <&dma SUN4I_DMA_DEDICATED 24>;
+	dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+	status = "disabled";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0>;
+
+			hdmi_in_tcon0: endpoint {
+				remote-endpoint = <&tcon0_out_hdmi>;
+			};
+		};
+
+		port@1 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <1>;
+
+			hdmi_out_con: endpoint {
+				remote-endpoint = <&hdmi_con_in>;
+			};
+		};
+	};
+};
+
 tve0: tv-encoder@01c0a000 {
 	compatible = "allwinner,sun4i-a10-tv-encoder";
 	reg = <0x01c0a000 0x1000>;
-- 
git-series 0.8.11

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

* [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

One of the possible output of the display pipeline, on the SoCs that have
it, is the HDMI controller.

Add a binding for it.

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 79 +++++++-
 1 file changed, 79 insertions(+), 0 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 57a8d0610062..91ce2c920750 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -4,6 +4,34 @@ Allwinner A10 Display Pipeline
 The Allwinner A10 Display pipeline is composed of several components
 that are going to be documented below:
 
+HDMI Encoder
+------------
+
+The HDMI Encoder supports the HDMI video and audio outputs, and does
+CEC. It is one end of the pipeline.
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun5i-a10s-hdmi
+  - reg: base address and size of memory-mapped region
+  - interrupts: interrupt associated to this IP
+  - clocks: phandles to the clocks feeding the HDMI encoder
+    * ahb: the HDMI interface clock
+    * mod: the HDMI module clock
+    * pll-0: the first video PLL
+    * pll-1: the second video PLL
+  - clock-names: the clock names mentioned above
+  - dmas: phandles to the DMA channels used by the HDMI encoder
+    * ddc-tx: The channel for DDC transmission
+    * ddc-rx: The channel for DDC reception
+    * audio-tx: The channel used for audio transmission
+  - dma-names: the channel names mentioned above
+
+  - ports: A ports node with endpoint definitions as defined in
+    Documentation/devicetree/bindings/media/video-interfaces.txt. The
+    first port should be the input endpoint. The second should be the
+    output, usually to an HDMI connector.
+
 TV Encoder
 ----------
 
@@ -173,6 +201,57 @@ panel: panel {
 	};
 };
 
+connector {
+	compatible = "hdmi-connector";
+	type = "a";
+
+	port {
+		hdmi_con_in: endpoint {
+			remote-endpoint = <&hdmi_out_con>;
+		};
+	};
+};
+
+hdmi: hdmi@01c16000 {
+	compatible = "allwinner,sun5i-a10s-hdmi";
+	reg = <0x01c16000 0x1000>;
+	interrupts = <58>;
+	clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
+		 <&ccu CLK_PLL_VIDEO0_2X>,
+		 <&ccu CLK_PLL_VIDEO1_2X>;
+	clock-names = "ahb", "mod", "pll-0", "pll-1";
+	dmas = <&dma SUN4I_DMA_NORMAL 16>,
+	       <&dma SUN4I_DMA_NORMAL 16>,
+	       <&dma SUN4I_DMA_DEDICATED 24>;
+	dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+	status = "disabled";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0>;
+
+			hdmi_in_tcon0: endpoint {
+				remote-endpoint = <&tcon0_out_hdmi>;
+			};
+		};
+
+		port@1 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <1>;
+
+			hdmi_out_con: endpoint {
+				remote-endpoint = <&hdmi_con_in>;
+			};
+		};
+	};
+};
+
 tve0: tv-encoder@01c0a000 {
 	compatible = "allwinner,sun4i-a10-tv-encoder";
 	reg = <0x01c0a000 0x1000>;
-- 
git-series 0.8.11

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

* [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

One of the possible output of the display pipeline, on the SoCs that have
it, is the HDMI controller.

Add a binding for it.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 79 +++++++-
 1 file changed, 79 insertions(+), 0 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 57a8d0610062..91ce2c920750 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -4,6 +4,34 @@ Allwinner A10 Display Pipeline
 The Allwinner A10 Display pipeline is composed of several components
 that are going to be documented below:
 
+HDMI Encoder
+------------
+
+The HDMI Encoder supports the HDMI video and audio outputs, and does
+CEC. It is one end of the pipeline.
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun5i-a10s-hdmi
+  - reg: base address and size of memory-mapped region
+  - interrupts: interrupt associated to this IP
+  - clocks: phandles to the clocks feeding the HDMI encoder
+    * ahb: the HDMI interface clock
+    * mod: the HDMI module clock
+    * pll-0: the first video PLL
+    * pll-1: the second video PLL
+  - clock-names: the clock names mentioned above
+  - dmas: phandles to the DMA channels used by the HDMI encoder
+    * ddc-tx: The channel for DDC transmission
+    * ddc-rx: The channel for DDC reception
+    * audio-tx: The channel used for audio transmission
+  - dma-names: the channel names mentioned above
+
+  - ports: A ports node with endpoint definitions as defined in
+    Documentation/devicetree/bindings/media/video-interfaces.txt. The
+    first port should be the input endpoint. The second should be the
+    output, usually to an HDMI connector.
+
 TV Encoder
 ----------
 
@@ -173,6 +201,57 @@ panel: panel {
 	};
 };
 
+connector {
+	compatible = "hdmi-connector";
+	type = "a";
+
+	port {
+		hdmi_con_in: endpoint {
+			remote-endpoint = <&hdmi_out_con>;
+		};
+	};
+};
+
+hdmi: hdmi at 01c16000 {
+	compatible = "allwinner,sun5i-a10s-hdmi";
+	reg = <0x01c16000 0x1000>;
+	interrupts = <58>;
+	clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
+		 <&ccu CLK_PLL_VIDEO0_2X>,
+		 <&ccu CLK_PLL_VIDEO1_2X>;
+	clock-names = "ahb", "mod", "pll-0", "pll-1";
+	dmas = <&dma SUN4I_DMA_NORMAL 16>,
+	       <&dma SUN4I_DMA_NORMAL 16>,
+	       <&dma SUN4I_DMA_DEDICATED 24>;
+	dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+	status = "disabled";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port at 0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0>;
+
+			hdmi_in_tcon0: endpoint {
+				remote-endpoint = <&tcon0_out_hdmi>;
+			};
+		};
+
+		port at 1 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <1>;
+
+			hdmi_out_con: endpoint {
+				remote-endpoint = <&hdmi_con_in>;
+			};
+		};
+	};
+};
+
 tve0: tv-encoder at 01c0a000 {
 	compatible = "allwinner,sun4i-a10-tv-encoder";
 	reg = <0x01c0a000 0x1000>;
-- 
git-series 0.8.11

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

* [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The Allwinner Timings Controller has two, mutually exclusive, channels.
When the binding has been introduced, it was assumed that there would be
only a single user per channel in the system.

While this is likely for the channel 0 which only connects to LCD displays,
it turns out that the channel 1 can be connected to multiple controllers in
the SoC (HDMI and TV encoders for example). And while the simultaneous use
of HDMI and TV outputs cannot be achieved, switching from one to the other
at runtime definitely sounds plausible.

Add an extra property, allwinner,tcon-channel, to specify for a given
endpoint which TCON channel it is connected to, while falling back to the
previous mechanism if that property is missing.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 91ce2c920750..acdc0c17c00e 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -75,10 +75,13 @@ Required properties:
   Documentation/devicetree/bindings/media/video-interfaces.txt. The
   first port should be the input endpoint, the second one the output
 
-  The output should have two endpoints. The first is the block
-  connected to the TCON channel 0 (usually a panel or a bridge), the
-  second the block connected to the TCON channel 1 (usually the TV
-  encoder)
+  The output may have multiple endpoints. The TCON has two channels,
+  usually with the first channel being used for the panels interfaces
+  (RGB, LVDS, etc.), and the second being used for the outputs that
+  require another controller (TV Encoder, HDMI, etc.). The endpoints
+  will take an extra property, allwinner,tcon-channel, to specify the
+  channel the endpoint is associated to. If that property is not
+  present, the endpoint number will be used as the channel number.
 
 On SoCs other than the A33, there is one more clock required:
    - 'tcon-ch1': The clock driving the TCON channel 1
-- 
git-series 0.8.11

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

* [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The Allwinner Timings Controller has two, mutually exclusive, channels.
When the binding has been introduced, it was assumed that there would be
only a single user per channel in the system.

While this is likely for the channel 0 which only connects to LCD displays,
it turns out that the channel 1 can be connected to multiple controllers in
the SoC (HDMI and TV encoders for example). And while the simultaneous use
of HDMI and TV outputs cannot be achieved, switching from one to the other
at runtime definitely sounds plausible.

Add an extra property, allwinner,tcon-channel, to specify for a given
endpoint which TCON channel it is connected to, while falling back to the
previous mechanism if that property is missing.

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 91ce2c920750..acdc0c17c00e 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -75,10 +75,13 @@ Required properties:
   Documentation/devicetree/bindings/media/video-interfaces.txt. The
   first port should be the input endpoint, the second one the output
 
-  The output should have two endpoints. The first is the block
-  connected to the TCON channel 0 (usually a panel or a bridge), the
-  second the block connected to the TCON channel 1 (usually the TV
-  encoder)
+  The output may have multiple endpoints. The TCON has two channels,
+  usually with the first channel being used for the panels interfaces
+  (RGB, LVDS, etc.), and the second being used for the outputs that
+  require another controller (TV Encoder, HDMI, etc.). The endpoints
+  will take an extra property, allwinner,tcon-channel, to specify the
+  channel the endpoint is associated to. If that property is not
+  present, the endpoint number will be used as the channel number.
 
 On SoCs other than the A33, there is one more clock required:
    - 'tcon-ch1': The clock driving the TCON channel 1
-- 
git-series 0.8.11

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

* [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner, tcon-channel property
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The Allwinner Timings Controller has two, mutually exclusive, channels.
When the binding has been introduced, it was assumed that there would be
only a single user per channel in the system.

While this is likely for the channel 0 which only connects to LCD displays,
it turns out that the channel 1 can be connected to multiple controllers in
the SoC (HDMI and TV encoders for example). And while the simultaneous use
of HDMI and TV outputs cannot be achieved, switching from one to the other
at runtime definitely sounds plausible.

Add an extra property, allwinner,tcon-channel, to specify for a given
endpoint which TCON channel it is connected to, while falling back to the
previous mechanism if that property is missing.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 91ce2c920750..acdc0c17c00e 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -75,10 +75,13 @@ Required properties:
   Documentation/devicetree/bindings/media/video-interfaces.txt. The
   first port should be the input endpoint, the second one the output
 
-  The output should have two endpoints. The first is the block
-  connected to the TCON channel 0 (usually a panel or a bridge), the
-  second the block connected to the TCON channel 1 (usually the TV
-  encoder)
+  The output may have multiple endpoints. The TCON has two channels,
+  usually with the first channel being used for the panels interfaces
+  (RGB, LVDS, etc.), and the second being used for the outputs that
+  require another controller (TV Encoder, HDMI, etc.). The endpoints
+  will take an extra property, allwinner,tcon-channel, to specify the
+  channel the endpoint is associated to. If that property is not
+  present, the endpoint number will be used as the channel number.
 
 On SoCs other than the A33, there is one more clock required:
    - 'tcon-ch1': The clock driving the TCON channel 1
-- 
git-series 0.8.11

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

* [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
controller.

That HDMI controller is able to do audio and CEC, but those have been left
out for now.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/Kconfig               |   9 +-
 drivers/gpu/drm/sun4i/Makefile              |   6 +-
 drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
 6 files changed, 1017 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index a4b357db8856..35299c4e2594 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -12,3 +12,12 @@ config DRM_SUN4I
 	  Choose this option if you have an Allwinner SoC with a
 	  Display Engine. If M is selected the module will be called
 	  sun4i-drm.
+
+if DRM_SUN4I
+config DRM_SUN4I_HDMI
+       tristate "Allwinner A10 HDMI Controller Support"
+       help
+	  Choose this option if you have an Allwinner SoC with an HDMI
+	  controller.
+
+endif
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 59b757350a1f..c09bf8093710 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i-drm.o sun4i-tcon.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun4i_backend.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun6i_drc.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
+
+sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
+sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
+sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
+
+obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
new file mode 100644
index 000000000000..40d57b195b48
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#ifndef _SUN4I_HDMI_H_
+#define _SUN4I_HDMI_H_
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+#define SUN4I_HDMI_CTRL_REG		0x004
+#define SUN4I_HDMI_CTRL_ENABLE			BIT(31)
+
+#define SUN4I_HDMI_IRQ_REG		0x008
+#define SUN4I_HDMI_IRQ_STA_MASK			0x73
+#define SUN4I_HDMI_IRQ_STA_FIFO_OF		BIT(1)
+#define SUN4I_HDMI_IRQ_STA_FIFO_UF		BIT(0)
+
+#define SUN4I_HDMI_HPD_REG		0x00c
+#define SUN4I_HDMI_HPD_HIGH			BIT(0)
+
+#define SUN4I_HDMI_VID_CTRL_REG		0x010
+#define SUN4I_HDMI_VID_CTRL_ENABLE		BIT(31)
+#define SUN4I_HDMI_VID_CTRL_HDMI_MODE		BIT(30)
+
+#define SUN4I_HDMI_VID_TIMING_ACT_REG	0x014
+#define SUN4I_HDMI_VID_TIMING_BP_REG	0x018
+#define SUN4I_HDMI_VID_TIMING_FP_REG	0x01c
+#define SUN4I_HDMI_VID_TIMING_SPW_REG	0x020
+
+#define SUN4I_HDMI_VID_TIMING_X(x)		((((x) - 1) & GENMASK(11, 0)))
+#define SUN4I_HDMI_VID_TIMING_Y(y)		((((y) - 1) & GENMASK(11, 0)) << 16)
+
+#define SUN4I_HDMI_VID_TIMING_POL_REG	0x024
+#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
+#define SUN4I_HDMI_VID_TIMING_POL_VSYNC		BIT(1)
+#define SUN4I_HDMI_VID_TIMING_POL_HSYNC		BIT(0)
+
+#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)	(0x080 + (n))
+
+#define SUN4I_HDMI_PAD_CTRL0_REG	0x200
+#define SUN4I_HDMI_PAD_CTRL0_BIASEN		BIT(31)
+#define SUN4I_HDMI_PAD_CTRL0_LDOCEN		BIT(30)
+#define SUN4I_HDMI_PAD_CTRL0_LDODEN		BIT(29)
+#define SUN4I_HDMI_PAD_CTRL0_PWENC		BIT(28)
+#define SUN4I_HDMI_PAD_CTRL0_PWEND		BIT(27)
+#define SUN4I_HDMI_PAD_CTRL0_PWENG		BIT(26)
+#define SUN4I_HDMI_PAD_CTRL0_CKEN		BIT(25)
+#define SUN4I_HDMI_PAD_CTRL0_TXEN		BIT(23)
+
+#define SUN4I_HDMI_PAD_CTRL1_REG	0x204
+#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT		BIT(23)
+#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT		BIT(22)
+#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT		BIT(20)
+#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT		BIT(19)
+#define SUN4I_HDMI_PAD_CTRL1_REG_DEN		BIT(15)
+#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK		BIT(14)
+#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)		(((n) & 7) << 10)
+#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK		BIT(6)
+#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)		(((n) & 7) << 3)
+
+#define SUN4I_HDMI_PLL_CTRL_REG		0x208
+#define SUN4I_HDMI_PLL_CTRL_PLL_EN		BIT(31)
+#define SUN4I_HDMI_PLL_CTRL_BWS			BIT(30)
+#define SUN4I_HDMI_PLL_CTRL_HV_IS_33		BIT(29)
+#define SUN4I_HDMI_PLL_CTRL_LDO1_EN		BIT(28)
+#define SUN4I_HDMI_PLL_CTRL_LDO2_EN		BIT(27)
+#define SUN4I_HDMI_PLL_CTRL_SDIV2		BIT(25)
+#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)		(((n) & 7) << 20)
+#define SUN4I_HDMI_PLL_CTRL_S(n)		(((n) & 7) << 16)
+#define SUN4I_HDMI_PLL_CTRL_CP_S(n)		(((n) & 0xf) << 12)
+#define SUN4I_HDMI_PLL_CTRL_CS(n)		(((n) & 0xf) << 8)
+#define SUN4I_HDMI_PLL_CTRL_DIV(n)		(((n) & 0xf) << 4)
+#define SUN4I_HDMI_PLL_CTRL_DIV_MASK		GENMASK(7, 4)
+#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)		((n) & 0xf)
+
+#define SUN4I_HDMI_PLL_DBG0_REG		0x20c
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)	(((n) & 1) << 21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK	BIT(21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT	21
+
+#define SUN4I_HDMI_PKT_CTRL_REG(n)	(0x2f0 + (4 * (n)))
+#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)		((t) << (((n) % 4) * 4))
+
+#define SUN4I_HDMI_UNKNOWN_REG		0x300
+#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC		BIT(27)
+
+#define SUN4I_HDMI_DDC_CTRL_REG		0x500
+#define SUN4I_HDMI_DDC_CTRL_ENABLE		BIT(31)
+#define SUN4I_HDMI_DDC_CTRL_START_CMD		BIT(30)
+#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK	BIT(8)
+#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ	(0 << 8)
+#define SUN4I_HDMI_DDC_CTRL_RESET		BIT(0)
+
+#define SUN4I_HDMI_DDC_ADDR_REG		0x504
+#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)	(((seg) & 0xff) << 24)
+#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)		(((addr) & 0xff) << 16)
+#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)		(((off) & 0xff) << 8)
+#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)		((addr) & 0xff)
+
+#define SUN4I_HDMI_DDC_FIFO_CTRL_REG	0x510
+#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR		BIT(31)
+
+#define SUN4I_HDMI_DDC_FIFO_DATA_REG	0x518
+#define SUN4I_HDMI_DDC_BYTE_COUNT_REG	0x51c
+
+#define SUN4I_HDMI_DDC_CMD_REG		0x520
+#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ	6
+
+#define SUN4I_HDMI_DDC_CLK_REG		0x528
+#define SUN4I_HDMI_DDC_CLK_M(m)			(((m) & 0x7) << 3)
+#define SUN4I_HDMI_DDC_CLK_N(n)			((n) & 0x7)
+
+#define SUN4I_HDMI_DDC_LINE_CTRL_REG	0x540
+#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE	BIT(9)
+#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE	BIT(8)
+
+#define SUN4I_HDMI_DDC_FIFO_SIZE	16
+
+enum sun4i_hdmi_pkt_type {
+	SUN4I_HDMI_PKT_AVI = 2,
+	SUN4I_HDMI_PKT_END = 15,
+};
+
+struct sun4i_hdmi {
+	struct drm_connector	connector;
+	struct drm_encoder	encoder;
+	struct device		*dev;
+
+	void __iomem		*base;
+
+	/* Parent clocks */
+	struct clk		*bus_clk;
+	struct clk		*mod_clk;
+	struct clk		*pll0_clk;
+	struct clk		*pll1_clk;
+
+	/* And the clocks we create */
+	struct clk		*ddc_clk;
+	struct clk		*tmds_clk;
+
+	struct sun4i_drv	*drv;
+
+	bool			hdmi_monitor;
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
+
+#endif /* _SUN4I_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
new file mode 100644
index 000000000000..4692e8c345ed
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_ddc {
+	struct clk_hw		hw;
+	struct sun4i_hdmi	*hdmi;
+};
+
+static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_ddc, hw);
+}
+
+static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
+					    unsigned long parent_rate,
+					    u8 *m, u8 *n)
+{
+	unsigned long best_rate = 0;
+	u8 best_m = 0, best_n = 0, _m, _n;
+
+	for (_m = 0; _m < 8; _m++) {
+		for (_n = 0; _n < 8; _n++) {
+			unsigned long tmp_rate;
+
+			tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
+
+			if (tmp_rate > rate)
+				continue;
+
+			if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = _m;
+				best_n = _n;
+			}
+		}
+	}
+
+	if (m && n) {
+		*m = best_m;
+		*n = best_n;
+	}
+
+	return best_rate;
+}
+
+static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long *prate)
+{
+	return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
+}
+
+static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+	u32 reg;
+	u8 m, n;
+
+	reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+	m = (reg >> 3) & 0x7;
+	n = reg & 0x7;
+
+	return (((parent_rate / 2) / 10) >> n) / (m + 1);
+}
+
+static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long parent_rate)
+{
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+	u8 div_m, div_n;
+
+	sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
+
+	writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
+	       ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+
+	return 0;
+}
+
+static const struct clk_ops sun4i_ddc_ops = {
+	.recalc_rate	= sun4i_ddc_recalc_rate,
+	.round_rate	= sun4i_ddc_round_rate,
+	.set_rate	= sun4i_ddc_set_rate,
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
+{
+	struct clk_init_data init;
+	struct sun4i_ddc *ddc;
+	const char *parent_name;
+
+	parent_name = __clk_get_name(parent);
+	if (!parent_name)
+		return -ENODEV;
+
+	ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
+	if (!ddc)
+		return -ENOMEM;
+
+	init.name = "hdmi-ddc";
+	init.ops = &sun4i_ddc_ops;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	ddc->hdmi = hdmi;
+	ddc->hw.init = &init;
+
+	hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
+	if (IS_ERR(hdmi->ddc_clk))
+		return PTR_ERR(hdmi->ddc_clk);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
new file mode 100644
index 000000000000..9bec23164f53
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+#include "sun4i_hdmi.h"
+#include "sun4i_tcon.h"
+
+#define DDC_SEGMENT_ADDR	0x30
+
+static inline struct sun4i_hdmi *
+drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct sun4i_hdmi,
+			    encoder);
+}
+
+static inline struct sun4i_hdmi *
+drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
+{
+	return container_of(connector, struct sun4i_hdmi,
+			    connector);
+}
+
+static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
+					   struct drm_display_mode *mode)
+{
+	struct hdmi_avi_infoframe frame;
+	u8 buffer[17];
+	int i, ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+	if (ret < 0) {
+		DRM_ERROR("Failed to get infoframes from mode\n");
+		return ret;
+	}
+
+	ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (ret < 0) {
+		DRM_ERROR("Failed to pack infoframes\n");
+		return ret;
+	}
+
+	for (i = 0; i < sizeof(buffer); i++)
+		writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
+
+	return 0;
+}
+
+static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
+				   struct drm_crtc_state *crtc_state,
+				   struct drm_connector_state *conn_state)
+{
+	struct drm_display_mode *mode = &crtc_state->mode;
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void sun4i_hdmi_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	u32 val;
+
+	DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
+
+	val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+	val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
+	writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun4i_hdmi_enable(struct drm_encoder *encoder)
+{
+	struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	u32 val = 0;
+
+	DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+
+	sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
+	val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
+	val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
+	writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
+
+	val = SUN4I_HDMI_VID_CTRL_ENABLE;
+	if (hdmi->hdmi_monitor)
+		val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
+
+	writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+}
+
+static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
+				struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	unsigned int x, y;
+	u32 val;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 1, encoder);
+
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+	clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
+	clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
+
+	/* Set input sync enable */
+	writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
+	       hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
+
+	/* Setup timing registers */
+	writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
+	       SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
+
+	x = mode->htotal - mode->hsync_start;
+	y = mode->vtotal - mode->vsync_start;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
+
+	x = mode->hsync_start - mode->hdisplay;
+	y = mode->vsync_start - mode->vdisplay;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
+
+	x = mode->hsync_end - mode->hsync_start;
+	y = mode->vsync_end - mode->vsync_start;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
+
+	val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
+
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
+
+	writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
+}
+
+static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
+	.atomic_check	= sun4i_hdmi_atomic_check,
+	.disable	= sun4i_hdmi_disable,
+	.enable		= sun4i_hdmi_enable,
+	.mode_set	= sun4i_hdmi_mode_set,
+};
+
+static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
+	.destroy	= drm_encoder_cleanup,
+};
+
+static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
+				     unsigned int blk, unsigned int offset,
+				     u8 *buf, unsigned int count)
+{
+	unsigned long reg;
+	int i;
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
+	writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+	writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
+	       SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
+	       SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
+	       SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
+	       hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+	writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
+	       hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+
+	writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
+	writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
+	       hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+			       !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
+			       100, 100000))
+		return -EIO;
+
+	for (i = 0; i < count; i++)
+		buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
+
+	return 0;
+}
+
+static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
+				      size_t length)
+{
+	struct sun4i_hdmi *hdmi = data;
+	int retry = 2, i;
+
+	do {
+		for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
+			unsigned char offset = blk * EDID_LENGTH + i;
+			unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
+						 length - i);
+			int ret;
+
+			ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
+							buf + i, count);
+			if (ret)
+				return ret;
+		}
+	} while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
+
+	return 0;
+}
+
+static int sun4i_hdmi_get_modes(struct drm_connector *connector)
+{
+	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+	unsigned long reg;
+	struct edid *edid;
+	int ret;
+
+	/* Reset i2c controller */
+	writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+			       !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
+			       100, 2000))
+		return -EIO;
+
+	writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
+	       SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
+	       hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
+
+	clk_set_rate(hdmi->ddc_clk, 100000);
+
+	edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
+	if (!edid)
+		return 0;
+
+	hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
+	DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
+			 hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
+
+	drm_mode_connector_update_edid_property(connector, edid);
+	ret = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	return ret;
+}
+
+static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
+	.get_modes	= sun4i_hdmi_get_modes,
+};
+
+static enum drm_connector_status
+sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+	unsigned long reg;
+
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
+			       reg & SUN4I_HDMI_HPD_HIGH,
+			       0, 500000))
+		return connector_status_disconnected;
+
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
+	.dpms			= drm_atomic_helper_connector_dpms,
+	.detect			= sun4i_hdmi_connector_detect,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.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 sun4i_hdmi_bind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct sun4i_drv *drv = drm->dev_private;
+	struct sun4i_hdmi *hdmi;
+	struct resource *res;
+	u32 reg;
+	int ret;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+	dev_set_drvdata(dev, hdmi);
+	hdmi->dev = dev;
+	hdmi->drv = drv;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->base)) {
+		dev_err(dev, "Couldn't map the HDMI encoder registers\n");
+		return PTR_ERR(hdmi->base);
+	}
+
+	hdmi->bus_clk = devm_clk_get(dev, "ahb");
+	if (IS_ERR(hdmi->bus_clk)) {
+		dev_err(dev, "Couldn't get the HDMI bus clock\n");
+		return PTR_ERR(hdmi->bus_clk);
+	}
+	clk_prepare_enable(hdmi->bus_clk);
+
+	hdmi->mod_clk = devm_clk_get(dev, "mod");
+	if (IS_ERR(hdmi->mod_clk)) {
+		dev_err(dev, "Couldn't get the HDMI mod clock\n");
+		return PTR_ERR(hdmi->mod_clk);
+	}
+	clk_prepare_enable(hdmi->mod_clk);
+
+	hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
+	if (IS_ERR(hdmi->pll0_clk)) {
+		dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
+		return PTR_ERR(hdmi->pll0_clk);
+	}
+
+	hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
+	if (IS_ERR(hdmi->pll1_clk)) {
+		dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
+		return PTR_ERR(hdmi->pll1_clk);
+	}
+
+	ret = sun4i_tmds_create(hdmi);
+	if (ret) {
+		dev_err(dev, "Couldn't create the TMDS clock\n");
+		return ret;
+	}
+
+	writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
+
+	writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
+	       SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
+	       SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
+	       SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
+	       hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
+
+	/*
+	 * We can't just initialize the register there, we need to
+	 * protect the clock bits that have already been read out and
+	 * cached by the clock framework.
+	 */
+	reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
+		SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
+		SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
+		SUN4I_HDMI_PAD_CTRL1_REG_DEN |
+		SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
+		SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
+		SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
+		SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
+	writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+	reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
+		SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |
+		SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
+		SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
+		SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
+		SUN4I_HDMI_PLL_CTRL_PLL_EN;
+	writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+	ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+	if (ret) {
+		dev_err(dev, "Couldn't create the DDC clock\n");
+		return ret;
+	}
+
+	drm_encoder_helper_add(&hdmi->encoder,
+			       &sun4i_hdmi_helper_funcs);
+	ret = drm_encoder_init(drm,
+			       &hdmi->encoder,
+			       &sun4i_hdmi_funcs,
+			       DRM_MODE_ENCODER_TMDS,
+			       NULL);
+	if (ret) {
+		dev_err(dev, "Couldn't initialise the HDMI encoder\n");
+		return ret;
+	}
+
+	hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
+								  dev->of_node);
+	if (!hdmi->encoder.possible_crtcs)
+		return ret;
+
+	drm_connector_helper_add(&hdmi->connector,
+				 &sun4i_hdmi_connector_helper_funcs);
+	ret = drm_connector_init(drm, &hdmi->connector,
+				 &sun4i_hdmi_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret) {
+		dev_err(dev,
+			"Couldn't initialise the HDMI connector\n");
+		goto err_cleanup_connector;
+	}
+
+	drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
+
+	return 0;
+
+err_cleanup_connector:
+	drm_encoder_cleanup(&hdmi->encoder);
+	return ret;
+}
+
+static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
+
+	drm_connector_cleanup(&hdmi->connector);
+	drm_encoder_cleanup(&hdmi->encoder);
+}
+
+static const struct component_ops sun4i_hdmi_ops = {
+	.bind	= sun4i_hdmi_bind,
+	.unbind	= sun4i_hdmi_unbind,
+};
+
+static int sun4i_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun4i_hdmi_ops);
+}
+
+static int sun4i_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun4i_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun4i_hdmi_of_table[] = {
+	{ .compatible = "allwinner,sun5i-a10s-hdmi" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
+
+static struct platform_driver sun4i_hdmi_driver = {
+	.probe		= sun4i_hdmi_probe,
+	.remove		= sun4i_hdmi_remove,
+	.driver		= {
+		.name		= "sun4i-hdmi",
+		.of_match_table	= sun4i_hdmi_of_table,
+	},
+};
+module_platform_driver(sun4i_hdmi_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
new file mode 100644
index 000000000000..5cf2527bffc8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_tmds {
+	struct clk_hw		hw;
+	struct sun4i_hdmi	*hdmi;
+};
+
+static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_tmds, hw);
+}
+
+
+static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
+					     unsigned long parent_rate,
+					     u8 *div,
+					     bool *half)
+{
+	unsigned long best_rate = 0;
+	u8 best_m = 0, m;
+	bool is_double;
+
+	for (m = 1; m < 16; m++) {
+		u8 d;
+
+		for (d = 1; d < 3; d++) {
+			unsigned long tmp_rate;
+
+			tmp_rate = parent_rate / m / d;
+
+			if (tmp_rate > rate)
+				continue;
+
+			if (!best_rate ||
+			    (rate - tmp_rate) < (rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = m;
+				is_double = d;
+			}
+		}
+	}
+
+	if (div && half) {
+		*div = best_m;
+		*half = is_double;
+	}
+
+	return best_rate;
+}
+
+
+static int sun4i_tmds_determine_rate(struct clk_hw *hw,
+				     struct clk_rate_request *req)
+{
+	struct clk_hw *parent;
+	unsigned long best_parent = 0;
+	unsigned long rate = req->rate;
+	int best_div = 1, best_half = 1;
+	int i, j;
+
+	/*
+	 * We only consider PLL3, since the TCON is very likely to be
+	 * clocked from it, and to have the same rate than our HDMI
+	 * clock, so we should not need to do anything.
+	 */
+
+	parent = clk_hw_get_parent_by_index(hw, 0);
+	if (!parent)
+		return -EINVAL;
+
+	for (i = 1; i < 3; i++) {
+		for (j = 1; j < 16; j++) {
+			unsigned long ideal = rate * i * j;
+			unsigned long rounded;
+
+			rounded = clk_hw_round_rate(parent, ideal);
+
+			if (rounded == ideal) {
+				best_parent = rounded;
+				best_half = i;
+				best_div = j;
+				goto out;
+			}
+
+			if (abs(rate - rounded / i) <
+			    abs(rate - best_parent / best_div)) {
+				best_parent = rounded;
+				best_div = i;
+			}
+		}
+	}
+
+out:
+	req->rate = best_parent / best_half / best_div;
+	req->best_parent_rate = best_parent;
+	req->best_parent_hw = parent;
+
+	return 0;
+}
+
+static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
+		parent_rate /= 2;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg = (reg >> 4) & 0xf;
+	if (!reg)
+		reg = 1;
+
+	return parent_rate / reg;
+}
+
+static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	bool half;
+	u32 reg;
+	u8 div;
+
+	sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	if (half)
+		reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
+	       tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+	return 0;
+}
+
+static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+	return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
+		SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
+}
+
+static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	if (index > 1)
+		return -EINVAL;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+	reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
+	writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
+	       tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+
+	return 0;
+}
+
+static const struct clk_ops sun4i_tmds_ops = {
+	.determine_rate	= sun4i_tmds_determine_rate,
+	.recalc_rate	= sun4i_tmds_recalc_rate,
+	.set_rate	= sun4i_tmds_set_rate,
+
+	.get_parent	= sun4i_tmds_get_parent,
+	.set_parent	= sun4i_tmds_set_parent,
+};
+
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
+{
+	struct clk_init_data init;
+	struct sun4i_tmds *tmds;
+	const char *parents[2];
+
+	parents[0] = __clk_get_name(hdmi->pll0_clk);
+	if (!parents[0])
+		return -ENODEV;
+
+	parents[1] = __clk_get_name(hdmi->pll1_clk);
+	if (!parents[1])
+		return -ENODEV;
+
+	tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
+	if (!tmds)
+		return -ENOMEM;
+
+	init.name = "hdmi-tmds";
+	init.ops = &sun4i_tmds_ops;
+	init.parent_names = parents;
+	init.num_parents = 2;
+	init.flags = CLK_SET_RATE_PARENT;
+
+	tmds->hdmi = hdmi;
+	tmds->hw.init = &init;
+
+	hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
+	if (IS_ERR(hdmi->tmds_clk))
+		return PTR_ERR(hdmi->tmds_clk);
+
+	return 0;
+}
-- 
git-series 0.8.11

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

* [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
controller.

That HDMI controller is able to do audio and CEC, but those have been left
out for now.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 drivers/gpu/drm/sun4i/Kconfig               |   9 +-
 drivers/gpu/drm/sun4i/Makefile              |   6 +-
 drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
 6 files changed, 1017 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index a4b357db8856..35299c4e2594 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -12,3 +12,12 @@ config DRM_SUN4I
 	  Choose this option if you have an Allwinner SoC with a
 	  Display Engine. If M is selected the module will be called
 	  sun4i-drm.
+
+if DRM_SUN4I
+config DRM_SUN4I_HDMI
+       tristate "Allwinner A10 HDMI Controller Support"
+       help
+	  Choose this option if you have an Allwinner SoC with an HDMI
+	  controller.
+
+endif
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 59b757350a1f..c09bf8093710 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i-drm.o sun4i-tcon.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun4i_backend.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun6i_drc.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
+
+sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
+sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
+sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
+
+obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
new file mode 100644
index 000000000000..40d57b195b48
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#ifndef _SUN4I_HDMI_H_
+#define _SUN4I_HDMI_H_
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+#define SUN4I_HDMI_CTRL_REG		0x004
+#define SUN4I_HDMI_CTRL_ENABLE			BIT(31)
+
+#define SUN4I_HDMI_IRQ_REG		0x008
+#define SUN4I_HDMI_IRQ_STA_MASK			0x73
+#define SUN4I_HDMI_IRQ_STA_FIFO_OF		BIT(1)
+#define SUN4I_HDMI_IRQ_STA_FIFO_UF		BIT(0)
+
+#define SUN4I_HDMI_HPD_REG		0x00c
+#define SUN4I_HDMI_HPD_HIGH			BIT(0)
+
+#define SUN4I_HDMI_VID_CTRL_REG		0x010
+#define SUN4I_HDMI_VID_CTRL_ENABLE		BIT(31)
+#define SUN4I_HDMI_VID_CTRL_HDMI_MODE		BIT(30)
+
+#define SUN4I_HDMI_VID_TIMING_ACT_REG	0x014
+#define SUN4I_HDMI_VID_TIMING_BP_REG	0x018
+#define SUN4I_HDMI_VID_TIMING_FP_REG	0x01c
+#define SUN4I_HDMI_VID_TIMING_SPW_REG	0x020
+
+#define SUN4I_HDMI_VID_TIMING_X(x)		((((x) - 1) & GENMASK(11, 0)))
+#define SUN4I_HDMI_VID_TIMING_Y(y)		((((y) - 1) & GENMASK(11, 0)) << 16)
+
+#define SUN4I_HDMI_VID_TIMING_POL_REG	0x024
+#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
+#define SUN4I_HDMI_VID_TIMING_POL_VSYNC		BIT(1)
+#define SUN4I_HDMI_VID_TIMING_POL_HSYNC		BIT(0)
+
+#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)	(0x080 + (n))
+
+#define SUN4I_HDMI_PAD_CTRL0_REG	0x200
+#define SUN4I_HDMI_PAD_CTRL0_BIASEN		BIT(31)
+#define SUN4I_HDMI_PAD_CTRL0_LDOCEN		BIT(30)
+#define SUN4I_HDMI_PAD_CTRL0_LDODEN		BIT(29)
+#define SUN4I_HDMI_PAD_CTRL0_PWENC		BIT(28)
+#define SUN4I_HDMI_PAD_CTRL0_PWEND		BIT(27)
+#define SUN4I_HDMI_PAD_CTRL0_PWENG		BIT(26)
+#define SUN4I_HDMI_PAD_CTRL0_CKEN		BIT(25)
+#define SUN4I_HDMI_PAD_CTRL0_TXEN		BIT(23)
+
+#define SUN4I_HDMI_PAD_CTRL1_REG	0x204
+#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT		BIT(23)
+#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT		BIT(22)
+#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT		BIT(20)
+#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT		BIT(19)
+#define SUN4I_HDMI_PAD_CTRL1_REG_DEN		BIT(15)
+#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK		BIT(14)
+#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)		(((n) & 7) << 10)
+#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK		BIT(6)
+#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)		(((n) & 7) << 3)
+
+#define SUN4I_HDMI_PLL_CTRL_REG		0x208
+#define SUN4I_HDMI_PLL_CTRL_PLL_EN		BIT(31)
+#define SUN4I_HDMI_PLL_CTRL_BWS			BIT(30)
+#define SUN4I_HDMI_PLL_CTRL_HV_IS_33		BIT(29)
+#define SUN4I_HDMI_PLL_CTRL_LDO1_EN		BIT(28)
+#define SUN4I_HDMI_PLL_CTRL_LDO2_EN		BIT(27)
+#define SUN4I_HDMI_PLL_CTRL_SDIV2		BIT(25)
+#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)		(((n) & 7) << 20)
+#define SUN4I_HDMI_PLL_CTRL_S(n)		(((n) & 7) << 16)
+#define SUN4I_HDMI_PLL_CTRL_CP_S(n)		(((n) & 0xf) << 12)
+#define SUN4I_HDMI_PLL_CTRL_CS(n)		(((n) & 0xf) << 8)
+#define SUN4I_HDMI_PLL_CTRL_DIV(n)		(((n) & 0xf) << 4)
+#define SUN4I_HDMI_PLL_CTRL_DIV_MASK		GENMASK(7, 4)
+#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)		((n) & 0xf)
+
+#define SUN4I_HDMI_PLL_DBG0_REG		0x20c
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)	(((n) & 1) << 21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK	BIT(21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT	21
+
+#define SUN4I_HDMI_PKT_CTRL_REG(n)	(0x2f0 + (4 * (n)))
+#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)		((t) << (((n) % 4) * 4))
+
+#define SUN4I_HDMI_UNKNOWN_REG		0x300
+#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC		BIT(27)
+
+#define SUN4I_HDMI_DDC_CTRL_REG		0x500
+#define SUN4I_HDMI_DDC_CTRL_ENABLE		BIT(31)
+#define SUN4I_HDMI_DDC_CTRL_START_CMD		BIT(30)
+#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK	BIT(8)
+#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ	(0 << 8)
+#define SUN4I_HDMI_DDC_CTRL_RESET		BIT(0)
+
+#define SUN4I_HDMI_DDC_ADDR_REG		0x504
+#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)	(((seg) & 0xff) << 24)
+#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)		(((addr) & 0xff) << 16)
+#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)		(((off) & 0xff) << 8)
+#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)		((addr) & 0xff)
+
+#define SUN4I_HDMI_DDC_FIFO_CTRL_REG	0x510
+#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR		BIT(31)
+
+#define SUN4I_HDMI_DDC_FIFO_DATA_REG	0x518
+#define SUN4I_HDMI_DDC_BYTE_COUNT_REG	0x51c
+
+#define SUN4I_HDMI_DDC_CMD_REG		0x520
+#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ	6
+
+#define SUN4I_HDMI_DDC_CLK_REG		0x528
+#define SUN4I_HDMI_DDC_CLK_M(m)			(((m) & 0x7) << 3)
+#define SUN4I_HDMI_DDC_CLK_N(n)			((n) & 0x7)
+
+#define SUN4I_HDMI_DDC_LINE_CTRL_REG	0x540
+#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE	BIT(9)
+#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE	BIT(8)
+
+#define SUN4I_HDMI_DDC_FIFO_SIZE	16
+
+enum sun4i_hdmi_pkt_type {
+	SUN4I_HDMI_PKT_AVI = 2,
+	SUN4I_HDMI_PKT_END = 15,
+};
+
+struct sun4i_hdmi {
+	struct drm_connector	connector;
+	struct drm_encoder	encoder;
+	struct device		*dev;
+
+	void __iomem		*base;
+
+	/* Parent clocks */
+	struct clk		*bus_clk;
+	struct clk		*mod_clk;
+	struct clk		*pll0_clk;
+	struct clk		*pll1_clk;
+
+	/* And the clocks we create */
+	struct clk		*ddc_clk;
+	struct clk		*tmds_clk;
+
+	struct sun4i_drv	*drv;
+
+	bool			hdmi_monitor;
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
+
+#endif /* _SUN4I_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
new file mode 100644
index 000000000000..4692e8c345ed
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_ddc {
+	struct clk_hw		hw;
+	struct sun4i_hdmi	*hdmi;
+};
+
+static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_ddc, hw);
+}
+
+static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
+					    unsigned long parent_rate,
+					    u8 *m, u8 *n)
+{
+	unsigned long best_rate = 0;
+	u8 best_m = 0, best_n = 0, _m, _n;
+
+	for (_m = 0; _m < 8; _m++) {
+		for (_n = 0; _n < 8; _n++) {
+			unsigned long tmp_rate;
+
+			tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
+
+			if (tmp_rate > rate)
+				continue;
+
+			if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = _m;
+				best_n = _n;
+			}
+		}
+	}
+
+	if (m && n) {
+		*m = best_m;
+		*n = best_n;
+	}
+
+	return best_rate;
+}
+
+static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long *prate)
+{
+	return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
+}
+
+static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+	u32 reg;
+	u8 m, n;
+
+	reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+	m = (reg >> 3) & 0x7;
+	n = reg & 0x7;
+
+	return (((parent_rate / 2) / 10) >> n) / (m + 1);
+}
+
+static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long parent_rate)
+{
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+	u8 div_m, div_n;
+
+	sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
+
+	writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
+	       ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+
+	return 0;
+}
+
+static const struct clk_ops sun4i_ddc_ops = {
+	.recalc_rate	= sun4i_ddc_recalc_rate,
+	.round_rate	= sun4i_ddc_round_rate,
+	.set_rate	= sun4i_ddc_set_rate,
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
+{
+	struct clk_init_data init;
+	struct sun4i_ddc *ddc;
+	const char *parent_name;
+
+	parent_name = __clk_get_name(parent);
+	if (!parent_name)
+		return -ENODEV;
+
+	ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
+	if (!ddc)
+		return -ENOMEM;
+
+	init.name = "hdmi-ddc";
+	init.ops = &sun4i_ddc_ops;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	ddc->hdmi = hdmi;
+	ddc->hw.init = &init;
+
+	hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
+	if (IS_ERR(hdmi->ddc_clk))
+		return PTR_ERR(hdmi->ddc_clk);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
new file mode 100644
index 000000000000..9bec23164f53
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+#include "sun4i_hdmi.h"
+#include "sun4i_tcon.h"
+
+#define DDC_SEGMENT_ADDR	0x30
+
+static inline struct sun4i_hdmi *
+drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct sun4i_hdmi,
+			    encoder);
+}
+
+static inline struct sun4i_hdmi *
+drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
+{
+	return container_of(connector, struct sun4i_hdmi,
+			    connector);
+}
+
+static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
+					   struct drm_display_mode *mode)
+{
+	struct hdmi_avi_infoframe frame;
+	u8 buffer[17];
+	int i, ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+	if (ret < 0) {
+		DRM_ERROR("Failed to get infoframes from mode\n");
+		return ret;
+	}
+
+	ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (ret < 0) {
+		DRM_ERROR("Failed to pack infoframes\n");
+		return ret;
+	}
+
+	for (i = 0; i < sizeof(buffer); i++)
+		writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
+
+	return 0;
+}
+
+static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
+				   struct drm_crtc_state *crtc_state,
+				   struct drm_connector_state *conn_state)
+{
+	struct drm_display_mode *mode = &crtc_state->mode;
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void sun4i_hdmi_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	u32 val;
+
+	DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
+
+	val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+	val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
+	writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun4i_hdmi_enable(struct drm_encoder *encoder)
+{
+	struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	u32 val = 0;
+
+	DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+
+	sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
+	val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
+	val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
+	writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
+
+	val = SUN4I_HDMI_VID_CTRL_ENABLE;
+	if (hdmi->hdmi_monitor)
+		val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
+
+	writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+}
+
+static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
+				struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	unsigned int x, y;
+	u32 val;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 1, encoder);
+
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+	clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
+	clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
+
+	/* Set input sync enable */
+	writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
+	       hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
+
+	/* Setup timing registers */
+	writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
+	       SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
+
+	x = mode->htotal - mode->hsync_start;
+	y = mode->vtotal - mode->vsync_start;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
+
+	x = mode->hsync_start - mode->hdisplay;
+	y = mode->vsync_start - mode->vdisplay;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
+
+	x = mode->hsync_end - mode->hsync_start;
+	y = mode->vsync_end - mode->vsync_start;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
+
+	val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
+
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
+
+	writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
+}
+
+static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
+	.atomic_check	= sun4i_hdmi_atomic_check,
+	.disable	= sun4i_hdmi_disable,
+	.enable		= sun4i_hdmi_enable,
+	.mode_set	= sun4i_hdmi_mode_set,
+};
+
+static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
+	.destroy	= drm_encoder_cleanup,
+};
+
+static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
+				     unsigned int blk, unsigned int offset,
+				     u8 *buf, unsigned int count)
+{
+	unsigned long reg;
+	int i;
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
+	writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+	writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
+	       SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
+	       SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
+	       SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
+	       hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+	writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
+	       hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+
+	writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
+	writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
+	       hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+			       !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
+			       100, 100000))
+		return -EIO;
+
+	for (i = 0; i < count; i++)
+		buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
+
+	return 0;
+}
+
+static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
+				      size_t length)
+{
+	struct sun4i_hdmi *hdmi = data;
+	int retry = 2, i;
+
+	do {
+		for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
+			unsigned char offset = blk * EDID_LENGTH + i;
+			unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
+						 length - i);
+			int ret;
+
+			ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
+							buf + i, count);
+			if (ret)
+				return ret;
+		}
+	} while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
+
+	return 0;
+}
+
+static int sun4i_hdmi_get_modes(struct drm_connector *connector)
+{
+	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+	unsigned long reg;
+	struct edid *edid;
+	int ret;
+
+	/* Reset i2c controller */
+	writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+			       !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
+			       100, 2000))
+		return -EIO;
+
+	writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
+	       SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
+	       hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
+
+	clk_set_rate(hdmi->ddc_clk, 100000);
+
+	edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
+	if (!edid)
+		return 0;
+
+	hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
+	DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
+			 hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
+
+	drm_mode_connector_update_edid_property(connector, edid);
+	ret = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	return ret;
+}
+
+static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
+	.get_modes	= sun4i_hdmi_get_modes,
+};
+
+static enum drm_connector_status
+sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+	unsigned long reg;
+
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
+			       reg & SUN4I_HDMI_HPD_HIGH,
+			       0, 500000))
+		return connector_status_disconnected;
+
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
+	.dpms			= drm_atomic_helper_connector_dpms,
+	.detect			= sun4i_hdmi_connector_detect,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.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 sun4i_hdmi_bind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct sun4i_drv *drv = drm->dev_private;
+	struct sun4i_hdmi *hdmi;
+	struct resource *res;
+	u32 reg;
+	int ret;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+	dev_set_drvdata(dev, hdmi);
+	hdmi->dev = dev;
+	hdmi->drv = drv;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->base)) {
+		dev_err(dev, "Couldn't map the HDMI encoder registers\n");
+		return PTR_ERR(hdmi->base);
+	}
+
+	hdmi->bus_clk = devm_clk_get(dev, "ahb");
+	if (IS_ERR(hdmi->bus_clk)) {
+		dev_err(dev, "Couldn't get the HDMI bus clock\n");
+		return PTR_ERR(hdmi->bus_clk);
+	}
+	clk_prepare_enable(hdmi->bus_clk);
+
+	hdmi->mod_clk = devm_clk_get(dev, "mod");
+	if (IS_ERR(hdmi->mod_clk)) {
+		dev_err(dev, "Couldn't get the HDMI mod clock\n");
+		return PTR_ERR(hdmi->mod_clk);
+	}
+	clk_prepare_enable(hdmi->mod_clk);
+
+	hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
+	if (IS_ERR(hdmi->pll0_clk)) {
+		dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
+		return PTR_ERR(hdmi->pll0_clk);
+	}
+
+	hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
+	if (IS_ERR(hdmi->pll1_clk)) {
+		dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
+		return PTR_ERR(hdmi->pll1_clk);
+	}
+
+	ret = sun4i_tmds_create(hdmi);
+	if (ret) {
+		dev_err(dev, "Couldn't create the TMDS clock\n");
+		return ret;
+	}
+
+	writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
+
+	writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
+	       SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
+	       SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
+	       SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
+	       hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
+
+	/*
+	 * We can't just initialize the register there, we need to
+	 * protect the clock bits that have already been read out and
+	 * cached by the clock framework.
+	 */
+	reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
+		SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
+		SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
+		SUN4I_HDMI_PAD_CTRL1_REG_DEN |
+		SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
+		SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
+		SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
+		SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
+	writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+	reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
+		SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |
+		SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
+		SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
+		SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
+		SUN4I_HDMI_PLL_CTRL_PLL_EN;
+	writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+	ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+	if (ret) {
+		dev_err(dev, "Couldn't create the DDC clock\n");
+		return ret;
+	}
+
+	drm_encoder_helper_add(&hdmi->encoder,
+			       &sun4i_hdmi_helper_funcs);
+	ret = drm_encoder_init(drm,
+			       &hdmi->encoder,
+			       &sun4i_hdmi_funcs,
+			       DRM_MODE_ENCODER_TMDS,
+			       NULL);
+	if (ret) {
+		dev_err(dev, "Couldn't initialise the HDMI encoder\n");
+		return ret;
+	}
+
+	hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
+								  dev->of_node);
+	if (!hdmi->encoder.possible_crtcs)
+		return ret;
+
+	drm_connector_helper_add(&hdmi->connector,
+				 &sun4i_hdmi_connector_helper_funcs);
+	ret = drm_connector_init(drm, &hdmi->connector,
+				 &sun4i_hdmi_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret) {
+		dev_err(dev,
+			"Couldn't initialise the HDMI connector\n");
+		goto err_cleanup_connector;
+	}
+
+	drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
+
+	return 0;
+
+err_cleanup_connector:
+	drm_encoder_cleanup(&hdmi->encoder);
+	return ret;
+}
+
+static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
+
+	drm_connector_cleanup(&hdmi->connector);
+	drm_encoder_cleanup(&hdmi->encoder);
+}
+
+static const struct component_ops sun4i_hdmi_ops = {
+	.bind	= sun4i_hdmi_bind,
+	.unbind	= sun4i_hdmi_unbind,
+};
+
+static int sun4i_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun4i_hdmi_ops);
+}
+
+static int sun4i_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun4i_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun4i_hdmi_of_table[] = {
+	{ .compatible = "allwinner,sun5i-a10s-hdmi" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
+
+static struct platform_driver sun4i_hdmi_driver = {
+	.probe		= sun4i_hdmi_probe,
+	.remove		= sun4i_hdmi_remove,
+	.driver		= {
+		.name		= "sun4i-hdmi",
+		.of_match_table	= sun4i_hdmi_of_table,
+	},
+};
+module_platform_driver(sun4i_hdmi_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>");
+MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
new file mode 100644
index 000000000000..5cf2527bffc8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_tmds {
+	struct clk_hw		hw;
+	struct sun4i_hdmi	*hdmi;
+};
+
+static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_tmds, hw);
+}
+
+
+static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
+					     unsigned long parent_rate,
+					     u8 *div,
+					     bool *half)
+{
+	unsigned long best_rate = 0;
+	u8 best_m = 0, m;
+	bool is_double;
+
+	for (m = 1; m < 16; m++) {
+		u8 d;
+
+		for (d = 1; d < 3; d++) {
+			unsigned long tmp_rate;
+
+			tmp_rate = parent_rate / m / d;
+
+			if (tmp_rate > rate)
+				continue;
+
+			if (!best_rate ||
+			    (rate - tmp_rate) < (rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = m;
+				is_double = d;
+			}
+		}
+	}
+
+	if (div && half) {
+		*div = best_m;
+		*half = is_double;
+	}
+
+	return best_rate;
+}
+
+
+static int sun4i_tmds_determine_rate(struct clk_hw *hw,
+				     struct clk_rate_request *req)
+{
+	struct clk_hw *parent;
+	unsigned long best_parent = 0;
+	unsigned long rate = req->rate;
+	int best_div = 1, best_half = 1;
+	int i, j;
+
+	/*
+	 * We only consider PLL3, since the TCON is very likely to be
+	 * clocked from it, and to have the same rate than our HDMI
+	 * clock, so we should not need to do anything.
+	 */
+
+	parent = clk_hw_get_parent_by_index(hw, 0);
+	if (!parent)
+		return -EINVAL;
+
+	for (i = 1; i < 3; i++) {
+		for (j = 1; j < 16; j++) {
+			unsigned long ideal = rate * i * j;
+			unsigned long rounded;
+
+			rounded = clk_hw_round_rate(parent, ideal);
+
+			if (rounded == ideal) {
+				best_parent = rounded;
+				best_half = i;
+				best_div = j;
+				goto out;
+			}
+
+			if (abs(rate - rounded / i) <
+			    abs(rate - best_parent / best_div)) {
+				best_parent = rounded;
+				best_div = i;
+			}
+		}
+	}
+
+out:
+	req->rate = best_parent / best_half / best_div;
+	req->best_parent_rate = best_parent;
+	req->best_parent_hw = parent;
+
+	return 0;
+}
+
+static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
+		parent_rate /= 2;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg = (reg >> 4) & 0xf;
+	if (!reg)
+		reg = 1;
+
+	return parent_rate / reg;
+}
+
+static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	bool half;
+	u32 reg;
+	u8 div;
+
+	sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	if (half)
+		reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
+	       tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+	return 0;
+}
+
+static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+	return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
+		SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
+}
+
+static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	if (index > 1)
+		return -EINVAL;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+	reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
+	writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
+	       tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+
+	return 0;
+}
+
+static const struct clk_ops sun4i_tmds_ops = {
+	.determine_rate	= sun4i_tmds_determine_rate,
+	.recalc_rate	= sun4i_tmds_recalc_rate,
+	.set_rate	= sun4i_tmds_set_rate,
+
+	.get_parent	= sun4i_tmds_get_parent,
+	.set_parent	= sun4i_tmds_set_parent,
+};
+
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
+{
+	struct clk_init_data init;
+	struct sun4i_tmds *tmds;
+	const char *parents[2];
+
+	parents[0] = __clk_get_name(hdmi->pll0_clk);
+	if (!parents[0])
+		return -ENODEV;
+
+	parents[1] = __clk_get_name(hdmi->pll1_clk);
+	if (!parents[1])
+		return -ENODEV;
+
+	tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
+	if (!tmds)
+		return -ENOMEM;
+
+	init.name = "hdmi-tmds";
+	init.ops = &sun4i_tmds_ops;
+	init.parent_names = parents;
+	init.num_parents = 2;
+	init.flags = CLK_SET_RATE_PARENT;
+
+	tmds->hdmi = hdmi;
+	tmds->hw.init = &init;
+
+	hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
+	if (IS_ERR(hdmi->tmds_clk))
+		return PTR_ERR(hdmi->tmds_clk);
+
+	return 0;
+}
-- 
git-series 0.8.11

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

* [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
controller.

That HDMI controller is able to do audio and CEC, but those have been left
out for now.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 drivers/gpu/drm/sun4i/Kconfig               |   9 +-
 drivers/gpu/drm/sun4i/Makefile              |   6 +-
 drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
 6 files changed, 1017 insertions(+), 0 deletions(-)
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
 create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c

diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
index a4b357db8856..35299c4e2594 100644
--- a/drivers/gpu/drm/sun4i/Kconfig
+++ b/drivers/gpu/drm/sun4i/Kconfig
@@ -12,3 +12,12 @@ config DRM_SUN4I
 	  Choose this option if you have an Allwinner SoC with a
 	  Display Engine. If M is selected the module will be called
 	  sun4i-drm.
+
+if DRM_SUN4I
+config DRM_SUN4I_HDMI
+       tristate "Allwinner A10 HDMI Controller Support"
+       help
+	  Choose this option if you have an Allwinner SoC with an HDMI
+	  controller.
+
+endif
diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
index 59b757350a1f..c09bf8093710 100644
--- a/drivers/gpu/drm/sun4i/Makefile
+++ b/drivers/gpu/drm/sun4i/Makefile
@@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)		+= sun4i-drm.o sun4i-tcon.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun4i_backend.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun6i_drc.o
 obj-$(CONFIG_DRM_SUN4I)		+= sun4i_tv.o
+
+sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
+sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
+sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
+
+obj-$(CONFIG_DRM_SUN4I_HDMI)	+= sun4i-drm-hdmi.o
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
new file mode 100644
index 000000000000..40d57b195b48
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#ifndef _SUN4I_HDMI_H_
+#define _SUN4I_HDMI_H_
+
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+#define SUN4I_HDMI_CTRL_REG		0x004
+#define SUN4I_HDMI_CTRL_ENABLE			BIT(31)
+
+#define SUN4I_HDMI_IRQ_REG		0x008
+#define SUN4I_HDMI_IRQ_STA_MASK			0x73
+#define SUN4I_HDMI_IRQ_STA_FIFO_OF		BIT(1)
+#define SUN4I_HDMI_IRQ_STA_FIFO_UF		BIT(0)
+
+#define SUN4I_HDMI_HPD_REG		0x00c
+#define SUN4I_HDMI_HPD_HIGH			BIT(0)
+
+#define SUN4I_HDMI_VID_CTRL_REG		0x010
+#define SUN4I_HDMI_VID_CTRL_ENABLE		BIT(31)
+#define SUN4I_HDMI_VID_CTRL_HDMI_MODE		BIT(30)
+
+#define SUN4I_HDMI_VID_TIMING_ACT_REG	0x014
+#define SUN4I_HDMI_VID_TIMING_BP_REG	0x018
+#define SUN4I_HDMI_VID_TIMING_FP_REG	0x01c
+#define SUN4I_HDMI_VID_TIMING_SPW_REG	0x020
+
+#define SUN4I_HDMI_VID_TIMING_X(x)		((((x) - 1) & GENMASK(11, 0)))
+#define SUN4I_HDMI_VID_TIMING_Y(y)		((((y) - 1) & GENMASK(11, 0)) << 16)
+
+#define SUN4I_HDMI_VID_TIMING_POL_REG	0x024
+#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
+#define SUN4I_HDMI_VID_TIMING_POL_VSYNC		BIT(1)
+#define SUN4I_HDMI_VID_TIMING_POL_HSYNC		BIT(0)
+
+#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)	(0x080 + (n))
+
+#define SUN4I_HDMI_PAD_CTRL0_REG	0x200
+#define SUN4I_HDMI_PAD_CTRL0_BIASEN		BIT(31)
+#define SUN4I_HDMI_PAD_CTRL0_LDOCEN		BIT(30)
+#define SUN4I_HDMI_PAD_CTRL0_LDODEN		BIT(29)
+#define SUN4I_HDMI_PAD_CTRL0_PWENC		BIT(28)
+#define SUN4I_HDMI_PAD_CTRL0_PWEND		BIT(27)
+#define SUN4I_HDMI_PAD_CTRL0_PWENG		BIT(26)
+#define SUN4I_HDMI_PAD_CTRL0_CKEN		BIT(25)
+#define SUN4I_HDMI_PAD_CTRL0_TXEN		BIT(23)
+
+#define SUN4I_HDMI_PAD_CTRL1_REG	0x204
+#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT		BIT(23)
+#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT		BIT(22)
+#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT		BIT(20)
+#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT		BIT(19)
+#define SUN4I_HDMI_PAD_CTRL1_REG_DEN		BIT(15)
+#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK		BIT(14)
+#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)		(((n) & 7) << 10)
+#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK		BIT(6)
+#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)		(((n) & 7) << 3)
+
+#define SUN4I_HDMI_PLL_CTRL_REG		0x208
+#define SUN4I_HDMI_PLL_CTRL_PLL_EN		BIT(31)
+#define SUN4I_HDMI_PLL_CTRL_BWS			BIT(30)
+#define SUN4I_HDMI_PLL_CTRL_HV_IS_33		BIT(29)
+#define SUN4I_HDMI_PLL_CTRL_LDO1_EN		BIT(28)
+#define SUN4I_HDMI_PLL_CTRL_LDO2_EN		BIT(27)
+#define SUN4I_HDMI_PLL_CTRL_SDIV2		BIT(25)
+#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)		(((n) & 7) << 20)
+#define SUN4I_HDMI_PLL_CTRL_S(n)		(((n) & 7) << 16)
+#define SUN4I_HDMI_PLL_CTRL_CP_S(n)		(((n) & 0xf) << 12)
+#define SUN4I_HDMI_PLL_CTRL_CS(n)		(((n) & 0xf) << 8)
+#define SUN4I_HDMI_PLL_CTRL_DIV(n)		(((n) & 0xf) << 4)
+#define SUN4I_HDMI_PLL_CTRL_DIV_MASK		GENMASK(7, 4)
+#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)		((n) & 0xf)
+
+#define SUN4I_HDMI_PLL_DBG0_REG		0x20c
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)	(((n) & 1) << 21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK	BIT(21)
+#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT	21
+
+#define SUN4I_HDMI_PKT_CTRL_REG(n)	(0x2f0 + (4 * (n)))
+#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)		((t) << (((n) % 4) * 4))
+
+#define SUN4I_HDMI_UNKNOWN_REG		0x300
+#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC		BIT(27)
+
+#define SUN4I_HDMI_DDC_CTRL_REG		0x500
+#define SUN4I_HDMI_DDC_CTRL_ENABLE		BIT(31)
+#define SUN4I_HDMI_DDC_CTRL_START_CMD		BIT(30)
+#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK	BIT(8)
+#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ	(0 << 8)
+#define SUN4I_HDMI_DDC_CTRL_RESET		BIT(0)
+
+#define SUN4I_HDMI_DDC_ADDR_REG		0x504
+#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)	(((seg) & 0xff) << 24)
+#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)		(((addr) & 0xff) << 16)
+#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)		(((off) & 0xff) << 8)
+#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)		((addr) & 0xff)
+
+#define SUN4I_HDMI_DDC_FIFO_CTRL_REG	0x510
+#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR		BIT(31)
+
+#define SUN4I_HDMI_DDC_FIFO_DATA_REG	0x518
+#define SUN4I_HDMI_DDC_BYTE_COUNT_REG	0x51c
+
+#define SUN4I_HDMI_DDC_CMD_REG		0x520
+#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ	6
+
+#define SUN4I_HDMI_DDC_CLK_REG		0x528
+#define SUN4I_HDMI_DDC_CLK_M(m)			(((m) & 0x7) << 3)
+#define SUN4I_HDMI_DDC_CLK_N(n)			((n) & 0x7)
+
+#define SUN4I_HDMI_DDC_LINE_CTRL_REG	0x540
+#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE	BIT(9)
+#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE	BIT(8)
+
+#define SUN4I_HDMI_DDC_FIFO_SIZE	16
+
+enum sun4i_hdmi_pkt_type {
+	SUN4I_HDMI_PKT_AVI = 2,
+	SUN4I_HDMI_PKT_END = 15,
+};
+
+struct sun4i_hdmi {
+	struct drm_connector	connector;
+	struct drm_encoder	encoder;
+	struct device		*dev;
+
+	void __iomem		*base;
+
+	/* Parent clocks */
+	struct clk		*bus_clk;
+	struct clk		*mod_clk;
+	struct clk		*pll0_clk;
+	struct clk		*pll1_clk;
+
+	/* And the clocks we create */
+	struct clk		*ddc_clk;
+	struct clk		*tmds_clk;
+
+	struct sun4i_drv	*drv;
+
+	bool			hdmi_monitor;
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
+
+#endif /* _SUN4I_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
new file mode 100644
index 000000000000..4692e8c345ed
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_ddc {
+	struct clk_hw		hw;
+	struct sun4i_hdmi	*hdmi;
+};
+
+static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_ddc, hw);
+}
+
+static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
+					    unsigned long parent_rate,
+					    u8 *m, u8 *n)
+{
+	unsigned long best_rate = 0;
+	u8 best_m = 0, best_n = 0, _m, _n;
+
+	for (_m = 0; _m < 8; _m++) {
+		for (_n = 0; _n < 8; _n++) {
+			unsigned long tmp_rate;
+
+			tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
+
+			if (tmp_rate > rate)
+				continue;
+
+			if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = _m;
+				best_n = _n;
+			}
+		}
+	}
+
+	if (m && n) {
+		*m = best_m;
+		*n = best_n;
+	}
+
+	return best_rate;
+}
+
+static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long *prate)
+{
+	return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
+}
+
+static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+	u32 reg;
+	u8 m, n;
+
+	reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+	m = (reg >> 3) & 0x7;
+	n = reg & 0x7;
+
+	return (((parent_rate / 2) / 10) >> n) / (m + 1);
+}
+
+static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long parent_rate)
+{
+	struct sun4i_ddc *ddc = hw_to_ddc(hw);
+	u8 div_m, div_n;
+
+	sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
+
+	writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
+	       ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
+
+	return 0;
+}
+
+static const struct clk_ops sun4i_ddc_ops = {
+	.recalc_rate	= sun4i_ddc_recalc_rate,
+	.round_rate	= sun4i_ddc_round_rate,
+	.set_rate	= sun4i_ddc_set_rate,
+};
+
+int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
+{
+	struct clk_init_data init;
+	struct sun4i_ddc *ddc;
+	const char *parent_name;
+
+	parent_name = __clk_get_name(parent);
+	if (!parent_name)
+		return -ENODEV;
+
+	ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
+	if (!ddc)
+		return -ENOMEM;
+
+	init.name = "hdmi-ddc";
+	init.ops = &sun4i_ddc_ops;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	ddc->hdmi = hdmi;
+	ddc->hw.init = &init;
+
+	hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
+	if (IS_ERR(hdmi->ddc_clk))
+		return PTR_ERR(hdmi->ddc_clk);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
new file mode 100644
index 000000000000..9bec23164f53
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include "sun4i_backend.h"
+#include "sun4i_drv.h"
+#include "sun4i_hdmi.h"
+#include "sun4i_tcon.h"
+
+#define DDC_SEGMENT_ADDR	0x30
+
+static inline struct sun4i_hdmi *
+drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct sun4i_hdmi,
+			    encoder);
+}
+
+static inline struct sun4i_hdmi *
+drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
+{
+	return container_of(connector, struct sun4i_hdmi,
+			    connector);
+}
+
+static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
+					   struct drm_display_mode *mode)
+{
+	struct hdmi_avi_infoframe frame;
+	u8 buffer[17];
+	int i, ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
+	if (ret < 0) {
+		DRM_ERROR("Failed to get infoframes from mode\n");
+		return ret;
+	}
+
+	ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
+	if (ret < 0) {
+		DRM_ERROR("Failed to pack infoframes\n");
+		return ret;
+	}
+
+	for (i = 0; i < sizeof(buffer); i++)
+		writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
+
+	return 0;
+}
+
+static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
+				   struct drm_crtc_state *crtc_state,
+				   struct drm_connector_state *conn_state)
+{
+	struct drm_display_mode *mode = &crtc_state->mode;
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void sun4i_hdmi_disable(struct drm_encoder *encoder)
+{
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	u32 val;
+
+	DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
+
+	val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+	val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
+	writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+
+	sun4i_tcon_channel_disable(tcon, 1);
+}
+
+static void sun4i_hdmi_enable(struct drm_encoder *encoder)
+{
+	struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	u32 val = 0;
+
+	DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
+
+	sun4i_tcon_channel_enable(tcon, 1);
+
+	sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
+	val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
+	val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
+	writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
+
+	val = SUN4I_HDMI_VID_CTRL_ENABLE;
+	if (hdmi->hdmi_monitor)
+		val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
+
+	writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
+}
+
+static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
+				struct drm_display_mode *mode,
+				struct drm_display_mode *adjusted_mode)
+{
+	struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
+	struct sun4i_drv *drv = hdmi->drv;
+	struct sun4i_tcon *tcon = drv->tcon;
+	unsigned int x, y;
+	u32 val;
+
+	sun4i_tcon1_mode_set(tcon, mode);
+	sun4i_tcon_set_mux(tcon, 1, encoder);
+
+	clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
+	clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
+	clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
+
+	/* Set input sync enable */
+	writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
+	       hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
+
+	/* Setup timing registers */
+	writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
+	       SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
+
+	x = mode->htotal - mode->hsync_start;
+	y = mode->vtotal - mode->vsync_start;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
+
+	x = mode->hsync_start - mode->hdisplay;
+	y = mode->vsync_start - mode->vdisplay;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
+
+	x = mode->hsync_end - mode->hsync_start;
+	y = mode->vsync_end - mode->vsync_start;
+	writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
+	       hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
+
+	val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
+
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
+
+	writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
+}
+
+static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
+	.atomic_check	= sun4i_hdmi_atomic_check,
+	.disable	= sun4i_hdmi_disable,
+	.enable		= sun4i_hdmi_enable,
+	.mode_set	= sun4i_hdmi_mode_set,
+};
+
+static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
+	.destroy	= drm_encoder_cleanup,
+};
+
+static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
+				     unsigned int blk, unsigned int offset,
+				     u8 *buf, unsigned int count)
+{
+	unsigned long reg;
+	int i;
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
+	writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+	writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
+	       SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
+	       SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
+	       SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
+	       hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+	writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
+	       hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
+
+	writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
+	writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
+	       hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+			       !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
+			       100, 100000))
+		return -EIO;
+
+	for (i = 0; i < count; i++)
+		buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
+
+	return 0;
+}
+
+static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
+				      size_t length)
+{
+	struct sun4i_hdmi *hdmi = data;
+	int retry = 2, i;
+
+	do {
+		for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
+			unsigned char offset = blk * EDID_LENGTH + i;
+			unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
+						 length - i);
+			int ret;
+
+			ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
+							buf + i, count);
+			if (ret)
+				return ret;
+		}
+	} while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
+
+	return 0;
+}
+
+static int sun4i_hdmi_get_modes(struct drm_connector *connector)
+{
+	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+	unsigned long reg;
+	struct edid *edid;
+	int ret;
+
+	/* Reset i2c controller */
+	writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
+	       hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
+			       !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
+			       100, 2000))
+		return -EIO;
+
+	writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
+	       SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
+	       hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
+
+	clk_set_rate(hdmi->ddc_clk, 100000);
+
+	edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
+	if (!edid)
+		return 0;
+
+	hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
+	DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
+			 hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
+
+	drm_mode_connector_update_edid_property(connector, edid);
+	ret = drm_add_edid_modes(connector, edid);
+	kfree(edid);
+
+	return ret;
+}
+
+static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
+	.get_modes	= sun4i_hdmi_get_modes,
+};
+
+static enum drm_connector_status
+sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
+	unsigned long reg;
+
+	if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
+			       reg & SUN4I_HDMI_HPD_HIGH,
+			       0, 500000))
+		return connector_status_disconnected;
+
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
+	.dpms			= drm_atomic_helper_connector_dpms,
+	.detect			= sun4i_hdmi_connector_detect,
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.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 sun4i_hdmi_bind(struct device *dev, struct device *master,
+			   void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = data;
+	struct sun4i_drv *drv = drm->dev_private;
+	struct sun4i_hdmi *hdmi;
+	struct resource *res;
+	u32 reg;
+	int ret;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+	dev_set_drvdata(dev, hdmi);
+	hdmi->dev = dev;
+	hdmi->drv = drv;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	hdmi->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(hdmi->base)) {
+		dev_err(dev, "Couldn't map the HDMI encoder registers\n");
+		return PTR_ERR(hdmi->base);
+	}
+
+	hdmi->bus_clk = devm_clk_get(dev, "ahb");
+	if (IS_ERR(hdmi->bus_clk)) {
+		dev_err(dev, "Couldn't get the HDMI bus clock\n");
+		return PTR_ERR(hdmi->bus_clk);
+	}
+	clk_prepare_enable(hdmi->bus_clk);
+
+	hdmi->mod_clk = devm_clk_get(dev, "mod");
+	if (IS_ERR(hdmi->mod_clk)) {
+		dev_err(dev, "Couldn't get the HDMI mod clock\n");
+		return PTR_ERR(hdmi->mod_clk);
+	}
+	clk_prepare_enable(hdmi->mod_clk);
+
+	hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
+	if (IS_ERR(hdmi->pll0_clk)) {
+		dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
+		return PTR_ERR(hdmi->pll0_clk);
+	}
+
+	hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
+	if (IS_ERR(hdmi->pll1_clk)) {
+		dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
+		return PTR_ERR(hdmi->pll1_clk);
+	}
+
+	ret = sun4i_tmds_create(hdmi);
+	if (ret) {
+		dev_err(dev, "Couldn't create the TMDS clock\n");
+		return ret;
+	}
+
+	writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
+
+	writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
+	       SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
+	       SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
+	       SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
+	       hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
+
+	/*
+	 * We can't just initialize the register there, we need to
+	 * protect the clock bits that have already been read out and
+	 * cached by the clock framework.
+	 */
+	reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
+		SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
+		SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
+		SUN4I_HDMI_PAD_CTRL1_REG_DEN |
+		SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
+		SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
+		SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
+		SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
+	writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+	reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+	reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
+		SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |
+		SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
+		SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
+		SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
+		SUN4I_HDMI_PLL_CTRL_PLL_EN;
+	writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+	ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+	if (ret) {
+		dev_err(dev, "Couldn't create the DDC clock\n");
+		return ret;
+	}
+
+	drm_encoder_helper_add(&hdmi->encoder,
+			       &sun4i_hdmi_helper_funcs);
+	ret = drm_encoder_init(drm,
+			       &hdmi->encoder,
+			       &sun4i_hdmi_funcs,
+			       DRM_MODE_ENCODER_TMDS,
+			       NULL);
+	if (ret) {
+		dev_err(dev, "Couldn't initialise the HDMI encoder\n");
+		return ret;
+	}
+
+	hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
+								  dev->of_node);
+	if (!hdmi->encoder.possible_crtcs)
+		return ret;
+
+	drm_connector_helper_add(&hdmi->connector,
+				 &sun4i_hdmi_connector_helper_funcs);
+	ret = drm_connector_init(drm, &hdmi->connector,
+				 &sun4i_hdmi_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret) {
+		dev_err(dev,
+			"Couldn't initialise the HDMI connector\n");
+		goto err_cleanup_connector;
+	}
+
+	drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
+
+	return 0;
+
+err_cleanup_connector:
+	drm_encoder_cleanup(&hdmi->encoder);
+	return ret;
+}
+
+static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
+
+	drm_connector_cleanup(&hdmi->connector);
+	drm_encoder_cleanup(&hdmi->encoder);
+}
+
+static const struct component_ops sun4i_hdmi_ops = {
+	.bind	= sun4i_hdmi_bind,
+	.unbind	= sun4i_hdmi_unbind,
+};
+
+static int sun4i_hdmi_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &sun4i_hdmi_ops);
+}
+
+static int sun4i_hdmi_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &sun4i_hdmi_ops);
+
+	return 0;
+}
+
+static const struct of_device_id sun4i_hdmi_of_table[] = {
+	{ .compatible = "allwinner,sun5i-a10s-hdmi" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
+
+static struct platform_driver sun4i_hdmi_driver = {
+	.probe		= sun4i_hdmi_probe,
+	.remove		= sun4i_hdmi_remove,
+	.driver		= {
+		.name		= "sun4i-hdmi",
+		.of_match_table	= sun4i_hdmi_of_table,
+	},
+};
+module_platform_driver(sun4i_hdmi_driver);
+
+MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
+MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
new file mode 100644
index 000000000000..5cf2527bffc8
--- /dev/null
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 Free Electrons
+ * Copyright (C) 2016 NextThing Co
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+
+#include "sun4i_tcon.h"
+#include "sun4i_hdmi.h"
+
+struct sun4i_tmds {
+	struct clk_hw		hw;
+	struct sun4i_hdmi	*hdmi;
+};
+
+static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_tmds, hw);
+}
+
+
+static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
+					     unsigned long parent_rate,
+					     u8 *div,
+					     bool *half)
+{
+	unsigned long best_rate = 0;
+	u8 best_m = 0, m;
+	bool is_double;
+
+	for (m = 1; m < 16; m++) {
+		u8 d;
+
+		for (d = 1; d < 3; d++) {
+			unsigned long tmp_rate;
+
+			tmp_rate = parent_rate / m / d;
+
+			if (tmp_rate > rate)
+				continue;
+
+			if (!best_rate ||
+			    (rate - tmp_rate) < (rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = m;
+				is_double = d;
+			}
+		}
+	}
+
+	if (div && half) {
+		*div = best_m;
+		*half = is_double;
+	}
+
+	return best_rate;
+}
+
+
+static int sun4i_tmds_determine_rate(struct clk_hw *hw,
+				     struct clk_rate_request *req)
+{
+	struct clk_hw *parent;
+	unsigned long best_parent = 0;
+	unsigned long rate = req->rate;
+	int best_div = 1, best_half = 1;
+	int i, j;
+
+	/*
+	 * We only consider PLL3, since the TCON is very likely to be
+	 * clocked from it, and to have the same rate than our HDMI
+	 * clock, so we should not need to do anything.
+	 */
+
+	parent = clk_hw_get_parent_by_index(hw, 0);
+	if (!parent)
+		return -EINVAL;
+
+	for (i = 1; i < 3; i++) {
+		for (j = 1; j < 16; j++) {
+			unsigned long ideal = rate * i * j;
+			unsigned long rounded;
+
+			rounded = clk_hw_round_rate(parent, ideal);
+
+			if (rounded == ideal) {
+				best_parent = rounded;
+				best_half = i;
+				best_div = j;
+				goto out;
+			}
+
+			if (abs(rate - rounded / i) <
+			    abs(rate - best_parent / best_div)) {
+				best_parent = rounded;
+				best_div = i;
+			}
+		}
+	}
+
+out:
+	req->rate = best_parent / best_half / best_div;
+	req->best_parent_rate = best_parent;
+	req->best_parent_hw = parent;
+
+	return 0;
+}
+
+static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
+		parent_rate /= 2;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg = (reg >> 4) & 0xf;
+	if (!reg)
+		reg = 1;
+
+	return parent_rate / reg;
+}
+
+static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	bool half;
+	u32 reg;
+	u8 div;
+
+	sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+	reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	if (half)
+		reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
+	writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+	reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
+	writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
+	       tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
+
+	return 0;
+}
+
+static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+	return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
+		SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
+}
+
+static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct sun4i_tmds *tmds = hw_to_tmds(hw);
+	u32 reg;
+
+	if (index > 1)
+		return -EINVAL;
+
+	reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+	reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
+	writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
+	       tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
+
+	return 0;
+}
+
+static const struct clk_ops sun4i_tmds_ops = {
+	.determine_rate	= sun4i_tmds_determine_rate,
+	.recalc_rate	= sun4i_tmds_recalc_rate,
+	.set_rate	= sun4i_tmds_set_rate,
+
+	.get_parent	= sun4i_tmds_get_parent,
+	.set_parent	= sun4i_tmds_set_parent,
+};
+
+int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
+{
+	struct clk_init_data init;
+	struct sun4i_tmds *tmds;
+	const char *parents[2];
+
+	parents[0] = __clk_get_name(hdmi->pll0_clk);
+	if (!parents[0])
+		return -ENODEV;
+
+	parents[1] = __clk_get_name(hdmi->pll1_clk);
+	if (!parents[1])
+		return -ENODEV;
+
+	tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
+	if (!tmds)
+		return -ENOMEM;
+
+	init.name = "hdmi-tmds";
+	init.ops = &sun4i_tmds_ops;
+	init.parent_names = parents;
+	init.num_parents = 2;
+	init.flags = CLK_SET_RATE_PARENT;
+
+	tmds->hdmi = hdmi;
+	tmds->hw.init = &init;
+
+	hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
+	if (IS_ERR(hdmi->tmds_clk))
+		return PTR_ERR(hdmi->tmds_clk);
+
+	return 0;
+}
-- 
git-series 0.8.11

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

* [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The A10s has an HDMI controller connected to the second TCON channel. Add
it to our DT.

Since the TV Encoder was the only channel 1 user so far, also add the
property now that we have several users.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 arch/arm/boot/dts/sun5i-a10s.dtsi | 50 ++++++++++++++++++++++++++++++++-
 arch/arm/boot/dts/sun5i.dtsi      |  1 +-
 2 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
index 1e38ff80366c..c9d4ee12599d 100644
--- a/arch/arm/boot/dts/sun5i-a10s.dtsi
+++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
@@ -71,7 +71,49 @@
 		};
 	};
 
+	display-engine {
+		compatible = "allwinner,sun5i-a10s-display-engine",
+			     "allwinner,sun5i-a13-display-engine";
+		allwinner,pipelines = <&fe0>;
+	};
+
 	soc@01c00000 {
+		hdmi: hdmi@01c16000 {
+			compatible = "allwinner,sun5i-a10s-hdmi";
+			reg = <0x01c16000 0x1000>;
+			interrupts = <58>;
+			clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
+				 <&ccu CLK_PLL_VIDEO0_2X>,
+				 <&ccu CLK_PLL_VIDEO1_2X>;
+			clock-names = "ahb", "mod", "pll-0", "pll-1";
+			dmas = <&dma SUN4I_DMA_NORMAL 16>,
+			       <&dma SUN4I_DMA_NORMAL 16>,
+			       <&dma SUN4I_DMA_DEDICATED 24>;
+			dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				hdmi_in: port@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+
+					hdmi_in_tcon0: endpoint {
+						remote-endpoint = <&tcon0_out_hdmi>;
+					};
+				};
+
+				hdmi_out: port@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+				};
+			};
+		};
+
 		pwm: pwm@01c20e00 {
 			compatible = "allwinner,sun5i-a10s-pwm";
 			reg = <0x01c20e00 0xc>;
@@ -128,3 +170,11 @@
 
 &sram_a {
 };
+
+&tcon0_out {
+	tcon0_out_hdmi: endpoint@2 {
+		reg = <2>;
+		remote-endpoint = <&hdmi_in_tcon0>;
+		allwinner,tcon-channel = <1>;
+	};
+};
diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi
index 5175f9cc9bed..0e29f1d98a9e 100644
--- a/arch/arm/boot/dts/sun5i.dtsi
+++ b/arch/arm/boot/dts/sun5i.dtsi
@@ -272,6 +272,7 @@
 					tcon0_out_tve0: endpoint@1 {
 						reg = <1>;
 						remote-endpoint = <&tve0_in_tcon0>;
+						allwinner,tcon-channel = <1>;
 					};
 				};
 			};
-- 
git-series 0.8.11

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

* [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The A10s has an HDMI controller connected to the second TCON channel. Add
it to our DT.

Since the TV Encoder was the only channel 1 user so far, also add the
property now that we have several users.

Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 arch/arm/boot/dts/sun5i-a10s.dtsi | 50 ++++++++++++++++++++++++++++++++-
 arch/arm/boot/dts/sun5i.dtsi      |  1 +-
 2 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
index 1e38ff80366c..c9d4ee12599d 100644
--- a/arch/arm/boot/dts/sun5i-a10s.dtsi
+++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
@@ -71,7 +71,49 @@
 		};
 	};
 
+	display-engine {
+		compatible = "allwinner,sun5i-a10s-display-engine",
+			     "allwinner,sun5i-a13-display-engine";
+		allwinner,pipelines = <&fe0>;
+	};
+
 	soc@01c00000 {
+		hdmi: hdmi@01c16000 {
+			compatible = "allwinner,sun5i-a10s-hdmi";
+			reg = <0x01c16000 0x1000>;
+			interrupts = <58>;
+			clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
+				 <&ccu CLK_PLL_VIDEO0_2X>,
+				 <&ccu CLK_PLL_VIDEO1_2X>;
+			clock-names = "ahb", "mod", "pll-0", "pll-1";
+			dmas = <&dma SUN4I_DMA_NORMAL 16>,
+			       <&dma SUN4I_DMA_NORMAL 16>,
+			       <&dma SUN4I_DMA_DEDICATED 24>;
+			dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				hdmi_in: port@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+
+					hdmi_in_tcon0: endpoint {
+						remote-endpoint = <&tcon0_out_hdmi>;
+					};
+				};
+
+				hdmi_out: port@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+				};
+			};
+		};
+
 		pwm: pwm@01c20e00 {
 			compatible = "allwinner,sun5i-a10s-pwm";
 			reg = <0x01c20e00 0xc>;
@@ -128,3 +170,11 @@
 
 &sram_a {
 };
+
+&tcon0_out {
+	tcon0_out_hdmi: endpoint@2 {
+		reg = <2>;
+		remote-endpoint = <&hdmi_in_tcon0>;
+		allwinner,tcon-channel = <1>;
+	};
+};
diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi
index 5175f9cc9bed..0e29f1d98a9e 100644
--- a/arch/arm/boot/dts/sun5i.dtsi
+++ b/arch/arm/boot/dts/sun5i.dtsi
@@ -272,6 +272,7 @@
 					tcon0_out_tve0: endpoint@1 {
 						reg = <1>;
 						remote-endpoint = <&tve0_in_tcon0>;
+						allwinner,tcon-channel = <1>;
 					};
 				};
 			};
-- 
git-series 0.8.11

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

* [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The A10s has an HDMI controller connected to the second TCON channel. Add
it to our DT.

Since the TV Encoder was the only channel 1 user so far, also add the
property now that we have several users.

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 arch/arm/boot/dts/sun5i-a10s.dtsi | 50 ++++++++++++++++++++++++++++++++-
 arch/arm/boot/dts/sun5i.dtsi      |  1 +-
 2 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
index 1e38ff80366c..c9d4ee12599d 100644
--- a/arch/arm/boot/dts/sun5i-a10s.dtsi
+++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
@@ -71,7 +71,49 @@
 		};
 	};
 
+	display-engine {
+		compatible = "allwinner,sun5i-a10s-display-engine",
+			     "allwinner,sun5i-a13-display-engine";
+		allwinner,pipelines = <&fe0>;
+	};
+
 	soc at 01c00000 {
+		hdmi: hdmi at 01c16000 {
+			compatible = "allwinner,sun5i-a10s-hdmi";
+			reg = <0x01c16000 0x1000>;
+			interrupts = <58>;
+			clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
+				 <&ccu CLK_PLL_VIDEO0_2X>,
+				 <&ccu CLK_PLL_VIDEO1_2X>;
+			clock-names = "ahb", "mod", "pll-0", "pll-1";
+			dmas = <&dma SUN4I_DMA_NORMAL 16>,
+			       <&dma SUN4I_DMA_NORMAL 16>,
+			       <&dma SUN4I_DMA_DEDICATED 24>;
+			dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				hdmi_in: port at 0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+
+					hdmi_in_tcon0: endpoint {
+						remote-endpoint = <&tcon0_out_hdmi>;
+					};
+				};
+
+				hdmi_out: port at 1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+				};
+			};
+		};
+
 		pwm: pwm at 01c20e00 {
 			compatible = "allwinner,sun5i-a10s-pwm";
 			reg = <0x01c20e00 0xc>;
@@ -128,3 +170,11 @@
 
 &sram_a {
 };
+
+&tcon0_out {
+	tcon0_out_hdmi: endpoint at 2 {
+		reg = <2>;
+		remote-endpoint = <&hdmi_in_tcon0>;
+		allwinner,tcon-channel = <1>;
+	};
+};
diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi
index 5175f9cc9bed..0e29f1d98a9e 100644
--- a/arch/arm/boot/dts/sun5i.dtsi
+++ b/arch/arm/boot/dts/sun5i.dtsi
@@ -272,6 +272,7 @@
 					tcon0_out_tve0: endpoint at 1 {
 						reg = <1>;
 						remote-endpoint = <&tve0_in_tcon0>;
+						allwinner,tcon-channel = <1>;
 					};
 				};
 			};
-- 
git-series 0.8.11

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

* [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie, dri-devel, Mark Rutland,
	Rob Herring, devicetree, linux-arm-kernel, linux-clk,
	linux-kernel, linux-sunxi

The A10s Olinuxino has an HDMI connector. Make sure we can use it.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
 1 file changed, 29 insertions(+), 0 deletions(-)

diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
index 894f874a5beb..1d13b6407222 100644
--- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
+++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
@@ -63,6 +63,17 @@
 		stdout-path = "serial0:115200n8";
 	};
 
+	connector {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_out_con>;
+			};
+		};
+	};
+
 	leds {
 		compatible = "gpio-leds";
 		pinctrl-names = "default";
@@ -76,6 +87,10 @@
 	};
 };
 
+&be0 {
+	status = "okay";
+};
+
 &ehci0 {
 	status = "okay";
 };
@@ -91,6 +106,16 @@
 	status = "okay";
 };
 
+&hdmi {
+	status = "okay";
+};
+
+&hdmi_out {
+	hdmi_out_con: endpoint {
+		remote-endpoint = <&hdmi_con_in>;
+	};
+};
+
 &i2c0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&i2c0_pins_a>;
@@ -248,6 +273,10 @@
 	status = "okay";
 };
 
+&tcon0 {
+	status = "okay";
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins_a>;
-- 
git-series 0.8.11

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

* [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Maxime Ripard
  Cc: Daniel Vetter, David Airlie,
	dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Mark Rutland,
	Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

The A10s Olinuxino has an HDMI connector. Make sure we can use it.

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
---
 arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
 1 file changed, 29 insertions(+), 0 deletions(-)

diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
index 894f874a5beb..1d13b6407222 100644
--- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
+++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
@@ -63,6 +63,17 @@
 		stdout-path = "serial0:115200n8";
 	};
 
+	connector {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_out_con>;
+			};
+		};
+	};
+
 	leds {
 		compatible = "gpio-leds";
 		pinctrl-names = "default";
@@ -76,6 +87,10 @@
 	};
 };
 
+&be0 {
+	status = "okay";
+};
+
 &ehci0 {
 	status = "okay";
 };
@@ -91,6 +106,16 @@
 	status = "okay";
 };
 
+&hdmi {
+	status = "okay";
+};
+
+&hdmi_out {
+	hdmi_out_con: endpoint {
+		remote-endpoint = <&hdmi_con_in>;
+	};
+};
+
 &i2c0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&i2c0_pins_a>;
@@ -248,6 +273,10 @@
 	status = "okay";
 };
 
+&tcon0 {
+	status = "okay";
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins_a>;
-- 
git-series 0.8.11

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

* [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-03 11:59   ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-03 11:59 UTC (permalink / raw)
  To: linux-arm-kernel

The A10s Olinuxino has an HDMI connector. Make sure we can use it.

Acked-by: Chen-Yu Tsai <wens@csie.org>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
---
 arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
 1 file changed, 29 insertions(+), 0 deletions(-)

diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
index 894f874a5beb..1d13b6407222 100644
--- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
+++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
@@ -63,6 +63,17 @@
 		stdout-path = "serial0:115200n8";
 	};
 
+	connector {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_out_con>;
+			};
+		};
+	};
+
 	leds {
 		compatible = "gpio-leds";
 		pinctrl-names = "default";
@@ -76,6 +87,10 @@
 	};
 };
 
+&be0 {
+	status = "okay";
+};
+
 &ehci0 {
 	status = "okay";
 };
@@ -91,6 +106,16 @@
 	status = "okay";
 };
 
+&hdmi {
+	status = "okay";
+};
+
+&hdmi_out {
+	hdmi_out_con: endpoint {
+		remote-endpoint = <&hdmi_con_in>;
+	};
+};
+
 &i2c0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&i2c0_pins_a>;
@@ -248,6 +273,10 @@
 	status = "okay";
 };
 
+&tcon0 {
+	status = "okay";
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins_a>;
-- 
git-series 0.8.11

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

* Re: [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock
@ 2017-05-03 12:53     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-03 12:53 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> So far, divider_round_rate only considers the parent clock returned by
> clk_hw_get_parent.
>
> This works fine on clocks that have a single parents, this doesn't work on
> muxes, since we will only consider the first parent, while other parents
> may totally be able to provide a better combination.
>
> Clocks in that case cannot use divider_round_rate, so would have to come up
> with a very similar logic to work around it. Instead of having to do
> something like this, and duplicate that logic everywhere, create a
> divider_round_rate parent to allow caller to give an additional parameter
> for the parent clock to consider.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/clk/clk-divider.c    | 26 ++++++++++++++++++--------
>  include/linux/clk-provider.h |  4 ++++
>  2 files changed, 22 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> index 96386ffc8483..48750439b1cd 100644
> --- a/drivers/clk/clk-divider.c
> +++ b/drivers/clk/clk-divider.c
> @@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
>         return div;
>  }
>
> -static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
> +static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate,
>                                unsigned long *best_parent_rate,
>                                const struct clk_div_table *table, u8 width,
>                                unsigned long flags)
> @@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
>                         *best_parent_rate = parent_rate_saved;
>                         return i;
>                 }
> -               parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
> -                                              rate * i);
> +               parent_rate = clk_hw_round_rate(parent, rate * i);
>                 now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
>                 if (_is_best_div(rate, now, best, flags)) {
>                         bestdiv = i;
> @@ -326,22 +326,32 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
>
>         if (!bestdiv) {
>                 bestdiv = _get_maxdiv(table, width, flags);
> -               *best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
> +               *best_parent_rate = clk_hw_round_rate(parent, 1);
>         }
>
>         return bestdiv;
>  }
>
> -long divider_round_rate(struct clk_hw *hw, unsigned long rate,
> -                       unsigned long *prate, const struct clk_div_table *table,
> -                       u8 width, unsigned long flags)
> +long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate, unsigned long *prate,
> +                              const struct clk_div_table *table,
> +                              u8 width, unsigned long flags)
>  {
>         int div;
>
> -       div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
> +       div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
>
>         return DIV_ROUND_UP_ULL((u64)*prate, div);
>  }
> +EXPORT_SYMBOL_GPL(divider_round_rate_parent);
> +
> +long divider_round_rate(struct clk_hw *hw, unsigned long rate,
> +                       unsigned long *prate, const struct clk_div_table *table,
> +                       u8 width, unsigned long flags)
> +{
> +       return divider_round_rate_parent(hw, clk_hw_get_parent(hw), rate, prate,
> +                                        table, width, flags);
> +}
>  EXPORT_SYMBOL_GPL(divider_round_rate);

Could this be made a static inline instead? Otherwise,

Reviewed-by: Chen-Yu Tsai <wens@csie.org>

>
>  static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> index a428aec36ace..14102f783f64 100644
> --- a/include/linux/clk-provider.h
> +++ b/include/linux/clk-provider.h
> @@ -412,6 +412,10 @@ extern const struct clk_ops clk_divider_ro_ops;
>  unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
>                 unsigned int val, const struct clk_div_table *table,
>                 unsigned long flags);
> +long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate, unsigned long *prate,
> +                              const struct clk_div_table *table,
> +                              u8 width, unsigned long flags);
>  long divider_round_rate(struct clk_hw *hw, unsigned long rate,
>                 unsigned long *prate, const struct clk_div_table *table,
>                 u8 width, unsigned long flags);
> --
> git-series 0.8.11

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

* Re: [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock
@ 2017-05-03 12:53     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-03 12:53 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> So far, divider_round_rate only considers the parent clock returned by
> clk_hw_get_parent.
>
> This works fine on clocks that have a single parents, this doesn't work on
> muxes, since we will only consider the first parent, while other parents
> may totally be able to provide a better combination.
>
> Clocks in that case cannot use divider_round_rate, so would have to come up
> with a very similar logic to work around it. Instead of having to do
> something like this, and duplicate that logic everywhere, create a
> divider_round_rate parent to allow caller to give an additional parameter
> for the parent clock to consider.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/clk/clk-divider.c    | 26 ++++++++++++++++++--------
>  include/linux/clk-provider.h |  4 ++++
>  2 files changed, 22 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> index 96386ffc8483..48750439b1cd 100644
> --- a/drivers/clk/clk-divider.c
> +++ b/drivers/clk/clk-divider.c
> @@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
>         return div;
>  }
>
> -static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
> +static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate,
>                                unsigned long *best_parent_rate,
>                                const struct clk_div_table *table, u8 width,
>                                unsigned long flags)
> @@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
>                         *best_parent_rate = parent_rate_saved;
>                         return i;
>                 }
> -               parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
> -                                              rate * i);
> +               parent_rate = clk_hw_round_rate(parent, rate * i);
>                 now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
>                 if (_is_best_div(rate, now, best, flags)) {
>                         bestdiv = i;
> @@ -326,22 +326,32 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
>
>         if (!bestdiv) {
>                 bestdiv = _get_maxdiv(table, width, flags);
> -               *best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
> +               *best_parent_rate = clk_hw_round_rate(parent, 1);
>         }
>
>         return bestdiv;
>  }
>
> -long divider_round_rate(struct clk_hw *hw, unsigned long rate,
> -                       unsigned long *prate, const struct clk_div_table *table,
> -                       u8 width, unsigned long flags)
> +long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate, unsigned long *prate,
> +                              const struct clk_div_table *table,
> +                              u8 width, unsigned long flags)
>  {
>         int div;
>
> -       div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
> +       div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
>
>         return DIV_ROUND_UP_ULL((u64)*prate, div);
>  }
> +EXPORT_SYMBOL_GPL(divider_round_rate_parent);
> +
> +long divider_round_rate(struct clk_hw *hw, unsigned long rate,
> +                       unsigned long *prate, const struct clk_div_table *table,
> +                       u8 width, unsigned long flags)
> +{
> +       return divider_round_rate_parent(hw, clk_hw_get_parent(hw), rate, prate,
> +                                        table, width, flags);
> +}
>  EXPORT_SYMBOL_GPL(divider_round_rate);

Could this be made a static inline instead? Otherwise,

Reviewed-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

>
>  static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> index a428aec36ace..14102f783f64 100644
> --- a/include/linux/clk-provider.h
> +++ b/include/linux/clk-provider.h
> @@ -412,6 +412,10 @@ extern const struct clk_ops clk_divider_ro_ops;
>  unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
>                 unsigned int val, const struct clk_div_table *table,
>                 unsigned long flags);
> +long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate, unsigned long *prate,
> +                              const struct clk_div_table *table,
> +                              u8 width, unsigned long flags);
>  long divider_round_rate(struct clk_hw *hw, unsigned long rate,
>                 unsigned long *prate, const struct clk_div_table *table,
>                 u8 width, unsigned long flags);
> --
> git-series 0.8.11

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

* [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock
@ 2017-05-03 12:53     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-03 12:53 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> So far, divider_round_rate only considers the parent clock returned by
> clk_hw_get_parent.
>
> This works fine on clocks that have a single parents, this doesn't work on
> muxes, since we will only consider the first parent, while other parents
> may totally be able to provide a better combination.
>
> Clocks in that case cannot use divider_round_rate, so would have to come up
> with a very similar logic to work around it. Instead of having to do
> something like this, and duplicate that logic everywhere, create a
> divider_round_rate parent to allow caller to give an additional parameter
> for the parent clock to consider.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/clk/clk-divider.c    | 26 ++++++++++++++++++--------
>  include/linux/clk-provider.h |  4 ++++
>  2 files changed, 22 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
> index 96386ffc8483..48750439b1cd 100644
> --- a/drivers/clk/clk-divider.c
> +++ b/drivers/clk/clk-divider.c
> @@ -275,7 +275,8 @@ static int _next_div(const struct clk_div_table *table, int div,
>         return div;
>  }
>
> -static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
> +static int clk_divider_bestdiv(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate,
>                                unsigned long *best_parent_rate,
>                                const struct clk_div_table *table, u8 width,
>                                unsigned long flags)
> @@ -314,8 +315,7 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
>                         *best_parent_rate = parent_rate_saved;
>                         return i;
>                 }
> -               parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
> -                                              rate * i);
> +               parent_rate = clk_hw_round_rate(parent, rate * i);
>                 now = DIV_ROUND_UP_ULL((u64)parent_rate, i);
>                 if (_is_best_div(rate, now, best, flags)) {
>                         bestdiv = i;
> @@ -326,22 +326,32 @@ static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
>
>         if (!bestdiv) {
>                 bestdiv = _get_maxdiv(table, width, flags);
> -               *best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
> +               *best_parent_rate = clk_hw_round_rate(parent, 1);
>         }
>
>         return bestdiv;
>  }
>
> -long divider_round_rate(struct clk_hw *hw, unsigned long rate,
> -                       unsigned long *prate, const struct clk_div_table *table,
> -                       u8 width, unsigned long flags)
> +long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate, unsigned long *prate,
> +                              const struct clk_div_table *table,
> +                              u8 width, unsigned long flags)
>  {
>         int div;
>
> -       div = clk_divider_bestdiv(hw, rate, prate, table, width, flags);
> +       div = clk_divider_bestdiv(hw, parent, rate, prate, table, width, flags);
>
>         return DIV_ROUND_UP_ULL((u64)*prate, div);
>  }
> +EXPORT_SYMBOL_GPL(divider_round_rate_parent);
> +
> +long divider_round_rate(struct clk_hw *hw, unsigned long rate,
> +                       unsigned long *prate, const struct clk_div_table *table,
> +                       u8 width, unsigned long flags)
> +{
> +       return divider_round_rate_parent(hw, clk_hw_get_parent(hw), rate, prate,
> +                                        table, width, flags);
> +}
>  EXPORT_SYMBOL_GPL(divider_round_rate);

Could this be made a static inline instead? Otherwise,

Reviewed-by: Chen-Yu Tsai <wens@csie.org>

>
>  static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
> diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
> index a428aec36ace..14102f783f64 100644
> --- a/include/linux/clk-provider.h
> +++ b/include/linux/clk-provider.h
> @@ -412,6 +412,10 @@ extern const struct clk_ops clk_divider_ro_ops;
>  unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
>                 unsigned int val, const struct clk_div_table *table,
>                 unsigned long flags);
> +long divider_round_rate_parent(struct clk_hw *hw, struct clk_hw *parent,
> +                              unsigned long rate, unsigned long *prate,
> +                              const struct clk_div_table *table,
> +                              u8 width, unsigned long flags);
>  long divider_round_rate(struct clk_hw *hw, unsigned long rate,
>                 unsigned long *prate, const struct clk_div_table *table,
>                 u8 width, unsigned long flags);
> --
> git-series 0.8.11

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

* Re: [PATCH v2 2/20] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
  2017-05-03 11:59   ` Maxime Ripard
@ 2017-05-04  3:19     ` Chen-Yu Tsai
  -1 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:19 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The clocks might need to modify their parent clocks. In order to make that
> possible, give them access to the parent clock being evaluated, and to a
> pointer to the parent rate so that they can modify it if needed.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* [PATCH v2 2/20] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate
@ 2017-05-04  3:19     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:19 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The clocks might need to modify their parent clocks. In order to make that
> possible, give them access to the parent clock being evaluated, and to a
> pointer to the parent rate so that they can modify it if needed.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 3/20] clk: sunxi-ng: div: Switch to divider_round_rate
  2017-05-03 11:59   ` Maxime Ripard
@ 2017-05-04  3:20     ` Chen-Yu Tsai
  -1 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:20 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> divider_round_rate already evaluates changing the parent rate if

  ^^^ Might want to update this, as you are now using the new function
      you added in patch 1.

> CLK_SET_RATE_PARENT is set. Now that we can do that on muxes too, let's
> just use it.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* [PATCH v2 3/20] clk: sunxi-ng: div: Switch to divider_round_rate
@ 2017-05-04  3:20     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:20 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> divider_round_rate already evaluates changing the parent rate if

  ^^^ Might want to update this, as you are now using the new function
      you added in patch 1.

> CLK_SET_RATE_PARENT is set. Now that we can do that on muxes too, let's
> just use it.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
@ 2017-05-04  3:23     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:23 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The current code only rely on the parent to change its rate in the case
> where CLK_SET_RATE_PARENT is set.
>
> However, some clock rates might be obtained only through a modification of
> the parent and the clock divider. Just rely on the round rate of the clocks
> to give us the best computation that might be achieved for a given rate.
>
> round_rate functions now need to honor CLK_SET_RATE_PARENT, but either the
> functions already do that if they modify the parent, or don't modify the
> praents at all.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
@ 2017-05-04  3:23     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:23 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The current code only rely on the parent to change its rate in the case
> where CLK_SET_RATE_PARENT is set.
>
> However, some clock rates might be obtained only through a modification of
> the parent and the clock divider. Just rely on the round rate of the clocks
> to give us the best computation that might be achieved for a given rate.
>
> round_rate functions now need to honor CLK_SET_RATE_PARENT, but either the
> functions already do that if they modify the parent, or don't modify the
> praents at all.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

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

* [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT
@ 2017-05-04  3:23     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:23 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The current code only rely on the parent to change its rate in the case
> where CLK_SET_RATE_PARENT is set.
>
> However, some clock rates might be obtained only through a modification of
> the parent and the clock divider. Just rely on the round rate of the clocks
> to give us the best computation that might be achieved for a given rate.
>
> round_rate functions now need to honor CLK_SET_RATE_PARENT, but either the
> functions already do that if they modify the parent, or don't modify the
> praents at all.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code
@ 2017-05-04  3:25     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:25 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The pre-divider retrieval code was merged into the function to apply the
> current pre-divider onto the parent clock rate so that we can use that
> adjusted value to do our factors computation.
>
> However, since we'll need to do the reverse operation, we need to split out
> that code into a function that will be shared.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

Seems this would conflict with my "clk: sunxi-ng: Support multiple variable
pre-dividers" patch though. We'll see how this works out.

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

* Re: [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code
@ 2017-05-04  3:25     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:25 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The pre-divider retrieval code was merged into the function to apply the
> current pre-divider onto the parent clock rate so that we can use that
> adjusted value to do our factors computation.
>
> However, since we'll need to do the reverse operation, we need to split out
> that code into a function that will be shared.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

Seems this would conflict with my "clk: sunxi-ng: Support multiple variable
pre-dividers" patch though. We'll see how this works out.

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

* [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code
@ 2017-05-04  3:25     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:25 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The pre-divider retrieval code was merged into the function to apply the
> current pre-divider onto the parent clock rate so that we can use that
> adjusted value to do our factors computation.
>
> However, since we'll need to do the reverse operation, we need to split out
> that code into a function that will be shared.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

Seems this would conflict with my "clk: sunxi-ng: Support multiple variable
pre-dividers" patch though. We'll see how this works out.

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

* Re: [PATCH v2 6/20] clk: sunxi-ng: mux: Change pre-divider application function prototype
  2017-05-03 11:59   ` Maxime Ripard
@ 2017-05-04  3:27     ` Chen-Yu Tsai
  -1 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:27 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The current function name is a bit confusing, and doesn't really allow to
> create an explicit function to reverse the operation.
>
> We also for now change the parent rate through a pointer, while we don't
> return anything.
>
> In order to be less confusing, and easier to use for downstream users,
> change the function name to something hopefully clearer, and return the
> adjusted rate instead of changing the pointer.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* [PATCH v2 6/20] clk: sunxi-ng: mux: Change pre-divider application function prototype
@ 2017-05-04  3:27     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:27 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The current function name is a bit confusing, and doesn't really allow to
> create an explicit function to reverse the operation.
>
> We also for now change the parent rate through a pointer, while we don't
> return anything.
>
> In order to be less confusing, and easier to use for downstream users,
> change the function name to something hopefully clearer, and return the
> adjusted rate instead of changing the pointer.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate
@ 2017-05-04  3:28     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:28 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Currently, the parent rate given back to the clock framework in our
> request is the original parent rate we calculated before trying to round
> the rate of our clock.
>
> This works fine unless our clock also changes its parent rate, in which
> case we will simply ignore that change and still use the previous parent
> rate.
>
> Create a new function to re-adjust the parent rate to take the pre-dividers
> into account, and give that back to the clock framework.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/clk/sunxi-ng/ccu_mux.c | 33 ++++++++++++++++++++++++++++-----
>  1 file changed, 28 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
> index c33210972581..1ce62cc23f8a 100644
> --- a/drivers/clk/sunxi-ng/ccu_mux.c
> +++ b/drivers/clk/sunxi-ng/ccu_mux.c
> @@ -64,6 +64,14 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
>         return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
>  }
>
> +unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
> +                                           struct ccu_mux_internal *cm,
> +                                           int parent_index,
> +                                           unsigned long parent_rate)
> +{
> +       return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
> +}
> +
>  int ccu_mux_helper_determine_rate(struct ccu_common *common,
>                                   struct ccu_mux_internal *cm,
>                                   struct clk_rate_request *req,
> @@ -89,22 +97,37 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
>                 best_rate = round(cm, best_parent, &adj_parent_rate,
>                                   req->rate, data);
>
> +               /*
> +                * parent_rate might have been modified by our clock.
> +                * Re-apply the pre-divider if there's one, and give

Might want to reword the comments to match the new name.

> +                * the actual frequency the parent needs to run at.
> +                */
> +               best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
> +                                                                adj_parent_rate);
> +
>                 goto out;
>         }
>
>         for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
> -               unsigned long tmp_rate, parent_rate, adj_parent_rate;
> +               unsigned long tmp_rate, parent_rate;
>                 struct clk_hw *parent;
>
>                 parent = clk_hw_get_parent_by_index(hw, i);
>                 if (!parent)
>                         continue;
>
> -               parent_rate = clk_hw_get_rate(parent);
> -               adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
> -                                                             parent_rate);
> +               parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
> +                                                         clk_hw_get_rate(parent));
> +
> +               tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
>
> -               tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
> +               /*
> +                * parent_rate might have been modified by our clock.
> +                * Re-apply the pre-divider if there's one, and give

Same here. Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

> +                * the actual frequency the parent needs to run at.
> +                */
> +               parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
> +                                                           parent_rate);
>                 if (tmp_rate == req->rate) {
>                         best_parent = parent;
>                         best_parent_rate = parent_rate;
> --
> git-series 0.8.11

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

* Re: [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate
@ 2017-05-04  3:28     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:28 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> Currently, the parent rate given back to the clock framework in our
> request is the original parent rate we calculated before trying to round
> the rate of our clock.
>
> This works fine unless our clock also changes its parent rate, in which
> case we will simply ignore that change and still use the previous parent
> rate.
>
> Create a new function to re-adjust the parent rate to take the pre-dividers
> into account, and give that back to the clock framework.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/clk/sunxi-ng/ccu_mux.c | 33 ++++++++++++++++++++++++++++-----
>  1 file changed, 28 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
> index c33210972581..1ce62cc23f8a 100644
> --- a/drivers/clk/sunxi-ng/ccu_mux.c
> +++ b/drivers/clk/sunxi-ng/ccu_mux.c
> @@ -64,6 +64,14 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
>         return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
>  }
>
> +unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
> +                                           struct ccu_mux_internal *cm,
> +                                           int parent_index,
> +                                           unsigned long parent_rate)
> +{
> +       return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
> +}
> +
>  int ccu_mux_helper_determine_rate(struct ccu_common *common,
>                                   struct ccu_mux_internal *cm,
>                                   struct clk_rate_request *req,
> @@ -89,22 +97,37 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
>                 best_rate = round(cm, best_parent, &adj_parent_rate,
>                                   req->rate, data);
>
> +               /*
> +                * parent_rate might have been modified by our clock.
> +                * Re-apply the pre-divider if there's one, and give

Might want to reword the comments to match the new name.

> +                * the actual frequency the parent needs to run at.
> +                */
> +               best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
> +                                                                adj_parent_rate);
> +
>                 goto out;
>         }
>
>         for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
> -               unsigned long tmp_rate, parent_rate, adj_parent_rate;
> +               unsigned long tmp_rate, parent_rate;
>                 struct clk_hw *parent;
>
>                 parent = clk_hw_get_parent_by_index(hw, i);
>                 if (!parent)
>                         continue;
>
> -               parent_rate = clk_hw_get_rate(parent);
> -               adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
> -                                                             parent_rate);
> +               parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
> +                                                         clk_hw_get_rate(parent));
> +
> +               tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
>
> -               tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
> +               /*
> +                * parent_rate might have been modified by our clock.
> +                * Re-apply the pre-divider if there's one, and give

Same here. Otherwise,

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

> +                * the actual frequency the parent needs to run at.
> +                */
> +               parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
> +                                                           parent_rate);
>                 if (tmp_rate == req->rate) {
>                         best_parent = parent;
>                         best_parent_rate = parent_rate;
> --
> git-series 0.8.11

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

* [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate
@ 2017-05-04  3:28     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:28 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Currently, the parent rate given back to the clock framework in our
> request is the original parent rate we calculated before trying to round
> the rate of our clock.
>
> This works fine unless our clock also changes its parent rate, in which
> case we will simply ignore that change and still use the previous parent
> rate.
>
> Create a new function to re-adjust the parent rate to take the pre-dividers
> into account, and give that back to the clock framework.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/clk/sunxi-ng/ccu_mux.c | 33 ++++++++++++++++++++++++++++-----
>  1 file changed, 28 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/clk/sunxi-ng/ccu_mux.c b/drivers/clk/sunxi-ng/ccu_mux.c
> index c33210972581..1ce62cc23f8a 100644
> --- a/drivers/clk/sunxi-ng/ccu_mux.c
> +++ b/drivers/clk/sunxi-ng/ccu_mux.c
> @@ -64,6 +64,14 @@ unsigned long ccu_mux_helper_apply_prediv(struct ccu_common *common,
>         return parent_rate / ccu_mux_get_prediv(common, cm, parent_index);
>  }
>
> +unsigned long ccu_mux_helper_unapply_prediv(struct ccu_common *common,
> +                                           struct ccu_mux_internal *cm,
> +                                           int parent_index,
> +                                           unsigned long parent_rate)
> +{
> +       return parent_rate * ccu_mux_get_prediv(common, cm, parent_index);
> +}
> +
>  int ccu_mux_helper_determine_rate(struct ccu_common *common,
>                                   struct ccu_mux_internal *cm,
>                                   struct clk_rate_request *req,
> @@ -89,22 +97,37 @@ int ccu_mux_helper_determine_rate(struct ccu_common *common,
>                 best_rate = round(cm, best_parent, &adj_parent_rate,
>                                   req->rate, data);
>
> +               /*
> +                * parent_rate might have been modified by our clock.
> +                * Re-apply the pre-divider if there's one, and give

Might want to reword the comments to match the new name.

> +                * the actual frequency the parent needs to run at.
> +                */
> +               best_parent_rate = ccu_mux_helper_unapply_prediv(common, cm, -1,
> +                                                                adj_parent_rate);
> +
>                 goto out;
>         }
>
>         for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
> -               unsigned long tmp_rate, parent_rate, adj_parent_rate;
> +               unsigned long tmp_rate, parent_rate;
>                 struct clk_hw *parent;
>
>                 parent = clk_hw_get_parent_by_index(hw, i);
>                 if (!parent)
>                         continue;
>
> -               parent_rate = clk_hw_get_rate(parent);
> -               adj_parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
> -                                                             parent_rate);
> +               parent_rate = ccu_mux_helper_apply_prediv(common, cm, i,
> +                                                         clk_hw_get_rate(parent));
> +
> +               tmp_rate = round(cm, parent, &parent_rate, req->rate, data);
>
> -               tmp_rate = round(cm, parent, &adj_parent_rate, req->rate, data);
> +               /*
> +                * parent_rate might have been modified by our clock.
> +                * Re-apply the pre-divider if there's one, and give

Same here. Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

> +                * the actual frequency the parent needs to run at.
> +                */
> +               parent_rate = ccu_mux_helper_unapply_prediv(common, cm, i,
> +                                                           parent_rate);
>                 if (tmp_rate == req->rate) {
>                         best_parent = parent;
>                         best_parent_rate = parent_rate;
> --
> git-series 0.8.11

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

* Re: [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs
@ 2017-05-04  3:28     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:28 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The video PLLs are used directly by the HDMI controller. Export them so
> that we can use them in our DT node.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs
@ 2017-05-04  3:28     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:28 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The video PLLs are used directly by the HDMI controller. Export them so
> that we can use them in our DT node.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

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

* [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs
@ 2017-05-04  3:28     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:28 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The video PLLs are used directly by the HDMI controller. Export them so
> that we can use them in our DT node.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function
@ 2017-05-04  3:54     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:54 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The muxing can actually happen on both channels on some SoCs, so it makes
> more sense to just move it out of the sun4i_tcon1_mode_set function and
> create a separate function that needs to be called by the encoders.
>
> Let's do that and convert the existing drivers.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_rgb.c  |  1 +
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 22 ++++++++++++++++------
>  drivers/gpu/drm/sun4i/sun4i_tcon.h |  2 ++
>  drivers/gpu/drm/sun4i/sun4i_tv.c   |  1 +
>  4 files changed, 20 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> index 67f0b91a99de..3003d290c635 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> @@ -175,6 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
>         struct sun4i_tcon *tcon = rgb->tcon;
>
>         sun4i_tcon0_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 0, encoder);
>
>         clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 66a5bb9b85e9..0204d9fadb66 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -108,6 +108,22 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>  }
>  EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>
> +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
> +                       struct drm_encoder *encoder)

The channel doesn't really matter. What is needed is which TCON and encoder
are supposed to be muxed together. This is going to be per SoC type anyway.

I have something in the works, though it's not finished yet.
I think this works for now.

Acked-by: Chen-Yu Tsai <wens@csie.org>

> +{
> +       if (!tcon->quirks->has_unknown_mux)
> +               return;
> +
> +       if (channel != 1)
> +               return;
> +
> +       /*
> +        * FIXME: Undocumented bits
> +        */
> +       regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
> +}
> +EXPORT_SYMBOL(sun4i_tcon_set_mux);
> +
>  static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
>                                     int channel)
>  {
> @@ -266,12 +282,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>         regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
>                            SUN4I_TCON_GCTL_IOMAP_MASK,
>                            SUN4I_TCON_GCTL_IOMAP_TCON1);
> -
> -       /*
> -        * FIXME: Undocumented bits
> -        */
> -       if (tcon->quirks->has_unknown_mux)
> -               regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
>  }
>  EXPORT_SYMBOL(sun4i_tcon1_mode_set);
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
> index f636343a935d..0350936b413c 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
> @@ -190,6 +190,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
>  /* Mode Related Controls */
>  void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
>                                  bool enable);
> +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
> +                       struct drm_encoder *encoder);
>  void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
>                           struct drm_display_mode *mode);
>  void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
> index 49c49431a053..03c494b8159c 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tv.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
> @@ -393,6 +393,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
>         const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
>
>         sun4i_tcon1_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 1, encoder);
>
>         /* Enable and map the DAC to the output */
>         regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
> --
> git-series 0.8.11

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

* Re: [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function
@ 2017-05-04  3:54     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:54 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The muxing can actually happen on both channels on some SoCs, so it makes
> more sense to just move it out of the sun4i_tcon1_mode_set function and
> create a separate function that needs to be called by the encoders.
>
> Let's do that and convert the existing drivers.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/gpu/drm/sun4i/sun4i_rgb.c  |  1 +
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 22 ++++++++++++++++------
>  drivers/gpu/drm/sun4i/sun4i_tcon.h |  2 ++
>  drivers/gpu/drm/sun4i/sun4i_tv.c   |  1 +
>  4 files changed, 20 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> index 67f0b91a99de..3003d290c635 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> @@ -175,6 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
>         struct sun4i_tcon *tcon = rgb->tcon;
>
>         sun4i_tcon0_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 0, encoder);
>
>         clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 66a5bb9b85e9..0204d9fadb66 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -108,6 +108,22 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>  }
>  EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>
> +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
> +                       struct drm_encoder *encoder)

The channel doesn't really matter. What is needed is which TCON and encoder
are supposed to be muxed together. This is going to be per SoC type anyway.

I have something in the works, though it's not finished yet.
I think this works for now.

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

> +{
> +       if (!tcon->quirks->has_unknown_mux)
> +               return;
> +
> +       if (channel != 1)
> +               return;
> +
> +       /*
> +        * FIXME: Undocumented bits
> +        */
> +       regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
> +}
> +EXPORT_SYMBOL(sun4i_tcon_set_mux);
> +
>  static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
>                                     int channel)
>  {
> @@ -266,12 +282,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>         regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
>                            SUN4I_TCON_GCTL_IOMAP_MASK,
>                            SUN4I_TCON_GCTL_IOMAP_TCON1);
> -
> -       /*
> -        * FIXME: Undocumented bits
> -        */
> -       if (tcon->quirks->has_unknown_mux)
> -               regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
>  }
>  EXPORT_SYMBOL(sun4i_tcon1_mode_set);
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
> index f636343a935d..0350936b413c 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
> @@ -190,6 +190,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
>  /* Mode Related Controls */
>  void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
>                                  bool enable);
> +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
> +                       struct drm_encoder *encoder);
>  void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
>                           struct drm_display_mode *mode);
>  void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
> index 49c49431a053..03c494b8159c 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tv.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
> @@ -393,6 +393,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
>         const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
>
>         sun4i_tcon1_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 1, encoder);
>
>         /* Enable and map the DAC to the output */
>         regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
> --
> git-series 0.8.11

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

* [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function
@ 2017-05-04  3:54     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:54 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The muxing can actually happen on both channels on some SoCs, so it makes
> more sense to just move it out of the sun4i_tcon1_mode_set function and
> create a separate function that needs to be called by the encoders.
>
> Let's do that and convert the existing drivers.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_rgb.c  |  1 +
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 22 ++++++++++++++++------
>  drivers/gpu/drm/sun4i/sun4i_tcon.h |  2 ++
>  drivers/gpu/drm/sun4i/sun4i_tv.c   |  1 +
>  4 files changed, 20 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> index 67f0b91a99de..3003d290c635 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_rgb.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c
> @@ -175,6 +175,7 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
>         struct sun4i_tcon *tcon = rgb->tcon;
>
>         sun4i_tcon0_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 0, encoder);
>
>         clk_set_rate(tcon->dclk, mode->crtc_clock * 1000);
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 66a5bb9b85e9..0204d9fadb66 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -108,6 +108,22 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>  }
>  EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>
> +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
> +                       struct drm_encoder *encoder)

The channel doesn't really matter. What is needed is which TCON and encoder
are supposed to be muxed together. This is going to be per SoC type anyway.

I have something in the works, though it's not finished yet.
I think this works for now.

Acked-by: Chen-Yu Tsai <wens@csie.org>

> +{
> +       if (!tcon->quirks->has_unknown_mux)
> +               return;
> +
> +       if (channel != 1)
> +               return;
> +
> +       /*
> +        * FIXME: Undocumented bits
> +        */
> +       regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
> +}
> +EXPORT_SYMBOL(sun4i_tcon_set_mux);
> +
>  static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode,
>                                     int channel)
>  {
> @@ -266,12 +282,6 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>         regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG,
>                            SUN4I_TCON_GCTL_IOMAP_MASK,
>                            SUN4I_TCON_GCTL_IOMAP_TCON1);
> -
> -       /*
> -        * FIXME: Undocumented bits
> -        */
> -       if (tcon->quirks->has_unknown_mux)
> -               regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1);
>  }
>  EXPORT_SYMBOL(sun4i_tcon1_mode_set);
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
> index f636343a935d..0350936b413c 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
> @@ -190,6 +190,8 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
>  /* Mode Related Controls */
>  void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
>                                  bool enable);
> +void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
> +                       struct drm_encoder *encoder);
>  void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
>                           struct drm_display_mode *mode);
>  void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c
> index 49c49431a053..03c494b8159c 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tv.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c
> @@ -393,6 +393,7 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
>         const struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode);
>
>         sun4i_tcon1_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 1, encoder);
>
>         /* Enable and map the DAC to the output */
>         regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG,
> --
> git-series 0.8.11

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

* Re: [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite
@ 2017-05-04  3:57     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:57 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Even though that mux is undocumented, it seems like it needs to be set to 1
> when using composite, and 0 when using HDMI.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite
@ 2017-05-04  3:57     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:57 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> Even though that mux is undocumented, it seems like it needs to be set to 1
> when using composite, and 0 when using HDMI.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite
@ 2017-05-04  3:57     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:57 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Even though that mux is undocumented, it seems like it needs to be set to 1
> when using composite, and 0 when using HDMI.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 13/20] drm/sun4i: tcon: Change vertical total size computation inconsistency
  2017-05-03 11:59   ` Maxime Ripard
@ 2017-05-04  3:58     ` Chen-Yu Tsai
  -1 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:58 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Both TCON channels need to have the resolution doubled, since the size the
> hardware is going to use is whatever we put in the register divided by two.
>
> However, we handle it differently for the two channels: in the channel 0,
> our register access macro does the multiplication of the value passed as
> paremeter, while in the channel 1, the macro doesn't do this, and we need
> to do it before calling it.
>
> Make this consistent by aligning the channel 0 with the channel 1
> behaviour.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* [PATCH v2 13/20] drm/sun4i: tcon: Change vertical total size computation inconsistency
@ 2017-05-04  3:58     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  3:58 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Both TCON channels need to have the resolution doubled, since the size the
> hardware is going to use is whatever we put in the register divided by two.
>
> However, we handle it differently for the two channels: in the channel 0,
> our register access macro does the multiplication of the value passed as
> paremeter, while in the channel 1, the macro doesn't do this, and we need
> to do it before calling it.
>
> Make this consistent by aligning the channel 0 with the channel 1
> behaviour.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>

Acked-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-04  4:07     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  4:07 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.

I think the total vertical resolution needs to be doubled in all cases.
It just happens that you should've been using mode->crtc_vtotal, which
is halved when the mode is interlaced. Instead you used mode->vtotal,
which is double the actual scan resolution in interlaced mode.

ChenYu

> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 0f91ec8a4b26..efa079c1a3f5 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>         /* Set vertical display timings */
>         bp = mode->crtc_vtotal - mode->crtc_vsync_start;
>         DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
> -                        mode->vtotal, bp);
> +                        mode->crtc_vtotal, bp);
>         regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
> -                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
> +                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |
>                      SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
>
>         /* Set Hsync and Vsync length */
> --
> git-series 0.8.11

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

* Re: [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-04  4:07     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  4:07 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.

I think the total vertical resolution needs to be doubled in all cases.
It just happens that you should've been using mode->crtc_vtotal, which
is halved when the mode is interlaced. Instead you used mode->vtotal,
which is double the actual scan resolution in interlaced mode.

ChenYu

> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 0f91ec8a4b26..efa079c1a3f5 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>         /* Set vertical display timings */
>         bp = mode->crtc_vtotal - mode->crtc_vsync_start;
>         DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
> -                        mode->vtotal, bp);
> +                        mode->crtc_vtotal, bp);
>         regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
> -                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
> +                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |
>                      SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
>
>         /* Set Hsync and Vsync length */
> --
> git-series 0.8.11

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

* [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-04  4:07     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  4:07 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.

I think the total vertical resolution needs to be doubled in all cases.
It just happens that you should've been using mode->crtc_vtotal, which
is halved when the mode is interlaced. Instead you used mode->vtotal,
which is double the actual scan resolution in interlaced mode.

ChenYu

> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 0f91ec8a4b26..efa079c1a3f5 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>         /* Set vertical display timings */
>         bp = mode->crtc_vtotal - mode->crtc_vsync_start;
>         DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
> -                        mode->vtotal, bp);
> +                        mode->crtc_vtotal, bp);
>         regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
> -                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
> +                    SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |
>                      SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
>
>         /* Set Hsync and Vsync length */
> --
> git-series 0.8.11

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

* Re: [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components
@ 2017-05-04  4:11     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  4:11 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The generic connectors such as hdmi-connector doesn't have any driver in,
> so if they are added to the component list, we will be waiting forever for
> a non-existing driver to probe.
>
> Add a list of the connectors we want to ignore when building our component
> list.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_drv.c | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
> index 8ddd72cd5873..8c9d2e36be55 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_drv.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
> @@ -164,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
>         .unbind = sun4i_drv_unbind,
>  };
>
> +static bool sun4i_drv_node_is_connector(struct device_node *node)
> +{
> +       return of_device_is_compatible(node, "hdmi-connector");
> +}
> +
>  static bool sun4i_drv_node_is_frontend(struct device_node *node)
>  {
>         return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
> @@ -204,7 +209,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
>             !of_device_is_available(node))
>                 return 0;
>
> -       if (!sun4i_drv_node_is_frontend(node)) {
> +       if (!sun4i_drv_node_is_connector(node) &&

I think it's better to just bail out (return 0) if the current node
is a connector. The connector is the end of the pipeline anyway.
Nothing after it should be considered for the component graph.
The way you currently have it, traversal will continue beyond
the connector node.

ChenYu

> +           !sun4i_drv_node_is_frontend(node)) {
>                 /* Add current component */
>                 DRM_DEBUG_DRIVER("Adding component %s\n",
>                                  of_node_full_name(node));
> --
> git-series 0.8.11

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

* Re: [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components
@ 2017-05-04  4:11     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  4:11 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The generic connectors such as hdmi-connector doesn't have any driver in,
> so if they are added to the component list, we will be waiting forever for
> a non-existing driver to probe.
>
> Add a list of the connectors we want to ignore when building our component
> list.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/gpu/drm/sun4i/sun4i_drv.c | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
> index 8ddd72cd5873..8c9d2e36be55 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_drv.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
> @@ -164,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
>         .unbind = sun4i_drv_unbind,
>  };
>
> +static bool sun4i_drv_node_is_connector(struct device_node *node)
> +{
> +       return of_device_is_compatible(node, "hdmi-connector");
> +}
> +
>  static bool sun4i_drv_node_is_frontend(struct device_node *node)
>  {
>         return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
> @@ -204,7 +209,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
>             !of_device_is_available(node))
>                 return 0;
>
> -       if (!sun4i_drv_node_is_frontend(node)) {
> +       if (!sun4i_drv_node_is_connector(node) &&

I think it's better to just bail out (return 0) if the current node
is a connector. The connector is the end of the pipeline anyway.
Nothing after it should be considered for the component graph.
The way you currently have it, traversal will continue beyond
the connector node.

ChenYu

> +           !sun4i_drv_node_is_frontend(node)) {
>                 /* Add current component */
>                 DRM_DEBUG_DRIVER("Adding component %s\n",
>                                  of_node_full_name(node));
> --
> git-series 0.8.11

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

* [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components
@ 2017-05-04  4:11     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  4:11 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The generic connectors such as hdmi-connector doesn't have any driver in,
> so if they are added to the component list, we will be waiting forever for
> a non-existing driver to probe.
>
> Add a list of the connectors we want to ignore when building our component
> list.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_drv.c | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c
> index 8ddd72cd5873..8c9d2e36be55 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_drv.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c
> @@ -164,6 +164,11 @@ static const struct component_master_ops sun4i_drv_master_ops = {
>         .unbind = sun4i_drv_unbind,
>  };
>
> +static bool sun4i_drv_node_is_connector(struct device_node *node)
> +{
> +       return of_device_is_compatible(node, "hdmi-connector");
> +}
> +
>  static bool sun4i_drv_node_is_frontend(struct device_node *node)
>  {
>         return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
> @@ -204,7 +209,8 @@ static int sun4i_drv_add_endpoints(struct device *dev,
>             !of_device_is_available(node))
>                 return 0;
>
> -       if (!sun4i_drv_node_is_frontend(node)) {
> +       if (!sun4i_drv_node_is_connector(node) &&

I think it's better to just bail out (return 0) if the current node
is a connector. The connector is the end of the pipeline anyway.
Nothing after it should be considered for the component graph.
The way you currently have it, traversal will continue beyond
the connector node.

ChenYu

> +           !sun4i_drv_node_is_frontend(node)) {
>                 /* Add current component */
>                 DRM_DEBUG_DRIVER("Adding component %s\n",
>                                  of_node_full_name(node));
> --
> git-series 0.8.11

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

* Re: [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-04  7:55     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  7:55 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
> controller.
>
> That HDMI controller is able to do audio and CEC, but those have been left
> out for now.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/Kconfig               |   9 +-
>  drivers/gpu/drm/sun4i/Makefile              |   6 +-
>  drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
>  6 files changed, 1017 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>
> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
> index a4b357db8856..35299c4e2594 100644
> --- a/drivers/gpu/drm/sun4i/Kconfig
> +++ b/drivers/gpu/drm/sun4i/Kconfig
> @@ -12,3 +12,12 @@ config DRM_SUN4I
>           Choose this option if you have an Allwinner SoC with a
>           Display Engine. If M is selected the module will be called
>           sun4i-drm.
> +
> +if DRM_SUN4I
> +config DRM_SUN4I_HDMI
> +       tristate "Allwinner A10 HDMI Controller Support"
> +       help
> +         Choose this option if you have an Allwinner SoC with an HDMI
> +         controller.
> +
> +endif
> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
> index 59b757350a1f..c09bf8093710 100644
> --- a/drivers/gpu/drm/sun4i/Makefile
> +++ b/drivers/gpu/drm/sun4i/Makefile
> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)               += sun4i-drm.o sun4i-tcon.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun6i_drc.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
> +
> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
> +
> +obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> new file mode 100644
> index 000000000000..40d57b195b48
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> @@ -0,0 +1,157 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#ifndef _SUN4I_HDMI_H_
> +#define _SUN4I_HDMI_H_
> +
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +#define SUN4I_HDMI_CTRL_REG            0x004
> +#define SUN4I_HDMI_CTRL_ENABLE                 BIT(31)
> +
> +#define SUN4I_HDMI_IRQ_REG             0x008
> +#define SUN4I_HDMI_IRQ_STA_MASK                        0x73
> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF             BIT(1)
> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF             BIT(0)
> +
> +#define SUN4I_HDMI_HPD_REG             0x00c
> +#define SUN4I_HDMI_HPD_HIGH                    BIT(0)
> +
> +#define SUN4I_HDMI_VID_CTRL_REG                0x010
> +#define SUN4I_HDMI_VID_CTRL_ENABLE             BIT(31)
> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE          BIT(30)
> +
> +#define SUN4I_HDMI_VID_TIMING_ACT_REG  0x014
> +#define SUN4I_HDMI_VID_TIMING_BP_REG   0x018
> +#define SUN4I_HDMI_VID_TIMING_FP_REG   0x01c
> +#define SUN4I_HDMI_VID_TIMING_SPW_REG  0x020
> +
> +#define SUN4I_HDMI_VID_TIMING_X(x)             ((((x) - 1) & GENMASK(11, 0)))
> +#define SUN4I_HDMI_VID_TIMING_Y(y)             ((((y) - 1) & GENMASK(11, 0)) << 16)
> +
> +#define SUN4I_HDMI_VID_TIMING_POL_REG  0x024
> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC                BIT(1)
> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC                BIT(0)
> +
> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)        (0x080 + (n))
> +
> +#define SUN4I_HDMI_PAD_CTRL0_REG       0x200
> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN            BIT(31)
> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN            BIT(30)
> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN            BIT(29)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENC             BIT(28)
> +#define SUN4I_HDMI_PAD_CTRL0_PWEND             BIT(27)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENG             BIT(26)
> +#define SUN4I_HDMI_PAD_CTRL0_CKEN              BIT(25)
> +#define SUN4I_HDMI_PAD_CTRL0_TXEN              BIT(23)
> +
> +#define SUN4I_HDMI_PAD_CTRL1_REG       0x204
> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT           BIT(23)
> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT         BIT(22)
> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT           BIT(20)
> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT         BIT(19)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN           BIT(15)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK         BIT(14)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)                (((n) & 7) << 10)
> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK         BIT(6)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)                (((n) & 7) << 3)
> +
> +#define SUN4I_HDMI_PLL_CTRL_REG                0x208
> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN             BIT(31)
> +#define SUN4I_HDMI_PLL_CTRL_BWS                        BIT(30)
> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33           BIT(29)
> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN            BIT(28)
> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN            BIT(27)
> +#define SUN4I_HDMI_PLL_CTRL_SDIV2              BIT(25)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)                (((n) & 7) << 20)
> +#define SUN4I_HDMI_PLL_CTRL_S(n)               (((n) & 7) << 16)
> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)
> +#define SUN4I_HDMI_PLL_CTRL_CS(n)              (((n) & 0xf) << 8)
> +#define SUN4I_HDMI_PLL_CTRL_DIV(n)             (((n) & 0xf) << 4)
> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK           GENMASK(7, 4)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)           ((n) & 0xf)
> +
> +#define SUN4I_HDMI_PLL_DBG0_REG                0x20c
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)     (((n) & 1) << 21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK   BIT(21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT  21
> +
> +#define SUN4I_HDMI_PKT_CTRL_REG(n)     (0x2f0 + (4 * (n)))
> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)         ((t) << (((n) % 4) * 4))
> +
> +#define SUN4I_HDMI_UNKNOWN_REG         0x300
> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC          BIT(27)
> +
> +#define SUN4I_HDMI_DDC_CTRL_REG                0x500
> +#define SUN4I_HDMI_DDC_CTRL_ENABLE             BIT(31)
> +#define SUN4I_HDMI_DDC_CTRL_START_CMD          BIT(30)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK      BIT(8)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ      (0 << 8)
> +#define SUN4I_HDMI_DDC_CTRL_RESET              BIT(0)
> +
> +#define SUN4I_HDMI_DDC_ADDR_REG                0x504
> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)       (((seg) & 0xff) << 24)
> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)         (((addr) & 0xff) << 16)
> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)                (((off) & 0xff) << 8)
> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)                ((addr) & 0xff)
> +
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG   0x510
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR         BIT(31)
> +
> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG   0x518
> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG  0x51c
> +
> +#define SUN4I_HDMI_DDC_CMD_REG         0x520
> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ  6
> +
> +#define SUN4I_HDMI_DDC_CLK_REG         0x528
> +#define SUN4I_HDMI_DDC_CLK_M(m)                        (((m) & 0x7) << 3)
> +#define SUN4I_HDMI_DDC_CLK_N(n)                        ((n) & 0x7)
> +
> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG   0x540
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE    BIT(9)
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE    BIT(8)
> +
> +#define SUN4I_HDMI_DDC_FIFO_SIZE       16
> +
> +enum sun4i_hdmi_pkt_type {
> +       SUN4I_HDMI_PKT_AVI = 2,
> +       SUN4I_HDMI_PKT_END = 15,
> +};
> +
> +struct sun4i_hdmi {
> +       struct drm_connector    connector;
> +       struct drm_encoder      encoder;
> +       struct device           *dev;
> +
> +       void __iomem            *base;
> +
> +       /* Parent clocks */
> +       struct clk              *bus_clk;
> +       struct clk              *mod_clk;
> +       struct clk              *pll0_clk;
> +       struct clk              *pll1_clk;
> +
> +       /* And the clocks we create */
> +       struct clk              *ddc_clk;
> +       struct clk              *tmds_clk;
> +
> +       struct sun4i_drv        *drv;
> +
> +       bool                    hdmi_monitor;
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
> +
> +#endif /* _SUN4I_HDMI_H_ */
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> new file mode 100644
> index 000000000000..4692e8c345ed
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> @@ -0,0 +1,127 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_ddc {
> +       struct clk_hw           hw;
> +       struct sun4i_hdmi       *hdmi;
> +};
> +
> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct sun4i_ddc, hw);
> +}
> +
> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
> +                                           unsigned long parent_rate,
> +                                           u8 *m, u8 *n)
> +{
> +       unsigned long best_rate = 0;
> +       u8 best_m = 0, best_n = 0, _m, _n;
> +
> +       for (_m = 0; _m < 8; _m++) {
> +               for (_n = 0; _n < 8; _n++) {
> +                       unsigned long tmp_rate;
> +
> +                       tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
> +
> +                       if (tmp_rate > rate)
> +                               continue;
> +
> +                       if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
> +                               best_rate = tmp_rate;
> +                               best_m = _m;
> +                               best_n = _n;
> +                       }
> +               }
> +       }
> +
> +       if (m && n) {
> +               *m = best_m;
> +               *n = best_n;
> +       }
> +
> +       return best_rate;
> +}
> +
> +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                unsigned long *prate)
> +{
> +       return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
> +}
> +
> +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
> +       u32 reg;
> +       u8 m, n;
> +
> +       reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +       m = (reg >> 3) & 0x7;
> +       n = reg & 0x7;
> +
> +       return (((parent_rate / 2) / 10) >> n) / (m + 1);
> +}
> +
> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
> +                             unsigned long parent_rate)
> +{
> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
> +       u8 div_m, div_n;
> +
> +       sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
> +
> +       writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
> +              ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops sun4i_ddc_ops = {
> +       .recalc_rate    = sun4i_ddc_recalc_rate,
> +       .round_rate     = sun4i_ddc_round_rate,
> +       .set_rate       = sun4i_ddc_set_rate,
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
> +{
> +       struct clk_init_data init;
> +       struct sun4i_ddc *ddc;
> +       const char *parent_name;
> +
> +       parent_name = __clk_get_name(parent);
> +       if (!parent_name)
> +               return -ENODEV;
> +
> +       ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
> +       if (!ddc)
> +               return -ENOMEM;
> +
> +       init.name = "hdmi-ddc";
> +       init.ops = &sun4i_ddc_ops;
> +       init.parent_names = &parent_name;
> +       init.num_parents = 1;
> +
> +       ddc->hdmi = hdmi;
> +       ddc->hw.init = &init;
> +
> +       hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
> +       if (IS_ERR(hdmi->ddc_clk))
> +               return PTR_ERR(hdmi->ddc_clk);
> +
> +       return 0;
> +}
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> new file mode 100644
> index 000000000000..9bec23164f53
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> @@ -0,0 +1,493 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/iopoll.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "sun4i_backend.h"
> +#include "sun4i_drv.h"
> +#include "sun4i_hdmi.h"
> +#include "sun4i_tcon.h"
> +
> +#define DDC_SEGMENT_ADDR       0x30
> +
> +static inline struct sun4i_hdmi *
> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
> +{
> +       return container_of(encoder, struct sun4i_hdmi,
> +                           encoder);
> +}
> +
> +static inline struct sun4i_hdmi *
> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
> +{
> +       return container_of(connector, struct sun4i_hdmi,
> +                           connector);
> +}
> +
> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
> +                                          struct drm_display_mode *mode)
> +{
> +       struct hdmi_avi_infoframe frame;
> +       u8 buffer[17];
> +       int i, ret;
> +
> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
> +       if (ret < 0) {
> +               DRM_ERROR("Failed to get infoframes from mode\n");
> +               return ret;
> +       }
> +
> +       ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (ret < 0) {
> +               DRM_ERROR("Failed to pack infoframes\n");
> +               return ret;
> +       }
> +
> +       for (i = 0; i < sizeof(buffer); i++)
> +               writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
> +                                  struct drm_crtc_state *crtc_state,
> +                                  struct drm_connector_state *conn_state)
> +{
> +       struct drm_display_mode *mode = &crtc_state->mode;
> +
> +       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
> +{
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;

I assume you didn't take into consideration the latest patches I sent
that remove this field.

Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.

> +       u32 val;
> +
> +       DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
> +
> +       val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +       val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +
> +       sun4i_tcon_channel_disable(tcon, 1);
> +}
> +
> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
> +{
> +       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;
> +       u32 val = 0;
> +
> +       DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
> +
> +       sun4i_tcon_channel_enable(tcon, 1);
> +
> +       sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
> +       writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
> +
> +       val = SUN4I_HDMI_VID_CTRL_ENABLE;
> +       if (hdmi->hdmi_monitor)
> +               val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
> +
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +}
> +
> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
> +                               struct drm_display_mode *mode,
> +                               struct drm_display_mode *adjusted_mode)
> +{
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;
> +       unsigned int x, y;
> +       u32 val;
> +
> +       sun4i_tcon1_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 1, encoder);
> +
> +       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
> +       clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
> +       clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
> +
> +       /* Set input sync enable */
> +       writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
> +              hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
> +
> +       /* Setup timing registers */
> +       writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
> +              SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),

I can't figure out whether we should use mode->crtc_* or not...

> +              hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
> +
> +       x = mode->htotal - mode->hsync_start;
> +       y = mode->vtotal - mode->vsync_start;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
> +
> +       x = mode->hsync_start - mode->hdisplay;
> +       y = mode->vsync_start - mode->vdisplay;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
> +
> +       x = mode->hsync_end - mode->hsync_start;
> +       y = mode->vsync_end - mode->vsync_start;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
> +
> +       val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
> +       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> +               val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
> +
> +       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> +               val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
> +
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
> +}
> +
> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
> +       .atomic_check   = sun4i_hdmi_atomic_check,
> +       .disable        = sun4i_hdmi_disable,
> +       .enable         = sun4i_hdmi_enable,
> +       .mode_set       = sun4i_hdmi_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
> +       .destroy        = drm_encoder_cleanup,
> +};
> +
> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
> +                                    unsigned int blk, unsigned int offset,
> +                                    u8 *buf, unsigned int count)
> +{
> +       unsigned long reg;
> +       int i;
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
> +       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> +       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
> +              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
> +              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
> +              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
> +              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
> +              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +
> +       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
> +       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
> +              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> +                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
> +                              100, 100000))
> +               return -EIO;
> +
> +       for (i = 0; i < count; i++)
> +               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
> +                                     size_t length)
> +{
> +       struct sun4i_hdmi *hdmi = data;
> +       int retry = 2, i;
> +
> +       do {
> +               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
> +                       unsigned char offset = blk * EDID_LENGTH + i;
> +                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
> +                                                length - i);
> +                       int ret;
> +
> +                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
> +                                                       buf + i, count);
> +                       if (ret)
> +                               return ret;
> +               }
> +       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
> +{
> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> +       unsigned long reg;
> +       struct edid *edid;
> +       int ret;
> +
> +       /* Reset i2c controller */
> +       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> +                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
> +                              100, 2000))
> +               return -EIO;
> +
> +       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
> +              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
> +              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
> +
> +       clk_set_rate(hdmi->ddc_clk, 100000);

You might want to explicitly enable the DDC clk around the following call.

> +
> +       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
> +       if (!edid)
> +               return 0;
> +
> +       hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
> +       DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
> +                        hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
> +
> +       drm_mode_connector_update_edid_property(connector, edid);
> +       ret = drm_add_edid_modes(connector, edid);
> +       kfree(edid);
> +
> +       return ret;
> +}
> +
> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
> +       .get_modes      = sun4i_hdmi_get_modes,
> +};
> +
> +static enum drm_connector_status
> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> +       unsigned long reg;
> +
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
> +                              reg & SUN4I_HDMI_HPD_HIGH,
> +                              0, 500000))
> +               return connector_status_disconnected;
> +
> +       return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
> +       .dpms                   = drm_atomic_helper_connector_dpms,
> +       .detect                 = sun4i_hdmi_connector_detect,
> +       .fill_modes             = drm_helper_probe_single_connector_modes,
> +       .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 sun4i_hdmi_bind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct drm_device *drm = data;
> +       struct sun4i_drv *drv = drm->dev_private;
> +       struct sun4i_hdmi *hdmi;
> +       struct resource *res;
> +       u32 reg;
> +       int ret;
> +
> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> +       if (!hdmi)
> +               return -ENOMEM;
> +       dev_set_drvdata(dev, hdmi);
> +       hdmi->dev = dev;
> +       hdmi->drv = drv;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       hdmi->base = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(hdmi->base)) {
> +               dev_err(dev, "Couldn't map the HDMI encoder registers\n");
> +               return PTR_ERR(hdmi->base);
> +       }
> +
> +       hdmi->bus_clk = devm_clk_get(dev, "ahb");
> +       if (IS_ERR(hdmi->bus_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI bus clock\n");
> +               return PTR_ERR(hdmi->bus_clk);
> +       }
> +       clk_prepare_enable(hdmi->bus_clk);
> +
> +       hdmi->mod_clk = devm_clk_get(dev, "mod");
> +       if (IS_ERR(hdmi->mod_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI mod clock\n");
> +               return PTR_ERR(hdmi->mod_clk);
> +       }
> +       clk_prepare_enable(hdmi->mod_clk);
> +
> +       hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
> +       if (IS_ERR(hdmi->pll0_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
> +               return PTR_ERR(hdmi->pll0_clk);
> +       }
> +
> +       hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
> +       if (IS_ERR(hdmi->pll1_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
> +               return PTR_ERR(hdmi->pll1_clk);
> +       }
> +
> +       ret = sun4i_tmds_create(hdmi);
> +       if (ret) {
> +               dev_err(dev, "Couldn't create the TMDS clock\n");
> +               return ret;
> +       }
> +
> +       writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
> +
> +       writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
> +              SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
> +              SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
> +              SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
> +              hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
> +
> +       /*
> +        * We can't just initialize the register there, we need to
> +        * protect the clock bits that have already been read out and
> +        * cached by the clock framework.
> +        */
> +       reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;

Nit: just use the &= short hand.

> +       reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
> +               SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
> +               SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
> +               SUN4I_HDMI_PAD_CTRL1_REG_DEN |
> +               SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
> +       writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;

Same here.

> +       reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
> +               SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |
> +               SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
> +               SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
> +               SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
> +               SUN4I_HDMI_PLL_CTRL_PLL_EN;
> +       writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> +       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
> +       if (ret) {
> +               dev_err(dev, "Couldn't create the DDC clock\n");
> +               return ret;
> +       }
> +
> +       drm_encoder_helper_add(&hdmi->encoder,
> +                              &sun4i_hdmi_helper_funcs);
> +       ret = drm_encoder_init(drm,
> +                              &hdmi->encoder,
> +                              &sun4i_hdmi_funcs,
> +                              DRM_MODE_ENCODER_TMDS,
> +                              NULL);
> +       if (ret) {
> +               dev_err(dev, "Couldn't initialise the HDMI encoder\n");
> +               return ret;
> +       }
> +
> +       hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
> +                                                                 dev->of_node);
> +       if (!hdmi->encoder.possible_crtcs)
> +               return ret;

You need a valid error return value here. Maybe -EPROBE_DEFER?

> +
> +       drm_connector_helper_add(&hdmi->connector,
> +                                &sun4i_hdmi_connector_helper_funcs);

/* There is no HPD interrupt, so we need to poll the controller */
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
                         DRM_CONNECTOR_POLL_DISCONNECT;

> +       ret = drm_connector_init(drm, &hdmi->connector,
> +                                &sun4i_hdmi_connector_funcs,
> +                                DRM_MODE_CONNECTOR_HDMIA);
> +       if (ret) {
> +               dev_err(dev,
> +                       "Couldn't initialise the HDMI connector\n");
> +               goto err_cleanup_connector;
> +       }
> +
> +       drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
> +
> +       return 0;
> +
> +err_cleanup_connector:
> +       drm_encoder_cleanup(&hdmi->encoder);
> +       return ret;
> +}
> +
> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
> +                           void *data)
> +{
> +       struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       drm_connector_cleanup(&hdmi->connector);
> +       drm_encoder_cleanup(&hdmi->encoder);
> +}
> +
> +static const struct component_ops sun4i_hdmi_ops = {
> +       .bind   = sun4i_hdmi_bind,
> +       .unbind = sun4i_hdmi_unbind,
> +};
> +
> +static int sun4i_hdmi_probe(struct platform_device *pdev)
> +{
> +       return component_add(&pdev->dev, &sun4i_hdmi_ops);
> +}
> +
> +static int sun4i_hdmi_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &sun4i_hdmi_ops);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id sun4i_hdmi_of_table[] = {
> +       { .compatible = "allwinner,sun5i-a10s-hdmi" },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
> +
> +static struct platform_driver sun4i_hdmi_driver = {
> +       .probe          = sun4i_hdmi_probe,
> +       .remove         = sun4i_hdmi_remove,
> +       .driver         = {
> +               .name           = "sun4i-hdmi",
> +               .of_match_table = sun4i_hdmi_of_table,
> +       },
> +};
> +module_platform_driver(sun4i_hdmi_driver);
> +
> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> new file mode 100644
> index 000000000000..5cf2527bffc8
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> @@ -0,0 +1,225 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_tmds {
> +       struct clk_hw           hw;
> +       struct sun4i_hdmi       *hdmi;
> +};
> +
> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct sun4i_tmds, hw);
> +}
> +
> +
> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
> +                                            unsigned long parent_rate,
> +                                            u8 *div,
> +                                            bool *half)
> +{
> +       unsigned long best_rate = 0;
> +       u8 best_m = 0, m;
> +       bool is_double;
> +
> +       for (m = 1; m < 16; m++) {
> +               u8 d;
> +
> +               for (d = 1; d < 3; d++) {
> +                       unsigned long tmp_rate;
> +
> +                       tmp_rate = parent_rate / m / d;
> +
> +                       if (tmp_rate > rate)
> +                               continue;
> +
> +                       if (!best_rate ||
> +                           (rate - tmp_rate) < (rate - best_rate)) {
> +                               best_rate = tmp_rate;
> +                               best_m = m;
> +                               is_double = d;
> +                       }
> +               }
> +       }
> +
> +       if (div && half) {
> +               *div = best_m;
> +               *half = is_double;
> +       }
> +
> +       return best_rate;
> +}
> +
> +
> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
> +                                    struct clk_rate_request *req)
> +{
> +       struct clk_hw *parent;
> +       unsigned long best_parent = 0;
> +       unsigned long rate = req->rate;
> +       int best_div = 1, best_half = 1;
> +       int i, j;
> +
> +       /*
> +        * We only consider PLL3, since the TCON is very likely to be
> +        * clocked from it, and to have the same rate than our HDMI
> +        * clock, so we should not need to do anything.
> +        */
> +
> +       parent = clk_hw_get_parent_by_index(hw, 0);
> +       if (!parent)
> +               return -EINVAL;
> +
> +       for (i = 1; i < 3; i++) {
> +               for (j = 1; j < 16; j++) {
> +                       unsigned long ideal = rate * i * j;
> +                       unsigned long rounded;
> +
> +                       rounded = clk_hw_round_rate(parent, ideal);
> +
> +                       if (rounded == ideal) {
> +                               best_parent = rounded;
> +                               best_half = i;
> +                               best_div = j;
> +                               goto out;
> +                       }
> +
> +                       if (abs(rate - rounded / i) <
> +                           abs(rate - best_parent / best_div)) {
> +                               best_parent = rounded;
> +                               best_div = i;
> +                       }
> +               }
> +       }
> +
> +out:
> +       req->rate = best_parent / best_half / best_div;
> +       req->best_parent_rate = best_parent;
> +       req->best_parent_hw = parent;
> +
> +       return 0;
> +}
> +
> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
> +               parent_rate /= 2;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg = (reg >> 4) & 0xf;
> +       if (!reg)
> +               reg = 1;
> +
> +       return parent_rate / reg;
> +}
> +
> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       bool half;
> +       u32 reg;
> +       u8 div;
> +
> +       sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> +       if (half)
> +               reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> +       writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
> +       writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
> +              tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> +       return 0;
> +}
> +
> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +       return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
> +               SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
> +}
> +
> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +       reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
> +       writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
> +              tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops sun4i_tmds_ops = {
> +       .determine_rate = sun4i_tmds_determine_rate,
> +       .recalc_rate    = sun4i_tmds_recalc_rate,
> +       .set_rate       = sun4i_tmds_set_rate,
> +
> +       .get_parent     = sun4i_tmds_get_parent,
> +       .set_parent     = sun4i_tmds_set_parent,
> +};
> +
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
> +{
> +       struct clk_init_data init;
> +       struct sun4i_tmds *tmds;
> +       const char *parents[2];
> +
> +       parents[0] = __clk_get_name(hdmi->pll0_clk);
> +       if (!parents[0])
> +               return -ENODEV;
> +
> +       parents[1] = __clk_get_name(hdmi->pll1_clk);
> +       if (!parents[1])
> +               return -ENODEV;
> +
> +       tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
> +       if (!tmds)
> +               return -ENOMEM;
> +
> +       init.name = "hdmi-tmds";
> +       init.ops = &sun4i_tmds_ops;
> +       init.parent_names = parents;
> +       init.num_parents = 2;
> +       init.flags = CLK_SET_RATE_PARENT;
> +
> +       tmds->hdmi = hdmi;
> +       tmds->hw.init = &init;
> +
> +       hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
> +       if (IS_ERR(hdmi->tmds_clk))
> +               return PTR_ERR(hdmi->tmds_clk);
> +
> +       return 0;
> +}
> --

The rest looks good. This is coming together nicely.

Reviewed-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-04  7:55     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  7:55 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
> controller.
>
> That HDMI controller is able to do audio and CEC, but those have been left
> out for now.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/gpu/drm/sun4i/Kconfig               |   9 +-
>  drivers/gpu/drm/sun4i/Makefile              |   6 +-
>  drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
>  6 files changed, 1017 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>
> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
> index a4b357db8856..35299c4e2594 100644
> --- a/drivers/gpu/drm/sun4i/Kconfig
> +++ b/drivers/gpu/drm/sun4i/Kconfig
> @@ -12,3 +12,12 @@ config DRM_SUN4I
>           Choose this option if you have an Allwinner SoC with a
>           Display Engine. If M is selected the module will be called
>           sun4i-drm.
> +
> +if DRM_SUN4I
> +config DRM_SUN4I_HDMI
> +       tristate "Allwinner A10 HDMI Controller Support"
> +       help
> +         Choose this option if you have an Allwinner SoC with an HDMI
> +         controller.
> +
> +endif
> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
> index 59b757350a1f..c09bf8093710 100644
> --- a/drivers/gpu/drm/sun4i/Makefile
> +++ b/drivers/gpu/drm/sun4i/Makefile
> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)               += sun4i-drm.o sun4i-tcon.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun6i_drc.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
> +
> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
> +
> +obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> new file mode 100644
> index 000000000000..40d57b195b48
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> @@ -0,0 +1,157 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#ifndef _SUN4I_HDMI_H_
> +#define _SUN4I_HDMI_H_
> +
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +#define SUN4I_HDMI_CTRL_REG            0x004
> +#define SUN4I_HDMI_CTRL_ENABLE                 BIT(31)
> +
> +#define SUN4I_HDMI_IRQ_REG             0x008
> +#define SUN4I_HDMI_IRQ_STA_MASK                        0x73
> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF             BIT(1)
> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF             BIT(0)
> +
> +#define SUN4I_HDMI_HPD_REG             0x00c
> +#define SUN4I_HDMI_HPD_HIGH                    BIT(0)
> +
> +#define SUN4I_HDMI_VID_CTRL_REG                0x010
> +#define SUN4I_HDMI_VID_CTRL_ENABLE             BIT(31)
> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE          BIT(30)
> +
> +#define SUN4I_HDMI_VID_TIMING_ACT_REG  0x014
> +#define SUN4I_HDMI_VID_TIMING_BP_REG   0x018
> +#define SUN4I_HDMI_VID_TIMING_FP_REG   0x01c
> +#define SUN4I_HDMI_VID_TIMING_SPW_REG  0x020
> +
> +#define SUN4I_HDMI_VID_TIMING_X(x)             ((((x) - 1) & GENMASK(11, 0)))
> +#define SUN4I_HDMI_VID_TIMING_Y(y)             ((((y) - 1) & GENMASK(11, 0)) << 16)
> +
> +#define SUN4I_HDMI_VID_TIMING_POL_REG  0x024
> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC                BIT(1)
> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC                BIT(0)
> +
> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)        (0x080 + (n))
> +
> +#define SUN4I_HDMI_PAD_CTRL0_REG       0x200
> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN            BIT(31)
> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN            BIT(30)
> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN            BIT(29)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENC             BIT(28)
> +#define SUN4I_HDMI_PAD_CTRL0_PWEND             BIT(27)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENG             BIT(26)
> +#define SUN4I_HDMI_PAD_CTRL0_CKEN              BIT(25)
> +#define SUN4I_HDMI_PAD_CTRL0_TXEN              BIT(23)
> +
> +#define SUN4I_HDMI_PAD_CTRL1_REG       0x204
> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT           BIT(23)
> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT         BIT(22)
> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT           BIT(20)
> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT         BIT(19)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN           BIT(15)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK         BIT(14)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)                (((n) & 7) << 10)
> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK         BIT(6)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)                (((n) & 7) << 3)
> +
> +#define SUN4I_HDMI_PLL_CTRL_REG                0x208
> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN             BIT(31)
> +#define SUN4I_HDMI_PLL_CTRL_BWS                        BIT(30)
> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33           BIT(29)
> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN            BIT(28)
> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN            BIT(27)
> +#define SUN4I_HDMI_PLL_CTRL_SDIV2              BIT(25)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)                (((n) & 7) << 20)
> +#define SUN4I_HDMI_PLL_CTRL_S(n)               (((n) & 7) << 16)
> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)
> +#define SUN4I_HDMI_PLL_CTRL_CS(n)              (((n) & 0xf) << 8)
> +#define SUN4I_HDMI_PLL_CTRL_DIV(n)             (((n) & 0xf) << 4)
> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK           GENMASK(7, 4)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)           ((n) & 0xf)
> +
> +#define SUN4I_HDMI_PLL_DBG0_REG                0x20c
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)     (((n) & 1) << 21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK   BIT(21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT  21
> +
> +#define SUN4I_HDMI_PKT_CTRL_REG(n)     (0x2f0 + (4 * (n)))
> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)         ((t) << (((n) % 4) * 4))
> +
> +#define SUN4I_HDMI_UNKNOWN_REG         0x300
> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC          BIT(27)
> +
> +#define SUN4I_HDMI_DDC_CTRL_REG                0x500
> +#define SUN4I_HDMI_DDC_CTRL_ENABLE             BIT(31)
> +#define SUN4I_HDMI_DDC_CTRL_START_CMD          BIT(30)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK      BIT(8)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ      (0 << 8)
> +#define SUN4I_HDMI_DDC_CTRL_RESET              BIT(0)
> +
> +#define SUN4I_HDMI_DDC_ADDR_REG                0x504
> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)       (((seg) & 0xff) << 24)
> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)         (((addr) & 0xff) << 16)
> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)                (((off) & 0xff) << 8)
> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)                ((addr) & 0xff)
> +
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG   0x510
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR         BIT(31)
> +
> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG   0x518
> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG  0x51c
> +
> +#define SUN4I_HDMI_DDC_CMD_REG         0x520
> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ  6
> +
> +#define SUN4I_HDMI_DDC_CLK_REG         0x528
> +#define SUN4I_HDMI_DDC_CLK_M(m)                        (((m) & 0x7) << 3)
> +#define SUN4I_HDMI_DDC_CLK_N(n)                        ((n) & 0x7)
> +
> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG   0x540
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE    BIT(9)
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE    BIT(8)
> +
> +#define SUN4I_HDMI_DDC_FIFO_SIZE       16
> +
> +enum sun4i_hdmi_pkt_type {
> +       SUN4I_HDMI_PKT_AVI = 2,
> +       SUN4I_HDMI_PKT_END = 15,
> +};
> +
> +struct sun4i_hdmi {
> +       struct drm_connector    connector;
> +       struct drm_encoder      encoder;
> +       struct device           *dev;
> +
> +       void __iomem            *base;
> +
> +       /* Parent clocks */
> +       struct clk              *bus_clk;
> +       struct clk              *mod_clk;
> +       struct clk              *pll0_clk;
> +       struct clk              *pll1_clk;
> +
> +       /* And the clocks we create */
> +       struct clk              *ddc_clk;
> +       struct clk              *tmds_clk;
> +
> +       struct sun4i_drv        *drv;
> +
> +       bool                    hdmi_monitor;
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
> +
> +#endif /* _SUN4I_HDMI_H_ */
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> new file mode 100644
> index 000000000000..4692e8c345ed
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> @@ -0,0 +1,127 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_ddc {
> +       struct clk_hw           hw;
> +       struct sun4i_hdmi       *hdmi;
> +};
> +
> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct sun4i_ddc, hw);
> +}
> +
> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
> +                                           unsigned long parent_rate,
> +                                           u8 *m, u8 *n)
> +{
> +       unsigned long best_rate = 0;
> +       u8 best_m = 0, best_n = 0, _m, _n;
> +
> +       for (_m = 0; _m < 8; _m++) {
> +               for (_n = 0; _n < 8; _n++) {
> +                       unsigned long tmp_rate;
> +
> +                       tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
> +
> +                       if (tmp_rate > rate)
> +                               continue;
> +
> +                       if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
> +                               best_rate = tmp_rate;
> +                               best_m = _m;
> +                               best_n = _n;
> +                       }
> +               }
> +       }
> +
> +       if (m && n) {
> +               *m = best_m;
> +               *n = best_n;
> +       }
> +
> +       return best_rate;
> +}
> +
> +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                unsigned long *prate)
> +{
> +       return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
> +}
> +
> +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
> +       u32 reg;
> +       u8 m, n;
> +
> +       reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +       m = (reg >> 3) & 0x7;
> +       n = reg & 0x7;
> +
> +       return (((parent_rate / 2) / 10) >> n) / (m + 1);
> +}
> +
> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
> +                             unsigned long parent_rate)
> +{
> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
> +       u8 div_m, div_n;
> +
> +       sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
> +
> +       writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
> +              ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops sun4i_ddc_ops = {
> +       .recalc_rate    = sun4i_ddc_recalc_rate,
> +       .round_rate     = sun4i_ddc_round_rate,
> +       .set_rate       = sun4i_ddc_set_rate,
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
> +{
> +       struct clk_init_data init;
> +       struct sun4i_ddc *ddc;
> +       const char *parent_name;
> +
> +       parent_name = __clk_get_name(parent);
> +       if (!parent_name)
> +               return -ENODEV;
> +
> +       ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
> +       if (!ddc)
> +               return -ENOMEM;
> +
> +       init.name = "hdmi-ddc";
> +       init.ops = &sun4i_ddc_ops;
> +       init.parent_names = &parent_name;
> +       init.num_parents = 1;
> +
> +       ddc->hdmi = hdmi;
> +       ddc->hw.init = &init;
> +
> +       hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
> +       if (IS_ERR(hdmi->ddc_clk))
> +               return PTR_ERR(hdmi->ddc_clk);
> +
> +       return 0;
> +}
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> new file mode 100644
> index 000000000000..9bec23164f53
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> @@ -0,0 +1,493 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/iopoll.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "sun4i_backend.h"
> +#include "sun4i_drv.h"
> +#include "sun4i_hdmi.h"
> +#include "sun4i_tcon.h"
> +
> +#define DDC_SEGMENT_ADDR       0x30
> +
> +static inline struct sun4i_hdmi *
> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
> +{
> +       return container_of(encoder, struct sun4i_hdmi,
> +                           encoder);
> +}
> +
> +static inline struct sun4i_hdmi *
> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
> +{
> +       return container_of(connector, struct sun4i_hdmi,
> +                           connector);
> +}
> +
> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
> +                                          struct drm_display_mode *mode)
> +{
> +       struct hdmi_avi_infoframe frame;
> +       u8 buffer[17];
> +       int i, ret;
> +
> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
> +       if (ret < 0) {
> +               DRM_ERROR("Failed to get infoframes from mode\n");
> +               return ret;
> +       }
> +
> +       ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (ret < 0) {
> +               DRM_ERROR("Failed to pack infoframes\n");
> +               return ret;
> +       }
> +
> +       for (i = 0; i < sizeof(buffer); i++)
> +               writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
> +                                  struct drm_crtc_state *crtc_state,
> +                                  struct drm_connector_state *conn_state)
> +{
> +       struct drm_display_mode *mode = &crtc_state->mode;
> +
> +       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
> +{
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;

I assume you didn't take into consideration the latest patches I sent
that remove this field.

Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.

> +       u32 val;
> +
> +       DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
> +
> +       val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +       val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +
> +       sun4i_tcon_channel_disable(tcon, 1);
> +}
> +
> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
> +{
> +       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;
> +       u32 val = 0;
> +
> +       DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
> +
> +       sun4i_tcon_channel_enable(tcon, 1);
> +
> +       sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
> +       writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
> +
> +       val = SUN4I_HDMI_VID_CTRL_ENABLE;
> +       if (hdmi->hdmi_monitor)
> +               val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
> +
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +}
> +
> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
> +                               struct drm_display_mode *mode,
> +                               struct drm_display_mode *adjusted_mode)
> +{
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;
> +       unsigned int x, y;
> +       u32 val;
> +
> +       sun4i_tcon1_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 1, encoder);
> +
> +       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
> +       clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
> +       clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
> +
> +       /* Set input sync enable */
> +       writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
> +              hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
> +
> +       /* Setup timing registers */
> +       writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
> +              SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),

I can't figure out whether we should use mode->crtc_* or not...

> +              hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
> +
> +       x = mode->htotal - mode->hsync_start;
> +       y = mode->vtotal - mode->vsync_start;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
> +
> +       x = mode->hsync_start - mode->hdisplay;
> +       y = mode->vsync_start - mode->vdisplay;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
> +
> +       x = mode->hsync_end - mode->hsync_start;
> +       y = mode->vsync_end - mode->vsync_start;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
> +
> +       val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
> +       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> +               val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
> +
> +       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> +               val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
> +
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
> +}
> +
> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
> +       .atomic_check   = sun4i_hdmi_atomic_check,
> +       .disable        = sun4i_hdmi_disable,
> +       .enable         = sun4i_hdmi_enable,
> +       .mode_set       = sun4i_hdmi_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
> +       .destroy        = drm_encoder_cleanup,
> +};
> +
> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
> +                                    unsigned int blk, unsigned int offset,
> +                                    u8 *buf, unsigned int count)
> +{
> +       unsigned long reg;
> +       int i;
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
> +       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> +       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
> +              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
> +              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
> +              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
> +              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
> +              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +
> +       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
> +       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
> +              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> +                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
> +                              100, 100000))
> +               return -EIO;
> +
> +       for (i = 0; i < count; i++)
> +               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
> +                                     size_t length)
> +{
> +       struct sun4i_hdmi *hdmi = data;
> +       int retry = 2, i;
> +
> +       do {
> +               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
> +                       unsigned char offset = blk * EDID_LENGTH + i;
> +                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
> +                                                length - i);
> +                       int ret;
> +
> +                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
> +                                                       buf + i, count);
> +                       if (ret)
> +                               return ret;
> +               }
> +       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
> +{
> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> +       unsigned long reg;
> +       struct edid *edid;
> +       int ret;
> +
> +       /* Reset i2c controller */
> +       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> +                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
> +                              100, 2000))
> +               return -EIO;
> +
> +       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
> +              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
> +              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
> +
> +       clk_set_rate(hdmi->ddc_clk, 100000);

You might want to explicitly enable the DDC clk around the following call.

> +
> +       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
> +       if (!edid)
> +               return 0;
> +
> +       hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
> +       DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
> +                        hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
> +
> +       drm_mode_connector_update_edid_property(connector, edid);
> +       ret = drm_add_edid_modes(connector, edid);
> +       kfree(edid);
> +
> +       return ret;
> +}
> +
> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
> +       .get_modes      = sun4i_hdmi_get_modes,
> +};
> +
> +static enum drm_connector_status
> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> +       unsigned long reg;
> +
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
> +                              reg & SUN4I_HDMI_HPD_HIGH,
> +                              0, 500000))
> +               return connector_status_disconnected;
> +
> +       return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
> +       .dpms                   = drm_atomic_helper_connector_dpms,
> +       .detect                 = sun4i_hdmi_connector_detect,
> +       .fill_modes             = drm_helper_probe_single_connector_modes,
> +       .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 sun4i_hdmi_bind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct drm_device *drm = data;
> +       struct sun4i_drv *drv = drm->dev_private;
> +       struct sun4i_hdmi *hdmi;
> +       struct resource *res;
> +       u32 reg;
> +       int ret;
> +
> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> +       if (!hdmi)
> +               return -ENOMEM;
> +       dev_set_drvdata(dev, hdmi);
> +       hdmi->dev = dev;
> +       hdmi->drv = drv;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       hdmi->base = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(hdmi->base)) {
> +               dev_err(dev, "Couldn't map the HDMI encoder registers\n");
> +               return PTR_ERR(hdmi->base);
> +       }
> +
> +       hdmi->bus_clk = devm_clk_get(dev, "ahb");
> +       if (IS_ERR(hdmi->bus_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI bus clock\n");
> +               return PTR_ERR(hdmi->bus_clk);
> +       }
> +       clk_prepare_enable(hdmi->bus_clk);
> +
> +       hdmi->mod_clk = devm_clk_get(dev, "mod");
> +       if (IS_ERR(hdmi->mod_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI mod clock\n");
> +               return PTR_ERR(hdmi->mod_clk);
> +       }
> +       clk_prepare_enable(hdmi->mod_clk);
> +
> +       hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
> +       if (IS_ERR(hdmi->pll0_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
> +               return PTR_ERR(hdmi->pll0_clk);
> +       }
> +
> +       hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
> +       if (IS_ERR(hdmi->pll1_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
> +               return PTR_ERR(hdmi->pll1_clk);
> +       }
> +
> +       ret = sun4i_tmds_create(hdmi);
> +       if (ret) {
> +               dev_err(dev, "Couldn't create the TMDS clock\n");
> +               return ret;
> +       }
> +
> +       writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
> +
> +       writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
> +              SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
> +              SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
> +              SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
> +              hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
> +
> +       /*
> +        * We can't just initialize the register there, we need to
> +        * protect the clock bits that have already been read out and
> +        * cached by the clock framework.
> +        */
> +       reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;

Nit: just use the &= short hand.

> +       reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
> +               SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
> +               SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
> +               SUN4I_HDMI_PAD_CTRL1_REG_DEN |
> +               SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
> +       writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;

Same here.

> +       reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
> +               SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |
> +               SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
> +               SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
> +               SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
> +               SUN4I_HDMI_PLL_CTRL_PLL_EN;
> +       writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> +       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
> +       if (ret) {
> +               dev_err(dev, "Couldn't create the DDC clock\n");
> +               return ret;
> +       }
> +
> +       drm_encoder_helper_add(&hdmi->encoder,
> +                              &sun4i_hdmi_helper_funcs);
> +       ret = drm_encoder_init(drm,
> +                              &hdmi->encoder,
> +                              &sun4i_hdmi_funcs,
> +                              DRM_MODE_ENCODER_TMDS,
> +                              NULL);
> +       if (ret) {
> +               dev_err(dev, "Couldn't initialise the HDMI encoder\n");
> +               return ret;
> +       }
> +
> +       hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
> +                                                                 dev->of_node);
> +       if (!hdmi->encoder.possible_crtcs)
> +               return ret;

You need a valid error return value here. Maybe -EPROBE_DEFER?

> +
> +       drm_connector_helper_add(&hdmi->connector,
> +                                &sun4i_hdmi_connector_helper_funcs);

/* There is no HPD interrupt, so we need to poll the controller */
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
                         DRM_CONNECTOR_POLL_DISCONNECT;

> +       ret = drm_connector_init(drm, &hdmi->connector,
> +                                &sun4i_hdmi_connector_funcs,
> +                                DRM_MODE_CONNECTOR_HDMIA);
> +       if (ret) {
> +               dev_err(dev,
> +                       "Couldn't initialise the HDMI connector\n");
> +               goto err_cleanup_connector;
> +       }
> +
> +       drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
> +
> +       return 0;
> +
> +err_cleanup_connector:
> +       drm_encoder_cleanup(&hdmi->encoder);
> +       return ret;
> +}
> +
> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
> +                           void *data)
> +{
> +       struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       drm_connector_cleanup(&hdmi->connector);
> +       drm_encoder_cleanup(&hdmi->encoder);
> +}
> +
> +static const struct component_ops sun4i_hdmi_ops = {
> +       .bind   = sun4i_hdmi_bind,
> +       .unbind = sun4i_hdmi_unbind,
> +};
> +
> +static int sun4i_hdmi_probe(struct platform_device *pdev)
> +{
> +       return component_add(&pdev->dev, &sun4i_hdmi_ops);
> +}
> +
> +static int sun4i_hdmi_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &sun4i_hdmi_ops);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id sun4i_hdmi_of_table[] = {
> +       { .compatible = "allwinner,sun5i-a10s-hdmi" },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
> +
> +static struct platform_driver sun4i_hdmi_driver = {
> +       .probe          = sun4i_hdmi_probe,
> +       .remove         = sun4i_hdmi_remove,
> +       .driver         = {
> +               .name           = "sun4i-hdmi",
> +               .of_match_table = sun4i_hdmi_of_table,
> +       },
> +};
> +module_platform_driver(sun4i_hdmi_driver);
> +
> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>");
> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> new file mode 100644
> index 000000000000..5cf2527bffc8
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> @@ -0,0 +1,225 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_tmds {
> +       struct clk_hw           hw;
> +       struct sun4i_hdmi       *hdmi;
> +};
> +
> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct sun4i_tmds, hw);
> +}
> +
> +
> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
> +                                            unsigned long parent_rate,
> +                                            u8 *div,
> +                                            bool *half)
> +{
> +       unsigned long best_rate = 0;
> +       u8 best_m = 0, m;
> +       bool is_double;
> +
> +       for (m = 1; m < 16; m++) {
> +               u8 d;
> +
> +               for (d = 1; d < 3; d++) {
> +                       unsigned long tmp_rate;
> +
> +                       tmp_rate = parent_rate / m / d;
> +
> +                       if (tmp_rate > rate)
> +                               continue;
> +
> +                       if (!best_rate ||
> +                           (rate - tmp_rate) < (rate - best_rate)) {
> +                               best_rate = tmp_rate;
> +                               best_m = m;
> +                               is_double = d;
> +                       }
> +               }
> +       }
> +
> +       if (div && half) {
> +               *div = best_m;
> +               *half = is_double;
> +       }
> +
> +       return best_rate;
> +}
> +
> +
> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
> +                                    struct clk_rate_request *req)
> +{
> +       struct clk_hw *parent;
> +       unsigned long best_parent = 0;
> +       unsigned long rate = req->rate;
> +       int best_div = 1, best_half = 1;
> +       int i, j;
> +
> +       /*
> +        * We only consider PLL3, since the TCON is very likely to be
> +        * clocked from it, and to have the same rate than our HDMI
> +        * clock, so we should not need to do anything.
> +        */
> +
> +       parent = clk_hw_get_parent_by_index(hw, 0);
> +       if (!parent)
> +               return -EINVAL;
> +
> +       for (i = 1; i < 3; i++) {
> +               for (j = 1; j < 16; j++) {
> +                       unsigned long ideal = rate * i * j;
> +                       unsigned long rounded;
> +
> +                       rounded = clk_hw_round_rate(parent, ideal);
> +
> +                       if (rounded == ideal) {
> +                               best_parent = rounded;
> +                               best_half = i;
> +                               best_div = j;
> +                               goto out;
> +                       }
> +
> +                       if (abs(rate - rounded / i) <
> +                           abs(rate - best_parent / best_div)) {
> +                               best_parent = rounded;
> +                               best_div = i;
> +                       }
> +               }
> +       }
> +
> +out:
> +       req->rate = best_parent / best_half / best_div;
> +       req->best_parent_rate = best_parent;
> +       req->best_parent_hw = parent;
> +
> +       return 0;
> +}
> +
> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
> +               parent_rate /= 2;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg = (reg >> 4) & 0xf;
> +       if (!reg)
> +               reg = 1;
> +
> +       return parent_rate / reg;
> +}
> +
> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       bool half;
> +       u32 reg;
> +       u8 div;
> +
> +       sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> +       if (half)
> +               reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> +       writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
> +       writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
> +              tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> +       return 0;
> +}
> +
> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +       return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
> +               SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
> +}
> +
> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +       reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
> +       writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
> +              tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops sun4i_tmds_ops = {
> +       .determine_rate = sun4i_tmds_determine_rate,
> +       .recalc_rate    = sun4i_tmds_recalc_rate,
> +       .set_rate       = sun4i_tmds_set_rate,
> +
> +       .get_parent     = sun4i_tmds_get_parent,
> +       .set_parent     = sun4i_tmds_set_parent,
> +};
> +
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
> +{
> +       struct clk_init_data init;
> +       struct sun4i_tmds *tmds;
> +       const char *parents[2];
> +
> +       parents[0] = __clk_get_name(hdmi->pll0_clk);
> +       if (!parents[0])
> +               return -ENODEV;
> +
> +       parents[1] = __clk_get_name(hdmi->pll1_clk);
> +       if (!parents[1])
> +               return -ENODEV;
> +
> +       tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
> +       if (!tmds)
> +               return -ENOMEM;
> +
> +       init.name = "hdmi-tmds";
> +       init.ops = &sun4i_tmds_ops;
> +       init.parent_names = parents;
> +       init.num_parents = 2;
> +       init.flags = CLK_SET_RATE_PARENT;
> +
> +       tmds->hdmi = hdmi;
> +       tmds->hw.init = &init;
> +
> +       hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
> +       if (IS_ERR(hdmi->tmds_clk))
> +               return PTR_ERR(hdmi->tmds_clk);
> +
> +       return 0;
> +}
> --

The rest looks good. This is coming together nicely.

Reviewed-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

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

* [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-04  7:55     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  7:55 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
> controller.
>
> That HDMI controller is able to do audio and CEC, but those have been left
> out for now.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/Kconfig               |   9 +-
>  drivers/gpu/drm/sun4i/Makefile              |   6 +-
>  drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
>  drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
>  6 files changed, 1017 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>
> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
> index a4b357db8856..35299c4e2594 100644
> --- a/drivers/gpu/drm/sun4i/Kconfig
> +++ b/drivers/gpu/drm/sun4i/Kconfig
> @@ -12,3 +12,12 @@ config DRM_SUN4I
>           Choose this option if you have an Allwinner SoC with a
>           Display Engine. If M is selected the module will be called
>           sun4i-drm.
> +
> +if DRM_SUN4I
> +config DRM_SUN4I_HDMI
> +       tristate "Allwinner A10 HDMI Controller Support"
> +       help
> +         Choose this option if you have an Allwinner SoC with an HDMI
> +         controller.
> +
> +endif
> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
> index 59b757350a1f..c09bf8093710 100644
> --- a/drivers/gpu/drm/sun4i/Makefile
> +++ b/drivers/gpu/drm/sun4i/Makefile
> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)               += sun4i-drm.o sun4i-tcon.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun6i_drc.o
>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
> +
> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
> +
> +obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> new file mode 100644
> index 000000000000..40d57b195b48
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
> @@ -0,0 +1,157 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#ifndef _SUN4I_HDMI_H_
> +#define _SUN4I_HDMI_H_
> +
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +#define SUN4I_HDMI_CTRL_REG            0x004
> +#define SUN4I_HDMI_CTRL_ENABLE                 BIT(31)
> +
> +#define SUN4I_HDMI_IRQ_REG             0x008
> +#define SUN4I_HDMI_IRQ_STA_MASK                        0x73
> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF             BIT(1)
> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF             BIT(0)
> +
> +#define SUN4I_HDMI_HPD_REG             0x00c
> +#define SUN4I_HDMI_HPD_HIGH                    BIT(0)
> +
> +#define SUN4I_HDMI_VID_CTRL_REG                0x010
> +#define SUN4I_HDMI_VID_CTRL_ENABLE             BIT(31)
> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE          BIT(30)
> +
> +#define SUN4I_HDMI_VID_TIMING_ACT_REG  0x014
> +#define SUN4I_HDMI_VID_TIMING_BP_REG   0x018
> +#define SUN4I_HDMI_VID_TIMING_FP_REG   0x01c
> +#define SUN4I_HDMI_VID_TIMING_SPW_REG  0x020
> +
> +#define SUN4I_HDMI_VID_TIMING_X(x)             ((((x) - 1) & GENMASK(11, 0)))
> +#define SUN4I_HDMI_VID_TIMING_Y(y)             ((((y) - 1) & GENMASK(11, 0)) << 16)
> +
> +#define SUN4I_HDMI_VID_TIMING_POL_REG  0x024
> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC                BIT(1)
> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC                BIT(0)
> +
> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)        (0x080 + (n))
> +
> +#define SUN4I_HDMI_PAD_CTRL0_REG       0x200
> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN            BIT(31)
> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN            BIT(30)
> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN            BIT(29)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENC             BIT(28)
> +#define SUN4I_HDMI_PAD_CTRL0_PWEND             BIT(27)
> +#define SUN4I_HDMI_PAD_CTRL0_PWENG             BIT(26)
> +#define SUN4I_HDMI_PAD_CTRL0_CKEN              BIT(25)
> +#define SUN4I_HDMI_PAD_CTRL0_TXEN              BIT(23)
> +
> +#define SUN4I_HDMI_PAD_CTRL1_REG       0x204
> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT           BIT(23)
> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT         BIT(22)
> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT           BIT(20)
> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT         BIT(19)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN           BIT(15)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK         BIT(14)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)                (((n) & 7) << 10)
> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK         BIT(6)
> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)                (((n) & 7) << 3)
> +
> +#define SUN4I_HDMI_PLL_CTRL_REG                0x208
> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN             BIT(31)
> +#define SUN4I_HDMI_PLL_CTRL_BWS                        BIT(30)
> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33           BIT(29)
> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN            BIT(28)
> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN            BIT(27)
> +#define SUN4I_HDMI_PLL_CTRL_SDIV2              BIT(25)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)                (((n) & 7) << 20)
> +#define SUN4I_HDMI_PLL_CTRL_S(n)               (((n) & 7) << 16)
> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)
> +#define SUN4I_HDMI_PLL_CTRL_CS(n)              (((n) & 0xf) << 8)
> +#define SUN4I_HDMI_PLL_CTRL_DIV(n)             (((n) & 0xf) << 4)
> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK           GENMASK(7, 4)
> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)           ((n) & 0xf)
> +
> +#define SUN4I_HDMI_PLL_DBG0_REG                0x20c
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)     (((n) & 1) << 21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK   BIT(21)
> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT  21
> +
> +#define SUN4I_HDMI_PKT_CTRL_REG(n)     (0x2f0 + (4 * (n)))
> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)         ((t) << (((n) % 4) * 4))
> +
> +#define SUN4I_HDMI_UNKNOWN_REG         0x300
> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC          BIT(27)
> +
> +#define SUN4I_HDMI_DDC_CTRL_REG                0x500
> +#define SUN4I_HDMI_DDC_CTRL_ENABLE             BIT(31)
> +#define SUN4I_HDMI_DDC_CTRL_START_CMD          BIT(30)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK      BIT(8)
> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ      (0 << 8)
> +#define SUN4I_HDMI_DDC_CTRL_RESET              BIT(0)
> +
> +#define SUN4I_HDMI_DDC_ADDR_REG                0x504
> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)       (((seg) & 0xff) << 24)
> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)         (((addr) & 0xff) << 16)
> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)                (((off) & 0xff) << 8)
> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)                ((addr) & 0xff)
> +
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG   0x510
> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR         BIT(31)
> +
> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG   0x518
> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG  0x51c
> +
> +#define SUN4I_HDMI_DDC_CMD_REG         0x520
> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ  6
> +
> +#define SUN4I_HDMI_DDC_CLK_REG         0x528
> +#define SUN4I_HDMI_DDC_CLK_M(m)                        (((m) & 0x7) << 3)
> +#define SUN4I_HDMI_DDC_CLK_N(n)                        ((n) & 0x7)
> +
> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG   0x540
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE    BIT(9)
> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE    BIT(8)
> +
> +#define SUN4I_HDMI_DDC_FIFO_SIZE       16
> +
> +enum sun4i_hdmi_pkt_type {
> +       SUN4I_HDMI_PKT_AVI = 2,
> +       SUN4I_HDMI_PKT_END = 15,
> +};
> +
> +struct sun4i_hdmi {
> +       struct drm_connector    connector;
> +       struct drm_encoder      encoder;
> +       struct device           *dev;
> +
> +       void __iomem            *base;
> +
> +       /* Parent clocks */
> +       struct clk              *bus_clk;
> +       struct clk              *mod_clk;
> +       struct clk              *pll0_clk;
> +       struct clk              *pll1_clk;
> +
> +       /* And the clocks we create */
> +       struct clk              *ddc_clk;
> +       struct clk              *tmds_clk;
> +
> +       struct sun4i_drv        *drv;
> +
> +       bool                    hdmi_monitor;
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
> +
> +#endif /* _SUN4I_HDMI_H_ */
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> new file mode 100644
> index 000000000000..4692e8c345ed
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
> @@ -0,0 +1,127 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_ddc {
> +       struct clk_hw           hw;
> +       struct sun4i_hdmi       *hdmi;
> +};
> +
> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct sun4i_ddc, hw);
> +}
> +
> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
> +                                           unsigned long parent_rate,
> +                                           u8 *m, u8 *n)
> +{
> +       unsigned long best_rate = 0;
> +       u8 best_m = 0, best_n = 0, _m, _n;
> +
> +       for (_m = 0; _m < 8; _m++) {
> +               for (_n = 0; _n < 8; _n++) {
> +                       unsigned long tmp_rate;
> +
> +                       tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
> +
> +                       if (tmp_rate > rate)
> +                               continue;
> +
> +                       if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
> +                               best_rate = tmp_rate;
> +                               best_m = _m;
> +                               best_n = _n;
> +                       }
> +               }
> +       }
> +
> +       if (m && n) {
> +               *m = best_m;
> +               *n = best_n;
> +       }
> +
> +       return best_rate;
> +}
> +
> +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                unsigned long *prate)
> +{
> +       return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
> +}
> +
> +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
> +       u32 reg;
> +       u8 m, n;
> +
> +       reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +       m = (reg >> 3) & 0x7;
> +       n = reg & 0x7;
> +
> +       return (((parent_rate / 2) / 10) >> n) / (m + 1);
> +}
> +
> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
> +                             unsigned long parent_rate)
> +{
> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
> +       u8 div_m, div_n;
> +
> +       sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
> +
> +       writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
> +              ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops sun4i_ddc_ops = {
> +       .recalc_rate    = sun4i_ddc_recalc_rate,
> +       .round_rate     = sun4i_ddc_round_rate,
> +       .set_rate       = sun4i_ddc_set_rate,
> +};
> +
> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
> +{
> +       struct clk_init_data init;
> +       struct sun4i_ddc *ddc;
> +       const char *parent_name;
> +
> +       parent_name = __clk_get_name(parent);
> +       if (!parent_name)
> +               return -ENODEV;
> +
> +       ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
> +       if (!ddc)
> +               return -ENOMEM;
> +
> +       init.name = "hdmi-ddc";
> +       init.ops = &sun4i_ddc_ops;
> +       init.parent_names = &parent_name;
> +       init.num_parents = 1;
> +
> +       ddc->hdmi = hdmi;
> +       ddc->hw.init = &init;
> +
> +       hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
> +       if (IS_ERR(hdmi->ddc_clk))
> +               return PTR_ERR(hdmi->ddc_clk);
> +
> +       return 0;
> +}
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> new file mode 100644
> index 000000000000..9bec23164f53
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
> @@ -0,0 +1,493 @@
> +/*
> + * Copyright (C) 2016 Maxime Ripard
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <drm/drmP.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/iopoll.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +
> +#include "sun4i_backend.h"
> +#include "sun4i_drv.h"
> +#include "sun4i_hdmi.h"
> +#include "sun4i_tcon.h"
> +
> +#define DDC_SEGMENT_ADDR       0x30
> +
> +static inline struct sun4i_hdmi *
> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
> +{
> +       return container_of(encoder, struct sun4i_hdmi,
> +                           encoder);
> +}
> +
> +static inline struct sun4i_hdmi *
> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
> +{
> +       return container_of(connector, struct sun4i_hdmi,
> +                           connector);
> +}
> +
> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
> +                                          struct drm_display_mode *mode)
> +{
> +       struct hdmi_avi_infoframe frame;
> +       u8 buffer[17];
> +       int i, ret;
> +
> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
> +       if (ret < 0) {
> +               DRM_ERROR("Failed to get infoframes from mode\n");
> +               return ret;
> +       }
> +
> +       ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
> +       if (ret < 0) {
> +               DRM_ERROR("Failed to pack infoframes\n");
> +               return ret;
> +       }
> +
> +       for (i = 0; i < sizeof(buffer); i++)
> +               writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
> +                                  struct drm_crtc_state *crtc_state,
> +                                  struct drm_connector_state *conn_state)
> +{
> +       struct drm_display_mode *mode = &crtc_state->mode;
> +
> +       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
> +{
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;

I assume you didn't take into consideration the latest patches I sent
that remove this field.

Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.

> +       u32 val;
> +
> +       DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
> +
> +       val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +       val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +
> +       sun4i_tcon_channel_disable(tcon, 1);
> +}
> +
> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
> +{
> +       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;
> +       u32 val = 0;
> +
> +       DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
> +
> +       sun4i_tcon_channel_enable(tcon, 1);
> +
> +       sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
> +       writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
> +
> +       val = SUN4I_HDMI_VID_CTRL_ENABLE;
> +       if (hdmi->hdmi_monitor)
> +               val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
> +
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
> +}
> +
> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
> +                               struct drm_display_mode *mode,
> +                               struct drm_display_mode *adjusted_mode)
> +{
> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
> +       struct sun4i_drv *drv = hdmi->drv;
> +       struct sun4i_tcon *tcon = drv->tcon;
> +       unsigned int x, y;
> +       u32 val;
> +
> +       sun4i_tcon1_mode_set(tcon, mode);
> +       sun4i_tcon_set_mux(tcon, 1, encoder);
> +
> +       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
> +       clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
> +       clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
> +
> +       /* Set input sync enable */
> +       writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
> +              hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
> +
> +       /* Setup timing registers */
> +       writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
> +              SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),

I can't figure out whether we should use mode->crtc_* or not...

> +              hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
> +
> +       x = mode->htotal - mode->hsync_start;
> +       y = mode->vtotal - mode->vsync_start;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
> +
> +       x = mode->hsync_start - mode->hdisplay;
> +       y = mode->vsync_start - mode->vdisplay;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
> +
> +       x = mode->hsync_end - mode->hsync_start;
> +       y = mode->vsync_end - mode->vsync_start;
> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
> +              hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
> +
> +       val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
> +       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
> +               val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
> +
> +       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
> +               val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
> +
> +       writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
> +}
> +
> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
> +       .atomic_check   = sun4i_hdmi_atomic_check,
> +       .disable        = sun4i_hdmi_disable,
> +       .enable         = sun4i_hdmi_enable,
> +       .mode_set       = sun4i_hdmi_mode_set,
> +};
> +
> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
> +       .destroy        = drm_encoder_cleanup,
> +};
> +
> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
> +                                    unsigned int blk, unsigned int offset,
> +                                    u8 *buf, unsigned int count)
> +{
> +       unsigned long reg;
> +       int i;
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
> +       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> +       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
> +              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
> +              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
> +              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
> +              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
> +              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
> +
> +       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
> +       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
> +              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> +                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
> +                              100, 100000))
> +               return -EIO;
> +
> +       for (i = 0; i < count; i++)
> +               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
> +                                     size_t length)
> +{
> +       struct sun4i_hdmi *hdmi = data;
> +       int retry = 2, i;
> +
> +       do {
> +               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
> +                       unsigned char offset = blk * EDID_LENGTH + i;
> +                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
> +                                                length - i);
> +                       int ret;
> +
> +                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
> +                                                       buf + i, count);
> +                       if (ret)
> +                               return ret;
> +               }
> +       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
> +
> +       return 0;
> +}
> +
> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
> +{
> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> +       unsigned long reg;
> +       struct edid *edid;
> +       int ret;
> +
> +       /* Reset i2c controller */
> +       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
> +                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
> +                              100, 2000))
> +               return -EIO;
> +
> +       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
> +              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
> +              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
> +
> +       clk_set_rate(hdmi->ddc_clk, 100000);

You might want to explicitly enable the DDC clk around the following call.

> +
> +       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
> +       if (!edid)
> +               return 0;
> +
> +       hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
> +       DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
> +                        hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
> +
> +       drm_mode_connector_update_edid_property(connector, edid);
> +       ret = drm_add_edid_modes(connector, edid);
> +       kfree(edid);
> +
> +       return ret;
> +}
> +
> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
> +       .get_modes      = sun4i_hdmi_get_modes,
> +};
> +
> +static enum drm_connector_status
> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
> +{
> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
> +       unsigned long reg;
> +
> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
> +                              reg & SUN4I_HDMI_HPD_HIGH,
> +                              0, 500000))
> +               return connector_status_disconnected;
> +
> +       return connector_status_connected;
> +}
> +
> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
> +       .dpms                   = drm_atomic_helper_connector_dpms,
> +       .detect                 = sun4i_hdmi_connector_detect,
> +       .fill_modes             = drm_helper_probe_single_connector_modes,
> +       .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 sun4i_hdmi_bind(struct device *dev, struct device *master,
> +                          void *data)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct drm_device *drm = data;
> +       struct sun4i_drv *drv = drm->dev_private;
> +       struct sun4i_hdmi *hdmi;
> +       struct resource *res;
> +       u32 reg;
> +       int ret;
> +
> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> +       if (!hdmi)
> +               return -ENOMEM;
> +       dev_set_drvdata(dev, hdmi);
> +       hdmi->dev = dev;
> +       hdmi->drv = drv;
> +
> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       hdmi->base = devm_ioremap_resource(dev, res);
> +       if (IS_ERR(hdmi->base)) {
> +               dev_err(dev, "Couldn't map the HDMI encoder registers\n");
> +               return PTR_ERR(hdmi->base);
> +       }
> +
> +       hdmi->bus_clk = devm_clk_get(dev, "ahb");
> +       if (IS_ERR(hdmi->bus_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI bus clock\n");
> +               return PTR_ERR(hdmi->bus_clk);
> +       }
> +       clk_prepare_enable(hdmi->bus_clk);
> +
> +       hdmi->mod_clk = devm_clk_get(dev, "mod");
> +       if (IS_ERR(hdmi->mod_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI mod clock\n");
> +               return PTR_ERR(hdmi->mod_clk);
> +       }
> +       clk_prepare_enable(hdmi->mod_clk);
> +
> +       hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
> +       if (IS_ERR(hdmi->pll0_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
> +               return PTR_ERR(hdmi->pll0_clk);
> +       }
> +
> +       hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
> +       if (IS_ERR(hdmi->pll1_clk)) {
> +               dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
> +               return PTR_ERR(hdmi->pll1_clk);
> +       }
> +
> +       ret = sun4i_tmds_create(hdmi);
> +       if (ret) {
> +               dev_err(dev, "Couldn't create the TMDS clock\n");
> +               return ret;
> +       }
> +
> +       writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
> +
> +       writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
> +              SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
> +              SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
> +              SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
> +              hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
> +
> +       /*
> +        * We can't just initialize the register there, we need to
> +        * protect the clock bits that have already been read out and
> +        * cached by the clock framework.
> +        */
> +       reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;

Nit: just use the &= short hand.

> +       reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
> +               SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
> +               SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
> +               SUN4I_HDMI_PAD_CTRL1_REG_DEN |
> +               SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
> +               SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
> +       writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> +       reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;

Same here.

> +       reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
> +               SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |
> +               SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
> +               SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
> +               SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
> +               SUN4I_HDMI_PLL_CTRL_PLL_EN;
> +       writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> +       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
> +       if (ret) {
> +               dev_err(dev, "Couldn't create the DDC clock\n");
> +               return ret;
> +       }
> +
> +       drm_encoder_helper_add(&hdmi->encoder,
> +                              &sun4i_hdmi_helper_funcs);
> +       ret = drm_encoder_init(drm,
> +                              &hdmi->encoder,
> +                              &sun4i_hdmi_funcs,
> +                              DRM_MODE_ENCODER_TMDS,
> +                              NULL);
> +       if (ret) {
> +               dev_err(dev, "Couldn't initialise the HDMI encoder\n");
> +               return ret;
> +       }
> +
> +       hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
> +                                                                 dev->of_node);
> +       if (!hdmi->encoder.possible_crtcs)
> +               return ret;

You need a valid error return value here. Maybe -EPROBE_DEFER?

> +
> +       drm_connector_helper_add(&hdmi->connector,
> +                                &sun4i_hdmi_connector_helper_funcs);

/* There is no HPD interrupt, so we need to poll the controller */
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
                         DRM_CONNECTOR_POLL_DISCONNECT;

> +       ret = drm_connector_init(drm, &hdmi->connector,
> +                                &sun4i_hdmi_connector_funcs,
> +                                DRM_MODE_CONNECTOR_HDMIA);
> +       if (ret) {
> +               dev_err(dev,
> +                       "Couldn't initialise the HDMI connector\n");
> +               goto err_cleanup_connector;
> +       }
> +
> +       drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
> +
> +       return 0;
> +
> +err_cleanup_connector:
> +       drm_encoder_cleanup(&hdmi->encoder);
> +       return ret;
> +}
> +
> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
> +                           void *data)
> +{
> +       struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
> +
> +       drm_connector_cleanup(&hdmi->connector);
> +       drm_encoder_cleanup(&hdmi->encoder);
> +}
> +
> +static const struct component_ops sun4i_hdmi_ops = {
> +       .bind   = sun4i_hdmi_bind,
> +       .unbind = sun4i_hdmi_unbind,
> +};
> +
> +static int sun4i_hdmi_probe(struct platform_device *pdev)
> +{
> +       return component_add(&pdev->dev, &sun4i_hdmi_ops);
> +}
> +
> +static int sun4i_hdmi_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &sun4i_hdmi_ops);
> +
> +       return 0;
> +}
> +
> +static const struct of_device_id sun4i_hdmi_of_table[] = {
> +       { .compatible = "allwinner,sun5i-a10s-hdmi" },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
> +
> +static struct platform_driver sun4i_hdmi_driver = {
> +       .probe          = sun4i_hdmi_probe,
> +       .remove         = sun4i_hdmi_remove,
> +       .driver         = {
> +               .name           = "sun4i-hdmi",
> +               .of_match_table = sun4i_hdmi_of_table,
> +       },
> +};
> +module_platform_driver(sun4i_hdmi_driver);
> +
> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> new file mode 100644
> index 000000000000..5cf2527bffc8
> --- /dev/null
> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
> @@ -0,0 +1,225 @@
> +/*
> + * Copyright (C) 2016 Free Electrons
> + * Copyright (C) 2016 NextThing Co
> + *
> + * Maxime Ripard <maxime.ripard@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "sun4i_tcon.h"
> +#include "sun4i_hdmi.h"
> +
> +struct sun4i_tmds {
> +       struct clk_hw           hw;
> +       struct sun4i_hdmi       *hdmi;
> +};
> +
> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
> +{
> +       return container_of(hw, struct sun4i_tmds, hw);
> +}
> +
> +
> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
> +                                            unsigned long parent_rate,
> +                                            u8 *div,
> +                                            bool *half)
> +{
> +       unsigned long best_rate = 0;
> +       u8 best_m = 0, m;
> +       bool is_double;
> +
> +       for (m = 1; m < 16; m++) {
> +               u8 d;
> +
> +               for (d = 1; d < 3; d++) {
> +                       unsigned long tmp_rate;
> +
> +                       tmp_rate = parent_rate / m / d;
> +
> +                       if (tmp_rate > rate)
> +                               continue;
> +
> +                       if (!best_rate ||
> +                           (rate - tmp_rate) < (rate - best_rate)) {
> +                               best_rate = tmp_rate;
> +                               best_m = m;
> +                               is_double = d;
> +                       }
> +               }
> +       }
> +
> +       if (div && half) {
> +               *div = best_m;
> +               *half = is_double;
> +       }
> +
> +       return best_rate;
> +}
> +
> +
> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
> +                                    struct clk_rate_request *req)
> +{
> +       struct clk_hw *parent;
> +       unsigned long best_parent = 0;
> +       unsigned long rate = req->rate;
> +       int best_div = 1, best_half = 1;
> +       int i, j;
> +
> +       /*
> +        * We only consider PLL3, since the TCON is very likely to be
> +        * clocked from it, and to have the same rate than our HDMI
> +        * clock, so we should not need to do anything.
> +        */
> +
> +       parent = clk_hw_get_parent_by_index(hw, 0);
> +       if (!parent)
> +               return -EINVAL;
> +
> +       for (i = 1; i < 3; i++) {
> +               for (j = 1; j < 16; j++) {
> +                       unsigned long ideal = rate * i * j;
> +                       unsigned long rounded;
> +
> +                       rounded = clk_hw_round_rate(parent, ideal);
> +
> +                       if (rounded == ideal) {
> +                               best_parent = rounded;
> +                               best_half = i;
> +                               best_div = j;
> +                               goto out;
> +                       }
> +
> +                       if (abs(rate - rounded / i) <
> +                           abs(rate - best_parent / best_div)) {
> +                               best_parent = rounded;
> +                               best_div = i;
> +                       }
> +               }
> +       }
> +
> +out:
> +       req->rate = best_parent / best_half / best_div;
> +       req->best_parent_rate = best_parent;
> +       req->best_parent_hw = parent;
> +
> +       return 0;
> +}
> +
> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
> +                                           unsigned long parent_rate)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
> +               parent_rate /= 2;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg = (reg >> 4) & 0xf;
> +       if (!reg)
> +               reg = 1;
> +
> +       return parent_rate / reg;
> +}
> +
> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
> +                              unsigned long parent_rate)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       bool half;
> +       u32 reg;
> +       u8 div;
> +
> +       sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +       reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> +       if (half)
> +               reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
> +       writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +       reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
> +       writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
> +              tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
> +
> +       return 0;
> +}
> +
> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +       return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
> +               SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
> +}
> +
> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
> +{
> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
> +       u32 reg;
> +
> +       if (index > 1)
> +               return -EINVAL;
> +
> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +       reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
> +       writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
> +              tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
> +
> +       return 0;
> +}
> +
> +static const struct clk_ops sun4i_tmds_ops = {
> +       .determine_rate = sun4i_tmds_determine_rate,
> +       .recalc_rate    = sun4i_tmds_recalc_rate,
> +       .set_rate       = sun4i_tmds_set_rate,
> +
> +       .get_parent     = sun4i_tmds_get_parent,
> +       .set_parent     = sun4i_tmds_set_parent,
> +};
> +
> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
> +{
> +       struct clk_init_data init;
> +       struct sun4i_tmds *tmds;
> +       const char *parents[2];
> +
> +       parents[0] = __clk_get_name(hdmi->pll0_clk);
> +       if (!parents[0])
> +               return -ENODEV;
> +
> +       parents[1] = __clk_get_name(hdmi->pll1_clk);
> +       if (!parents[1])
> +               return -ENODEV;
> +
> +       tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
> +       if (!tmds)
> +               return -ENOMEM;
> +
> +       init.name = "hdmi-tmds";
> +       init.ops = &sun4i_tmds_ops;
> +       init.parent_names = parents;
> +       init.num_parents = 2;
> +       init.flags = CLK_SET_RATE_PARENT;
> +
> +       tmds->hdmi = hdmi;
> +       tmds->hw.init = &init;
> +
> +       hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
> +       if (IS_ERR(hdmi->tmds_clk))
> +               return PTR_ERR(hdmi->tmds_clk);
> +
> +       return 0;
> +}
> --

The rest looks good. This is coming together nicely.

Reviewed-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node
@ 2017-05-04  8:04     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  8:04 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The A10s has an HDMI controller connected to the second TCON channel. Add
> it to our DT.
>
> Since the TV Encoder was the only channel 1 user so far, also add the
> property now that we have several users.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  arch/arm/boot/dts/sun5i-a10s.dtsi | 50 ++++++++++++++++++++++++++++++++-
>  arch/arm/boot/dts/sun5i.dtsi      |  1 +-
>  2 files changed, 51 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
> index 1e38ff80366c..c9d4ee12599d 100644
> --- a/arch/arm/boot/dts/sun5i-a10s.dtsi
> +++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
> @@ -71,7 +71,49 @@
>                 };
>         };
>
> +       display-engine {
> +               compatible = "allwinner,sun5i-a10s-display-engine",
> +                            "allwinner,sun5i-a13-display-engine";

I assume this indicates a difference between A10s and A13, namely the former
has an HDMI controller. The compatible string should be documented.

> +               allwinner,pipelines = <&fe0>;
> +       };
> +
>         soc@01c00000 {
> +               hdmi: hdmi@01c16000 {
> +                       compatible = "allwinner,sun5i-a10s-hdmi";
> +                       reg = <0x01c16000 0x1000>;
> +                       interrupts = <58>;
> +                       clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
> +                                <&ccu CLK_PLL_VIDEO0_2X>,
> +                                <&ccu CLK_PLL_VIDEO1_2X>;
> +                       clock-names = "ahb", "mod", "pll-0", "pll-1";
> +                       dmas = <&dma SUN4I_DMA_NORMAL 16>,
> +                              <&dma SUN4I_DMA_NORMAL 16>,
> +                              <&dma SUN4I_DMA_DEDICATED 24>;
> +                       dma-names = "ddc-tx", "ddc-rx", "audio-tx";
> +                       status = "disabled";
> +
> +                       ports {
> +                               #address-cells = <1>;
> +                               #size-cells = <0>;
> +
> +                               hdmi_in: port@0 {
> +                                       #address-cells = <1>;
> +                                       #size-cells = <0>;
> +                                       reg = <0>;
> +
> +                                       hdmi_in_tcon0: endpoint {

Missing reg property here, since you have #address-cells = 1.

Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

> +                                               remote-endpoint = <&tcon0_out_hdmi>;
> +                                       };
> +                               };
> +
> +                               hdmi_out: port@1 {
> +                                       #address-cells = <1>;
> +                                       #size-cells = <0>;
> +                                       reg = <1>;
> +                               };
> +                       };
> +               };
> +
>                 pwm: pwm@01c20e00 {
>                         compatible = "allwinner,sun5i-a10s-pwm";
>                         reg = <0x01c20e00 0xc>;
> @@ -128,3 +170,11 @@
>
>  &sram_a {
>  };
> +
> +&tcon0_out {
> +       tcon0_out_hdmi: endpoint@2 {
> +               reg = <2>;
> +               remote-endpoint = <&hdmi_in_tcon0>;
> +               allwinner,tcon-channel = <1>;
> +       };
> +};
> diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi
> index 5175f9cc9bed..0e29f1d98a9e 100644
> --- a/arch/arm/boot/dts/sun5i.dtsi
> +++ b/arch/arm/boot/dts/sun5i.dtsi
> @@ -272,6 +272,7 @@
>                                         tcon0_out_tve0: endpoint@1 {
>                                                 reg = <1>;
>                                                 remote-endpoint = <&tve0_in_tcon0>;
> +                                               allwinner,tcon-channel = <1>;
>                                         };
>                                 };
>                         };
> --
> git-series 0.8.11

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

* Re: [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node
@ 2017-05-04  8:04     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  8:04 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The A10s has an HDMI controller connected to the second TCON channel. Add
> it to our DT.
>
> Since the TV Encoder was the only channel 1 user so far, also add the
> property now that we have several users.
>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  arch/arm/boot/dts/sun5i-a10s.dtsi | 50 ++++++++++++++++++++++++++++++++-
>  arch/arm/boot/dts/sun5i.dtsi      |  1 +-
>  2 files changed, 51 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
> index 1e38ff80366c..c9d4ee12599d 100644
> --- a/arch/arm/boot/dts/sun5i-a10s.dtsi
> +++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
> @@ -71,7 +71,49 @@
>                 };
>         };
>
> +       display-engine {
> +               compatible = "allwinner,sun5i-a10s-display-engine",
> +                            "allwinner,sun5i-a13-display-engine";

I assume this indicates a difference between A10s and A13, namely the former
has an HDMI controller. The compatible string should be documented.

> +               allwinner,pipelines = <&fe0>;
> +       };
> +
>         soc@01c00000 {
> +               hdmi: hdmi@01c16000 {
> +                       compatible = "allwinner,sun5i-a10s-hdmi";
> +                       reg = <0x01c16000 0x1000>;
> +                       interrupts = <58>;
> +                       clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
> +                                <&ccu CLK_PLL_VIDEO0_2X>,
> +                                <&ccu CLK_PLL_VIDEO1_2X>;
> +                       clock-names = "ahb", "mod", "pll-0", "pll-1";
> +                       dmas = <&dma SUN4I_DMA_NORMAL 16>,
> +                              <&dma SUN4I_DMA_NORMAL 16>,
> +                              <&dma SUN4I_DMA_DEDICATED 24>;
> +                       dma-names = "ddc-tx", "ddc-rx", "audio-tx";
> +                       status = "disabled";
> +
> +                       ports {
> +                               #address-cells = <1>;
> +                               #size-cells = <0>;
> +
> +                               hdmi_in: port@0 {
> +                                       #address-cells = <1>;
> +                                       #size-cells = <0>;
> +                                       reg = <0>;
> +
> +                                       hdmi_in_tcon0: endpoint {

Missing reg property here, since you have #address-cells = 1.

Otherwise,

Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

> +                                               remote-endpoint = <&tcon0_out_hdmi>;
> +                                       };
> +                               };
> +
> +                               hdmi_out: port@1 {
> +                                       #address-cells = <1>;
> +                                       #size-cells = <0>;
> +                                       reg = <1>;
> +                               };
> +                       };
> +               };
> +
>                 pwm: pwm@01c20e00 {
>                         compatible = "allwinner,sun5i-a10s-pwm";
>                         reg = <0x01c20e00 0xc>;
> @@ -128,3 +170,11 @@
>
>  &sram_a {
>  };
> +
> +&tcon0_out {
> +       tcon0_out_hdmi: endpoint@2 {
> +               reg = <2>;
> +               remote-endpoint = <&hdmi_in_tcon0>;
> +               allwinner,tcon-channel = <1>;
> +       };
> +};
> diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi
> index 5175f9cc9bed..0e29f1d98a9e 100644
> --- a/arch/arm/boot/dts/sun5i.dtsi
> +++ b/arch/arm/boot/dts/sun5i.dtsi
> @@ -272,6 +272,7 @@
>                                         tcon0_out_tve0: endpoint@1 {
>                                                 reg = <1>;
>                                                 remote-endpoint = <&tve0_in_tcon0>;
> +                                               allwinner,tcon-channel = <1>;
>                                         };
>                                 };
>                         };
> --
> git-series 0.8.11

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

* [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node
@ 2017-05-04  8:04     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  8:04 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The A10s has an HDMI controller connected to the second TCON channel. Add
> it to our DT.
>
> Since the TV Encoder was the only channel 1 user so far, also add the
> property now that we have several users.
>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  arch/arm/boot/dts/sun5i-a10s.dtsi | 50 ++++++++++++++++++++++++++++++++-
>  arch/arm/boot/dts/sun5i.dtsi      |  1 +-
>  2 files changed, 51 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/boot/dts/sun5i-a10s.dtsi b/arch/arm/boot/dts/sun5i-a10s.dtsi
> index 1e38ff80366c..c9d4ee12599d 100644
> --- a/arch/arm/boot/dts/sun5i-a10s.dtsi
> +++ b/arch/arm/boot/dts/sun5i-a10s.dtsi
> @@ -71,7 +71,49 @@
>                 };
>         };
>
> +       display-engine {
> +               compatible = "allwinner,sun5i-a10s-display-engine",
> +                            "allwinner,sun5i-a13-display-engine";

I assume this indicates a difference between A10s and A13, namely the former
has an HDMI controller. The compatible string should be documented.

> +               allwinner,pipelines = <&fe0>;
> +       };
> +
>         soc at 01c00000 {
> +               hdmi: hdmi at 01c16000 {
> +                       compatible = "allwinner,sun5i-a10s-hdmi";
> +                       reg = <0x01c16000 0x1000>;
> +                       interrupts = <58>;
> +                       clocks = <&ccu CLK_AHB_HDMI>, <&ccu CLK_HDMI>,
> +                                <&ccu CLK_PLL_VIDEO0_2X>,
> +                                <&ccu CLK_PLL_VIDEO1_2X>;
> +                       clock-names = "ahb", "mod", "pll-0", "pll-1";
> +                       dmas = <&dma SUN4I_DMA_NORMAL 16>,
> +                              <&dma SUN4I_DMA_NORMAL 16>,
> +                              <&dma SUN4I_DMA_DEDICATED 24>;
> +                       dma-names = "ddc-tx", "ddc-rx", "audio-tx";
> +                       status = "disabled";
> +
> +                       ports {
> +                               #address-cells = <1>;
> +                               #size-cells = <0>;
> +
> +                               hdmi_in: port at 0 {
> +                                       #address-cells = <1>;
> +                                       #size-cells = <0>;
> +                                       reg = <0>;
> +
> +                                       hdmi_in_tcon0: endpoint {

Missing reg property here, since you have #address-cells = 1.

Otherwise,

Acked-by: Chen-Yu Tsai <wens@csie.org>

> +                                               remote-endpoint = <&tcon0_out_hdmi>;
> +                                       };
> +                               };
> +
> +                               hdmi_out: port at 1 {
> +                                       #address-cells = <1>;
> +                                       #size-cells = <0>;
> +                                       reg = <1>;
> +                               };
> +                       };
> +               };
> +
>                 pwm: pwm at 01c20e00 {
>                         compatible = "allwinner,sun5i-a10s-pwm";
>                         reg = <0x01c20e00 0xc>;
> @@ -128,3 +170,11 @@
>
>  &sram_a {
>  };
> +
> +&tcon0_out {
> +       tcon0_out_hdmi: endpoint at 2 {
> +               reg = <2>;
> +               remote-endpoint = <&hdmi_in_tcon0>;
> +               allwinner,tcon-channel = <1>;
> +       };
> +};
> diff --git a/arch/arm/boot/dts/sun5i.dtsi b/arch/arm/boot/dts/sun5i.dtsi
> index 5175f9cc9bed..0e29f1d98a9e 100644
> --- a/arch/arm/boot/dts/sun5i.dtsi
> +++ b/arch/arm/boot/dts/sun5i.dtsi
> @@ -272,6 +272,7 @@
>                                         tcon0_out_tve0: endpoint at 1 {
>                                                 reg = <1>;
>                                                 remote-endpoint = <&tve0_in_tcon0>;
> +                                               allwinner,tcon-channel = <1>;
>                                         };
>                                 };
>                         };
> --
> git-series 0.8.11

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

* Re: [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-04  8:05     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  8:05 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The A10s Olinuxino has an HDMI connector. Make sure we can use it.
>
> Acked-by: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
>  1 file changed, 29 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> index 894f874a5beb..1d13b6407222 100644
> --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> @@ -63,6 +63,17 @@
>                 stdout-path = "serial0:115200n8";
>         };
>
> +       connector {
> +               compatible = "hdmi-connector";
> +               type = "a";

Just curious, should we take this into consideration when creating
drm_connector?

ChenYu

> +
> +               port {
> +                       hdmi_con_in: endpoint {
> +                               remote-endpoint = <&hdmi_out_con>;
> +                       };
> +               };
> +       };
> +
>         leds {
>                 compatible = "gpio-leds";
>                 pinctrl-names = "default";
> @@ -76,6 +87,10 @@
>         };
>  };
>
> +&be0 {
> +       status = "okay";
> +};
> +
>  &ehci0 {
>         status = "okay";
>  };
> @@ -91,6 +106,16 @@
>         status = "okay";
>  };
>
> +&hdmi {
> +       status = "okay";
> +};
> +
> +&hdmi_out {
> +       hdmi_out_con: endpoint {
> +               remote-endpoint = <&hdmi_con_in>;
> +       };
> +};
> +
>  &i2c0 {
>         pinctrl-names = "default";
>         pinctrl-0 = <&i2c0_pins_a>;
> @@ -248,6 +273,10 @@
>         status = "okay";
>  };
>
> +&tcon0 {
> +       status = "okay";
> +};
> +
>  &uart0 {
>         pinctrl-names = "default";
>         pinctrl-0 = <&uart0_pins_a>;
> --
> git-series 0.8.11
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

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

* Re: [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-04  8:05     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  8:05 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
> The A10s Olinuxino has an HDMI connector. Make sure we can use it.
>
> Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
>  1 file changed, 29 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> index 894f874a5beb..1d13b6407222 100644
> --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> @@ -63,6 +63,17 @@
>                 stdout-path = "serial0:115200n8";
>         };
>
> +       connector {
> +               compatible = "hdmi-connector";
> +               type = "a";

Just curious, should we take this into consideration when creating
drm_connector?

ChenYu

> +
> +               port {
> +                       hdmi_con_in: endpoint {
> +                               remote-endpoint = <&hdmi_out_con>;
> +                       };
> +               };
> +       };
> +
>         leds {
>                 compatible = "gpio-leds";
>                 pinctrl-names = "default";
> @@ -76,6 +87,10 @@
>         };
>  };
>
> +&be0 {
> +       status = "okay";
> +};
> +
>  &ehci0 {
>         status = "okay";
>  };
> @@ -91,6 +106,16 @@
>         status = "okay";
>  };
>
> +&hdmi {
> +       status = "okay";
> +};
> +
> +&hdmi_out {
> +       hdmi_out_con: endpoint {
> +               remote-endpoint = <&hdmi_con_in>;
> +       };
> +};
> +
>  &i2c0 {
>         pinctrl-names = "default";
>         pinctrl-0 = <&i2c0_pins_a>;
> @@ -248,6 +273,10 @@
>         status = "okay";
>  };
>
> +&tcon0 {
> +       status = "okay";
> +};
> +
>  &uart0 {
>         pinctrl-names = "default";
>         pinctrl-0 = <&uart0_pins_a>;
> --
> git-series 0.8.11
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
> For more options, visit https://groups.google.com/d/optout.

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

* [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-04  8:05     ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-04  8:05 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> The A10s Olinuxino has an HDMI connector. Make sure we can use it.
>
> Acked-by: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
>  1 file changed, 29 insertions(+), 0 deletions(-)
>
> diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> index 894f874a5beb..1d13b6407222 100644
> --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> @@ -63,6 +63,17 @@
>                 stdout-path = "serial0:115200n8";
>         };
>
> +       connector {
> +               compatible = "hdmi-connector";
> +               type = "a";

Just curious, should we take this into consideration when creating
drm_connector?

ChenYu

> +
> +               port {
> +                       hdmi_con_in: endpoint {
> +                               remote-endpoint = <&hdmi_out_con>;
> +                       };
> +               };
> +       };
> +
>         leds {
>                 compatible = "gpio-leds";
>                 pinctrl-names = "default";
> @@ -76,6 +87,10 @@
>         };
>  };
>
> +&be0 {
> +       status = "okay";
> +};
> +
>  &ehci0 {
>         status = "okay";
>  };
> @@ -91,6 +106,16 @@
>         status = "okay";
>  };
>
> +&hdmi {
> +       status = "okay";
> +};
> +
> +&hdmi_out {
> +       hdmi_out_con: endpoint {
> +               remote-endpoint = <&hdmi_con_in>;
> +       };
> +};
> +
>  &i2c0 {
>         pinctrl-names = "default";
>         pinctrl-0 = <&i2c0_pins_a>;
> @@ -248,6 +273,10 @@
>         status = "okay";
>  };
>
> +&tcon0 {
> +       status = "okay";
> +};
> +
>  &uart0 {
>         pinctrl-names = "default";
>         pinctrl-0 = <&uart0_pins_a>;
> --
> git-series 0.8.11
>
> --
> You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe at googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

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

* Re: [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-05  8:16     ` icenowy-h8G6r0blFSE
  0 siblings, 0 replies; 125+ messages in thread
From: icenowy @ 2017-05-05  8:16 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi,
	linux-kernel-owner

在 2017-05-03 19:59,Maxime Ripard 写道:
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.
> 
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 0f91ec8a4b26..efa079c1a3f5 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>  	/* Set vertical display timings */
>  	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
>  	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
> -			 mode->vtotal, bp);
> +			 mode->crtc_vtotal, bp);
>  	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
> -		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
> +		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |

For TVE the value should directly be mode->vtotal, but not
mode->crtc_vtotal * 2.

vtotal is 625 when PAL. crtc_vtotal is thus 312, but if we restore the
vtotal value by doubling crtv_vtotal, we got 624, which will lead to
instability of the image displayed. (the image will loop to go higher
and then go lower, because wrong vtotal value)

Tested on patched H3 TV encoder.

I used a logic slightly changed from your v1 code:

```
	val = mode->vtotal;
	if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
		val = val * 2;
	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
		     SUN4I_TCON1_BASIC4_V_TOTAL(val) |
		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
```

>  		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
> 
>  	/* Set Hsync and Vsync length */

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

* Re: [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-05  8:16     ` icenowy-h8G6r0blFSE
  0 siblings, 0 replies; 125+ messages in thread
From: icenowy-h8G6r0blFSE @ 2017-05-05  8:16 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	Mark Rutland, Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
	linux-kernel-owner-u79uwXL29TY76Z2rM5mHXA

在 2017-05-03 19:59,Maxime Ripard 写道:
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.
> 
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 0f91ec8a4b26..efa079c1a3f5 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>  	/* Set vertical display timings */
>  	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
>  	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
> -			 mode->vtotal, bp);
> +			 mode->crtc_vtotal, bp);
>  	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
> -		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
> +		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |

For TVE the value should directly be mode->vtotal, but not
mode->crtc_vtotal * 2.

vtotal is 625 when PAL. crtc_vtotal is thus 312, but if we restore the
vtotal value by doubling crtv_vtotal, we got 624, which will lead to
instability of the image displayed. (the image will loop to go higher
and then go lower, because wrong vtotal value)

Tested on patched H3 TV encoder.

I used a logic slightly changed from your v1 code:

```
	val = mode->vtotal;
	if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
		val = val * 2;
	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
		     SUN4I_TCON1_BASIC4_V_TOTAL(val) |
		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
```

>  		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
> 
>  	/* Set Hsync and Vsync length */
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace
@ 2017-05-05  8:16     ` icenowy-h8G6r0blFSE
  0 siblings, 0 replies; 125+ messages in thread
From: icenowy at aosc.io @ 2017-05-05  8:16 UTC (permalink / raw)
  To: linux-arm-kernel

? 2017-05-03 19:59?Maxime Ripard ???
> It appears that the total vertical resolution needs to be doubled when
> we're not in interlaced. Make sure that is the case.
> 
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  drivers/gpu/drm/sun4i/sun4i_tcon.c | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 0f91ec8a4b26..efa079c1a3f5 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -272,9 +272,9 @@ void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon,
>  	/* Set vertical display timings */
>  	bp = mode->crtc_vtotal - mode->crtc_vsync_start;
>  	DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n",
> -			 mode->vtotal, bp);
> +			 mode->crtc_vtotal, bp);
>  	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
> -		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) |
> +		     SUN4I_TCON1_BASIC4_V_TOTAL(mode->crtc_vtotal * 2) |

For TVE the value should directly be mode->vtotal, but not
mode->crtc_vtotal * 2.

vtotal is 625 when PAL. crtc_vtotal is thus 312, but if we restore the
vtotal value by doubling crtv_vtotal, we got 624, which will lead to
instability of the image displayed. (the image will loop to go higher
and then go lower, because wrong vtotal value)

Tested on patched H3 TV encoder.

I used a logic slightly changed from your v1 code:

```
	val = mode->vtotal;
	if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
		val = val * 2;
	regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG,
		     SUN4I_TCON1_BASIC4_V_TOTAL(val) |
		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
```

>  		     SUN4I_TCON1_BASIC4_V_BACKPORCH(bp));
> 
>  	/* Set Hsync and Vsync length */

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

* Re: [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings
@ 2017-05-08 16:20     ` Rob Herring
  0 siblings, 0 replies; 125+ messages in thread
From: Rob Herring @ 2017-05-08 16:20 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 03, 2017 at 01:59:53PM +0200, Maxime Ripard wrote:
> One of the possible output of the display pipeline, on the SoCs that have
> it, is the HDMI controller.
> 
> Add a binding for it.
> 
> Acked-by: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 79 +++++++-
>  1 file changed, 79 insertions(+), 0 deletions(-)

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

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

* Re: [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings
@ 2017-05-08 16:20     ` Rob Herring
  0 siblings, 0 replies; 125+ messages in thread
From: Rob Herring @ 2017-05-08 16:20 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

On Wed, May 03, 2017 at 01:59:53PM +0200, Maxime Ripard wrote:
> One of the possible output of the display pipeline, on the SoCs that have
> it, is the HDMI controller.
> 
> Add a binding for it.
> 
> Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 79 +++++++-
>  1 file changed, 79 insertions(+), 0 deletions(-)

Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

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

* [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings
@ 2017-05-08 16:20     ` Rob Herring
  0 siblings, 0 replies; 125+ messages in thread
From: Rob Herring @ 2017-05-08 16:20 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 03, 2017 at 01:59:53PM +0200, Maxime Ripard wrote:
> One of the possible output of the display pipeline, on the SoCs that have
> it, is the HDMI controller.
> 
> Add a binding for it.
> 
> Acked-by: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 79 +++++++-
>  1 file changed, 79 insertions(+), 0 deletions(-)

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

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

* Re: [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property
@ 2017-05-08 16:24     ` Rob Herring
  0 siblings, 0 replies; 125+ messages in thread
From: Rob Herring @ 2017-05-08 16:24 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Wed, May 03, 2017 at 01:59:54PM +0200, Maxime Ripard wrote:
> The Allwinner Timings Controller has two, mutually exclusive, channels.
> When the binding has been introduced, it was assumed that there would be
> only a single user per channel in the system.
> 
> While this is likely for the channel 0 which only connects to LCD displays,
> it turns out that the channel 1 can be connected to multiple controllers in
> the SoC (HDMI and TV encoders for example). And while the simultaneous use
> of HDMI and TV outputs cannot be achieved, switching from one to the other
> at runtime definitely sounds plausible.
> 
> Add an extra property, allwinner,tcon-channel, to specify for a given
> endpoint which TCON channel it is connected to, while falling back to the
> previous mechanism if that property is missing.
> 
> Acked-by: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
>  1 file changed, 7 insertions(+), 4 deletions(-)

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

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

* Re: [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property
@ 2017-05-08 16:24     ` Rob Herring
  0 siblings, 0 replies; 125+ messages in thread
From: Rob Herring @ 2017-05-08 16:24 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Mike Turquette, Stephen Boyd, Chen-Yu Tsai, Daniel Vetter,
	David Airlie, dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
	Mark Rutland, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
	linux-clk-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw

On Wed, May 03, 2017 at 01:59:54PM +0200, Maxime Ripard wrote:
> The Allwinner Timings Controller has two, mutually exclusive, channels.
> When the binding has been introduced, it was assumed that there would be
> only a single user per channel in the system.
> 
> While this is likely for the channel 0 which only connects to LCD displays,
> it turns out that the channel 1 can be connected to multiple controllers in
> the SoC (HDMI and TV encoders for example). And while the simultaneous use
> of HDMI and TV outputs cannot be achieved, switching from one to the other
> at runtime definitely sounds plausible.
> 
> Add an extra property, allwinner,tcon-channel, to specify for a given
> endpoint which TCON channel it is connected to, while falling back to the
> previous mechanism if that property is missing.
> 
> Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
> ---
>  Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
>  1 file changed, 7 insertions(+), 4 deletions(-)

Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>

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

* [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property
@ 2017-05-08 16:24     ` Rob Herring
  0 siblings, 0 replies; 125+ messages in thread
From: Rob Herring @ 2017-05-08 16:24 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, May 03, 2017 at 01:59:54PM +0200, Maxime Ripard wrote:
> The Allwinner Timings Controller has two, mutually exclusive, channels.
> When the binding has been introduced, it was assumed that there would be
> only a single user per channel in the system.
> 
> While this is likely for the channel 0 which only connects to LCD displays,
> it turns out that the channel 1 can be connected to multiple controllers in
> the SoC (HDMI and TV encoders for example). And while the simultaneous use
> of HDMI and TV outputs cannot be achieved, switching from one to the other
> at runtime definitely sounds plausible.
> 
> Add an extra property, allwinner,tcon-channel, to specify for a given
> endpoint which TCON channel it is connected to, while falling back to the
> previous mechanism if that property is missing.
> 
> Acked-by: Chen-Yu Tsai <wens@csie.org>
> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> ---
>  Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 11 ++++---
>  1 file changed, 7 insertions(+), 4 deletions(-)

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

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

* Re: [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
  2017-05-04  8:05     ` Chen-Yu Tsai
  (?)
@ 2017-05-10 19:23       ` Maxime Ripard
  -1 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-10 19:23 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Mike Turquette, Stephen Boyd, Daniel Vetter, David Airlie,
	dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

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

Hi,

On Thu, May 04, 2017 at 04:05:18PM +0800, Chen-Yu Tsai wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
> > The A10s Olinuxino has an HDMI connector. Make sure we can use it.
> >
> > Acked-by: Chen-Yu Tsai <wens@csie.org>
> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> > ---
> >  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
> >  1 file changed, 29 insertions(+), 0 deletions(-)
> >
> > diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > index 894f874a5beb..1d13b6407222 100644
> > --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > @@ -63,6 +63,17 @@
> >                 stdout-path = "serial0:115200n8";
> >         };
> >
> > +       connector {
> > +               compatible = "hdmi-connector";
> > +               type = "a";
> 
> Just curious, should we take this into consideration when creating
> drm_connector?

I'm not sure, no other driver does so. And I haven't seen any of our
boards with a HDMI-B, C or mini-HDMI connector.

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

* Re: [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-10 19:23       ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-10 19:23 UTC (permalink / raw)
  To: Chen-Yu Tsai
  Cc: Mark Rutland, devicetree, Mike Turquette, Stephen Boyd,
	linux-kernel, dri-devel, linux-sunxi, Rob Herring, Daniel Vetter,
	linux-clk, linux-arm-kernel


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

Hi,

On Thu, May 04, 2017 at 04:05:18PM +0800, Chen-Yu Tsai wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
> > The A10s Olinuxino has an HDMI connector. Make sure we can use it.
> >
> > Acked-by: Chen-Yu Tsai <wens@csie.org>
> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> > ---
> >  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
> >  1 file changed, 29 insertions(+), 0 deletions(-)
> >
> > diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > index 894f874a5beb..1d13b6407222 100644
> > --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > @@ -63,6 +63,17 @@
> >                 stdout-path = "serial0:115200n8";
> >         };
> >
> > +       connector {
> > +               compatible = "hdmi-connector";
> > +               type = "a";
> 
> Just curious, should we take this into consideration when creating
> drm_connector?

I'm not sure, no other driver does so. And I haven't seen any of our
boards with a HDMI-B, C or mini-HDMI connector.

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 801 bytes --]

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

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

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

* [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-10 19:23       ` Maxime Ripard
  0 siblings, 0 replies; 125+ messages in thread
From: Maxime Ripard @ 2017-05-10 19:23 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Thu, May 04, 2017 at 04:05:18PM +0800, Chen-Yu Tsai wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
> > The A10s Olinuxino has an HDMI connector. Make sure we can use it.
> >
> > Acked-by: Chen-Yu Tsai <wens@csie.org>
> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
> > ---
> >  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
> >  1 file changed, 29 insertions(+), 0 deletions(-)
> >
> > diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > index 894f874a5beb..1d13b6407222 100644
> > --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
> > @@ -63,6 +63,17 @@
> >                 stdout-path = "serial0:115200n8";
> >         };
> >
> > +       connector {
> > +               compatible = "hdmi-connector";
> > +               type = "a";
> 
> Just curious, should we take this into consideration when creating
> drm_connector?

I'm not sure, no other driver does so. And I haven't seen any of our
boards with a HDMI-B, C or mini-HDMI connector.

Maxime

-- 
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 801 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170510/17e0817b/attachment.sig>

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

* Re: [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-11  6:25         ` icenowy-h8G6r0blFSE
  0 siblings, 0 replies; 125+ messages in thread
From: icenowy @ 2017-05-11  6:25 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Chen-Yu Tsai, Mark Rutland, devicetree, David Airlie,
	Mike Turquette, Stephen Boyd, linux-kernel, dri-devel,
	linux-sunxi, Rob Herring, Daniel Vetter, linux-clk,
	linux-arm-kernel

在 2017-05-11 03:23,Maxime Ripard 写道:
> Hi,
> 
> On Thu, May 04, 2017 at 04:05:18PM +0800, Chen-Yu Tsai wrote:
>> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
>> <maxime.ripard@free-electrons.com> wrote:
>> > The A10s Olinuxino has an HDMI connector. Make sure we can use it.
>> >
>> > Acked-by: Chen-Yu Tsai <wens@csie.org>
>> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>> > ---
>> >  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
>> >  1 file changed, 29 insertions(+), 0 deletions(-)
>> >
>> > diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > index 894f874a5beb..1d13b6407222 100644
>> > --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > @@ -63,6 +63,17 @@
>> >                 stdout-path = "serial0:115200n8";
>> >         };
>> >
>> > +       connector {
>> > +               compatible = "hdmi-connector";
>> > +               type = "a";
>> 
>> Just curious, should we take this into consideration when creating
>> drm_connector?
> 
> I'm not sure, no other driver does so. And I haven't seen any of our
> boards with a HDMI-B, C or mini-HDMI connector.

Tablets usually come with micro-HDMI connector.

> 
> Maxime
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* Re: [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-11  6:25         ` icenowy-h8G6r0blFSE
  0 siblings, 0 replies; 125+ messages in thread
From: icenowy-h8G6r0blFSE @ 2017-05-11  6:25 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Chen-Yu Tsai, Mark Rutland, devicetree, David Airlie,
	Mike Turquette, Stephen Boyd, linux-kernel, dri-devel,
	linux-sunxi, Rob Herring, Daniel Vetter, linux-clk,
	linux-arm-kernel

在 2017-05-11 03:23,Maxime Ripard 写道:
> Hi,
> 
> On Thu, May 04, 2017 at 04:05:18PM +0800, Chen-Yu Tsai wrote:
>> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
>> <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
>> > The A10s Olinuxino has an HDMI connector. Make sure we can use it.
>> >
>> > Acked-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>
>> > Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
>> > ---
>> >  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
>> >  1 file changed, 29 insertions(+), 0 deletions(-)
>> >
>> > diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > index 894f874a5beb..1d13b6407222 100644
>> > --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > @@ -63,6 +63,17 @@
>> >                 stdout-path = "serial0:115200n8";
>> >         };
>> >
>> > +       connector {
>> > +               compatible = "hdmi-connector";
>> > +               type = "a";
>> 
>> Just curious, should we take this into consideration when creating
>> drm_connector?
> 
> I'm not sure, no other driver does so. And I haven't seen any of our
> boards with a HDMI-B, C or mini-HDMI connector.

Tablets usually come with micro-HDMI connector.

> 
> Maxime
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

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

* [linux-sunxi] [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI
@ 2017-05-11  6:25         ` icenowy-h8G6r0blFSE
  0 siblings, 0 replies; 125+ messages in thread
From: icenowy at aosc.io @ 2017-05-11  6:25 UTC (permalink / raw)
  To: linux-arm-kernel

? 2017-05-11 03:23?Maxime Ripard ???
> Hi,
> 
> On Thu, May 04, 2017 at 04:05:18PM +0800, Chen-Yu Tsai wrote:
>> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
>> <maxime.ripard@free-electrons.com> wrote:
>> > The A10s Olinuxino has an HDMI connector. Make sure we can use it.
>> >
>> > Acked-by: Chen-Yu Tsai <wens@csie.org>
>> > Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>> > ---
>> >  arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts | 29 +++++++++++++++++-
>> >  1 file changed, 29 insertions(+), 0 deletions(-)
>> >
>> > diff --git a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > index 894f874a5beb..1d13b6407222 100644
>> > --- a/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > +++ b/arch/arm/boot/dts/sun5i-a10s-olinuxino-micro.dts
>> > @@ -63,6 +63,17 @@
>> >                 stdout-path = "serial0:115200n8";
>> >         };
>> >
>> > +       connector {
>> > +               compatible = "hdmi-connector";
>> > +               type = "a";
>> 
>> Just curious, should we take this into consideration when creating
>> drm_connector?
> 
> I'm not sure, no other driver does so. And I haven't seen any of our
> boards with a HDMI-B, C or mini-HDMI connector.

Tablets usually come with micro-HDMI connector.

> 
> Maxime
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

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

* Re: [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-12  6:50       ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-12  6:50 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Chen-Yu Tsai, Mike Turquette, Stephen Boyd, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai <wens@csie.org> wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
>> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
>> controller.
>>
>> That HDMI controller is able to do audio and CEC, but those have been left
>> out for now.
>>
>> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>> ---
>>  drivers/gpu/drm/sun4i/Kconfig               |   9 +-
>>  drivers/gpu/drm/sun4i/Makefile              |   6 +-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
>>  6 files changed, 1017 insertions(+), 0 deletions(-)
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>>
>> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
>> index a4b357db8856..35299c4e2594 100644
>> --- a/drivers/gpu/drm/sun4i/Kconfig
>> +++ b/drivers/gpu/drm/sun4i/Kconfig
>> @@ -12,3 +12,12 @@ config DRM_SUN4I
>>           Choose this option if you have an Allwinner SoC with a
>>           Display Engine. If M is selected the module will be called
>>           sun4i-drm.
>> +
>> +if DRM_SUN4I
>> +config DRM_SUN4I_HDMI
>> +       tristate "Allwinner A10 HDMI Controller Support"
>> +       help
>> +         Choose this option if you have an Allwinner SoC with an HDMI
>> +         controller.
>> +
>> +endif
>> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
>> index 59b757350a1f..c09bf8093710 100644
>> --- a/drivers/gpu/drm/sun4i/Makefile
>> +++ b/drivers/gpu/drm/sun4i/Makefile
>> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)               += sun4i-drm.o sun4i-tcon.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun6i_drc.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
>> +
>> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
>> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
>> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
>> +
>> +obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
>> new file mode 100644
>> index 000000000000..40d57b195b48
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
>> @@ -0,0 +1,157 @@
>> +/*
>> + * Copyright (C) 2016 Maxime Ripard
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#ifndef _SUN4I_HDMI_H_
>> +#define _SUN4I_HDMI_H_
>> +
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_encoder.h>
>> +
>> +#define SUN4I_HDMI_CTRL_REG            0x004
>> +#define SUN4I_HDMI_CTRL_ENABLE                 BIT(31)
>> +
>> +#define SUN4I_HDMI_IRQ_REG             0x008
>> +#define SUN4I_HDMI_IRQ_STA_MASK                        0x73
>> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF             BIT(1)
>> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF             BIT(0)
>> +
>> +#define SUN4I_HDMI_HPD_REG             0x00c
>> +#define SUN4I_HDMI_HPD_HIGH                    BIT(0)
>> +
>> +#define SUN4I_HDMI_VID_CTRL_REG                0x010
>> +#define SUN4I_HDMI_VID_CTRL_ENABLE             BIT(31)
>> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE          BIT(30)
>> +
>> +#define SUN4I_HDMI_VID_TIMING_ACT_REG  0x014
>> +#define SUN4I_HDMI_VID_TIMING_BP_REG   0x018
>> +#define SUN4I_HDMI_VID_TIMING_FP_REG   0x01c
>> +#define SUN4I_HDMI_VID_TIMING_SPW_REG  0x020
>> +
>> +#define SUN4I_HDMI_VID_TIMING_X(x)             ((((x) - 1) & GENMASK(11, 0)))
>> +#define SUN4I_HDMI_VID_TIMING_Y(y)             ((((y) - 1) & GENMASK(11, 0)) << 16)
>> +
>> +#define SUN4I_HDMI_VID_TIMING_POL_REG  0x024
>> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
>> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC                BIT(1)
>> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC                BIT(0)
>> +
>> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)        (0x080 + (n))
>> +
>> +#define SUN4I_HDMI_PAD_CTRL0_REG       0x200
>> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN            BIT(31)
>> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN            BIT(30)
>> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN            BIT(29)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWENC             BIT(28)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWEND             BIT(27)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWENG             BIT(26)
>> +#define SUN4I_HDMI_PAD_CTRL0_CKEN              BIT(25)
>> +#define SUN4I_HDMI_PAD_CTRL0_TXEN              BIT(23)
>> +
>> +#define SUN4I_HDMI_PAD_CTRL1_REG       0x204
>> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT           BIT(23)
>> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT         BIT(22)
>> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT           BIT(20)
>> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT         BIT(19)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN           BIT(15)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK         BIT(14)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)                (((n) & 7) << 10)
>> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK         BIT(6)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)                (((n) & 7) << 3)
>> +
>> +#define SUN4I_HDMI_PLL_CTRL_REG                0x208
>> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN             BIT(31)
>> +#define SUN4I_HDMI_PLL_CTRL_BWS                        BIT(30)
>> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33           BIT(29)
>> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN            BIT(28)
>> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN            BIT(27)
>> +#define SUN4I_HDMI_PLL_CTRL_SDIV2              BIT(25)
>> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)                (((n) & 7) << 20)
>> +#define SUN4I_HDMI_PLL_CTRL_S(n)               (((n) & 7) << 16)

This field is 19:17, so it should be (((n) & 7) << 17)

>> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)

And this field is bits 16:12, so it should be (((n) & 0x1f) << 12)

>> +#define SUN4I_HDMI_PLL_CTRL_CS(n)              (((n) & 0xf) << 8)
>> +#define SUN4I_HDMI_PLL_CTRL_DIV(n)             (((n) & 0xf) << 4)
>> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK           GENMASK(7, 4)
>> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)           ((n) & 0xf)
>> +
>> +#define SUN4I_HDMI_PLL_DBG0_REG                0x20c
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)     (((n) & 1) << 21)
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK   BIT(21)
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT  21
>> +
>> +#define SUN4I_HDMI_PKT_CTRL_REG(n)     (0x2f0 + (4 * (n)))
>> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)         ((t) << (((n) % 4) * 4))
>> +
>> +#define SUN4I_HDMI_UNKNOWN_REG         0x300
>> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC          BIT(27)
>> +
>> +#define SUN4I_HDMI_DDC_CTRL_REG                0x500
>> +#define SUN4I_HDMI_DDC_CTRL_ENABLE             BIT(31)
>> +#define SUN4I_HDMI_DDC_CTRL_START_CMD          BIT(30)
>> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK      BIT(8)
>> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ      (0 << 8)
>> +#define SUN4I_HDMI_DDC_CTRL_RESET              BIT(0)
>> +
>> +#define SUN4I_HDMI_DDC_ADDR_REG                0x504
>> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)       (((seg) & 0xff) << 24)
>> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)         (((addr) & 0xff) << 16)
>> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)                (((off) & 0xff) << 8)
>> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)                ((addr) & 0xff)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG   0x510
>> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR         BIT(31)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG   0x518
>> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG  0x51c
>> +
>> +#define SUN4I_HDMI_DDC_CMD_REG         0x520
>> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ  6
>> +
>> +#define SUN4I_HDMI_DDC_CLK_REG         0x528
>> +#define SUN4I_HDMI_DDC_CLK_M(m)                        (((m) & 0x7) << 3)
>> +#define SUN4I_HDMI_DDC_CLK_N(n)                        ((n) & 0x7)
>> +
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG   0x540
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE    BIT(9)
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE    BIT(8)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_SIZE       16
>> +
>> +enum sun4i_hdmi_pkt_type {
>> +       SUN4I_HDMI_PKT_AVI = 2,
>> +       SUN4I_HDMI_PKT_END = 15,
>> +};
>> +
>> +struct sun4i_hdmi {
>> +       struct drm_connector    connector;
>> +       struct drm_encoder      encoder;
>> +       struct device           *dev;
>> +
>> +       void __iomem            *base;
>> +
>> +       /* Parent clocks */
>> +       struct clk              *bus_clk;
>> +       struct clk              *mod_clk;
>> +       struct clk              *pll0_clk;
>> +       struct clk              *pll1_clk;
>> +
>> +       /* And the clocks we create */
>> +       struct clk              *ddc_clk;
>> +       struct clk              *tmds_clk;
>> +
>> +       struct sun4i_drv        *drv;
>> +
>> +       bool                    hdmi_monitor;
>> +};
>> +
>> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
>> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
>> +
>> +#endif /* _SUN4I_HDMI_H_ */
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>> new file mode 100644
>> index 000000000000..4692e8c345ed
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>> @@ -0,0 +1,127 @@
>> +/*
>> + * Copyright (C) 2016 Free Electrons
>> + * Copyright (C) 2016 NextThing Co
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +
>> +#include "sun4i_tcon.h"
>> +#include "sun4i_hdmi.h"
>> +
>> +struct sun4i_ddc {
>> +       struct clk_hw           hw;
>> +       struct sun4i_hdmi       *hdmi;
>> +};
>> +
>> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
>> +{
>> +       return container_of(hw, struct sun4i_ddc, hw);
>> +}
>> +
>> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
>> +                                           unsigned long parent_rate,
>> +                                           u8 *m, u8 *n)
>> +{
>> +       unsigned long best_rate = 0;
>> +       u8 best_m = 0, best_n = 0, _m, _n;
>> +
>> +       for (_m = 0; _m < 8; _m++) {
>> +               for (_n = 0; _n < 8; _n++) {
>> +                       unsigned long tmp_rate;
>> +
>> +                       tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
>> +
>> +                       if (tmp_rate > rate)
>> +                               continue;
>> +
>> +                       if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
>> +                               best_rate = tmp_rate;
>> +                               best_m = _m;
>> +                               best_n = _n;
>> +                       }
>> +               }
>> +       }
>> +
>> +       if (m && n) {
>> +               *m = best_m;
>> +               *n = best_n;
>> +       }
>> +
>> +       return best_rate;
>> +}
>> +
>> +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
>> +                                unsigned long *prate)
>> +{
>> +       return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
>> +}
>> +
>> +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
>> +                                           unsigned long parent_rate)
>> +{
>> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
>> +       u32 reg;
>> +       u8 m, n;
>> +
>> +       reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
>> +       m = (reg >> 3) & 0x7;
>> +       n = reg & 0x7;
>> +
>> +       return (((parent_rate / 2) / 10) >> n) / (m + 1);
>> +}
>> +
>> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
>> +                             unsigned long parent_rate)
>> +{
>> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
>> +       u8 div_m, div_n;
>> +
>> +       sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
>> +
>> +       writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
>> +              ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct clk_ops sun4i_ddc_ops = {
>> +       .recalc_rate    = sun4i_ddc_recalc_rate,
>> +       .round_rate     = sun4i_ddc_round_rate,
>> +       .set_rate       = sun4i_ddc_set_rate,
>> +};
>> +
>> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
>> +{
>> +       struct clk_init_data init;
>> +       struct sun4i_ddc *ddc;
>> +       const char *parent_name;
>> +
>> +       parent_name = __clk_get_name(parent);
>> +       if (!parent_name)
>> +               return -ENODEV;
>> +
>> +       ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
>> +       if (!ddc)
>> +               return -ENOMEM;
>> +
>> +       init.name = "hdmi-ddc";
>> +       init.ops = &sun4i_ddc_ops;
>> +       init.parent_names = &parent_name;
>> +       init.num_parents = 1;
>> +
>> +       ddc->hdmi = hdmi;
>> +       ddc->hw.init = &init;
>> +
>> +       hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
>> +       if (IS_ERR(hdmi->ddc_clk))
>> +               return PTR_ERR(hdmi->ddc_clk);
>> +
>> +       return 0;
>> +}
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>> new file mode 100644
>> index 000000000000..9bec23164f53
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>> @@ -0,0 +1,493 @@
>> +/*
>> + * Copyright (C) 2016 Maxime Ripard
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_edid.h>
>> +#include <drm/drm_encoder.h>
>> +#include <drm/drm_of.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#include <linux/clk.h>
>> +#include <linux/component.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "sun4i_backend.h"
>> +#include "sun4i_drv.h"
>> +#include "sun4i_hdmi.h"
>> +#include "sun4i_tcon.h"
>> +
>> +#define DDC_SEGMENT_ADDR       0x30
>> +
>> +static inline struct sun4i_hdmi *
>> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
>> +{
>> +       return container_of(encoder, struct sun4i_hdmi,
>> +                           encoder);
>> +}
>> +
>> +static inline struct sun4i_hdmi *
>> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
>> +{
>> +       return container_of(connector, struct sun4i_hdmi,
>> +                           connector);
>> +}
>> +
>> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
>> +                                          struct drm_display_mode *mode)
>> +{
>> +       struct hdmi_avi_infoframe frame;
>> +       u8 buffer[17];
>> +       int i, ret;
>> +
>> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
>> +       if (ret < 0) {
>> +               DRM_ERROR("Failed to get infoframes from mode\n");
>> +               return ret;
>> +       }
>> +
>> +       ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
>> +       if (ret < 0) {
>> +               DRM_ERROR("Failed to pack infoframes\n");
>> +               return ret;
>> +       }
>> +
>> +       for (i = 0; i < sizeof(buffer); i++)
>> +               writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
>> +                                  struct drm_crtc_state *crtc_state,
>> +                                  struct drm_connector_state *conn_state)
>> +{
>> +       struct drm_display_mode *mode = &crtc_state->mode;
>> +
>> +       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
>> +               return -EINVAL;
>> +
>> +       return 0;
>> +}
>> +
>> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>
> I assume you didn't take into consideration the latest patches I sent
> that remove this field.
>
> Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.
>
>> +       u32 val;
>> +
>> +       DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
>> +
>> +       val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +       val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +
>> +       sun4i_tcon_channel_disable(tcon, 1);
>> +}
>> +
>> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
>> +{
>> +       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>> +       u32 val = 0;
>> +
>> +       DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
>> +
>> +       sun4i_tcon_channel_enable(tcon, 1);
>> +
>> +       sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
>> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
>> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
>> +       writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
>> +
>> +       val = SUN4I_HDMI_VID_CTRL_ENABLE;
>> +       if (hdmi->hdmi_monitor)
>> +               val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
>> +
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +}
>> +
>> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
>> +                               struct drm_display_mode *mode,
>> +                               struct drm_display_mode *adjusted_mode)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>> +       unsigned int x, y;
>> +       u32 val;
>> +
>> +       sun4i_tcon1_mode_set(tcon, mode);
>> +       sun4i_tcon_set_mux(tcon, 1, encoder);
>> +
>> +       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
>> +       clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
>> +       clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
>> +
>> +       /* Set input sync enable */
>> +       writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
>> +              hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
>> +
>> +       /* Setup timing registers */
>> +       writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
>> +              SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
>
> I can't figure out whether we should use mode->crtc_* or not...
>
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
>> +
>> +       x = mode->htotal - mode->hsync_start;
>> +       y = mode->vtotal - mode->vsync_start;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
>> +
>> +       x = mode->hsync_start - mode->hdisplay;
>> +       y = mode->vsync_start - mode->vdisplay;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
>> +
>> +       x = mode->hsync_end - mode->hsync_start;
>> +       y = mode->vsync_end - mode->vsync_start;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
>> +
>> +       val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
>> +       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
>> +               val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
>> +
>> +       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
>> +               val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
>> +
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
>> +}
>> +
>> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
>> +       .atomic_check   = sun4i_hdmi_atomic_check,
>> +       .disable        = sun4i_hdmi_disable,
>> +       .enable         = sun4i_hdmi_enable,
>> +       .mode_set       = sun4i_hdmi_mode_set,
>> +};
>> +
>> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
>> +       .destroy        = drm_encoder_cleanup,
>> +};
>> +
>> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
>> +                                    unsigned int blk, unsigned int offset,
>> +                                    u8 *buf, unsigned int count)
>> +{
>> +       unsigned long reg;
>> +       int i;
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
>> +       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +
>> +       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
>> +              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
>> +              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
>> +              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
>> +              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
>> +       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
>> +              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
>> +
>> +       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
>> +       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
>> +              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
>> +                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
>> +                              100, 100000))
>> +               return -EIO;
>> +
>> +       for (i = 0; i < count; i++)
>> +               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
>> +                                     size_t length)
>> +{
>> +       struct sun4i_hdmi *hdmi = data;
>> +       int retry = 2, i;
>> +
>> +       do {
>> +               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
>> +                       unsigned char offset = blk * EDID_LENGTH + i;
>> +                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
>> +                                                length - i);
>> +                       int ret;
>> +
>> +                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
>> +                                                       buf + i, count);
>> +                       if (ret)
>> +                               return ret;
>> +               }
>> +       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
>> +       unsigned long reg;
>> +       struct edid *edid;
>> +       int ret;
>> +
>> +       /* Reset i2c controller */
>> +       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
>> +                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
>> +                              100, 2000))
>> +               return -EIO;
>> +
>> +       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
>> +              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
>> +              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
>> +
>> +       clk_set_rate(hdmi->ddc_clk, 100000);
>
> You might want to explicitly enable the DDC clk around the following call.
>
>> +
>> +       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
>> +       if (!edid)
>> +               return 0;
>> +
>> +       hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
>> +       DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
>> +                        hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
>> +
>> +       drm_mode_connector_update_edid_property(connector, edid);
>> +       ret = drm_add_edid_modes(connector, edid);
>> +       kfree(edid);
>> +
>> +       return ret;
>> +}
>> +
>> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
>> +       .get_modes      = sun4i_hdmi_get_modes,
>> +};
>> +
>> +static enum drm_connector_status
>> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
>> +       unsigned long reg;
>> +
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
>> +                              reg & SUN4I_HDMI_HPD_HIGH,
>> +                              0, 500000))
>> +               return connector_status_disconnected;
>> +
>> +       return connector_status_connected;
>> +}
>> +
>> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
>> +       .dpms                   = drm_atomic_helper_connector_dpms,
>> +       .detect                 = sun4i_hdmi_connector_detect,
>> +       .fill_modes             = drm_helper_probe_single_connector_modes,
>> +       .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 sun4i_hdmi_bind(struct device *dev, struct device *master,
>> +                          void *data)
>> +{
>> +       struct platform_device *pdev = to_platform_device(dev);
>> +       struct drm_device *drm = data;
>> +       struct sun4i_drv *drv = drm->dev_private;
>> +       struct sun4i_hdmi *hdmi;
>> +       struct resource *res;
>> +       u32 reg;
>> +       int ret;
>> +
>> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
>> +       if (!hdmi)
>> +               return -ENOMEM;
>> +       dev_set_drvdata(dev, hdmi);
>> +       hdmi->dev = dev;
>> +       hdmi->drv = drv;
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       hdmi->base = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(hdmi->base)) {
>> +               dev_err(dev, "Couldn't map the HDMI encoder registers\n");
>> +               return PTR_ERR(hdmi->base);
>> +       }
>> +
>> +       hdmi->bus_clk = devm_clk_get(dev, "ahb");
>> +       if (IS_ERR(hdmi->bus_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI bus clock\n");
>> +               return PTR_ERR(hdmi->bus_clk);
>> +       }
>> +       clk_prepare_enable(hdmi->bus_clk);
>> +
>> +       hdmi->mod_clk = devm_clk_get(dev, "mod");
>> +       if (IS_ERR(hdmi->mod_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI mod clock\n");
>> +               return PTR_ERR(hdmi->mod_clk);
>> +       }
>> +       clk_prepare_enable(hdmi->mod_clk);
>> +
>> +       hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
>> +       if (IS_ERR(hdmi->pll0_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
>> +               return PTR_ERR(hdmi->pll0_clk);
>> +       }
>> +
>> +       hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
>> +       if (IS_ERR(hdmi->pll1_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
>> +               return PTR_ERR(hdmi->pll1_clk);
>> +       }
>> +
>> +       ret = sun4i_tmds_create(hdmi);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't create the TMDS clock\n");
>> +               return ret;
>> +       }
>> +
>> +       writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
>> +
>> +       writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
>> +              SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
>> +              SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
>> +              SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
>> +              hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
>> +
>> +       /*
>> +        * We can't just initialize the register there, we need to
>> +        * protect the clock bits that have already been read out and
>> +        * cached by the clock framework.
>> +        */
>> +       reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>
> Nit: just use the &= short hand.
>
>> +       reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_DEN |
>> +               SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
>> +       writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;
>
> Same here.
>
>> +       reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
>> +               SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |

And your values here might be off.

ChenYu

>> +               SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
>> +               SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
>> +               SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
>> +               SUN4I_HDMI_PLL_CTRL_PLL_EN;
>> +       writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +
>> +       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't create the DDC clock\n");
>> +               return ret;
>> +       }
>> +
>> +       drm_encoder_helper_add(&hdmi->encoder,
>> +                              &sun4i_hdmi_helper_funcs);
>> +       ret = drm_encoder_init(drm,
>> +                              &hdmi->encoder,
>> +                              &sun4i_hdmi_funcs,
>> +                              DRM_MODE_ENCODER_TMDS,
>> +                              NULL);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't initialise the HDMI encoder\n");
>> +               return ret;
>> +       }
>> +
>> +       hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
>> +                                                                 dev->of_node);
>> +       if (!hdmi->encoder.possible_crtcs)
>> +               return ret;
>
> You need a valid error return value here. Maybe -EPROBE_DEFER?
>
>> +
>> +       drm_connector_helper_add(&hdmi->connector,
>> +                                &sun4i_hdmi_connector_helper_funcs);
>
> /* There is no HPD interrupt, so we need to poll the controller */
> hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
>                          DRM_CONNECTOR_POLL_DISCONNECT;
>
>> +       ret = drm_connector_init(drm, &hdmi->connector,
>> +                                &sun4i_hdmi_connector_funcs,
>> +                                DRM_MODE_CONNECTOR_HDMIA);
>> +       if (ret) {
>> +               dev_err(dev,
>> +                       "Couldn't initialise the HDMI connector\n");
>> +               goto err_cleanup_connector;
>> +       }
>> +
>> +       drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
>> +
>> +       return 0;
>> +
>> +err_cleanup_connector:
>> +       drm_encoder_cleanup(&hdmi->encoder);
>> +       return ret;
>> +}
>> +
>> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
>> +                           void *data)
>> +{
>> +       struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
>> +
>> +       drm_connector_cleanup(&hdmi->connector);
>> +       drm_encoder_cleanup(&hdmi->encoder);
>> +}
>> +
>> +static const struct component_ops sun4i_hdmi_ops = {
>> +       .bind   = sun4i_hdmi_bind,
>> +       .unbind = sun4i_hdmi_unbind,
>> +};
>> +
>> +static int sun4i_hdmi_probe(struct platform_device *pdev)
>> +{
>> +       return component_add(&pdev->dev, &sun4i_hdmi_ops);
>> +}
>> +
>> +static int sun4i_hdmi_remove(struct platform_device *pdev)
>> +{
>> +       component_del(&pdev->dev, &sun4i_hdmi_ops);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id sun4i_hdmi_of_table[] = {
>> +       { .compatible = "allwinner,sun5i-a10s-hdmi" },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
>> +
>> +static struct platform_driver sun4i_hdmi_driver = {
>> +       .probe          = sun4i_hdmi_probe,
>> +       .remove         = sun4i_hdmi_remove,
>> +       .driver         = {
>> +               .name           = "sun4i-hdmi",
>> +               .of_match_table = sun4i_hdmi_of_table,
>> +       },
>> +};
>> +module_platform_driver(sun4i_hdmi_driver);
>> +
>> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
>> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>> new file mode 100644
>> index 000000000000..5cf2527bffc8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>> @@ -0,0 +1,225 @@
>> +/*
>> + * Copyright (C) 2016 Free Electrons
>> + * Copyright (C) 2016 NextThing Co
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +
>> +#include "sun4i_tcon.h"
>> +#include "sun4i_hdmi.h"
>> +
>> +struct sun4i_tmds {
>> +       struct clk_hw           hw;
>> +       struct sun4i_hdmi       *hdmi;
>> +};
>> +
>> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
>> +{
>> +       return container_of(hw, struct sun4i_tmds, hw);
>> +}
>> +
>> +
>> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
>> +                                            unsigned long parent_rate,
>> +                                            u8 *div,
>> +                                            bool *half)
>> +{
>> +       unsigned long best_rate = 0;
>> +       u8 best_m = 0, m;
>> +       bool is_double;
>> +
>> +       for (m = 1; m < 16; m++) {
>> +               u8 d;
>> +
>> +               for (d = 1; d < 3; d++) {
>> +                       unsigned long tmp_rate;
>> +
>> +                       tmp_rate = parent_rate / m / d;
>> +
>> +                       if (tmp_rate > rate)
>> +                               continue;
>> +
>> +                       if (!best_rate ||
>> +                           (rate - tmp_rate) < (rate - best_rate)) {
>> +                               best_rate = tmp_rate;
>> +                               best_m = m;
>> +                               is_double = d;
>> +                       }
>> +               }
>> +       }
>> +
>> +       if (div && half) {
>> +               *div = best_m;
>> +               *half = is_double;
>> +       }
>> +
>> +       return best_rate;
>> +}
>> +
>> +
>> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
>> +                                    struct clk_rate_request *req)
>> +{
>> +       struct clk_hw *parent;
>> +       unsigned long best_parent = 0;
>> +       unsigned long rate = req->rate;
>> +       int best_div = 1, best_half = 1;
>> +       int i, j;
>> +
>> +       /*
>> +        * We only consider PLL3, since the TCON is very likely to be
>> +        * clocked from it, and to have the same rate than our HDMI
>> +        * clock, so we should not need to do anything.
>> +        */
>> +
>> +       parent = clk_hw_get_parent_by_index(hw, 0);
>> +       if (!parent)
>> +               return -EINVAL;
>> +
>> +       for (i = 1; i < 3; i++) {
>> +               for (j = 1; j < 16; j++) {
>> +                       unsigned long ideal = rate * i * j;
>> +                       unsigned long rounded;
>> +
>> +                       rounded = clk_hw_round_rate(parent, ideal);
>> +
>> +                       if (rounded == ideal) {
>> +                               best_parent = rounded;
>> +                               best_half = i;
>> +                               best_div = j;
>> +                               goto out;
>> +                       }
>> +
>> +                       if (abs(rate - rounded / i) <
>> +                           abs(rate - best_parent / best_div)) {
>> +                               best_parent = rounded;
>> +                               best_div = i;
>> +                       }
>> +               }
>> +       }
>> +
>> +out:
>> +       req->rate = best_parent / best_half / best_div;
>> +       req->best_parent_rate = best_parent;
>> +       req->best_parent_hw = parent;
>> +
>> +       return 0;
>> +}
>> +
>> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
>> +                                           unsigned long parent_rate)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
>> +               parent_rate /= 2;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg = (reg >> 4) & 0xf;
>> +       if (!reg)
>> +               reg = 1;
>> +
>> +       return parent_rate / reg;
>> +}
>> +
>> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
>> +                              unsigned long parent_rate)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       bool half;
>> +       u32 reg;
>> +       u8 div;
>> +
>> +       sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>> +       if (half)
>> +               reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>> +       writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
>> +       writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
>> +              tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +       return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
>> +               SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
>> +}
>> +
>> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       if (index > 1)
>> +               return -EINVAL;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +       reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
>> +       writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
>> +              tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct clk_ops sun4i_tmds_ops = {
>> +       .determine_rate = sun4i_tmds_determine_rate,
>> +       .recalc_rate    = sun4i_tmds_recalc_rate,
>> +       .set_rate       = sun4i_tmds_set_rate,
>> +
>> +       .get_parent     = sun4i_tmds_get_parent,
>> +       .set_parent     = sun4i_tmds_set_parent,
>> +};
>> +
>> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
>> +{
>> +       struct clk_init_data init;
>> +       struct sun4i_tmds *tmds;
>> +       const char *parents[2];
>> +
>> +       parents[0] = __clk_get_name(hdmi->pll0_clk);
>> +       if (!parents[0])
>> +               return -ENODEV;
>> +
>> +       parents[1] = __clk_get_name(hdmi->pll1_clk);
>> +       if (!parents[1])
>> +               return -ENODEV;
>> +
>> +       tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
>> +       if (!tmds)
>> +               return -ENOMEM;
>> +
>> +       init.name = "hdmi-tmds";
>> +       init.ops = &sun4i_tmds_ops;
>> +       init.parent_names = parents;
>> +       init.num_parents = 2;
>> +       init.flags = CLK_SET_RATE_PARENT;
>> +
>> +       tmds->hdmi = hdmi;
>> +       tmds->hw.init = &init;
>> +
>> +       hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
>> +       if (IS_ERR(hdmi->tmds_clk))
>> +               return PTR_ERR(hdmi->tmds_clk);
>> +
>> +       return 0;
>> +}
>> --
>
> The rest looks good. This is coming together nicely.
>
> Reviewed-by: Chen-Yu Tsai <wens@csie.org>

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

* Re: [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-12  6:50       ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-12  6:50 UTC (permalink / raw)
  To: Maxime Ripard
  Cc: Chen-Yu Tsai, Mike Turquette, Stephen Boyd, Daniel Vetter,
	David Airlie, dri-devel, Mark Rutland, Rob Herring, devicetree,
	linux-arm-kernel, linux-clk, linux-kernel, linux-sunxi

On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org> wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org> wrote:
>> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
>> controller.
>>
>> That HDMI controller is able to do audio and CEC, but those have been left
>> out for now.
>>
>> Signed-off-by: Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
>> ---
>>  drivers/gpu/drm/sun4i/Kconfig               |   9 +-
>>  drivers/gpu/drm/sun4i/Makefile              |   6 +-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
>>  6 files changed, 1017 insertions(+), 0 deletions(-)
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>>
>> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
>> index a4b357db8856..35299c4e2594 100644
>> --- a/drivers/gpu/drm/sun4i/Kconfig
>> +++ b/drivers/gpu/drm/sun4i/Kconfig
>> @@ -12,3 +12,12 @@ config DRM_SUN4I
>>           Choose this option if you have an Allwinner SoC with a
>>           Display Engine. If M is selected the module will be called
>>           sun4i-drm.
>> +
>> +if DRM_SUN4I
>> +config DRM_SUN4I_HDMI
>> +       tristate "Allwinner A10 HDMI Controller Support"
>> +       help
>> +         Choose this option if you have an Allwinner SoC with an HDMI
>> +         controller.
>> +
>> +endif
>> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
>> index 59b757350a1f..c09bf8093710 100644
>> --- a/drivers/gpu/drm/sun4i/Makefile
>> +++ b/drivers/gpu/drm/sun4i/Makefile
>> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)               += sun4i-drm.o sun4i-tcon.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun6i_drc.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
>> +
>> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
>> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
>> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
>> +
>> +obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
>> new file mode 100644
>> index 000000000000..40d57b195b48
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
>> @@ -0,0 +1,157 @@
>> +/*
>> + * Copyright (C) 2016 Maxime Ripard
>> + *
>> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#ifndef _SUN4I_HDMI_H_
>> +#define _SUN4I_HDMI_H_
>> +
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_encoder.h>
>> +
>> +#define SUN4I_HDMI_CTRL_REG            0x004
>> +#define SUN4I_HDMI_CTRL_ENABLE                 BIT(31)
>> +
>> +#define SUN4I_HDMI_IRQ_REG             0x008
>> +#define SUN4I_HDMI_IRQ_STA_MASK                        0x73
>> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF             BIT(1)
>> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF             BIT(0)
>> +
>> +#define SUN4I_HDMI_HPD_REG             0x00c
>> +#define SUN4I_HDMI_HPD_HIGH                    BIT(0)
>> +
>> +#define SUN4I_HDMI_VID_CTRL_REG                0x010
>> +#define SUN4I_HDMI_VID_CTRL_ENABLE             BIT(31)
>> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE          BIT(30)
>> +
>> +#define SUN4I_HDMI_VID_TIMING_ACT_REG  0x014
>> +#define SUN4I_HDMI_VID_TIMING_BP_REG   0x018
>> +#define SUN4I_HDMI_VID_TIMING_FP_REG   0x01c
>> +#define SUN4I_HDMI_VID_TIMING_SPW_REG  0x020
>> +
>> +#define SUN4I_HDMI_VID_TIMING_X(x)             ((((x) - 1) & GENMASK(11, 0)))
>> +#define SUN4I_HDMI_VID_TIMING_Y(y)             ((((y) - 1) & GENMASK(11, 0)) << 16)
>> +
>> +#define SUN4I_HDMI_VID_TIMING_POL_REG  0x024
>> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
>> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC                BIT(1)
>> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC                BIT(0)
>> +
>> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)        (0x080 + (n))
>> +
>> +#define SUN4I_HDMI_PAD_CTRL0_REG       0x200
>> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN            BIT(31)
>> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN            BIT(30)
>> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN            BIT(29)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWENC             BIT(28)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWEND             BIT(27)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWENG             BIT(26)
>> +#define SUN4I_HDMI_PAD_CTRL0_CKEN              BIT(25)
>> +#define SUN4I_HDMI_PAD_CTRL0_TXEN              BIT(23)
>> +
>> +#define SUN4I_HDMI_PAD_CTRL1_REG       0x204
>> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT           BIT(23)
>> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT         BIT(22)
>> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT           BIT(20)
>> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT         BIT(19)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN           BIT(15)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK         BIT(14)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)                (((n) & 7) << 10)
>> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK         BIT(6)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)                (((n) & 7) << 3)
>> +
>> +#define SUN4I_HDMI_PLL_CTRL_REG                0x208
>> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN             BIT(31)
>> +#define SUN4I_HDMI_PLL_CTRL_BWS                        BIT(30)
>> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33           BIT(29)
>> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN            BIT(28)
>> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN            BIT(27)
>> +#define SUN4I_HDMI_PLL_CTRL_SDIV2              BIT(25)
>> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)                (((n) & 7) << 20)
>> +#define SUN4I_HDMI_PLL_CTRL_S(n)               (((n) & 7) << 16)

This field is 19:17, so it should be (((n) & 7) << 17)

>> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)

And this field is bits 16:12, so it should be (((n) & 0x1f) << 12)

>> +#define SUN4I_HDMI_PLL_CTRL_CS(n)              (((n) & 0xf) << 8)
>> +#define SUN4I_HDMI_PLL_CTRL_DIV(n)             (((n) & 0xf) << 4)
>> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK           GENMASK(7, 4)
>> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)           ((n) & 0xf)
>> +
>> +#define SUN4I_HDMI_PLL_DBG0_REG                0x20c
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)     (((n) & 1) << 21)
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK   BIT(21)
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT  21
>> +
>> +#define SUN4I_HDMI_PKT_CTRL_REG(n)     (0x2f0 + (4 * (n)))
>> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)         ((t) << (((n) % 4) * 4))
>> +
>> +#define SUN4I_HDMI_UNKNOWN_REG         0x300
>> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC          BIT(27)
>> +
>> +#define SUN4I_HDMI_DDC_CTRL_REG                0x500
>> +#define SUN4I_HDMI_DDC_CTRL_ENABLE             BIT(31)
>> +#define SUN4I_HDMI_DDC_CTRL_START_CMD          BIT(30)
>> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK      BIT(8)
>> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ      (0 << 8)
>> +#define SUN4I_HDMI_DDC_CTRL_RESET              BIT(0)
>> +
>> +#define SUN4I_HDMI_DDC_ADDR_REG                0x504
>> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)       (((seg) & 0xff) << 24)
>> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)         (((addr) & 0xff) << 16)
>> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)                (((off) & 0xff) << 8)
>> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)                ((addr) & 0xff)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG   0x510
>> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR         BIT(31)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG   0x518
>> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG  0x51c
>> +
>> +#define SUN4I_HDMI_DDC_CMD_REG         0x520
>> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ  6
>> +
>> +#define SUN4I_HDMI_DDC_CLK_REG         0x528
>> +#define SUN4I_HDMI_DDC_CLK_M(m)                        (((m) & 0x7) << 3)
>> +#define SUN4I_HDMI_DDC_CLK_N(n)                        ((n) & 0x7)
>> +
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG   0x540
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE    BIT(9)
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE    BIT(8)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_SIZE       16
>> +
>> +enum sun4i_hdmi_pkt_type {
>> +       SUN4I_HDMI_PKT_AVI = 2,
>> +       SUN4I_HDMI_PKT_END = 15,
>> +};
>> +
>> +struct sun4i_hdmi {
>> +       struct drm_connector    connector;
>> +       struct drm_encoder      encoder;
>> +       struct device           *dev;
>> +
>> +       void __iomem            *base;
>> +
>> +       /* Parent clocks */
>> +       struct clk              *bus_clk;
>> +       struct clk              *mod_clk;
>> +       struct clk              *pll0_clk;
>> +       struct clk              *pll1_clk;
>> +
>> +       /* And the clocks we create */
>> +       struct clk              *ddc_clk;
>> +       struct clk              *tmds_clk;
>> +
>> +       struct sun4i_drv        *drv;
>> +
>> +       bool                    hdmi_monitor;
>> +};
>> +
>> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
>> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
>> +
>> +#endif /* _SUN4I_HDMI_H_ */
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>> new file mode 100644
>> index 000000000000..4692e8c345ed
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>> @@ -0,0 +1,127 @@
>> +/*
>> + * Copyright (C) 2016 Free Electrons
>> + * Copyright (C) 2016 NextThing Co
>> + *
>> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +
>> +#include "sun4i_tcon.h"
>> +#include "sun4i_hdmi.h"
>> +
>> +struct sun4i_ddc {
>> +       struct clk_hw           hw;
>> +       struct sun4i_hdmi       *hdmi;
>> +};
>> +
>> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
>> +{
>> +       return container_of(hw, struct sun4i_ddc, hw);
>> +}
>> +
>> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
>> +                                           unsigned long parent_rate,
>> +                                           u8 *m, u8 *n)
>> +{
>> +       unsigned long best_rate = 0;
>> +       u8 best_m = 0, best_n = 0, _m, _n;
>> +
>> +       for (_m = 0; _m < 8; _m++) {
>> +               for (_n = 0; _n < 8; _n++) {
>> +                       unsigned long tmp_rate;
>> +
>> +                       tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
>> +
>> +                       if (tmp_rate > rate)
>> +                               continue;
>> +
>> +                       if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
>> +                               best_rate = tmp_rate;
>> +                               best_m = _m;
>> +                               best_n = _n;
>> +                       }
>> +               }
>> +       }
>> +
>> +       if (m && n) {
>> +               *m = best_m;
>> +               *n = best_n;
>> +       }
>> +
>> +       return best_rate;
>> +}
>> +
>> +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
>> +                                unsigned long *prate)
>> +{
>> +       return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
>> +}
>> +
>> +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
>> +                                           unsigned long parent_rate)
>> +{
>> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
>> +       u32 reg;
>> +       u8 m, n;
>> +
>> +       reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
>> +       m = (reg >> 3) & 0x7;
>> +       n = reg & 0x7;
>> +
>> +       return (((parent_rate / 2) / 10) >> n) / (m + 1);
>> +}
>> +
>> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
>> +                             unsigned long parent_rate)
>> +{
>> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
>> +       u8 div_m, div_n;
>> +
>> +       sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
>> +
>> +       writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
>> +              ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct clk_ops sun4i_ddc_ops = {
>> +       .recalc_rate    = sun4i_ddc_recalc_rate,
>> +       .round_rate     = sun4i_ddc_round_rate,
>> +       .set_rate       = sun4i_ddc_set_rate,
>> +};
>> +
>> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
>> +{
>> +       struct clk_init_data init;
>> +       struct sun4i_ddc *ddc;
>> +       const char *parent_name;
>> +
>> +       parent_name = __clk_get_name(parent);
>> +       if (!parent_name)
>> +               return -ENODEV;
>> +
>> +       ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
>> +       if (!ddc)
>> +               return -ENOMEM;
>> +
>> +       init.name = "hdmi-ddc";
>> +       init.ops = &sun4i_ddc_ops;
>> +       init.parent_names = &parent_name;
>> +       init.num_parents = 1;
>> +
>> +       ddc->hdmi = hdmi;
>> +       ddc->hw.init = &init;
>> +
>> +       hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
>> +       if (IS_ERR(hdmi->ddc_clk))
>> +               return PTR_ERR(hdmi->ddc_clk);
>> +
>> +       return 0;
>> +}
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>> new file mode 100644
>> index 000000000000..9bec23164f53
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>> @@ -0,0 +1,493 @@
>> +/*
>> + * Copyright (C) 2016 Maxime Ripard
>> + *
>> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_edid.h>
>> +#include <drm/drm_encoder.h>
>> +#include <drm/drm_of.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#include <linux/clk.h>
>> +#include <linux/component.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "sun4i_backend.h"
>> +#include "sun4i_drv.h"
>> +#include "sun4i_hdmi.h"
>> +#include "sun4i_tcon.h"
>> +
>> +#define DDC_SEGMENT_ADDR       0x30
>> +
>> +static inline struct sun4i_hdmi *
>> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
>> +{
>> +       return container_of(encoder, struct sun4i_hdmi,
>> +                           encoder);
>> +}
>> +
>> +static inline struct sun4i_hdmi *
>> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
>> +{
>> +       return container_of(connector, struct sun4i_hdmi,
>> +                           connector);
>> +}
>> +
>> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
>> +                                          struct drm_display_mode *mode)
>> +{
>> +       struct hdmi_avi_infoframe frame;
>> +       u8 buffer[17];
>> +       int i, ret;
>> +
>> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
>> +       if (ret < 0) {
>> +               DRM_ERROR("Failed to get infoframes from mode\n");
>> +               return ret;
>> +       }
>> +
>> +       ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
>> +       if (ret < 0) {
>> +               DRM_ERROR("Failed to pack infoframes\n");
>> +               return ret;
>> +       }
>> +
>> +       for (i = 0; i < sizeof(buffer); i++)
>> +               writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
>> +                                  struct drm_crtc_state *crtc_state,
>> +                                  struct drm_connector_state *conn_state)
>> +{
>> +       struct drm_display_mode *mode = &crtc_state->mode;
>> +
>> +       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
>> +               return -EINVAL;
>> +
>> +       return 0;
>> +}
>> +
>> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>
> I assume you didn't take into consideration the latest patches I sent
> that remove this field.
>
> Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.
>
>> +       u32 val;
>> +
>> +       DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
>> +
>> +       val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +       val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +
>> +       sun4i_tcon_channel_disable(tcon, 1);
>> +}
>> +
>> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
>> +{
>> +       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>> +       u32 val = 0;
>> +
>> +       DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
>> +
>> +       sun4i_tcon_channel_enable(tcon, 1);
>> +
>> +       sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
>> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
>> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
>> +       writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
>> +
>> +       val = SUN4I_HDMI_VID_CTRL_ENABLE;
>> +       if (hdmi->hdmi_monitor)
>> +               val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
>> +
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +}
>> +
>> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
>> +                               struct drm_display_mode *mode,
>> +                               struct drm_display_mode *adjusted_mode)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>> +       unsigned int x, y;
>> +       u32 val;
>> +
>> +       sun4i_tcon1_mode_set(tcon, mode);
>> +       sun4i_tcon_set_mux(tcon, 1, encoder);
>> +
>> +       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
>> +       clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
>> +       clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
>> +
>> +       /* Set input sync enable */
>> +       writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
>> +              hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
>> +
>> +       /* Setup timing registers */
>> +       writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
>> +              SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
>
> I can't figure out whether we should use mode->crtc_* or not...
>
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
>> +
>> +       x = mode->htotal - mode->hsync_start;
>> +       y = mode->vtotal - mode->vsync_start;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
>> +
>> +       x = mode->hsync_start - mode->hdisplay;
>> +       y = mode->vsync_start - mode->vdisplay;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
>> +
>> +       x = mode->hsync_end - mode->hsync_start;
>> +       y = mode->vsync_end - mode->vsync_start;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
>> +
>> +       val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
>> +       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
>> +               val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
>> +
>> +       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
>> +               val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
>> +
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
>> +}
>> +
>> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
>> +       .atomic_check   = sun4i_hdmi_atomic_check,
>> +       .disable        = sun4i_hdmi_disable,
>> +       .enable         = sun4i_hdmi_enable,
>> +       .mode_set       = sun4i_hdmi_mode_set,
>> +};
>> +
>> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
>> +       .destroy        = drm_encoder_cleanup,
>> +};
>> +
>> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
>> +                                    unsigned int blk, unsigned int offset,
>> +                                    u8 *buf, unsigned int count)
>> +{
>> +       unsigned long reg;
>> +       int i;
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
>> +       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +
>> +       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
>> +              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
>> +              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
>> +              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
>> +              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
>> +       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
>> +              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
>> +
>> +       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
>> +       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
>> +              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
>> +                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
>> +                              100, 100000))
>> +               return -EIO;
>> +
>> +       for (i = 0; i < count; i++)
>> +               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
>> +                                     size_t length)
>> +{
>> +       struct sun4i_hdmi *hdmi = data;
>> +       int retry = 2, i;
>> +
>> +       do {
>> +               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
>> +                       unsigned char offset = blk * EDID_LENGTH + i;
>> +                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
>> +                                                length - i);
>> +                       int ret;
>> +
>> +                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
>> +                                                       buf + i, count);
>> +                       if (ret)
>> +                               return ret;
>> +               }
>> +       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
>> +       unsigned long reg;
>> +       struct edid *edid;
>> +       int ret;
>> +
>> +       /* Reset i2c controller */
>> +       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
>> +                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
>> +                              100, 2000))
>> +               return -EIO;
>> +
>> +       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
>> +              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
>> +              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
>> +
>> +       clk_set_rate(hdmi->ddc_clk, 100000);
>
> You might want to explicitly enable the DDC clk around the following call.
>
>> +
>> +       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
>> +       if (!edid)
>> +               return 0;
>> +
>> +       hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
>> +       DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
>> +                        hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
>> +
>> +       drm_mode_connector_update_edid_property(connector, edid);
>> +       ret = drm_add_edid_modes(connector, edid);
>> +       kfree(edid);
>> +
>> +       return ret;
>> +}
>> +
>> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
>> +       .get_modes      = sun4i_hdmi_get_modes,
>> +};
>> +
>> +static enum drm_connector_status
>> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
>> +       unsigned long reg;
>> +
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
>> +                              reg & SUN4I_HDMI_HPD_HIGH,
>> +                              0, 500000))
>> +               return connector_status_disconnected;
>> +
>> +       return connector_status_connected;
>> +}
>> +
>> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
>> +       .dpms                   = drm_atomic_helper_connector_dpms,
>> +       .detect                 = sun4i_hdmi_connector_detect,
>> +       .fill_modes             = drm_helper_probe_single_connector_modes,
>> +       .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 sun4i_hdmi_bind(struct device *dev, struct device *master,
>> +                          void *data)
>> +{
>> +       struct platform_device *pdev = to_platform_device(dev);
>> +       struct drm_device *drm = data;
>> +       struct sun4i_drv *drv = drm->dev_private;
>> +       struct sun4i_hdmi *hdmi;
>> +       struct resource *res;
>> +       u32 reg;
>> +       int ret;
>> +
>> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
>> +       if (!hdmi)
>> +               return -ENOMEM;
>> +       dev_set_drvdata(dev, hdmi);
>> +       hdmi->dev = dev;
>> +       hdmi->drv = drv;
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       hdmi->base = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(hdmi->base)) {
>> +               dev_err(dev, "Couldn't map the HDMI encoder registers\n");
>> +               return PTR_ERR(hdmi->base);
>> +       }
>> +
>> +       hdmi->bus_clk = devm_clk_get(dev, "ahb");
>> +       if (IS_ERR(hdmi->bus_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI bus clock\n");
>> +               return PTR_ERR(hdmi->bus_clk);
>> +       }
>> +       clk_prepare_enable(hdmi->bus_clk);
>> +
>> +       hdmi->mod_clk = devm_clk_get(dev, "mod");
>> +       if (IS_ERR(hdmi->mod_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI mod clock\n");
>> +               return PTR_ERR(hdmi->mod_clk);
>> +       }
>> +       clk_prepare_enable(hdmi->mod_clk);
>> +
>> +       hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
>> +       if (IS_ERR(hdmi->pll0_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
>> +               return PTR_ERR(hdmi->pll0_clk);
>> +       }
>> +
>> +       hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
>> +       if (IS_ERR(hdmi->pll1_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
>> +               return PTR_ERR(hdmi->pll1_clk);
>> +       }
>> +
>> +       ret = sun4i_tmds_create(hdmi);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't create the TMDS clock\n");
>> +               return ret;
>> +       }
>> +
>> +       writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
>> +
>> +       writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
>> +              SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
>> +              SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
>> +              SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
>> +              hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
>> +
>> +       /*
>> +        * We can't just initialize the register there, we need to
>> +        * protect the clock bits that have already been read out and
>> +        * cached by the clock framework.
>> +        */
>> +       reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>
> Nit: just use the &= short hand.
>
>> +       reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_DEN |
>> +               SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
>> +       writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;
>
> Same here.
>
>> +       reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
>> +               SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |

And your values here might be off.

ChenYu

>> +               SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
>> +               SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
>> +               SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
>> +               SUN4I_HDMI_PLL_CTRL_PLL_EN;
>> +       writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +
>> +       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't create the DDC clock\n");
>> +               return ret;
>> +       }
>> +
>> +       drm_encoder_helper_add(&hdmi->encoder,
>> +                              &sun4i_hdmi_helper_funcs);
>> +       ret = drm_encoder_init(drm,
>> +                              &hdmi->encoder,
>> +                              &sun4i_hdmi_funcs,
>> +                              DRM_MODE_ENCODER_TMDS,
>> +                              NULL);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't initialise the HDMI encoder\n");
>> +               return ret;
>> +       }
>> +
>> +       hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
>> +                                                                 dev->of_node);
>> +       if (!hdmi->encoder.possible_crtcs)
>> +               return ret;
>
> You need a valid error return value here. Maybe -EPROBE_DEFER?
>
>> +
>> +       drm_connector_helper_add(&hdmi->connector,
>> +                                &sun4i_hdmi_connector_helper_funcs);
>
> /* There is no HPD interrupt, so we need to poll the controller */
> hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
>                          DRM_CONNECTOR_POLL_DISCONNECT;
>
>> +       ret = drm_connector_init(drm, &hdmi->connector,
>> +                                &sun4i_hdmi_connector_funcs,
>> +                                DRM_MODE_CONNECTOR_HDMIA);
>> +       if (ret) {
>> +               dev_err(dev,
>> +                       "Couldn't initialise the HDMI connector\n");
>> +               goto err_cleanup_connector;
>> +       }
>> +
>> +       drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
>> +
>> +       return 0;
>> +
>> +err_cleanup_connector:
>> +       drm_encoder_cleanup(&hdmi->encoder);
>> +       return ret;
>> +}
>> +
>> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
>> +                           void *data)
>> +{
>> +       struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
>> +
>> +       drm_connector_cleanup(&hdmi->connector);
>> +       drm_encoder_cleanup(&hdmi->encoder);
>> +}
>> +
>> +static const struct component_ops sun4i_hdmi_ops = {
>> +       .bind   = sun4i_hdmi_bind,
>> +       .unbind = sun4i_hdmi_unbind,
>> +};
>> +
>> +static int sun4i_hdmi_probe(struct platform_device *pdev)
>> +{
>> +       return component_add(&pdev->dev, &sun4i_hdmi_ops);
>> +}
>> +
>> +static int sun4i_hdmi_remove(struct platform_device *pdev)
>> +{
>> +       component_del(&pdev->dev, &sun4i_hdmi_ops);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id sun4i_hdmi_of_table[] = {
>> +       { .compatible = "allwinner,sun5i-a10s-hdmi" },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
>> +
>> +static struct platform_driver sun4i_hdmi_driver = {
>> +       .probe          = sun4i_hdmi_probe,
>> +       .remove         = sun4i_hdmi_remove,
>> +       .driver         = {
>> +               .name           = "sun4i-hdmi",
>> +               .of_match_table = sun4i_hdmi_of_table,
>> +       },
>> +};
>> +module_platform_driver(sun4i_hdmi_driver);
>> +
>> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>");
>> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>> new file mode 100644
>> index 000000000000..5cf2527bffc8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>> @@ -0,0 +1,225 @@
>> +/*
>> + * Copyright (C) 2016 Free Electrons
>> + * Copyright (C) 2016 NextThing Co
>> + *
>> + * Maxime Ripard <maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +
>> +#include "sun4i_tcon.h"
>> +#include "sun4i_hdmi.h"
>> +
>> +struct sun4i_tmds {
>> +       struct clk_hw           hw;
>> +       struct sun4i_hdmi       *hdmi;
>> +};
>> +
>> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
>> +{
>> +       return container_of(hw, struct sun4i_tmds, hw);
>> +}
>> +
>> +
>> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
>> +                                            unsigned long parent_rate,
>> +                                            u8 *div,
>> +                                            bool *half)
>> +{
>> +       unsigned long best_rate = 0;
>> +       u8 best_m = 0, m;
>> +       bool is_double;
>> +
>> +       for (m = 1; m < 16; m++) {
>> +               u8 d;
>> +
>> +               for (d = 1; d < 3; d++) {
>> +                       unsigned long tmp_rate;
>> +
>> +                       tmp_rate = parent_rate / m / d;
>> +
>> +                       if (tmp_rate > rate)
>> +                               continue;
>> +
>> +                       if (!best_rate ||
>> +                           (rate - tmp_rate) < (rate - best_rate)) {
>> +                               best_rate = tmp_rate;
>> +                               best_m = m;
>> +                               is_double = d;
>> +                       }
>> +               }
>> +       }
>> +
>> +       if (div && half) {
>> +               *div = best_m;
>> +               *half = is_double;
>> +       }
>> +
>> +       return best_rate;
>> +}
>> +
>> +
>> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
>> +                                    struct clk_rate_request *req)
>> +{
>> +       struct clk_hw *parent;
>> +       unsigned long best_parent = 0;
>> +       unsigned long rate = req->rate;
>> +       int best_div = 1, best_half = 1;
>> +       int i, j;
>> +
>> +       /*
>> +        * We only consider PLL3, since the TCON is very likely to be
>> +        * clocked from it, and to have the same rate than our HDMI
>> +        * clock, so we should not need to do anything.
>> +        */
>> +
>> +       parent = clk_hw_get_parent_by_index(hw, 0);
>> +       if (!parent)
>> +               return -EINVAL;
>> +
>> +       for (i = 1; i < 3; i++) {
>> +               for (j = 1; j < 16; j++) {
>> +                       unsigned long ideal = rate * i * j;
>> +                       unsigned long rounded;
>> +
>> +                       rounded = clk_hw_round_rate(parent, ideal);
>> +
>> +                       if (rounded == ideal) {
>> +                               best_parent = rounded;
>> +                               best_half = i;
>> +                               best_div = j;
>> +                               goto out;
>> +                       }
>> +
>> +                       if (abs(rate - rounded / i) <
>> +                           abs(rate - best_parent / best_div)) {
>> +                               best_parent = rounded;
>> +                               best_div = i;
>> +                       }
>> +               }
>> +       }
>> +
>> +out:
>> +       req->rate = best_parent / best_half / best_div;
>> +       req->best_parent_rate = best_parent;
>> +       req->best_parent_hw = parent;
>> +
>> +       return 0;
>> +}
>> +
>> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
>> +                                           unsigned long parent_rate)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
>> +               parent_rate /= 2;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg = (reg >> 4) & 0xf;
>> +       if (!reg)
>> +               reg = 1;
>> +
>> +       return parent_rate / reg;
>> +}
>> +
>> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
>> +                              unsigned long parent_rate)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       bool half;
>> +       u32 reg;
>> +       u8 div;
>> +
>> +       sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>> +       if (half)
>> +               reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>> +       writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
>> +       writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
>> +              tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +       return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
>> +               SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
>> +}
>> +
>> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       if (index > 1)
>> +               return -EINVAL;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +       reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
>> +       writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
>> +              tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct clk_ops sun4i_tmds_ops = {
>> +       .determine_rate = sun4i_tmds_determine_rate,
>> +       .recalc_rate    = sun4i_tmds_recalc_rate,
>> +       .set_rate       = sun4i_tmds_set_rate,
>> +
>> +       .get_parent     = sun4i_tmds_get_parent,
>> +       .set_parent     = sun4i_tmds_set_parent,
>> +};
>> +
>> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
>> +{
>> +       struct clk_init_data init;
>> +       struct sun4i_tmds *tmds;
>> +       const char *parents[2];
>> +
>> +       parents[0] = __clk_get_name(hdmi->pll0_clk);
>> +       if (!parents[0])
>> +               return -ENODEV;
>> +
>> +       parents[1] = __clk_get_name(hdmi->pll1_clk);
>> +       if (!parents[1])
>> +               return -ENODEV;
>> +
>> +       tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
>> +       if (!tmds)
>> +               return -ENOMEM;
>> +
>> +       init.name = "hdmi-tmds";
>> +       init.ops = &sun4i_tmds_ops;
>> +       init.parent_names = parents;
>> +       init.num_parents = 2;
>> +       init.flags = CLK_SET_RATE_PARENT;
>> +
>> +       tmds->hdmi = hdmi;
>> +       tmds->hw.init = &init;
>> +
>> +       hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
>> +       if (IS_ERR(hdmi->tmds_clk))
>> +               return PTR_ERR(hdmi->tmds_clk);
>> +
>> +       return 0;
>> +}
>> --
>
> The rest looks good. This is coming together nicely.
>
> Reviewed-by: Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org>

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

* [linux-sunxi] [PATCH v2 18/20] drm/sun4i: Add HDMI support
@ 2017-05-12  6:50       ` Chen-Yu Tsai
  0 siblings, 0 replies; 125+ messages in thread
From: Chen-Yu Tsai @ 2017-05-12  6:50 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, May 4, 2017 at 3:55 PM, Chen-Yu Tsai <wens@csie.org> wrote:
> On Wed, May 3, 2017 at 7:59 PM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
>> The earlier Allwinner SoCs (A10, A10s, A20, A31) have an embedded HDMI
>> controller.
>>
>> That HDMI controller is able to do audio and CEC, but those have been left
>> out for now.
>>
>> Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>> ---
>>  drivers/gpu/drm/sun4i/Kconfig               |   9 +-
>>  drivers/gpu/drm/sun4i/Makefile              |   6 +-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi.h          | 157 +++++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c  | 127 +++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c      | 493 +++++++++++++++++++++-
>>  drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 225 ++++++++++-
>>  6 files changed, 1017 insertions(+), 0 deletions(-)
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi.h
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>>  create mode 100644 drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>>
>> diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig
>> index a4b357db8856..35299c4e2594 100644
>> --- a/drivers/gpu/drm/sun4i/Kconfig
>> +++ b/drivers/gpu/drm/sun4i/Kconfig
>> @@ -12,3 +12,12 @@ config DRM_SUN4I
>>           Choose this option if you have an Allwinner SoC with a
>>           Display Engine. If M is selected the module will be called
>>           sun4i-drm.
>> +
>> +if DRM_SUN4I
>> +config DRM_SUN4I_HDMI
>> +       tristate "Allwinner A10 HDMI Controller Support"
>> +       help
>> +         Choose this option if you have an Allwinner SoC with an HDMI
>> +         controller.
>> +
>> +endif
>> diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile
>> index 59b757350a1f..c09bf8093710 100644
>> --- a/drivers/gpu/drm/sun4i/Makefile
>> +++ b/drivers/gpu/drm/sun4i/Makefile
>> @@ -11,3 +11,9 @@ obj-$(CONFIG_DRM_SUN4I)               += sun4i-drm.o sun4i-tcon.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_backend.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun6i_drc.o
>>  obj-$(CONFIG_DRM_SUN4I)                += sun4i_tv.o
>> +
>> +sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
>> +sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
>> +sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
>> +
>> +obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
>> new file mode 100644
>> index 000000000000..40d57b195b48
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
>> @@ -0,0 +1,157 @@
>> +/*
>> + * Copyright (C) 2016 Maxime Ripard
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#ifndef _SUN4I_HDMI_H_
>> +#define _SUN4I_HDMI_H_
>> +
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_encoder.h>
>> +
>> +#define SUN4I_HDMI_CTRL_REG            0x004
>> +#define SUN4I_HDMI_CTRL_ENABLE                 BIT(31)
>> +
>> +#define SUN4I_HDMI_IRQ_REG             0x008
>> +#define SUN4I_HDMI_IRQ_STA_MASK                        0x73
>> +#define SUN4I_HDMI_IRQ_STA_FIFO_OF             BIT(1)
>> +#define SUN4I_HDMI_IRQ_STA_FIFO_UF             BIT(0)
>> +
>> +#define SUN4I_HDMI_HPD_REG             0x00c
>> +#define SUN4I_HDMI_HPD_HIGH                    BIT(0)
>> +
>> +#define SUN4I_HDMI_VID_CTRL_REG                0x010
>> +#define SUN4I_HDMI_VID_CTRL_ENABLE             BIT(31)
>> +#define SUN4I_HDMI_VID_CTRL_HDMI_MODE          BIT(30)
>> +
>> +#define SUN4I_HDMI_VID_TIMING_ACT_REG  0x014
>> +#define SUN4I_HDMI_VID_TIMING_BP_REG   0x018
>> +#define SUN4I_HDMI_VID_TIMING_FP_REG   0x01c
>> +#define SUN4I_HDMI_VID_TIMING_SPW_REG  0x020
>> +
>> +#define SUN4I_HDMI_VID_TIMING_X(x)             ((((x) - 1) & GENMASK(11, 0)))
>> +#define SUN4I_HDMI_VID_TIMING_Y(y)             ((((y) - 1) & GENMASK(11, 0)) << 16)
>> +
>> +#define SUN4I_HDMI_VID_TIMING_POL_REG  0x024
>> +#define SUN4I_HDMI_VID_TIMING_POL_TX_CLK        (0x3e0 << 16)
>> +#define SUN4I_HDMI_VID_TIMING_POL_VSYNC                BIT(1)
>> +#define SUN4I_HDMI_VID_TIMING_POL_HSYNC                BIT(0)
>> +
>> +#define SUN4I_HDMI_AVI_INFOFRAME_REG(n)        (0x080 + (n))
>> +
>> +#define SUN4I_HDMI_PAD_CTRL0_REG       0x200
>> +#define SUN4I_HDMI_PAD_CTRL0_BIASEN            BIT(31)
>> +#define SUN4I_HDMI_PAD_CTRL0_LDOCEN            BIT(30)
>> +#define SUN4I_HDMI_PAD_CTRL0_LDODEN            BIT(29)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWENC             BIT(28)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWEND             BIT(27)
>> +#define SUN4I_HDMI_PAD_CTRL0_PWENG             BIT(26)
>> +#define SUN4I_HDMI_PAD_CTRL0_CKEN              BIT(25)
>> +#define SUN4I_HDMI_PAD_CTRL0_TXEN              BIT(23)
>> +
>> +#define SUN4I_HDMI_PAD_CTRL1_REG       0x204
>> +#define SUN4I_HDMI_PAD_CTRL1_AMP_OPT           BIT(23)
>> +#define SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT         BIT(22)
>> +#define SUN4I_HDMI_PAD_CTRL1_EMP_OPT           BIT(20)
>> +#define SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT         BIT(19)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_DEN           BIT(15)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_DENCK         BIT(14)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_EMP(n)                (((n) & 7) << 10)
>> +#define SUN4I_HDMI_PAD_CTRL1_HALVE_CLK         BIT(6)
>> +#define SUN4I_HDMI_PAD_CTRL1_REG_AMP(n)                (((n) & 7) << 3)
>> +
>> +#define SUN4I_HDMI_PLL_CTRL_REG                0x208
>> +#define SUN4I_HDMI_PLL_CTRL_PLL_EN             BIT(31)
>> +#define SUN4I_HDMI_PLL_CTRL_BWS                        BIT(30)
>> +#define SUN4I_HDMI_PLL_CTRL_HV_IS_33           BIT(29)
>> +#define SUN4I_HDMI_PLL_CTRL_LDO1_EN            BIT(28)
>> +#define SUN4I_HDMI_PLL_CTRL_LDO2_EN            BIT(27)
>> +#define SUN4I_HDMI_PLL_CTRL_SDIV2              BIT(25)
>> +#define SUN4I_HDMI_PLL_CTRL_VCO_GAIN(n)                (((n) & 7) << 20)
>> +#define SUN4I_HDMI_PLL_CTRL_S(n)               (((n) & 7) << 16)

This field is 19:17, so it should be (((n) & 7) << 17)

>> +#define SUN4I_HDMI_PLL_CTRL_CP_S(n)            (((n) & 0xf) << 12)

And this field is bits 16:12, so it should be (((n) & 0x1f) << 12)

>> +#define SUN4I_HDMI_PLL_CTRL_CS(n)              (((n) & 0xf) << 8)
>> +#define SUN4I_HDMI_PLL_CTRL_DIV(n)             (((n) & 0xf) << 4)
>> +#define SUN4I_HDMI_PLL_CTRL_DIV_MASK           GENMASK(7, 4)
>> +#define SUN4I_HDMI_PLL_CTRL_VCO_S(n)           ((n) & 0xf)
>> +
>> +#define SUN4I_HDMI_PLL_DBG0_REG                0x20c
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(n)     (((n) & 1) << 21)
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK   BIT(21)
>> +#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT  21
>> +
>> +#define SUN4I_HDMI_PKT_CTRL_REG(n)     (0x2f0 + (4 * (n)))
>> +#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t)         ((t) << (((n) % 4) * 4))
>> +
>> +#define SUN4I_HDMI_UNKNOWN_REG         0x300
>> +#define SUN4I_HDMI_UNKNOWN_INPUT_SYNC          BIT(27)
>> +
>> +#define SUN4I_HDMI_DDC_CTRL_REG                0x500
>> +#define SUN4I_HDMI_DDC_CTRL_ENABLE             BIT(31)
>> +#define SUN4I_HDMI_DDC_CTRL_START_CMD          BIT(30)
>> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK      BIT(8)
>> +#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ      (0 << 8)
>> +#define SUN4I_HDMI_DDC_CTRL_RESET              BIT(0)
>> +
>> +#define SUN4I_HDMI_DDC_ADDR_REG                0x504
>> +#define SUN4I_HDMI_DDC_ADDR_SEGMENT(seg)       (((seg) & 0xff) << 24)
>> +#define SUN4I_HDMI_DDC_ADDR_EDDC(addr)         (((addr) & 0xff) << 16)
>> +#define SUN4I_HDMI_DDC_ADDR_OFFSET(off)                (((off) & 0xff) << 8)
>> +#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr)                ((addr) & 0xff)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_CTRL_REG   0x510
>> +#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR         BIT(31)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_DATA_REG   0x518
>> +#define SUN4I_HDMI_DDC_BYTE_COUNT_REG  0x51c
>> +
>> +#define SUN4I_HDMI_DDC_CMD_REG         0x520
>> +#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ  6
>> +
>> +#define SUN4I_HDMI_DDC_CLK_REG         0x528
>> +#define SUN4I_HDMI_DDC_CLK_M(m)                        (((m) & 0x7) << 3)
>> +#define SUN4I_HDMI_DDC_CLK_N(n)                        ((n) & 0x7)
>> +
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_REG   0x540
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE    BIT(9)
>> +#define SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE    BIT(8)
>> +
>> +#define SUN4I_HDMI_DDC_FIFO_SIZE       16
>> +
>> +enum sun4i_hdmi_pkt_type {
>> +       SUN4I_HDMI_PKT_AVI = 2,
>> +       SUN4I_HDMI_PKT_END = 15,
>> +};
>> +
>> +struct sun4i_hdmi {
>> +       struct drm_connector    connector;
>> +       struct drm_encoder      encoder;
>> +       struct device           *dev;
>> +
>> +       void __iomem            *base;
>> +
>> +       /* Parent clocks */
>> +       struct clk              *bus_clk;
>> +       struct clk              *mod_clk;
>> +       struct clk              *pll0_clk;
>> +       struct clk              *pll1_clk;
>> +
>> +       /* And the clocks we create */
>> +       struct clk              *ddc_clk;
>> +       struct clk              *tmds_clk;
>> +
>> +       struct sun4i_drv        *drv;
>> +
>> +       bool                    hdmi_monitor;
>> +};
>> +
>> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
>> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
>> +
>> +#endif /* _SUN4I_HDMI_H_ */
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>> new file mode 100644
>> index 000000000000..4692e8c345ed
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
>> @@ -0,0 +1,127 @@
>> +/*
>> + * Copyright (C) 2016 Free Electrons
>> + * Copyright (C) 2016 NextThing Co
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +
>> +#include "sun4i_tcon.h"
>> +#include "sun4i_hdmi.h"
>> +
>> +struct sun4i_ddc {
>> +       struct clk_hw           hw;
>> +       struct sun4i_hdmi       *hdmi;
>> +};
>> +
>> +static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
>> +{
>> +       return container_of(hw, struct sun4i_ddc, hw);
>> +}
>> +
>> +static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
>> +                                           unsigned long parent_rate,
>> +                                           u8 *m, u8 *n)
>> +{
>> +       unsigned long best_rate = 0;
>> +       u8 best_m = 0, best_n = 0, _m, _n;
>> +
>> +       for (_m = 0; _m < 8; _m++) {
>> +               for (_n = 0; _n < 8; _n++) {
>> +                       unsigned long tmp_rate;
>> +
>> +                       tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
>> +
>> +                       if (tmp_rate > rate)
>> +                               continue;
>> +
>> +                       if (abs(rate - tmp_rate) < abs(rate - best_rate)) {
>> +                               best_rate = tmp_rate;
>> +                               best_m = _m;
>> +                               best_n = _n;
>> +                       }
>> +               }
>> +       }
>> +
>> +       if (m && n) {
>> +               *m = best_m;
>> +               *n = best_n;
>> +       }
>> +
>> +       return best_rate;
>> +}
>> +
>> +static long sun4i_ddc_round_rate(struct clk_hw *hw, unsigned long rate,
>> +                                unsigned long *prate)
>> +{
>> +       return sun4i_ddc_calc_divider(rate, *prate, NULL, NULL);
>> +}
>> +
>> +static unsigned long sun4i_ddc_recalc_rate(struct clk_hw *hw,
>> +                                           unsigned long parent_rate)
>> +{
>> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
>> +       u32 reg;
>> +       u8 m, n;
>> +
>> +       reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
>> +       m = (reg >> 3) & 0x7;
>> +       n = reg & 0x7;
>> +
>> +       return (((parent_rate / 2) / 10) >> n) / (m + 1);
>> +}
>> +
>> +static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
>> +                             unsigned long parent_rate)
>> +{
>> +       struct sun4i_ddc *ddc = hw_to_ddc(hw);
>> +       u8 div_m, div_n;
>> +
>> +       sun4i_ddc_calc_divider(rate, parent_rate, &div_m, &div_n);
>> +
>> +       writel(SUN4I_HDMI_DDC_CLK_M(div_m) | SUN4I_HDMI_DDC_CLK_N(div_n),
>> +              ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct clk_ops sun4i_ddc_ops = {
>> +       .recalc_rate    = sun4i_ddc_recalc_rate,
>> +       .round_rate     = sun4i_ddc_round_rate,
>> +       .set_rate       = sun4i_ddc_set_rate,
>> +};
>> +
>> +int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
>> +{
>> +       struct clk_init_data init;
>> +       struct sun4i_ddc *ddc;
>> +       const char *parent_name;
>> +
>> +       parent_name = __clk_get_name(parent);
>> +       if (!parent_name)
>> +               return -ENODEV;
>> +
>> +       ddc = devm_kzalloc(hdmi->dev, sizeof(*ddc), GFP_KERNEL);
>> +       if (!ddc)
>> +               return -ENOMEM;
>> +
>> +       init.name = "hdmi-ddc";
>> +       init.ops = &sun4i_ddc_ops;
>> +       init.parent_names = &parent_name;
>> +       init.num_parents = 1;
>> +
>> +       ddc->hdmi = hdmi;
>> +       ddc->hw.init = &init;
>> +
>> +       hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
>> +       if (IS_ERR(hdmi->ddc_clk))
>> +               return PTR_ERR(hdmi->ddc_clk);
>> +
>> +       return 0;
>> +}
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>> new file mode 100644
>> index 000000000000..9bec23164f53
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
>> @@ -0,0 +1,493 @@
>> +/*
>> + * Copyright (C) 2016 Maxime Ripard
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <drm/drmP.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_crtc_helper.h>
>> +#include <drm/drm_edid.h>
>> +#include <drm/drm_encoder.h>
>> +#include <drm/drm_of.h>
>> +#include <drm/drm_panel.h>
>> +
>> +#include <linux/clk.h>
>> +#include <linux/component.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "sun4i_backend.h"
>> +#include "sun4i_drv.h"
>> +#include "sun4i_hdmi.h"
>> +#include "sun4i_tcon.h"
>> +
>> +#define DDC_SEGMENT_ADDR       0x30
>> +
>> +static inline struct sun4i_hdmi *
>> +drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
>> +{
>> +       return container_of(encoder, struct sun4i_hdmi,
>> +                           encoder);
>> +}
>> +
>> +static inline struct sun4i_hdmi *
>> +drm_connector_to_sun4i_hdmi(struct drm_connector *connector)
>> +{
>> +       return container_of(connector, struct sun4i_hdmi,
>> +                           connector);
>> +}
>> +
>> +static int sun4i_hdmi_setup_avi_infoframes(struct sun4i_hdmi *hdmi,
>> +                                          struct drm_display_mode *mode)
>> +{
>> +       struct hdmi_avi_infoframe frame;
>> +       u8 buffer[17];
>> +       int i, ret;
>> +
>> +       ret = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
>> +       if (ret < 0) {
>> +               DRM_ERROR("Failed to get infoframes from mode\n");
>> +               return ret;
>> +       }
>> +
>> +       ret = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer));
>> +       if (ret < 0) {
>> +               DRM_ERROR("Failed to pack infoframes\n");
>> +               return ret;
>> +       }
>> +
>> +       for (i = 0; i < sizeof(buffer); i++)
>> +               writeb(buffer[i], hdmi->base + SUN4I_HDMI_AVI_INFOFRAME_REG(i));
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_atomic_check(struct drm_encoder *encoder,
>> +                                  struct drm_crtc_state *crtc_state,
>> +                                  struct drm_connector_state *conn_state)
>> +{
>> +       struct drm_display_mode *mode = &crtc_state->mode;
>> +
>> +       if (mode->flags & DRM_MODE_FLAG_DBLCLK)
>> +               return -EINVAL;
>> +
>> +       return 0;
>> +}
>> +
>> +static void sun4i_hdmi_disable(struct drm_encoder *encoder)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>
> I assume you didn't take into consideration the latest patches I sent
> that remove this field.
>
> Instead you'll want to do drm_crtc_to_sun4i_crtc(encoder->crtc)->tcon.
>
>> +       u32 val;
>> +
>> +       DRM_DEBUG_DRIVER("Disabling the HDMI Output\n");
>> +
>> +       val = readl(hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +       val &= ~SUN4I_HDMI_VID_CTRL_ENABLE;
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +
>> +       sun4i_tcon_channel_disable(tcon, 1);
>> +}
>> +
>> +static void sun4i_hdmi_enable(struct drm_encoder *encoder)
>> +{
>> +       struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode;
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>> +       u32 val = 0;
>> +
>> +       DRM_DEBUG_DRIVER("Enabling the HDMI Output\n");
>> +
>> +       sun4i_tcon_channel_enable(tcon, 1);
>> +
>> +       sun4i_hdmi_setup_avi_infoframes(hdmi, mode);
>> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(0, SUN4I_HDMI_PKT_AVI);
>> +       val |= SUN4I_HDMI_PKT_CTRL_TYPE(1, SUN4I_HDMI_PKT_END);
>> +       writel(val, hdmi->base + SUN4I_HDMI_PKT_CTRL_REG(0));
>> +
>> +       val = SUN4I_HDMI_VID_CTRL_ENABLE;
>> +       if (hdmi->hdmi_monitor)
>> +               val |= SUN4I_HDMI_VID_CTRL_HDMI_MODE;
>> +
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_CTRL_REG);
>> +}
>> +
>> +static void sun4i_hdmi_mode_set(struct drm_encoder *encoder,
>> +                               struct drm_display_mode *mode,
>> +                               struct drm_display_mode *adjusted_mode)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_encoder_to_sun4i_hdmi(encoder);
>> +       struct sun4i_drv *drv = hdmi->drv;
>> +       struct sun4i_tcon *tcon = drv->tcon;
>> +       unsigned int x, y;
>> +       u32 val;
>> +
>> +       sun4i_tcon1_mode_set(tcon, mode);
>> +       sun4i_tcon_set_mux(tcon, 1, encoder);
>> +
>> +       clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000);
>> +       clk_set_rate(hdmi->mod_clk, mode->crtc_clock * 1000);
>> +       clk_set_rate(hdmi->tmds_clk, mode->crtc_clock * 1000);
>> +
>> +       /* Set input sync enable */
>> +       writel(SUN4I_HDMI_UNKNOWN_INPUT_SYNC,
>> +              hdmi->base + SUN4I_HDMI_UNKNOWN_REG);
>> +
>> +       /* Setup timing registers */
>> +       writel(SUN4I_HDMI_VID_TIMING_X(mode->hdisplay) |
>> +              SUN4I_HDMI_VID_TIMING_Y(mode->vdisplay),
>
> I can't figure out whether we should use mode->crtc_* or not...
>
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_ACT_REG);
>> +
>> +       x = mode->htotal - mode->hsync_start;
>> +       y = mode->vtotal - mode->vsync_start;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_BP_REG);
>> +
>> +       x = mode->hsync_start - mode->hdisplay;
>> +       y = mode->vsync_start - mode->vdisplay;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_FP_REG);
>> +
>> +       x = mode->hsync_end - mode->hsync_start;
>> +       y = mode->vsync_end - mode->vsync_start;
>> +       writel(SUN4I_HDMI_VID_TIMING_X(x) | SUN4I_HDMI_VID_TIMING_Y(y),
>> +              hdmi->base + SUN4I_HDMI_VID_TIMING_SPW_REG);
>> +
>> +       val = SUN4I_HDMI_VID_TIMING_POL_TX_CLK;
>> +       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
>> +               val |= SUN4I_HDMI_VID_TIMING_POL_HSYNC;
>> +
>> +       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
>> +               val |= SUN4I_HDMI_VID_TIMING_POL_VSYNC;
>> +
>> +       writel(val, hdmi->base + SUN4I_HDMI_VID_TIMING_POL_REG);
>> +}
>> +
>> +static const struct drm_encoder_helper_funcs sun4i_hdmi_helper_funcs = {
>> +       .atomic_check   = sun4i_hdmi_atomic_check,
>> +       .disable        = sun4i_hdmi_disable,
>> +       .enable         = sun4i_hdmi_enable,
>> +       .mode_set       = sun4i_hdmi_mode_set,
>> +};
>> +
>> +static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
>> +       .destroy        = drm_encoder_cleanup,
>> +};
>> +
>> +static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
>> +                                    unsigned int blk, unsigned int offset,
>> +                                    u8 *buf, unsigned int count)
>> +{
>> +       unsigned long reg;
>> +       int i;
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
>> +       writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +
>> +       writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
>> +              SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
>> +              SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
>> +              SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
>> +              hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
>> +       writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
>> +              hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
>> +
>> +       writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
>> +       writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
>> +              hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
>> +                              !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
>> +                              100, 100000))
>> +               return -EIO;
>> +
>> +       for (i = 0; i < count; i++)
>> +               buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
>> +                                     size_t length)
>> +{
>> +       struct sun4i_hdmi *hdmi = data;
>> +       int retry = 2, i;
>> +
>> +       do {
>> +               for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
>> +                       unsigned char offset = blk * EDID_LENGTH + i;
>> +                       unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
>> +                                                length - i);
>> +                       int ret;
>> +
>> +                       ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
>> +                                                       buf + i, count);
>> +                       if (ret)
>> +                               return ret;
>> +               }
>> +       } while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
>> +
>> +       return 0;
>> +}
>> +
>> +static int sun4i_hdmi_get_modes(struct drm_connector *connector)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
>> +       unsigned long reg;
>> +       struct edid *edid;
>> +       int ret;
>> +
>> +       /* Reset i2c controller */
>> +       writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
>> +              hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
>> +                              !(reg & SUN4I_HDMI_DDC_CTRL_RESET),
>> +                              100, 2000))
>> +               return -EIO;
>> +
>> +       writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
>> +              SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
>> +              hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
>> +
>> +       clk_set_rate(hdmi->ddc_clk, 100000);
>
> You might want to explicitly enable the DDC clk around the following call.
>
>> +
>> +       edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
>> +       if (!edid)
>> +               return 0;
>> +
>> +       hdmi->hdmi_monitor = drm_detect_hdmi_monitor(edid);
>> +       DRM_DEBUG_DRIVER("Monitor is %s monitor\n",
>> +                        hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
>> +
>> +       drm_mode_connector_update_edid_property(connector, edid);
>> +       ret = drm_add_edid_modes(connector, edid);
>> +       kfree(edid);
>> +
>> +       return ret;
>> +}
>> +
>> +static const struct drm_connector_helper_funcs sun4i_hdmi_connector_helper_funcs = {
>> +       .get_modes      = sun4i_hdmi_get_modes,
>> +};
>> +
>> +static enum drm_connector_status
>> +sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +       struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
>> +       unsigned long reg;
>> +
>> +       if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
>> +                              reg & SUN4I_HDMI_HPD_HIGH,
>> +                              0, 500000))
>> +               return connector_status_disconnected;
>> +
>> +       return connector_status_connected;
>> +}
>> +
>> +static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
>> +       .dpms                   = drm_atomic_helper_connector_dpms,
>> +       .detect                 = sun4i_hdmi_connector_detect,
>> +       .fill_modes             = drm_helper_probe_single_connector_modes,
>> +       .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 sun4i_hdmi_bind(struct device *dev, struct device *master,
>> +                          void *data)
>> +{
>> +       struct platform_device *pdev = to_platform_device(dev);
>> +       struct drm_device *drm = data;
>> +       struct sun4i_drv *drv = drm->dev_private;
>> +       struct sun4i_hdmi *hdmi;
>> +       struct resource *res;
>> +       u32 reg;
>> +       int ret;
>> +
>> +       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
>> +       if (!hdmi)
>> +               return -ENOMEM;
>> +       dev_set_drvdata(dev, hdmi);
>> +       hdmi->dev = dev;
>> +       hdmi->drv = drv;
>> +
>> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       hdmi->base = devm_ioremap_resource(dev, res);
>> +       if (IS_ERR(hdmi->base)) {
>> +               dev_err(dev, "Couldn't map the HDMI encoder registers\n");
>> +               return PTR_ERR(hdmi->base);
>> +       }
>> +
>> +       hdmi->bus_clk = devm_clk_get(dev, "ahb");
>> +       if (IS_ERR(hdmi->bus_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI bus clock\n");
>> +               return PTR_ERR(hdmi->bus_clk);
>> +       }
>> +       clk_prepare_enable(hdmi->bus_clk);
>> +
>> +       hdmi->mod_clk = devm_clk_get(dev, "mod");
>> +       if (IS_ERR(hdmi->mod_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI mod clock\n");
>> +               return PTR_ERR(hdmi->mod_clk);
>> +       }
>> +       clk_prepare_enable(hdmi->mod_clk);
>> +
>> +       hdmi->pll0_clk = devm_clk_get(dev, "pll-0");
>> +       if (IS_ERR(hdmi->pll0_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI PLL 0 clock\n");
>> +               return PTR_ERR(hdmi->pll0_clk);
>> +       }
>> +
>> +       hdmi->pll1_clk = devm_clk_get(dev, "pll-1");
>> +       if (IS_ERR(hdmi->pll1_clk)) {
>> +               dev_err(dev, "Couldn't get the HDMI PLL 1 clock\n");
>> +               return PTR_ERR(hdmi->pll1_clk);
>> +       }
>> +
>> +       ret = sun4i_tmds_create(hdmi);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't create the TMDS clock\n");
>> +               return ret;
>> +       }
>> +
>> +       writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
>> +
>> +       writel(SUN4I_HDMI_PAD_CTRL0_TXEN | SUN4I_HDMI_PAD_CTRL0_CKEN |
>> +              SUN4I_HDMI_PAD_CTRL0_PWENG | SUN4I_HDMI_PAD_CTRL0_PWEND |
>> +              SUN4I_HDMI_PAD_CTRL0_PWENC | SUN4I_HDMI_PAD_CTRL0_LDODEN |
>> +              SUN4I_HDMI_PAD_CTRL0_LDOCEN | SUN4I_HDMI_PAD_CTRL0_BIASEN,
>> +              hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);
>> +
>> +       /*
>> +        * We can't just initialize the register there, we need to
>> +        * protect the clock bits that have already been read out and
>> +        * cached by the clock framework.
>> +        */
>> +       reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       reg = reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>
> Nit: just use the &= short hand.
>
>> +       reg |= SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_EMP(2) |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_DENCK |
>> +               SUN4I_HDMI_PAD_CTRL1_REG_DEN |
>> +               SUN4I_HDMI_PAD_CTRL1_EMPCK_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_EMP_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
>> +               SUN4I_HDMI_PAD_CTRL1_AMP_OPT;
>> +       writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +
>> +       reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg = reg & SUN4I_HDMI_PLL_CTRL_DIV_MASK;
>
> Same here.
>
>> +       reg |= SUN4I_HDMI_PLL_CTRL_VCO_S(8) | SUN4I_HDMI_PLL_CTRL_CS(7) |
>> +               SUN4I_HDMI_PLL_CTRL_CP_S(239) | SUN4I_HDMI_PLL_CTRL_S(7) |

And your values here might be off.

ChenYu

>> +               SUN4I_HDMI_PLL_CTRL_VCO_GAIN(4) | SUN4I_HDMI_PLL_CTRL_SDIV2 |
>> +               SUN4I_HDMI_PLL_CTRL_LDO2_EN | SUN4I_HDMI_PLL_CTRL_LDO1_EN |
>> +               SUN4I_HDMI_PLL_CTRL_HV_IS_33 | SUN4I_HDMI_PLL_CTRL_BWS |
>> +               SUN4I_HDMI_PLL_CTRL_PLL_EN;
>> +       writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +
>> +       ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't create the DDC clock\n");
>> +               return ret;
>> +       }
>> +
>> +       drm_encoder_helper_add(&hdmi->encoder,
>> +                              &sun4i_hdmi_helper_funcs);
>> +       ret = drm_encoder_init(drm,
>> +                              &hdmi->encoder,
>> +                              &sun4i_hdmi_funcs,
>> +                              DRM_MODE_ENCODER_TMDS,
>> +                              NULL);
>> +       if (ret) {
>> +               dev_err(dev, "Couldn't initialise the HDMI encoder\n");
>> +               return ret;
>> +       }
>> +
>> +       hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
>> +                                                                 dev->of_node);
>> +       if (!hdmi->encoder.possible_crtcs)
>> +               return ret;
>
> You need a valid error return value here. Maybe -EPROBE_DEFER?
>
>> +
>> +       drm_connector_helper_add(&hdmi->connector,
>> +                                &sun4i_hdmi_connector_helper_funcs);
>
> /* There is no HPD interrupt, so we need to poll the controller */
> hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
>                          DRM_CONNECTOR_POLL_DISCONNECT;
>
>> +       ret = drm_connector_init(drm, &hdmi->connector,
>> +                                &sun4i_hdmi_connector_funcs,
>> +                                DRM_MODE_CONNECTOR_HDMIA);
>> +       if (ret) {
>> +               dev_err(dev,
>> +                       "Couldn't initialise the HDMI connector\n");
>> +               goto err_cleanup_connector;
>> +       }
>> +
>> +       drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
>> +
>> +       return 0;
>> +
>> +err_cleanup_connector:
>> +       drm_encoder_cleanup(&hdmi->encoder);
>> +       return ret;
>> +}
>> +
>> +static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
>> +                           void *data)
>> +{
>> +       struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
>> +
>> +       drm_connector_cleanup(&hdmi->connector);
>> +       drm_encoder_cleanup(&hdmi->encoder);
>> +}
>> +
>> +static const struct component_ops sun4i_hdmi_ops = {
>> +       .bind   = sun4i_hdmi_bind,
>> +       .unbind = sun4i_hdmi_unbind,
>> +};
>> +
>> +static int sun4i_hdmi_probe(struct platform_device *pdev)
>> +{
>> +       return component_add(&pdev->dev, &sun4i_hdmi_ops);
>> +}
>> +
>> +static int sun4i_hdmi_remove(struct platform_device *pdev)
>> +{
>> +       component_del(&pdev->dev, &sun4i_hdmi_ops);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct of_device_id sun4i_hdmi_of_table[] = {
>> +       { .compatible = "allwinner,sun5i-a10s-hdmi" },
>> +       { }
>> +};
>> +MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
>> +
>> +static struct platform_driver sun4i_hdmi_driver = {
>> +       .probe          = sun4i_hdmi_probe,
>> +       .remove         = sun4i_hdmi_remove,
>> +       .driver         = {
>> +               .name           = "sun4i-hdmi",
>> +               .of_match_table = sun4i_hdmi_of_table,
>> +       },
>> +};
>> +module_platform_driver(sun4i_hdmi_driver);
>> +
>> +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
>> +MODULE_DESCRIPTION("Allwinner A10 HDMI Driver");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>> new file mode 100644
>> index 000000000000..5cf2527bffc8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
>> @@ -0,0 +1,225 @@
>> +/*
>> + * Copyright (C) 2016 Free Electrons
>> + * Copyright (C) 2016 NextThing Co
>> + *
>> + * Maxime Ripard <maxime.ripard@free-electrons.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation; either version 2 of
>> + * the License, or (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +
>> +#include "sun4i_tcon.h"
>> +#include "sun4i_hdmi.h"
>> +
>> +struct sun4i_tmds {
>> +       struct clk_hw           hw;
>> +       struct sun4i_hdmi       *hdmi;
>> +};
>> +
>> +static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
>> +{
>> +       return container_of(hw, struct sun4i_tmds, hw);
>> +}
>> +
>> +
>> +static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
>> +                                            unsigned long parent_rate,
>> +                                            u8 *div,
>> +                                            bool *half)
>> +{
>> +       unsigned long best_rate = 0;
>> +       u8 best_m = 0, m;
>> +       bool is_double;
>> +
>> +       for (m = 1; m < 16; m++) {
>> +               u8 d;
>> +
>> +               for (d = 1; d < 3; d++) {
>> +                       unsigned long tmp_rate;
>> +
>> +                       tmp_rate = parent_rate / m / d;
>> +
>> +                       if (tmp_rate > rate)
>> +                               continue;
>> +
>> +                       if (!best_rate ||
>> +                           (rate - tmp_rate) < (rate - best_rate)) {
>> +                               best_rate = tmp_rate;
>> +                               best_m = m;
>> +                               is_double = d;
>> +                       }
>> +               }
>> +       }
>> +
>> +       if (div && half) {
>> +               *div = best_m;
>> +               *half = is_double;
>> +       }
>> +
>> +       return best_rate;
>> +}
>> +
>> +
>> +static int sun4i_tmds_determine_rate(struct clk_hw *hw,
>> +                                    struct clk_rate_request *req)
>> +{
>> +       struct clk_hw *parent;
>> +       unsigned long best_parent = 0;
>> +       unsigned long rate = req->rate;
>> +       int best_div = 1, best_half = 1;
>> +       int i, j;
>> +
>> +       /*
>> +        * We only consider PLL3, since the TCON is very likely to be
>> +        * clocked from it, and to have the same rate than our HDMI
>> +        * clock, so we should not need to do anything.
>> +        */
>> +
>> +       parent = clk_hw_get_parent_by_index(hw, 0);
>> +       if (!parent)
>> +               return -EINVAL;
>> +
>> +       for (i = 1; i < 3; i++) {
>> +               for (j = 1; j < 16; j++) {
>> +                       unsigned long ideal = rate * i * j;
>> +                       unsigned long rounded;
>> +
>> +                       rounded = clk_hw_round_rate(parent, ideal);
>> +
>> +                       if (rounded == ideal) {
>> +                               best_parent = rounded;
>> +                               best_half = i;
>> +                               best_div = j;
>> +                               goto out;
>> +                       }
>> +
>> +                       if (abs(rate - rounded / i) <
>> +                           abs(rate - best_parent / best_div)) {
>> +                               best_parent = rounded;
>> +                               best_div = i;
>> +                       }
>> +               }
>> +       }
>> +
>> +out:
>> +       req->rate = best_parent / best_half / best_div;
>> +       req->best_parent_rate = best_parent;
>> +       req->best_parent_hw = parent;
>> +
>> +       return 0;
>> +}
>> +
>> +static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
>> +                                           unsigned long parent_rate)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       if (reg & SUN4I_HDMI_PAD_CTRL1_HALVE_CLK)
>> +               parent_rate /= 2;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg = (reg >> 4) & 0xf;
>> +       if (!reg)
>> +               reg = 1;
>> +
>> +       return parent_rate / reg;
>> +}
>> +
>> +static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
>> +                              unsigned long parent_rate)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       bool half;
>> +       u32 reg;
>> +       u8 div;
>> +
>> +       sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +       reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>> +       if (half)
>> +               reg |= SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
>> +       writel(reg, tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +       reg &= ~SUN4I_HDMI_PLL_CTRL_DIV_MASK;
>> +       writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div),
>> +              tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static u8 sun4i_tmds_get_parent(struct clk_hw *hw)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +       return ((reg & SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK) >>
>> +               SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT);
>> +}
>> +
>> +static int sun4i_tmds_set_parent(struct clk_hw *hw, u8 index)
>> +{
>> +       struct sun4i_tmds *tmds = hw_to_tmds(hw);
>> +       u32 reg;
>> +
>> +       if (index > 1)
>> +               return -EINVAL;
>> +
>> +       reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +       reg &= ~SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK;
>> +       writel(reg | SUN4I_HDMI_PLL_DBG0_TMDS_PARENT(index),
>> +              tmds->hdmi->base + SUN4I_HDMI_PLL_DBG0_REG);
>> +
>> +       return 0;
>> +}
>> +
>> +static const struct clk_ops sun4i_tmds_ops = {
>> +       .determine_rate = sun4i_tmds_determine_rate,
>> +       .recalc_rate    = sun4i_tmds_recalc_rate,
>> +       .set_rate       = sun4i_tmds_set_rate,
>> +
>> +       .get_parent     = sun4i_tmds_get_parent,
>> +       .set_parent     = sun4i_tmds_set_parent,
>> +};
>> +
>> +int sun4i_tmds_create(struct sun4i_hdmi *hdmi)
>> +{
>> +       struct clk_init_data init;
>> +       struct sun4i_tmds *tmds;
>> +       const char *parents[2];
>> +
>> +       parents[0] = __clk_get_name(hdmi->pll0_clk);
>> +       if (!parents[0])
>> +               return -ENODEV;
>> +
>> +       parents[1] = __clk_get_name(hdmi->pll1_clk);
>> +       if (!parents[1])
>> +               return -ENODEV;
>> +
>> +       tmds = devm_kzalloc(hdmi->dev, sizeof(*tmds), GFP_KERNEL);
>> +       if (!tmds)
>> +               return -ENOMEM;
>> +
>> +       init.name = "hdmi-tmds";
>> +       init.ops = &sun4i_tmds_ops;
>> +       init.parent_names = parents;
>> +       init.num_parents = 2;
>> +       init.flags = CLK_SET_RATE_PARENT;
>> +
>> +       tmds->hdmi = hdmi;
>> +       tmds->hw.init = &init;
>> +
>> +       hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
>> +       if (IS_ERR(hdmi->tmds_clk))
>> +               return PTR_ERR(hdmi->tmds_clk);
>> +
>> +       return 0;
>> +}
>> --
>
> The rest looks good. This is coming together nicely.
>
> Reviewed-by: Chen-Yu Tsai <wens@csie.org>

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

end of thread, other threads:[~2017-05-12  6:50 UTC | newest]

Thread overview: 125+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-05-03 11:59 [PATCH v2 0/20] drm: sun4i: Add support for the HDMI controller Maxime Ripard
2017-05-03 11:59 ` Maxime Ripard
2017-05-03 11:59 ` Maxime Ripard
2017-05-03 11:59 ` [PATCH v2 1/20] clk: divider: Make divider_round_rate take the parent clock Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 12:53   ` Chen-Yu Tsai
2017-05-03 12:53     ` Chen-Yu Tsai
2017-05-03 12:53     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 2/20] clk: sunxi-ng: Pass the parent and a pointer to the clocks round rate Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:19   ` Chen-Yu Tsai
2017-05-04  3:19     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 3/20] clk: sunxi-ng: div: Switch to divider_round_rate Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:20   ` Chen-Yu Tsai
2017-05-04  3:20     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 4/20] clk: sunxi-ng: mux: Don't just rely on the parent for CLK_SET_RATE_PARENT Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:23   ` Chen-Yu Tsai
2017-05-04  3:23     ` Chen-Yu Tsai
2017-05-04  3:23     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 5/20] clk: sunxi-ng: mux: split out the pre-divider computation code Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:25   ` Chen-Yu Tsai
2017-05-04  3:25     ` Chen-Yu Tsai
2017-05-04  3:25     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 6/20] clk: sunxi-ng: mux: Change pre-divider application function prototype Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:27   ` Chen-Yu Tsai
2017-05-04  3:27     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 7/20] clk: sunxi-ng: mux: Re-adjust parent rate Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:28   ` Chen-Yu Tsai
2017-05-04  3:28     ` Chen-Yu Tsai
2017-05-04  3:28     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 8/20] clk: sunxi-ng: sun5i: Export video PLLs Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:28   ` Chen-Yu Tsai
2017-05-04  3:28     ` Chen-Yu Tsai
2017-05-04  3:28     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 9/20] drm/sun4i: tcon: Add channel debug Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59 ` [PATCH v2 10/20] drm/sun4i: tcon: Move the muxing out of the mode set function Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:54   ` Chen-Yu Tsai
2017-05-04  3:54     ` Chen-Yu Tsai
2017-05-04  3:54     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 11/20] drm/sun4i: tcon: Switch mux on only for composite Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:57   ` Chen-Yu Tsai
2017-05-04  3:57     ` Chen-Yu Tsai
2017-05-04  3:57     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 12/20] drm/sun4i: tcon: Fix tcon channel 1 backporch calculation Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59 ` [PATCH v2 13/20] drm/sun4i: tcon: Change vertical total size computation inconsistency Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  3:58   ` Chen-Yu Tsai
2017-05-04  3:58     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 14/20] drm/sun4i: tcon: multiply the vtotal when not in interlace Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  4:07   ` Chen-Yu Tsai
2017-05-04  4:07     ` Chen-Yu Tsai
2017-05-04  4:07     ` Chen-Yu Tsai
2017-05-05  8:16   ` icenowy
2017-05-05  8:16     ` icenowy at aosc.io
2017-05-05  8:16     ` icenowy-h8G6r0blFSE
2017-05-03 11:59 ` [PATCH v2 15/20] drm/sun4i: Ignore the generic connectors for components Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  4:11   ` Chen-Yu Tsai
2017-05-04  4:11     ` Chen-Yu Tsai
2017-05-04  4:11     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 16/20] dt-bindings: display: sun4i: Add HDMI display bindings Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-08 16:20   ` Rob Herring
2017-05-08 16:20     ` Rob Herring
2017-05-08 16:20     ` Rob Herring
2017-05-03 11:59 ` [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property Maxime Ripard
2017-05-03 11:59   ` [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner, tcon-channel property Maxime Ripard
2017-05-03 11:59   ` [PATCH v2 17/20] dt-bindings: display: sun4i: Add allwinner,tcon-channel property Maxime Ripard
2017-05-08 16:24   ` Rob Herring
2017-05-08 16:24     ` Rob Herring
2017-05-08 16:24     ` Rob Herring
2017-05-03 11:59 ` [PATCH v2 18/20] drm/sun4i: Add HDMI support Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  7:55   ` [linux-sunxi] " Chen-Yu Tsai
2017-05-04  7:55     ` Chen-Yu Tsai
2017-05-04  7:55     ` Chen-Yu Tsai
2017-05-12  6:50     ` [linux-sunxi] " Chen-Yu Tsai
2017-05-12  6:50       ` Chen-Yu Tsai
2017-05-12  6:50       ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 19/20] ARM: sun5i: a10s: Add the HDMI controller node Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  8:04   ` Chen-Yu Tsai
2017-05-04  8:04     ` Chen-Yu Tsai
2017-05-04  8:04     ` Chen-Yu Tsai
2017-05-03 11:59 ` [PATCH v2 20/20] ARM: sun5i: a10s-olinuxino: Enable HDMI Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-03 11:59   ` Maxime Ripard
2017-05-04  8:05   ` [linux-sunxi] " Chen-Yu Tsai
2017-05-04  8:05     ` Chen-Yu Tsai
2017-05-04  8:05     ` Chen-Yu Tsai
2017-05-10 19:23     ` [linux-sunxi] " Maxime Ripard
2017-05-10 19:23       ` Maxime Ripard
2017-05-10 19:23       ` Maxime Ripard
2017-05-11  6:25       ` icenowy
2017-05-11  6:25         ` icenowy at aosc.io
2017-05-11  6:25         ` icenowy-h8G6r0blFSE

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