2017-09-29 08:27:47

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 00/14] drm/sun4i: hdmi: Support HDMI controller on A31

Hi everyone,

This is v3 of my A31 HDMI support series.

Changes since v2:

- TCON muxing moved into functions for each platform, with pointers
to them in the TCON quirks structure.

- CCU "hdmi-ddc" clock renamed to "ddc".

- Added Maxime's acks.

Changes since v1:

- Core changes to sun4i-drm to support two display pipelines
have been merged into drm-misc and thus dropped from this
version

- Reworked DDC variant support onto new exposed I2C interface bits.

- Reworked DDC variant support to use regmap_fields.

- Patches to add variant support to various (TMDS, DDC, HDMI
controller) sub-blocks have been merged into one patch.

This series adds support for the HDMI controller found on Allwinner
A31/A31s SoCs. It builds upon Maxime's work that added support for
the HDMI controller on the Allwinner A10s SoC.

The HDMI controllers in the older generation Allwinner SoCs is very
similar. The A10/A10s/A20 all have the same hardware block, with the
A10 having slightly different initial configuration values. The A31's
variant splits out the DDC parent clock, has different formulas for
the DDC and TMDS clocks, and a different register layout for the DDC
block. Also, it does not expose the CEC pins outside of the SoC, which
is unfortunate.

Patch 1 exports the 2x outputs of the two video PLLs. These feed the
TMDS clock directly.

Patch 2 renames the A31 CCU's DDC clock, so that it doesn't conflict
with the DDC clock in the HDMI block.

Patch 3 moves the existing TCON muxing code for the A13 into a separate
function, pointed to by by function pointer in the quirks structure.
The existing sun4i_tcon_set_mux() function calls the function pointer
if it is set.

Patch 4 adds support for the TCON demuxing feature on the A31. This is
needed if the user wants to output through HDMI from the second display
pipeline.

Patch 5 adds proper error path cleanup to the HDMI driver.

Patch 6 adds a regmap for the HDMI driver, to be used in a subsequent
patch.

Patch 7 allows the HDMI TMDS clock to use the second PLL as its parent,
in case the first PLL is driving an incompatible dot clock.

Patch 8 adds the A31 HDMI controller variant to the device tree binding.

Patch 9 adds an iopoll-like polling macro for regmap_field. This is used
in the next patch within the DDC part to poll for reset and I/O
completion.

Patch 10 adds support for different variants of the HDMI controller
hardware, with the differences mentioned in the beginning of this
letter.

Patch 11 adds defines for the A31 specific DDC register offsets.

Patch 12 adds support for the A31's HDMI controller variant.

Patch 13 adds a device node for the HDMI controller on the A31.

Patch 14 enable HDMI video output on three boards that I have.


I also had simultaneous output on both display pipelines on the SinA31s,
one with an LCD panel and the other using HDMI. After boot, both screens
showed a proper console. The HDMI screen had higher resolution, so the
console was limited to the upper left corner.

Note that this series does not deal with conflicting pixel clocks.

Assuming everyone is happy with the patches, I propose the following:

1. We sunxi maintainers will take the clk and dts patches through
our tree with minimal but proper cross references.

2. Mark can either take the regmap patch on an immutable branch,
which we then merge into drm-misc before applying the drm/sun4i
patches, or give his Ack for us to merge that patch through
drm-misc.

As I still don't have a freedesktop.org account [1] to access drm-misc,
Maxime will have to apply the patches for me.


Regards
ChenYu


[1] https://bugs.freedesktop.org/show_bug.cgi?id=102920

Chen-Yu Tsai (14):
clk: sunxi-ng: sun6i: Export video PLLs
clk: sunxi-ng: sun6i: Rename HDMI DDC clock to avoid name collision
drm/sun4i: tcon: Add variant callback for TCON output muxing
drm/sun4i: tcon: Add support for demuxing TCON output on A31
drm/sun4i: hdmi: Disable clks in bind function error path and unbind
function
drm/sun4i: hdmi: create a regmap for later use
drm/sun4i: hdmi: Allow using second PLL as TMDS clk parent
dt-bindings: display: sun4i: Add binding for A31 HDMI controller
regmap: add iopoll-like polling macro for regmap_field
drm/sun4i: hdmi: Add support for controller hardware variants
drm/sun4i: hdmi: Add A31 specific DDC register definitions
drm/sun4i: hdmi: Add support for A31's HDMI controller
ARM: dts: sun6i: Add device node for HDMI controller
ARM: dts: sun6i: Enable HDMI support on some A31/A31s devices

.../bindings/display/sunxi/sun4i-drm.txt | 3 +
arch/arm/boot/dts/sun6i-a31-hummingbird.dts | 21 ++
arch/arm/boot/dts/sun6i-a31.dtsi | 55 +++++
arch/arm/boot/dts/sun6i-a31s-primo81.dts | 25 +++
arch/arm/boot/dts/sun6i-a31s-sina31s.dts | 25 +++
drivers/clk/sunxi-ng/ccu-sun6i-a31.c | 2 +-
drivers/clk/sunxi-ng/ccu-sun6i-a31.h | 8 +-
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 107 ++++++++++
drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 38 +++-
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 204 +++++++++++++++---
drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 227 +++++++++++++++------
drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 68 +++---
drivers/gpu/drm/sun4i/sun4i_tcon.c | 83 ++++++--
drivers/gpu/drm/sun4i/sun4i_tcon.h | 5 +
include/dt-bindings/clock/sun6i-a31-ccu.h | 4 +
include/linux/regmap.h | 39 ++++
16 files changed, 769 insertions(+), 145 deletions(-)

--
2.14.2


2017-09-29 08:23:28

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 03/14] drm/sun4i: tcon: Add variant callback for TCON output muxing

Different SoCs have different muxing options and values for the TCON
outputs. Instead of stuffing every possibility in sun4i_tcon_set_mux(),
add a callback pointer to sun4i_tcon_quirks that each TCON variant
can use to provide muxing support.

The current muxing options in sun4i_tcon_set_mux() for sun5i-a13 are
moved to a new sun5i-specific callback function.

Signed-off-by: Chen-Yu Tsai <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_tcon.c | 45 ++++++++++++++++++++++++--------------
drivers/gpu/drm/sun4i/sun4i_tcon.h | 5 +++++
2 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index e853dfe51389..7bf51abaee97 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -14,9 +14,12 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder.h>
#include <drm/drm_modes.h>
#include <drm/drm_of.h>

+#include <uapi/drm/drm_mode.h>
+
#include <linux/component.h>
#include <linux/ioport.h>
#include <linux/of_address.h>
@@ -112,23 +115,13 @@ 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;
+ int ret = -ENOTSUPP;

- if (channel != 1)
- return;
-
- if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
- val = 1;
- else
- val = 0;
+ if (tcon->quirks->set_mux)
+ ret = tcon->quirks->set_mux(tcon, encoder);

- /*
- * FIXME: Undocumented bits
- */
- regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
+ DRM_DEBUG_DRIVER("Muxing encoder %s to CRTC %s: %d\n",
+ encoder->name, encoder->crtc->name, ret);
}
EXPORT_SYMBOL(sun4i_tcon_set_mux);

@@ -767,9 +760,27 @@ static int sun4i_tcon_remove(struct platform_device *pdev)
return 0;
}

+/* platform specific TCON muxing callbacks */
+static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon,
+ struct drm_encoder *encoder)
+{
+ u32 val;
+
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TVDAC)
+ val = 1;
+ else
+ val = 0;
+
+ /*
+ * FIXME: Undocumented bits
+ */
+ return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
+}
+
static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
- .has_unknown_mux = true,
- .has_channel_1 = true,
+ .has_unknown_mux = true,
+ .has_channel_1 = true,
+ .set_mux = sun5i_a13_tcon_set_mux,
};

static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h
index 5a219d1ccc26..6e699cbcf768 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.h
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h
@@ -145,10 +145,15 @@

#define SUN4I_TCON_MAX_CHANNELS 2

+struct sun4i_tcon;
+
struct sun4i_tcon_quirks {
bool has_unknown_mux; /* sun5i has undocumented mux */
bool has_channel_1; /* a33 does not have channel 1 */
bool needs_de_be_mux; /* sun6i needs mux to select backend */
+
+ /* callback to handle tcon muxing options */
+ int (*set_mux)(struct sun4i_tcon *, struct drm_encoder *);
};

struct sun4i_tcon {
--
2.14.2

2017-09-29 08:23:52

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 13/14] ARM: dts: sun6i: Add device node for HDMI controller

Now that we support the HDMI controller on the A31 SoC, we can add it
to the device tree.

This adds a device node for the HDMI controller, and the of_graph nodes
connecting it to the 2 TCONs.

Signed-off-by: Chen-Yu Tsai <[email protected]>
---
arch/arm/boot/dts/sun6i-a31.dtsi | 55 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)

diff --git a/arch/arm/boot/dts/sun6i-a31.dtsi b/arch/arm/boot/dts/sun6i-a31.dtsi
index 93209cda28db..48b2382a18a9 100644
--- a/arch/arm/boot/dts/sun6i-a31.dtsi
+++ b/arch/arm/boot/dts/sun6i-a31.dtsi
@@ -289,6 +289,12 @@
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
+
+ tcon0_out_hdmi: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&hdmi_in_tcon0>;
+ allwinner,tcon-channel = <1>;
+ };
};
};
};
@@ -331,6 +337,12 @@
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
+
+ tcon1_out_hdmi: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&hdmi_in_tcon1>;
+ allwinner,tcon-channel = <1>;
+ };
};
};
};
@@ -411,6 +423,49 @@
#size-cells = <0>;
};

+ hdmi: hdmi@01c16000 {
+ compatible = "allwinner,sun6i-a31-hdmi";
+ reg = <0x01c16000 0x1000>;
+ interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&ccu CLK_AHB1_HDMI>, <&ccu CLK_HDMI>,
+ <&ccu CLK_HDMI_DDC>,
+ <&ccu CLK_PLL_VIDEO0_2X>,
+ <&ccu CLK_PLL_VIDEO1_2X>;
+ clock-names = "ahb", "mod", "ddc", "pll-0", "pll-1";
+ resets = <&ccu RST_AHB1_HDMI>;
+ reset-names = "ahb";
+ dma-names = "ddc-tx", "ddc-rx", "audio-tx";
+ dmas = <&dma 13>, <&dma 13>, <&dma 14>;
+ 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@0 {
+ reg = <0>;
+ remote-endpoint = <&tcon0_out_hdmi>;
+ };
+
+ hdmi_in_tcon1: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&tcon1_out_hdmi>;
+ };
+ };
+
+ hdmi_out: port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+ };
+ };
+ };
+
usb_otg: usb@01c19000 {
compatible = "allwinner,sun6i-a31-musb";
reg = <0x01c19000 0x0400>;
--
2.14.2

2017-09-29 08:23:50

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 14/14] ARM: dts: sun6i: Enable HDMI support on some A31/A31s devices

All the A31/A31s devices I own have some kind of HDMI connector wired
to the dedicated HDMI pins on the SoC:

- A31 Hummingbird (standard HDMI connector, display already enabled)
- Sinlinx SinA31s (standard HDMI connector)
- MSI Primo81 tablet (micro HDMI connector)

Enable the display pipeline (if needed) and HDMI output for them.

Signed-off-by: Chen-Yu Tsai <[email protected]>
---
arch/arm/boot/dts/sun6i-a31-hummingbird.dts | 21 +++++++++++++++++++++
arch/arm/boot/dts/sun6i-a31s-primo81.dts | 25 +++++++++++++++++++++++++
arch/arm/boot/dts/sun6i-a31s-sina31s.dts | 25 +++++++++++++++++++++++++
3 files changed, 71 insertions(+)

diff --git a/arch/arm/boot/dts/sun6i-a31-hummingbird.dts b/arch/arm/boot/dts/sun6i-a31-hummingbird.dts
index 9ecb5f0b3f83..19e382a11297 100644
--- a/arch/arm/boot/dts/sun6i-a31-hummingbird.dts
+++ b/arch/arm/boot/dts/sun6i-a31-hummingbird.dts
@@ -62,6 +62,17 @@
stdout-path = "serial0:115200n8";
};

+ hdmi-connector {
+ compatible = "hdmi-connector";
+ type = "a";
+
+ port {
+ hdmi_con_in: endpoint {
+ remote-endpoint = <&hdmi_out_con>;
+ };
+ };
+ };
+
vga-connector {
compatible = "vga-connector";

@@ -162,6 +173,16 @@
};
};

+&hdmi {
+ status = "okay";
+};
+
+&hdmi_out {
+ hdmi_out_con: endpoint {
+ remote-endpoint = <&hdmi_con_in>;
+ };
+};
+
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins_a>;
diff --git a/arch/arm/boot/dts/sun6i-a31s-primo81.dts b/arch/arm/boot/dts/sun6i-a31s-primo81.dts
index 4c10123509c4..0cdb38ab3377 100644
--- a/arch/arm/boot/dts/sun6i-a31s-primo81.dts
+++ b/arch/arm/boot/dts/sun6i-a31s-primo81.dts
@@ -52,17 +52,42 @@
/ {
model = "MSI Primo81 tablet";
compatible = "msi,primo81", "allwinner,sun6i-a31s";
+
+ hdmi-connector {
+ compatible = "hdmi-connector";
+ type = "c";
+
+ port {
+ hdmi_con_in: endpoint {
+ remote-endpoint = <&hdmi_out_con>;
+ };
+ };
+ };
};

&cpu0 {
cpu-supply = <&reg_dcdc3>;
};

+&de {
+ status = "okay";
+};
+
&ehci0 {
/* rtl8188etv wifi is connected here */
status = "okay";
};

+&hdmi {
+ status = "okay";
+};
+
+&hdmi_out {
+ hdmi_out_con: endpoint {
+ remote-endpoint = <&hdmi_con_in>;
+ };
+};
+
&i2c0 {
/* pull-ups and device VDDIO use AXP221 DLDO3 */
pinctrl-names = "default";
diff --git a/arch/arm/boot/dts/sun6i-a31s-sina31s.dts b/arch/arm/boot/dts/sun6i-a31s-sina31s.dts
index b3d98222bd81..298476485bb4 100644
--- a/arch/arm/boot/dts/sun6i-a31s-sina31s.dts
+++ b/arch/arm/boot/dts/sun6i-a31s-sina31s.dts
@@ -53,6 +53,17 @@
stdout-path = "serial0:115200n8";
};

+ hdmi-connector {
+ compatible = "hdmi-connector";
+ type = "a";
+
+ port {
+ hdmi_con_in: endpoint {
+ remote-endpoint = <&hdmi_out_con>;
+ };
+ };
+ };
+
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
@@ -90,6 +101,10 @@
status = "okay";
};

+&de {
+ status = "okay";
+};
+
&ehci0 {
/* USB 2.0 4 port hub IC */
status = "okay";
@@ -112,6 +127,16 @@
};
};

+&hdmi {
+ status = "okay";
+};
+
+&hdmi_out {
+ hdmi_out_con: endpoint {
+ remote-endpoint = <&hdmi_con_in>;
+ };
+};
+
&ir {
pinctrl-names = "default";
pinctrl-0 = <&ir_pins_a>;
--
2.14.2

2017-09-29 08:24:41

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 11/14] drm/sun4i: hdmi: Add A31 specific DDC register definitions

The DDC block for the HDMI controller is different on the A31.

This patch adds the register definitions.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index b1124a8f9f05..da4ea9efc6fa 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -153,6 +153,37 @@

#define SUN4I_HDMI_DDC_FIFO_SIZE 16

+/* A31 specific */
+#define SUN6I_HDMI_DDC_CTRL_REG 0x500
+#define SUN6I_HDMI_DDC_CTRL_RESET BIT(31)
+#define SUN6I_HDMI_DDC_CTRL_START_CMD BIT(27)
+#define SUN6I_HDMI_DDC_CTRL_SDA_ENABLE BIT(6)
+#define SUN6I_HDMI_DDC_CTRL_SCL_ENABLE BIT(4)
+#define SUN6I_HDMI_DDC_CTRL_ENABLE BIT(0)
+
+#define SUN6I_HDMI_DDC_CMD_REG 0x508
+#define SUN6I_HDMI_DDC_CMD_BYTE_COUNT(count) ((count) << 16)
+/* command types in lower 3 bits are the same as sun4i */
+
+#define SUN6I_HDMI_DDC_ADDR_REG 0x50c
+#define SUN6I_HDMI_DDC_ADDR_SEGMENT(seg) (((seg) & 0xff) << 24)
+#define SUN6I_HDMI_DDC_ADDR_EDDC(addr) (((addr) & 0xff) << 16)
+#define SUN6I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
+#define SUN6I_HDMI_DDC_ADDR_SLAVE(addr) (((addr) & 0xff) << 1)
+
+#define SUN6I_HDMI_DDC_INT_STATUS_REG 0x514
+#define SUN6I_HDMI_DDC_INT_STATUS_TIMEOUT BIT(8)
+/* lower 8 bits are the same as sun4i */
+
+#define SUN6I_HDMI_DDC_FIFO_CTRL_REG 0x518
+#define SUN6I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(15)
+/* lower 9 bits are the same as sun4i */
+
+#define SUN6I_HDMI_DDC_CLK_REG 0x520
+/* DDC CLK bit fields are the same, but the formula is not */
+
+#define SUN6I_HDMI_DDC_FIFO_DATA_REG 0x580
+
enum sun4i_hdmi_pkt_type {
SUN4I_HDMI_PKT_AVI = 2,
SUN4I_HDMI_PKT_END = 15,
--
2.14.2

2017-09-29 08:23:30

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 12/14] drm/sun4i: hdmi: Add support for A31's HDMI controller

The HDMI controller found in the A31 SoCs is slightly different
from the one already supported, which is found in the A10s:

- Need different initial values for the PLL related registers

- Different behavior of the DDC and TMDS clocks

- Different register layout for the DDC portion

- Separate DDC parent clock

This patch adds support for it.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 3 ++
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 58 ++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index da4ea9efc6fa..186624de9b60 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -59,10 +59,13 @@
#define SUN4I_HDMI_PAD_CTRL0_TXEN BIT(23)

#define SUN4I_HDMI_PAD_CTRL1_REG 0x204
+#define SUN4I_HDMI_PAD_CTRL1_UNKNOWN BIT(24) /* set on A31 */
#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_PWSCK BIT(18)
+#define SUN4I_HDMI_PAD_CTRL1_PWSDT BIT(17)
#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)
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index 114cbe60b3e6..027b5608dbe6 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -324,6 +324,63 @@ static const struct sun4i_hdmi_variant sun5i_variant = {
.ddc_fifo_has_dir = true,
};

+static const struct sun4i_hdmi_variant sun6i_variant = {
+ .has_ddc_parent_clk = true,
+ .has_reset_control = true,
+ .pad_ctrl0_init_val = 0xff |
+ 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,
+ .pad_ctrl1_init_val = SUN4I_HDMI_PAD_CTRL1_REG_AMP(6) |
+ SUN4I_HDMI_PAD_CTRL1_REG_EMP(4) |
+ 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_PWSDT |
+ SUN4I_HDMI_PAD_CTRL1_PWSCK |
+ SUN4I_HDMI_PAD_CTRL1_AMPCK_OPT |
+ SUN4I_HDMI_PAD_CTRL1_AMP_OPT |
+ SUN4I_HDMI_PAD_CTRL1_UNKNOWN,
+ .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
+ SUN4I_HDMI_PLL_CTRL_CS(3) |
+ SUN4I_HDMI_PLL_CTRL_CP_S(10) |
+ SUN4I_HDMI_PLL_CTRL_S(4) |
+ 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_PLL_EN,
+
+ .ddc_clk_reg = REG_FIELD(SUN6I_HDMI_DDC_CLK_REG, 0, 6),
+ .ddc_clk_pre_divider = 1,
+ .ddc_clk_m_offset = 2,
+
+ .tmds_clk_div_offset = 1,
+
+ .field_ddc_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 0, 0),
+ .field_ddc_start = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 27, 27),
+ .field_ddc_reset = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 31, 31),
+ .field_ddc_addr_reg = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 31),
+ .field_ddc_slave_addr = REG_FIELD(SUN6I_HDMI_DDC_ADDR_REG, 1, 7),
+ .field_ddc_int_status = REG_FIELD(SUN6I_HDMI_DDC_INT_STATUS_REG, 0, 8),
+ .field_ddc_fifo_clear = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 18, 18),
+ .field_ddc_fifo_rx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
+ .field_ddc_fifo_tx_thres = REG_FIELD(SUN6I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
+ .field_ddc_byte_count = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 16, 25),
+ .field_ddc_cmd = REG_FIELD(SUN6I_HDMI_DDC_CMD_REG, 0, 2),
+ .field_ddc_sda_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 6, 6),
+ .field_ddc_sck_en = REG_FIELD(SUN6I_HDMI_DDC_CTRL_REG, 4, 4),
+
+ .ddc_fifo_reg = SUN6I_HDMI_DDC_FIFO_DATA_REG,
+ .ddc_fifo_thres_incl = true,
+};
+
static const struct regmap_config sun4i_hdmi_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
@@ -551,6 +608,7 @@ static int sun4i_hdmi_remove(struct platform_device *pdev)

static const struct of_device_id sun4i_hdmi_of_table[] = {
{ .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, },
+ { .compatible = "allwinner,sun6i-a31-hdmi", .data = &sun6i_variant, },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
--
2.14.2

2017-09-29 08:23:27

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 02/14] clk: sunxi-ng: sun6i: Rename HDMI DDC clock to avoid name collision

The HDMI DDC clock found in the CCU is the parent of the actual DDC
clock within the HDMI controller. That clock is also named "hdmi-ddc".

Rename the one in the CCU to "ddc". This makes more sense than renaming
the one in the HDMI controller to something else.

Fixes: c6e6c96d8fa6 ("clk: sunxi-ng: Add A31/A31s clocks")
Signed-off-by: Chen-Yu Tsai <[email protected]>
---
drivers/clk/sunxi-ng/ccu-sun6i-a31.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-a31.c b/drivers/clk/sunxi-ng/ccu-sun6i-a31.c
index 8af434815fba..241fb13f1c06 100644
--- a/drivers/clk/sunxi-ng/ccu-sun6i-a31.c
+++ b/drivers/clk/sunxi-ng/ccu-sun6i-a31.c
@@ -608,7 +608,7 @@ static SUNXI_CCU_M_WITH_MUX_GATE(hdmi_clk, "hdmi", lcd_ch1_parents,
0x150, 0, 4, 24, 2, BIT(31),
CLK_SET_RATE_PARENT);

-static SUNXI_CCU_GATE(hdmi_ddc_clk, "hdmi-ddc", "osc24M", 0x150, BIT(30), 0);
+static SUNXI_CCU_GATE(hdmi_ddc_clk, "ddc", "osc24M", 0x150, BIT(30), 0);

static SUNXI_CCU_GATE(ps_clk, "ps", "lcd1-ch1", 0x140, BIT(31), 0);

--
2.14.2

2017-09-29 08:25:11

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 10/14] drm/sun4i: hdmi: Add support for controller hardware variants

The HDMI controller found in earlier Allwinner SoCs have slight
differences between the A10, A10s, and the A31:

- Need different initial values for the PLL related registers

- Different behavior of the DDC and TMDS clocks

- Different register layout for the DDC portion

- Separate DDC parent clock on the A31

- Explicit reset control

For the A31, the HDMI TMDS clock has a different value offset for
the divider. The HDMI DDC block is different from the one in the
other SoCs. As far as the DDC clock goes, it has no pre-divider,
as it is clocked from a slower parent clock, not the TMDS clock.
The divider offset from the register value is different. And the
clock control register is at a different offset.

A new variant data structure is created to store pointers to the
above functions, structures, and the different initial values.
Another flag notates whether there is a separate DDC parent clock.
If not, the TMDS clock is passed to the DDC clock create function,
as before.

Regmap fields are used to deal with the different register layout
of the DDC block.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 72 +++++++++
drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c | 38 +++--
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 112 +++++++++++---
drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c | 227 ++++++++++++++++++++--------
drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 17 ++-
5 files changed, 369 insertions(+), 97 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index b95512ec8eb6..b1124a8f9f05 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -14,6 +14,7 @@

#include <drm/drm_connector.h>
#include <drm/drm_encoder.h>
+#include <linux/regmap.h>

#include <media/cec.h>

@@ -157,6 +158,55 @@ enum sun4i_hdmi_pkt_type {
SUN4I_HDMI_PKT_END = 15,
};

+struct sun4i_hdmi_variant {
+ bool has_ddc_parent_clk;
+ bool has_reset_control;
+
+ u32 pad_ctrl0_init_val;
+ u32 pad_ctrl1_init_val;
+ u32 pll_ctrl_init_val;
+
+ struct reg_field ddc_clk_reg;
+ u8 ddc_clk_pre_divider;
+ u8 ddc_clk_m_offset;
+
+ u8 tmds_clk_div_offset;
+
+ /* Register fields for I2C adapter */
+ struct reg_field field_ddc_en;
+ struct reg_field field_ddc_start;
+ struct reg_field field_ddc_reset;
+ struct reg_field field_ddc_addr_reg;
+ struct reg_field field_ddc_slave_addr;
+ struct reg_field field_ddc_int_mask;
+ struct reg_field field_ddc_int_status;
+ struct reg_field field_ddc_fifo_clear;
+ struct reg_field field_ddc_fifo_rx_thres;
+ struct reg_field field_ddc_fifo_tx_thres;
+ struct reg_field field_ddc_byte_count;
+ struct reg_field field_ddc_cmd;
+ struct reg_field field_ddc_sda_en;
+ struct reg_field field_ddc_sck_en;
+
+ /* DDC FIFO register offset */
+ u32 ddc_fifo_reg;
+
+ /*
+ * DDC FIFO threshold boundary conditions
+ *
+ * This is used to cope with the threshold boundary condition
+ * being slightly different on sun5i and sun6i.
+ *
+ * On sun5i the threshold is exclusive, i.e. does not include,
+ * the value of the threshold. ( > for RX; < for TX )
+ * On sun6i the threshold is inclusive, i.e. includes, the
+ * value of the threshold. ( >= for RX; <= for TX )
+ */
+ bool ddc_fifo_thres_incl;
+
+ bool ddc_fifo_has_dir;
+};
+
struct sun4i_hdmi {
struct drm_connector connector;
struct drm_encoder encoder;
@@ -165,9 +215,13 @@ struct sun4i_hdmi {
void __iomem *base;
struct regmap *regmap;

+ /* Reset control */
+ struct reset_control *reset;
+
/* Parent clocks */
struct clk *bus_clk;
struct clk *mod_clk;
+ struct clk *ddc_parent_clk;
struct clk *pll0_clk;
struct clk *pll1_clk;

@@ -177,10 +231,28 @@ struct sun4i_hdmi {

struct i2c_adapter *i2c;

+ /* Regmap fields for I2C adapter */
+ struct regmap_field *field_ddc_en;
+ struct regmap_field *field_ddc_start;
+ struct regmap_field *field_ddc_reset;
+ struct regmap_field *field_ddc_addr_reg;
+ struct regmap_field *field_ddc_slave_addr;
+ struct regmap_field *field_ddc_int_mask;
+ struct regmap_field *field_ddc_int_status;
+ struct regmap_field *field_ddc_fifo_clear;
+ struct regmap_field *field_ddc_fifo_rx_thres;
+ struct regmap_field *field_ddc_fifo_tx_thres;
+ struct regmap_field *field_ddc_byte_count;
+ struct regmap_field *field_ddc_cmd;
+ struct regmap_field *field_ddc_sda_en;
+ struct regmap_field *field_ddc_sck_en;
+
struct sun4i_drv *drv;

bool hdmi_monitor;
struct cec_adapter *cec_adap;
+
+ const struct sun4i_hdmi_variant *variant;
};

int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
index 4692e8c345ed..04f85b1cf922 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_ddc_clk.c
@@ -11,6 +11,7 @@
*/

#include <linux/clk-provider.h>
+#include <linux/regmap.h>

#include "sun4i_tcon.h"
#include "sun4i_hdmi.h"
@@ -18,6 +19,9 @@
struct sun4i_ddc {
struct clk_hw hw;
struct sun4i_hdmi *hdmi;
+ struct regmap_field *reg;
+ u8 pre_div;
+ u8 m_offset;
};

static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)
@@ -27,6 +31,8 @@ static inline struct sun4i_ddc *hw_to_ddc(struct clk_hw *hw)

static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
unsigned long parent_rate,
+ const u8 pre_div,
+ const u8 m_offset,
u8 *m, u8 *n)
{
unsigned long best_rate = 0;
@@ -36,7 +42,8 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long rate,
for (_n = 0; _n < 8; _n++) {
unsigned long tmp_rate;

- tmp_rate = (((parent_rate / 2) / 10) >> _n) / (_m + 1);
+ tmp_rate = (((parent_rate / pre_div) / 10) >> _n) /
+ (_m + m_offset);

if (tmp_rate > rate)
continue;
@@ -60,21 +67,25 @@ static unsigned long sun4i_ddc_calc_divider(unsigned long 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);
+ struct sun4i_ddc *ddc = hw_to_ddc(hw);
+
+ return sun4i_ddc_calc_divider(rate, *prate, ddc->pre_div,
+ ddc->m_offset, 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;
+ unsigned int reg;
u8 m, n;

- reg = readl(ddc->hdmi->base + SUN4I_HDMI_DDC_CLK_REG);
- m = (reg >> 3) & 0x7;
+ regmap_field_read(ddc->reg, &reg);
+ m = (reg >> 3) & 0xf;
n = reg & 0x7;

- return (((parent_rate / 2) / 10) >> n) / (m + 1);
+ return (((parent_rate / ddc->pre_div) / 10) >> n) /
+ (m + ddc->m_offset);
}

static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long rate,
@@ -83,10 +94,12 @@ static int sun4i_ddc_set_rate(struct clk_hw *hw, unsigned long 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);
+ sun4i_ddc_calc_divider(rate, parent_rate, ddc->pre_div,
+ ddc->m_offset, &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);
+ regmap_field_write(ddc->reg,
+ SUN4I_HDMI_DDC_CLK_M(div_m) |
+ SUN4I_HDMI_DDC_CLK_N(div_n));

return 0;
}
@@ -111,6 +124,11 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)
if (!ddc)
return -ENOMEM;

+ ddc->reg = devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->ddc_clk_reg);
+ if (IS_ERR(ddc->reg))
+ return PTR_ERR(ddc->reg);
+
init.name = "hdmi-ddc";
init.ops = &sun4i_ddc_ops;
init.parent_names = &parent_name;
@@ -118,6 +136,8 @@ int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *parent)

ddc->hdmi = hdmi;
ddc->hw.init = &init;
+ ddc->pre_div = hdmi->variant->ddc_clk_pre_divider;
+ ddc->m_offset = hdmi->variant->ddc_clk_m_offset;

hdmi->ddc_clk = devm_clk_register(hdmi->dev, &ddc->hw);
if (IS_ERR(hdmi->ddc_clk))
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index 5ab811cda00e..114cbe60b3e6 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -20,9 +20,11 @@
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/iopoll.h>
+#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
+#include <linux/reset.h>

#include "sun4i_backend.h"
#include "sun4i_crtc.h"
@@ -268,6 +270,60 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
};
#endif

+#define SUN4I_HDMI_PAD_CTRL1_MASK (GENMASK(24, 7) | GENMASK(5, 0))
+#define SUN4I_HDMI_PLL_CTRL_MASK (GENMASK(31, 8) | GENMASK(3, 0))
+
+static const struct sun4i_hdmi_variant sun5i_variant = {
+ .pad_ctrl0_init_val = 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,
+ .pad_ctrl1_init_val = 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,
+ .pll_ctrl_init_val = SUN4I_HDMI_PLL_CTRL_VCO_S(8) |
+ SUN4I_HDMI_PLL_CTRL_CS(7) |
+ SUN4I_HDMI_PLL_CTRL_CP_S(15) |
+ 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,
+
+ .ddc_clk_reg = REG_FIELD(SUN4I_HDMI_DDC_CLK_REG, 0, 6),
+ .ddc_clk_pre_divider = 2,
+ .ddc_clk_m_offset = 1,
+
+ .field_ddc_en = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 31, 31),
+ .field_ddc_start = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 30, 30),
+ .field_ddc_reset = REG_FIELD(SUN4I_HDMI_DDC_CTRL_REG, 0, 0),
+ .field_ddc_addr_reg = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 31),
+ .field_ddc_slave_addr = REG_FIELD(SUN4I_HDMI_DDC_ADDR_REG, 0, 6),
+ .field_ddc_int_status = REG_FIELD(SUN4I_HDMI_DDC_INT_STATUS_REG, 0, 8),
+ .field_ddc_fifo_clear = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 31, 31),
+ .field_ddc_fifo_rx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 4, 7),
+ .field_ddc_fifo_tx_thres = REG_FIELD(SUN4I_HDMI_DDC_FIFO_CTRL_REG, 0, 3),
+ .field_ddc_byte_count = REG_FIELD(SUN4I_HDMI_DDC_BYTE_COUNT_REG, 0, 9),
+ .field_ddc_cmd = REG_FIELD(SUN4I_HDMI_DDC_CMD_REG, 0, 2),
+ .field_ddc_sda_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 9, 9),
+ .field_ddc_sck_en = REG_FIELD(SUN4I_HDMI_DDC_LINE_CTRL_REG, 8, 8),
+
+ .ddc_fifo_reg = SUN4I_HDMI_DDC_FIFO_DATA_REG,
+ .ddc_fifo_has_dir = true,
+};
+
static const struct regmap_config sun4i_hdmi_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
@@ -293,6 +349,10 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
hdmi->dev = dev;
hdmi->drv = drv;

+ hdmi->variant = of_device_get_match_data(dev);
+ if (!hdmi->variant)
+ return -EINVAL;
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hdmi->base = devm_ioremap_resource(dev, res);
if (IS_ERR(hdmi->base)) {
@@ -300,10 +360,25 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
return PTR_ERR(hdmi->base);
}

+ if (hdmi->variant->has_reset_control) {
+ hdmi->reset = devm_reset_control_get(dev, NULL);
+ if (IS_ERR(hdmi->reset)) {
+ dev_err(dev, "Couldn't get the HDMI reset control\n");
+ return PTR_ERR(hdmi->reset);
+ }
+
+ ret = reset_control_deassert(hdmi->reset);
+ if (ret) {
+ dev_err(dev, "Couldn't deassert HDMI reset\n");
+ return ret;
+ }
+ }
+
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);
+ ret = PTR_ERR(hdmi->bus_clk);
+ goto err_assert_reset;
}
clk_prepare_enable(hdmi->bus_clk);

@@ -342,12 +417,19 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
goto err_disable_mod_clk;
}

+ if (hdmi->variant->has_ddc_parent_clk) {
+ hdmi->ddc_parent_clk = devm_clk_get(dev, "ddc");
+ if (IS_ERR(hdmi->ddc_parent_clk)) {
+ dev_err(dev, "Couldn't get the HDMI DDC clock\n");
+ return PTR_ERR(hdmi->ddc_parent_clk);
+ }
+ } else {
+ hdmi->ddc_parent_clk = hdmi->tmds_clk;
+ }
+
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,
+ writel(hdmi->variant->pad_ctrl0_init_val,
hdmi->base + SUN4I_HDMI_PAD_CTRL0_REG);

/*
@@ -357,24 +439,12 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
*/
reg = readl(hdmi->base + SUN4I_HDMI_PAD_CTRL1_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;
+ reg |= hdmi->variant->pad_ctrl1_init_val;
writel(reg, hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);

reg = readl(hdmi->base + SUN4I_HDMI_PLL_CTRL_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(15) | 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;
+ reg |= hdmi->variant->pll_ctrl_init_val;
writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);

ret = sun4i_hdmi_i2c_create(dev, hdmi);
@@ -444,6 +514,8 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
clk_disable_unprepare(hdmi->mod_clk);
err_disable_bus_clk:
clk_disable_unprepare(hdmi->bus_clk);
+err_assert_reset:
+ reset_control_assert(hdmi->reset);
return ret;
}

@@ -478,7 +550,7 @@ static int sun4i_hdmi_remove(struct platform_device *pdev)
}

static const struct of_device_id sun4i_hdmi_of_table[] = {
- { .compatible = "allwinner,sun5i-a10s-hdmi" },
+ { .compatible = "allwinner,sun5i-a10s-hdmi", .data = &sun5i_variant, },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_hdmi_of_table);
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
index 2e42d09ab42e..58e9d37e8c17 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_i2c.c
@@ -25,8 +25,6 @@

/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */
#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX
-/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */
-#define TX_THRESHOLD 1

static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
{
@@ -39,27 +37,36 @@ static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
u32 reg;
+ /*
+ * If threshold is inclusive, then the FIFO may only have
+ * RX_THRESHOLD number of bytes, instead of RX_THRESHOLD + 1.
+ */
+ int read_len = RX_THRESHOLD +
+ (hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);

- /* Limit transfer length by FIFO threshold */
- len = min_t(int, len, read ? (RX_THRESHOLD + 1) :
- (SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1));
+ /*
+ * Limit transfer length by FIFO threshold or FIFO size.
+ * For TX the threshold is for an empty FIFO.
+ */
+ len = min_t(int, len, read ? read_len : SUN4I_HDMI_DDC_FIFO_SIZE);

/* Wait until error, FIFO request bit set or transfer complete */
- if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg,
- reg & mask, len * byte_time_ns, 100000))
+ if (regmap_field_read_poll_timeout(hdmi->field_ddc_int_status, reg,
+ reg & mask, len * byte_time_ns,
+ 100000))
return -ETIMEDOUT;

if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK)
return -EIO;

if (read)
- readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
+ readsb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);
else
- writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
+ writesb(hdmi->base + hdmi->variant->ddc_fifo_reg, buf, len);

- /* Clear FIFO request bit */
- writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST,
- hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
+ /* Clear FIFO request bit by forcing a write to that bit */
+ regmap_field_force_write(hdmi->field_ddc_int_status,
+ SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST);

return len;
}
@@ -70,50 +77,52 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
u32 reg;

/* Set FIFO direction */
- reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
- reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
- reg |= (msg->flags & I2C_M_RD) ?
- SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
- SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
- writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+ if (hdmi->variant->ddc_fifo_has_dir) {
+ reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+ reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
+ reg |= (msg->flags & I2C_M_RD) ?
+ SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
+ SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
+ writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+ }
+
+ /* Clear address register (not cleared by soft reset) */
+ regmap_field_write(hdmi->field_ddc_addr_reg, 0);

/* Set I2C address */
- writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr),
- hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
-
- /* Set FIFO RX/TX thresholds and clear FIFO */
- reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
- reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR;
- reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK;
- reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD);
- reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK;
- reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD);
- writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
- if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG,
- reg,
- !(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR),
- 100, 2000))
+ regmap_field_write(hdmi->field_ddc_slave_addr, msg->addr);
+
+ /*
+ * Set FIFO RX/TX thresholds and clear FIFO
+ *
+ * If threshold is inclusive, we can set the TX threshold to
+ * 0 instead of 1.
+ */
+ regmap_field_write(hdmi->field_ddc_fifo_tx_thres,
+ hdmi->variant->ddc_fifo_thres_incl ? 0 : 1);
+ regmap_field_write(hdmi->field_ddc_fifo_rx_thres, RX_THRESHOLD);
+ regmap_field_write(hdmi->field_ddc_fifo_clear, 1);
+ if (regmap_field_read_poll_timeout(hdmi->field_ddc_fifo_clear,
+ reg, !reg, 100, 2000))
return -EIO;

/* Set transfer length */
- writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
+ regmap_field_write(hdmi->field_ddc_byte_count, msg->len);

/* Set command */
- writel(msg->flags & I2C_M_RD ?
- SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
- SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE,
- hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
+ regmap_field_write(hdmi->field_ddc_cmd,
+ msg->flags & I2C_M_RD ?
+ SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
+ SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE);

- /* Clear interrupt status bits */
- writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
- SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
- SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE,
- hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
+ /* Clear interrupt status bits by forcing a write */
+ regmap_field_force_write(hdmi->field_ddc_int_status,
+ SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
+ SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
+ SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE);

/* Start command */
- reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
- writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
- hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
+ regmap_field_write(hdmi->field_ddc_start, 1);

/* Transfer bytes */
for (i = 0; i < msg->len; i += len) {
@@ -124,14 +133,12 @@ static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
}

/* Wait for command to finish */
- if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG,
- reg,
- !(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
- 100, 100000))
+ if (regmap_field_read_poll_timeout(hdmi->field_ddc_start,
+ reg, !reg, 100, 100000))
return -EIO;

/* Check for errors */
- reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
+ regmap_field_read(hdmi->field_ddc_int_status, &reg);
if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
!(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
return -EIO;
@@ -154,20 +161,21 @@ static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap,
return -EINVAL;
}

+ /* DDC clock needs to be enabled for the module to work */
+ clk_prepare_enable(hdmi->ddc_clk);
+ clk_set_rate(hdmi->ddc_clk, 100000);
+
/* 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))
+ regmap_field_write(hdmi->field_ddc_en, 1);
+ regmap_field_write(hdmi->field_ddc_reset, 1);
+ if (regmap_field_read_poll_timeout(hdmi->field_ddc_reset,
+ reg, !reg, 100, 2000)) {
+ clk_disable_unprepare(hdmi->ddc_clk);
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_prepare_enable(hdmi->ddc_clk);
- clk_set_rate(hdmi->ddc_clk, 100000);
+ regmap_field_write(hdmi->field_ddc_sck_en, 1);
+ regmap_field_write(hdmi->field_ddc_sda_en, 1);

for (i = 0; i < num; i++) {
err = xfer_msg(hdmi, &msgs[i]);
@@ -191,12 +199,105 @@ static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = {
.functionality = sun4i_hdmi_i2c_func,
};

+static int sun4i_hdmi_init_regmap_fields(struct sun4i_hdmi *hdmi)
+{
+ hdmi->field_ddc_en =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_en);
+ if (IS_ERR(hdmi->field_ddc_en))
+ return PTR_ERR(hdmi->field_ddc_en);
+
+ hdmi->field_ddc_start =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_start);
+ if (IS_ERR(hdmi->field_ddc_start))
+ return PTR_ERR(hdmi->field_ddc_start);
+
+ hdmi->field_ddc_reset =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_reset);
+ if (IS_ERR(hdmi->field_ddc_reset))
+ return PTR_ERR(hdmi->field_ddc_reset);
+
+ hdmi->field_ddc_addr_reg =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_addr_reg);
+ if (IS_ERR(hdmi->field_ddc_addr_reg))
+ return PTR_ERR(hdmi->field_ddc_addr_reg);
+
+ hdmi->field_ddc_slave_addr =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_slave_addr);
+ if (IS_ERR(hdmi->field_ddc_slave_addr))
+ return PTR_ERR(hdmi->field_ddc_slave_addr);
+
+ hdmi->field_ddc_int_mask =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_int_mask);
+ if (IS_ERR(hdmi->field_ddc_int_mask))
+ return PTR_ERR(hdmi->field_ddc_int_mask);
+
+ hdmi->field_ddc_int_status =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_int_status);
+ if (IS_ERR(hdmi->field_ddc_int_status))
+ return PTR_ERR(hdmi->field_ddc_int_status);
+
+ hdmi->field_ddc_fifo_clear =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_fifo_clear);
+ if (IS_ERR(hdmi->field_ddc_fifo_clear))
+ return PTR_ERR(hdmi->field_ddc_fifo_clear);
+
+ hdmi->field_ddc_fifo_rx_thres =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_fifo_rx_thres);
+ if (IS_ERR(hdmi->field_ddc_fifo_rx_thres))
+ return PTR_ERR(hdmi->field_ddc_fifo_rx_thres);
+
+ hdmi->field_ddc_fifo_tx_thres =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_fifo_tx_thres);
+ if (IS_ERR(hdmi->field_ddc_fifo_tx_thres))
+ return PTR_ERR(hdmi->field_ddc_fifo_tx_thres);
+
+ hdmi->field_ddc_byte_count =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_byte_count);
+ if (IS_ERR(hdmi->field_ddc_byte_count))
+ return PTR_ERR(hdmi->field_ddc_byte_count);
+
+ hdmi->field_ddc_cmd =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_cmd);
+ if (IS_ERR(hdmi->field_ddc_cmd))
+ return PTR_ERR(hdmi->field_ddc_cmd);
+
+ hdmi->field_ddc_sda_en =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_sda_en);
+ if (IS_ERR(hdmi->field_ddc_sda_en))
+ return PTR_ERR(hdmi->field_ddc_sda_en);
+
+ hdmi->field_ddc_sck_en =
+ devm_regmap_field_alloc(hdmi->dev, hdmi->regmap,
+ hdmi->variant->field_ddc_sck_en);
+ if (IS_ERR(hdmi->field_ddc_sck_en))
+ return PTR_ERR(hdmi->field_ddc_sck_en);
+
+ return 0;
+}
+
int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi)
{
struct i2c_adapter *adap;
int ret = 0;

- ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
+ ret = sun4i_ddc_create(hdmi, hdmi->ddc_parent_clk);
+ if (ret)
+ return ret;
+
+ ret = sun4i_hdmi_init_regmap_fields(hdmi);
if (ret)
return ret;

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
index e8d4c311b80d..1b6b37aefc38 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -18,6 +18,8 @@
struct sun4i_tmds {
struct clk_hw hw;
struct sun4i_hdmi *hdmi;
+
+ u8 div_offset;
};

static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)
@@ -28,6 +30,7 @@ static inline struct sun4i_tmds *hw_to_tmds(struct clk_hw *hw)

static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
unsigned long parent_rate,
+ u8 div_offset,
u8 *div,
bool *half)
{
@@ -35,7 +38,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
u8 best_m = 0, m;
bool is_double;

- for (m = 1; m < 16; m++) {
+ for (m = div_offset ?: 1; m < (16 + div_offset); m++) {
u8 d;

for (d = 1; d < 3; d++) {
@@ -67,6 +70,7 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
static int sun4i_tmds_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
+ struct sun4i_tmds *tmds = hw_to_tmds(hw);
struct clk_hw *parent = NULL;
unsigned long best_parent = 0;
unsigned long rate = req->rate;
@@ -85,7 +89,8 @@ static int sun4i_tmds_determine_rate(struct clk_hw *hw,
continue;

for (i = 1; i < 3; i++) {
- for (j = 1; j < 16; j++) {
+ for (j = tmds->div_offset ?: 1;
+ j < (16 + tmds->div_offset); j++) {
unsigned long ideal = rate * i * j;
unsigned long rounded;

@@ -129,7 +134,7 @@ static unsigned long sun4i_tmds_recalc_rate(struct clk_hw *hw,
parent_rate /= 2;

reg = readl(tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
- reg = (reg >> 4) & 0xf;
+ reg = ((reg >> 4) & 0xf) + tmds->div_offset;
if (!reg)
reg = 1;

@@ -144,7 +149,8 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,
u32 reg;
u8 div;

- sun4i_tmds_calc_divider(rate, parent_rate, &div, &half);
+ sun4i_tmds_calc_divider(rate, parent_rate, tmds->div_offset,
+ &div, &half);

reg = readl(tmds->hdmi->base + SUN4I_HDMI_PAD_CTRL1_REG);
reg &= ~SUN4I_HDMI_PAD_CTRL1_HALVE_CLK;
@@ -154,7 +160,7 @@ static int sun4i_tmds_set_rate(struct clk_hw *hw, unsigned long rate,

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),
+ writel(reg | SUN4I_HDMI_PLL_CTRL_DIV(div - tmds->div_offset),
tmds->hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);

return 0;
@@ -221,6 +227,7 @@ int sun4i_tmds_create(struct sun4i_hdmi *hdmi)

tmds->hdmi = hdmi;
tmds->hw.init = &init;
+ tmds->div_offset = hdmi->variant->tmds_clk_div_offset;

hdmi->tmds_clk = devm_clk_register(hdmi->dev, &tmds->hw);
if (IS_ERR(hdmi->tmds_clk))
--
2.14.2

2017-09-29 08:25:09

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 09/14] regmap: add iopoll-like polling macro for regmap_field

This patch adds a macro regmap_field_read_poll_timeout that works
similar to the readx_poll_timeout defined in linux/iopoll.h, except
that this can also return the error value returned by a failed
regmap_field_read.

Signed-off-by: Chen-Yu Tsai <[email protected]>
---
include/linux/regmap.h | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)

diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 978abfbac617..93a4663d7acb 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -139,6 +139,45 @@ struct reg_sequence {
pollret ?: ((cond) ? 0 : -ETIMEDOUT); \
})

+/**
+ * regmap_field_read_poll_timeout - Poll until a condition is met or timeout
+ *
+ * @field: Regmap field to read from
+ * @val: Unsigned integer variable to read the value into
+ * @cond: Break condition (usually involving @val)
+ * @sleep_us: Maximum time to sleep between reads in us (0
+ * tight-loops). Should be less than ~20ms since usleep_range
+ * is used (see Documentation/timers/timers-howto.txt).
+ * @timeout_us: Timeout in us, 0 means never timeout
+ *
+ * Returns 0 on success and -ETIMEDOUT upon a timeout or the regmap_field_read
+ * error return value in case of a error read. In the two former cases,
+ * the last read value at @addr is stored in @val. Must not be called
+ * from atomic context if sleep_us or timeout_us are used.
+ *
+ * This is modelled after the readx_poll_timeout macros in linux/iopoll.h.
+ */
+#define regmap_field_read_poll_timeout(field, val, cond, sleep_us, timeout_us) \
+({ \
+ ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \
+ int pollret; \
+ might_sleep_if(sleep_us); \
+ for (;;) { \
+ pollret = regmap_field_read((field), &(val)); \
+ if (pollret) \
+ break; \
+ if (cond) \
+ break; \
+ if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \
+ pollret = regmap_field_read((field), &(val)); \
+ break; \
+ } \
+ if (sleep_us) \
+ usleep_range((sleep_us >> 2) + 1, sleep_us); \
+ } \
+ pollret ?: ((cond) ? 0 : -ETIMEDOUT); \
+})
+
#ifdef CONFIG_REGMAP

enum regmap_endian {
--
2.14.2

2017-09-29 08:23:25

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 04/14] drm/sun4i: tcon: Add support for demuxing TCON output on A31

On systems with 2 TCONs such as the A31, it is possible to demux the
output of the TCONs to one encoder.

Add support for this for the A31.

Signed-off-by: Chen-Yu Tsai <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_tcon.c | 38 ++++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)

diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
index 7bf51abaee97..c949309d4285 100644
--- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
+++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
@@ -112,6 +112,21 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
}
EXPORT_SYMBOL(sun4i_tcon_enable_vblank);

+static struct sun4i_tcon *sun4i_get_first_tcon(struct drm_device *drm)
+{
+ struct sun4i_drv *drv = drm->dev_private;
+ struct sun4i_tcon *tcon;
+
+ list_for_each_entry(tcon, &drv->tcon_list, list)
+ if (tcon->id == 0)
+ return tcon;
+
+ dev_warn(drm->dev,
+ "TCON0 not found, display output muxing may not work\n");
+
+ return tcon;
+}
+
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
struct drm_encoder *encoder)
{
@@ -777,6 +792,28 @@ static int sun5i_a13_tcon_set_mux(struct sun4i_tcon *tcon,
return regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, val);
}

+static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon,
+ struct drm_encoder *encoder)
+{
+ struct sun4i_tcon *tcon0 = sun4i_get_first_tcon(encoder->dev);
+ u32 shift;
+
+ switch (encoder->encoder_type) {
+ case DRM_MODE_ENCODER_TMDS:
+ /* HDMI */
+ shift = 8;
+ break;
+ default:
+ /* TODO A31 has MIPI DSI but A31s does not */
+ return -EINVAL;
+ }
+
+ regmap_update_bits(tcon0->regs, SUN4I_TCON_MUX_CTRL_REG,
+ 0x3 << shift, tcon->id << shift);
+
+ return 0;
+}
+
static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
.has_unknown_mux = true,
.has_channel_1 = true,
@@ -786,6 +823,7 @@ static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
.has_channel_1 = true,
.needs_de_be_mux = true,
+ .set_mux = sun6i_tcon_set_mux,
};

static const struct sun4i_tcon_quirks sun6i_a31s_quirks = {
--
2.14.2

2017-09-29 08:25:58

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 08/14] dt-bindings: display: sun4i: Add binding for A31 HDMI controller

The HDMI controller in the A31 SoC is slightly different from the
earlier version. In addition to the TMDS clock and DDC controls,
this version now takes a second DDC clock input.

Add a compatible string for it, and add the DDC clock input to the
list of clocks required.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Rob Herring <[email protected]>
---
Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt | 3 +++
1 file changed, 3 insertions(+)

diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
index 92441086caba..46df3b78ae9e 100644
--- a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
+++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
@@ -41,14 +41,17 @@ CEC. It is one end of the pipeline.
Required properties:
- compatible: value must be one of:
* allwinner,sun5i-a10s-hdmi
+ * allwinner,sun6i-a31-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
+ * ddc: the HDMI ddc clock (A31 only)
* pll-0: the first video PLL
* pll-1: the second video PLL
- clock-names: the clock names mentioned above
+ - resets: phandle to the reset control for the HDMI encoder (A31 only)
- 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
--
2.14.2

2017-09-29 08:25:56

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 06/14] drm/sun4i: hdmi: create a regmap for later use

The HDMI driver is written with readl/writel I/O to the registers.
However, to support the A31 variant, which has a different layout
for the DDC registers, it was recommended to use regfields to have
a cleaner implementation. To use regfields, we need to create an
underlying regmap.

This patch only adds the regmap. It does not convert the existing
driver accesses to use regmap.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_hdmi.h | 1 +
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 15 +++++++++++++++
2 files changed, 16 insertions(+)

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
index 1457750988da..b95512ec8eb6 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h
@@ -163,6 +163,7 @@ struct sun4i_hdmi {
struct device *dev;

void __iomem *base;
+ struct regmap *regmap;

/* Parent clocks */
struct clk *bus_clk;
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index 3cf1a6932fac..5ab811cda00e 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -22,6 +22,7 @@
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/regmap.h>

#include "sun4i_backend.h"
#include "sun4i_crtc.h"
@@ -267,6 +268,13 @@ static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
};
#endif

+static const struct regmap_config sun4i_hdmi_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x580,
+};
+
static int sun4i_hdmi_bind(struct device *dev, struct device *master,
void *data)
{
@@ -321,6 +329,13 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
goto err_disable_mod_clk;
}

+ hdmi->regmap = devm_regmap_init_mmio(dev, hdmi->base,
+ &sun4i_hdmi_regmap_config);
+ if (IS_ERR(hdmi->regmap)) {
+ dev_err(dev, "Couldn't create HDMI encoder regmap\n");
+ return PTR_ERR(hdmi->regmap);
+ }
+
ret = sun4i_tmds_create(hdmi);
if (ret) {
dev_err(dev, "Couldn't create the TMDS clock\n");
--
2.14.2

2017-09-29 08:25:53

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 07/14] drm/sun4i: hdmi: Allow using second PLL as TMDS clk parent

On SoCs with two display pipelines, it is possible that the two
pipelines are active at the same time, with potentially incompatible
dot clocks.

Let the HDMI encoder's TMDS clock go through all of its parents when
calculating possible clock rates. This allows usage of the second video
PLL as its parent.

Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c | 53 ++++++++++++++++-------------
1 file changed, 29 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
index 5cf2527bffc8..e8d4c311b80d 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_tmds_clk.c
@@ -67,11 +67,11 @@ static unsigned long sun4i_tmds_calc_divider(unsigned long rate,
static int sun4i_tmds_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
- struct clk_hw *parent;
+ struct clk_hw *parent = NULL;
unsigned long best_parent = 0;
unsigned long rate = req->rate;
int best_div = 1, best_half = 1;
- int i, j;
+ int i, j, p;

/*
* We only consider PLL3, since the TCON is very likely to be
@@ -79,32 +79,37 @@ static int sun4i_tmds_determine_rate(struct clk_hw *hw,
* 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;
+ for (p = 0; p < clk_hw_get_num_parents(hw); p++) {
+ parent = clk_hw_get_parent_by_index(hw, p);
+ if (!parent)
+ continue;
+
+ 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;
+ }
}
}
}

+ if (!parent)
+ return -EINVAL;
+
out:
req->rate = best_parent / best_half / best_div;
req->best_parent_rate = best_parent;
--
2.14.2

2017-09-29 08:27:19

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 05/14] drm/sun4i: hdmi: Disable clks in bind function error path and unbind function

The HDMI driver enables the bus and mod clocks in the bind function, but
does not disable them if it then bails our due to any errors. Neither
does it disable the clocks in the unbind function.

Fix this by adding a proper error path to the bind function, and
clk_disable_unprepare calls to the unbind function.

Also rename the err_cleanup_connector label to err_cleanup_encoder,
since it is the encoder that gets cleaned up.

Fixes: 9c5681011a0c ("drm/sun4i: Add HDMI support")
Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
index 9ea6cd5a1370..3cf1a6932fac 100644
--- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
+++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
@@ -302,26 +302,29 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
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);
+ ret = PTR_ERR(hdmi->mod_clk);
+ goto err_disable_bus_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);
+ ret = PTR_ERR(hdmi->pll0_clk);
+ goto err_disable_mod_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 = PTR_ERR(hdmi->pll1_clk);
+ goto err_disable_mod_clk;
}

ret = sun4i_tmds_create(hdmi);
if (ret) {
dev_err(dev, "Couldn't create the TMDS clock\n");
- return ret;
+ goto err_disable_mod_clk;
}

writel(SUN4I_HDMI_CTRL_ENABLE, hdmi->base + SUN4I_HDMI_CTRL_REG);
@@ -362,7 +365,7 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
ret = sun4i_hdmi_i2c_create(dev, hdmi);
if (ret) {
dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
- return ret;
+ goto err_disable_mod_clk;
}

drm_encoder_helper_add(&hdmi->encoder,
@@ -422,6 +425,10 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
drm_encoder_cleanup(&hdmi->encoder);
err_del_i2c_adapter:
i2c_del_adapter(hdmi->i2c);
+err_disable_mod_clk:
+ clk_disable_unprepare(hdmi->mod_clk);
+err_disable_bus_clk:
+ clk_disable_unprepare(hdmi->bus_clk);
return ret;
}

@@ -434,6 +441,8 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
drm_connector_cleanup(&hdmi->connector);
drm_encoder_cleanup(&hdmi->encoder);
i2c_del_adapter(hdmi->i2c);
+ clk_disable_unprepare(hdmi->mod_clk);
+ clk_disable_unprepare(hdmi->bus_clk);
}

static const struct component_ops sun4i_hdmi_ops = {
--
2.14.2

2017-09-29 08:27:44

by Chen-Yu Tsai

[permalink] [raw]
Subject: [PATCH v3 01/14] clk: sunxi-ng: sun6i: Export video PLLs

The 2x outputs of the 2 video PLL clocks are directly used by the
HDMI controller block.

Export them so they can be referenced in the device tree.

Fixes: c6e6c96d8fa6 ("clk: sunxi-ng: Add A31/A31s clocks")
Signed-off-by: Chen-Yu Tsai <[email protected]>
Acked-by: Maxime Ripard <[email protected]>
---
drivers/clk/sunxi-ng/ccu-sun6i-a31.h | 8 ++++++--
include/dt-bindings/clock/sun6i-a31-ccu.h | 4 ++++
2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/clk/sunxi-ng/ccu-sun6i-a31.h b/drivers/clk/sunxi-ng/ccu-sun6i-a31.h
index 4e434011e9e7..27e6ad4133ab 100644
--- a/drivers/clk/sunxi-ng/ccu-sun6i-a31.h
+++ b/drivers/clk/sunxi-ng/ccu-sun6i-a31.h
@@ -27,7 +27,9 @@
#define CLK_PLL_AUDIO_4X 4
#define CLK_PLL_AUDIO_8X 5
#define CLK_PLL_VIDEO0 6
-#define CLK_PLL_VIDEO0_2X 7
+
+/* The PLL_VIDEO0_2X clock is exported */
+
#define CLK_PLL_VE 8
#define CLK_PLL_DDR 9

@@ -35,7 +37,9 @@

#define CLK_PLL_PERIPH_2X 11
#define CLK_PLL_VIDEO1 12
-#define CLK_PLL_VIDEO1_2X 13
+
+/* The PLL_VIDEO1_2X clock is exported */
+
#define CLK_PLL_GPU 14
#define CLK_PLL_MIPI 15
#define CLK_PLL9 16
diff --git a/include/dt-bindings/clock/sun6i-a31-ccu.h b/include/dt-bindings/clock/sun6i-a31-ccu.h
index 4482530fb6f5..c5d13340184a 100644
--- a/include/dt-bindings/clock/sun6i-a31-ccu.h
+++ b/include/dt-bindings/clock/sun6i-a31-ccu.h
@@ -43,8 +43,12 @@
#ifndef _DT_BINDINGS_CLK_SUN6I_A31_H_
#define _DT_BINDINGS_CLK_SUN6I_A31_H_

+#define CLK_PLL_VIDEO0_2X 7
+
#define CLK_PLL_PERIPH 10

+#define CLK_PLL_VIDEO1_2X 13
+
#define CLK_CPU 18

#define CLK_AHB1_MIPIDSI 23
--
2.14.2

2017-09-29 08:47:13

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH v3 01/14] clk: sunxi-ng: sun6i: Export video PLLs

On Fri, Sep 29, 2017 at 08:22:53AM +0000, Chen-Yu Tsai wrote:
> The 2x outputs of the 2 video PLL clocks are directly used by the
> HDMI controller block.
>
> Export them so they can be referenced in the device tree.
>
> Fixes: c6e6c96d8fa6 ("clk: sunxi-ng: Add A31/A31s clocks")
> Signed-off-by: Chen-Yu Tsai <[email protected]>
> Acked-by: Maxime Ripard <[email protected]>

Applied, thanks!
Maxime

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


Attachments:
(No filename) (513.00 B)
signature.asc (801.00 B)
Download all attachments

2017-09-29 08:48:11

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH v3 02/14] clk: sunxi-ng: sun6i: Rename HDMI DDC clock to avoid name collision

On Fri, Sep 29, 2017 at 08:22:54AM +0000, Chen-Yu Tsai wrote:
> The HDMI DDC clock found in the CCU is the parent of the actual DDC
> clock within the HDMI controller. That clock is also named "hdmi-ddc".
>
> Rename the one in the CCU to "ddc". This makes more sense than renaming
> the one in the HDMI controller to something else.
>
> Fixes: c6e6c96d8fa6 ("clk: sunxi-ng: Add A31/A31s clocks")
> Signed-off-by: Chen-Yu Tsai <[email protected]>

Applied, thanks!
Maxime

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


Attachments:
(No filename) (568.00 B)
signature.asc (801.00 B)
Download all attachments

2017-09-29 10:19:22

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH v3 03/14] drm/sun4i: tcon: Add variant callback for TCON output muxing

On Fri, Sep 29, 2017 at 08:22:55AM +0000, Chen-Yu Tsai wrote:
> static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
> - .has_unknown_mux = true,
> - .has_channel_1 = true,
> + .has_unknown_mux = true,
> + .has_channel_1 = true,
> + .set_mux = sun5i_a13_tcon_set_mux,

I guess we could even retire has_unknown_mux now, since it provides
the same information than if set_mux is set or to NULL.

Maxime

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


Attachments:
(No filename) (509.00 B)
signature.asc (801.00 B)
Download all attachments

2017-09-29 10:20:14

by Maxime Ripard

[permalink] [raw]
Subject: Re: [PATCH v3 04/14] drm/sun4i: tcon: Add support for demuxing TCON output on A31

On Fri, Sep 29, 2017 at 08:22:56AM +0000, Chen-Yu Tsai wrote:
> On systems with 2 TCONs such as the A31, it is possible to demux the
> output of the TCONs to one encoder.
>
> Add support for this for the A31.
>
> Signed-off-by: Chen-Yu Tsai <[email protected]>
> ---
> drivers/gpu/drm/sun4i/sun4i_tcon.c | 38 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 38 insertions(+)
>
> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> index 7bf51abaee97..c949309d4285 100644
> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
> @@ -112,6 +112,21 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
> }
> EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>
> +static struct sun4i_tcon *sun4i_get_first_tcon(struct drm_device *drm)

Would that make sense to make it a bit more generic, and pass the id
to look for as an argument?

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


Attachments:
(No filename) (0.99 kB)
signature.asc (801.00 B)
Download all attachments

2017-09-29 10:23:10

by Chen-Yu Tsai

[permalink] [raw]
Subject: Re: [PATCH v3 04/14] drm/sun4i: tcon: Add support for demuxing TCON output on A31

On Fri, Sep 29, 2017 at 6:20 PM, Maxime Ripard
<[email protected]> wrote:
> On Fri, Sep 29, 2017 at 08:22:56AM +0000, Chen-Yu Tsai wrote:
>> On systems with 2 TCONs such as the A31, it is possible to demux the
>> output of the TCONs to one encoder.
>>
>> Add support for this for the A31.
>>
>> Signed-off-by: Chen-Yu Tsai <[email protected]>
>> ---
>> drivers/gpu/drm/sun4i/sun4i_tcon.c | 38 ++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 38 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>> index 7bf51abaee97..c949309d4285 100644
>> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
>> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>> @@ -112,6 +112,21 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>> }
>> EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>>
>> +static struct sun4i_tcon *sun4i_get_first_tcon(struct drm_device *drm)
>
> Would that make sense to make it a bit more generic, and pass the id
> to look for as an argument?

The reason to look for TCON0 explicitly is to access the muxing registers, which
are only available in TCON0. Other than that, there's nothing else
shared between
the two TCONs. So there's no particular reason to look for TCON1 explicitly.

ChenYu

2017-09-30 05:36:23

by Julian Calaby

[permalink] [raw]
Subject: Re: [linux-sunxi] Re: [PATCH v3 04/14] drm/sun4i: tcon: Add support for demuxing TCON output on A31

Hi Chen-Yu,

On Fri, Sep 29, 2017 at 8:22 PM, Chen-Yu Tsai <[email protected]> wrote:
> On Fri, Sep 29, 2017 at 6:20 PM, Maxime Ripard
> <[email protected]> wrote:
>> On Fri, Sep 29, 2017 at 08:22:56AM +0000, Chen-Yu Tsai wrote:
>>> On systems with 2 TCONs such as the A31, it is possible to demux the
>>> output of the TCONs to one encoder.
>>>
>>> Add support for this for the A31.
>>>
>>> Signed-off-by: Chen-Yu Tsai <[email protected]>
>>> ---
>>> drivers/gpu/drm/sun4i/sun4i_tcon.c | 38 ++++++++++++++++++++++++++++++++++++++
>>> 1 file changed, 38 insertions(+)
>>>
>>> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>> index 7bf51abaee97..c949309d4285 100644
>>> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>> @@ -112,6 +112,21 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>>> }
>>> EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>>>
>>> +static struct sun4i_tcon *sun4i_get_first_tcon(struct drm_device *drm)
>>
>> Would that make sense to make it a bit more generic, and pass the id
>> to look for as an argument?
>
> The reason to look for TCON0 explicitly is to access the muxing registers, which
> are only available in TCON0. Other than that, there's nothing else
> shared between
> the two TCONs. So there's no particular reason to look for TCON1 explicitly.

In that case: in the bizarre case where we're trying to use this mux
type and there is no TCON0, shouldn't we fail?

(Also, the code doesn't make sense if we have some TCON1 and TCON2 in
that order as it'll return TCON2)

Thanks,

--
Julian Calaby

Email: [email protected]
Profile: http://www.google.com/profiles/julian.calaby/

2017-09-30 05:58:55

by Chen-Yu Tsai

[permalink] [raw]
Subject: Re: [linux-sunxi] Re: [PATCH v3 04/14] drm/sun4i: tcon: Add support for demuxing TCON output on A31

On Sat, Sep 30, 2017 at 1:35 PM, Julian Calaby <[email protected]> wrote:
> Hi Chen-Yu,
>
> On Fri, Sep 29, 2017 at 8:22 PM, Chen-Yu Tsai <[email protected]> wrote:
>> On Fri, Sep 29, 2017 at 6:20 PM, Maxime Ripard
>> <[email protected]> wrote:
>>> On Fri, Sep 29, 2017 at 08:22:56AM +0000, Chen-Yu Tsai wrote:
>>>> On systems with 2 TCONs such as the A31, it is possible to demux the
>>>> output of the TCONs to one encoder.
>>>>
>>>> Add support for this for the A31.
>>>>
>>>> Signed-off-by: Chen-Yu Tsai <[email protected]>
>>>> ---
>>>> drivers/gpu/drm/sun4i/sun4i_tcon.c | 38 ++++++++++++++++++++++++++++++++++++++
>>>> 1 file changed, 38 insertions(+)
>>>>
>>>> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>>> index 7bf51abaee97..c949309d4285 100644
>>>> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>>> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>>> @@ -112,6 +112,21 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>>>> }
>>>> EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>>>>
>>>> +static struct sun4i_tcon *sun4i_get_first_tcon(struct drm_device *drm)
>>>
>>> Would that make sense to make it a bit more generic, and pass the id
>>> to look for as an argument?
>>
>> The reason to look for TCON0 explicitly is to access the muxing registers, which
>> are only available in TCON0. Other than that, there's nothing else
>> shared between
>> the two TCONs. So there's no particular reason to look for TCON1 explicitly.
>
> In that case: in the bizarre case where we're trying to use this mux
> type and there is no TCON0, shouldn't we fail?

It gives out a big warning, indicating something is wrong. If TCON0 is not found
it is most likely your device tree is broken. There's nothing more the
driver can do.
Are you suggesting to return NULL in this case, and also do error
handling in the
callers?

> (Also, the code doesn't make sense if we have some TCON1 and TCON2 in
> that order as it'll return TCON2)

I'm guessing you want it to return NULL.

ChenYu

2017-09-30 06:26:56

by Julian Calaby

[permalink] [raw]
Subject: Re: [linux-sunxi] Re: [PATCH v3 04/14] drm/sun4i: tcon: Add support for demuxing TCON output on A31

Hi Chen-Yu,

On Sat, Sep 30, 2017 at 3:58 PM, Chen-Yu Tsai <[email protected]> wrote:
> On Sat, Sep 30, 2017 at 1:35 PM, Julian Calaby <[email protected]> wrote:
>> Hi Chen-Yu,
>>
>> On Fri, Sep 29, 2017 at 8:22 PM, Chen-Yu Tsai <[email protected]> wrote:
>>> On Fri, Sep 29, 2017 at 6:20 PM, Maxime Ripard
>>> <[email protected]> wrote:
>>>> On Fri, Sep 29, 2017 at 08:22:56AM +0000, Chen-Yu Tsai wrote:
>>>>> On systems with 2 TCONs such as the A31, it is possible to demux the
>>>>> output of the TCONs to one encoder.
>>>>>
>>>>> Add support for this for the A31.
>>>>>
>>>>> Signed-off-by: Chen-Yu Tsai <[email protected]>
>>>>> ---
>>>>> drivers/gpu/drm/sun4i/sun4i_tcon.c | 38 ++++++++++++++++++++++++++++++++++++++
>>>>> 1 file changed, 38 insertions(+)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>>>> index 7bf51abaee97..c949309d4285 100644
>>>>> --- a/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>>>> +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c
>>>>> @@ -112,6 +112,21 @@ void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable)
>>>>> }
>>>>> EXPORT_SYMBOL(sun4i_tcon_enable_vblank);
>>>>>
>>>>> +static struct sun4i_tcon *sun4i_get_first_tcon(struct drm_device *drm)
>>>>
>>>> Would that make sense to make it a bit more generic, and pass the id
>>>> to look for as an argument?
>>>
>>> The reason to look for TCON0 explicitly is to access the muxing registers, which
>>> are only available in TCON0. Other than that, there's nothing else
>>> shared between
>>> the two TCONs. So there's no particular reason to look for TCON1 explicitly.
>>
>> In that case: in the bizarre case where we're trying to use this mux
>> type and there is no TCON0, shouldn't we fail?
>
> It gives out a big warning, indicating something is wrong. If TCON0 is not found
> it is most likely your device tree is broken. There's nothing more the
> driver can do.
> Are you suggesting to return NULL in this case, and also do error
> handling in the
> callers?

You're already returning -EINVAL for other failure cases, so a lack of
TCON0 might as well do the same.

>> (Also, the code doesn't make sense if we have some TCON1 and TCON2 in
>> that order as it'll return TCON2)
>
> I'm guessing you want it to return NULL.

I'm just pointing out the mismatch between getting the "first" TCON
and the actual behaviour.

Thanks,

--
Julian Calaby

Email: [email protected]
Profile: http://www.google.com/profiles/julian.calaby/