Merge airlied/drm-next into drm-misc-next
authorSean Paul <seanpaul@chromium.org>
Fri, 16 Feb 2018 20:47:43 +0000 (15:47 -0500)
committerSean Paul <seanpaul@chromium.org>
Fri, 16 Feb 2018 20:47:43 +0000 (15:47 -0500)
Backmerge 4.15 and hdcp topic branch

Signed-off-by: Sean Paul <seanpaul@chromium.org>
17 files changed:
Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt [new file with mode: 0644]
Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt
drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
drivers/gpu/drm/imx/dw_hdmi-imx.c
drivers/gpu/drm/meson/meson_dw_hdmi.c
drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c
drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c
drivers/gpu/drm/sun4i/Kconfig
drivers/gpu/drm/sun4i/Makefile
drivers/gpu/drm/sun4i/sun4i_tcon.c
drivers/gpu/drm/sun4i/sun4i_tcon.h
drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c [new file with mode: 0644]
drivers/gpu/drm/sun4i/sun8i_mixer.c
include/drm/bridge/dw_hdmi.h
include/drm/drm_dp_helper.h

diff --git a/Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt b/Documentation/devicetree/bindings/display/rockchip/cdn-dp-rockchip.txt
new file mode 100644 (file)
index 0000000..8df7d2e
--- /dev/null
@@ -0,0 +1,74 @@
+Rockchip RK3399 specific extensions to the cdn Display Port
+================================
+
+Required properties:
+- compatible: must be "rockchip,rk3399-cdn-dp"
+
+- reg: physical base address of the controller and length
+
+- clocks: from common clock binding: handle to dp clock.
+
+- clock-names: from common clock binding:
+              Required elements: "core-clk" "pclk" "spdif" "grf"
+
+- resets : a list of phandle + reset specifier pairs
+- reset-names : string of reset names
+               Required elements: "apb", "core", "dptx", "spdif"
+- power-domains : power-domain property defined with a phandle
+                 to respective power domain.
+- assigned-clocks: main clock, should be <&cru SCLK_DP_CORE>
+- assigned-clock-rates : the DP core clk frequency, shall be: 100000000
+
+- rockchip,grf: this soc should set GRF regs, so need get grf here.
+
+- ports: contain a port nodes with endpoint definitions as defined in
+        Documentation/devicetree/bindings/media/video-interfaces.txt.
+        contained 2 endpoints, connecting to the output of vop.
+
+- phys: from general PHY binding: the phandle for the PHY device.
+
+- extcon: extcon specifier for the Power Delivery
+
+- #sound-dai-cells = it must be 1 if your system is using 2 DAIs: I2S, SPDIF
+
+-------------------------------------------------------------------------------
+
+Example:
+       cdn_dp: dp@fec00000 {
+               compatible = "rockchip,rk3399-cdn-dp";
+               reg = <0x0 0xfec00000 0x0 0x100000>;
+               interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
+               clocks = <&cru SCLK_DP_CORE>, <&cru PCLK_DP_CTRL>,
+                        <&cru SCLK_SPDIF_REC_DPTX>, <&cru PCLK_VIO_GRF>;
+               clock-names = "core-clk", "pclk", "spdif", "grf";
+               assigned-clocks = <&cru SCLK_DP_CORE>;
+               assigned-clock-rates = <100000000>;
+               power-domains = <&power RK3399_PD_HDCP>;
+               phys = <&tcphy0_dp>, <&tcphy1_dp>;
+               resets = <&cru SRST_DPTX_SPDIF_REC>;
+               reset-names = "spdif";
+               extcon = <&fusb0>, <&fusb1>;
+               rockchip,grf = <&grf>;
+               #address-cells = <1>;
+               #size-cells = <0>;
+               #sound-dai-cells = <1>;
+
+               ports {
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+
+                       dp_in: port {
+                               #address-cells = <1>;
+                               #size-cells = <0>;
+                               dp_in_vopb: endpoint@0 {
+                                       reg = <0>;
+                                       remote-endpoint = <&vopb_out_dp>;
+                               };
+
+                               dp_in_vopl: endpoint@1 {
+                                       reg = <1>;
+                                       remote-endpoint = <&vopl_out_dp>;
+                               };
+                       };
+               };
+       };
index cd626ee1147a2c92da8592dbd6a48728b54181e0..b995bfee734aeb7976b5ff2bae916cd8c39971be 100644 (file)
@@ -64,6 +64,52 @@ Required properties:
     first port should be the input endpoint. The second should be the
     output, usually to an HDMI connector.
 
+DWC HDMI TX Encoder
+-------------------
+
+The HDMI transmitter is a Synopsys DesignWare HDMI 1.4 TX controller IP
+with Allwinner's own PHY IP. It supports audio and video outputs and CEC.
+
+These DT bindings follow the Synopsys DWC HDMI TX bindings defined in
+Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt with the
+following device-specific properties.
+
+Required properties:
+
+  - compatible: value must be one of:
+    * "allwinner,sun8i-a83t-dw-hdmi"
+  - reg: base address and size of memory-mapped region
+  - reg-io-width: See dw_hdmi.txt. Shall be 1.
+  - interrupts: HDMI interrupt number
+  - clocks: phandles to the clocks feeding the HDMI encoder
+    * iahb: the HDMI bus clock
+    * isfr: the HDMI register clock
+    * tmds: TMDS clock
+  - clock-names: the clock names mentioned above
+  - resets: phandle to the reset controller
+  - reset-names: must be "ctrl"
+  - phys: phandle to the DWC HDMI PHY
+  - phy-names: must be "phy"
+
+  - 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.
+
+DWC HDMI PHY
+------------
+
+Required properties:
+  - compatible: value must be one of:
+    * allwinner,sun8i-a83t-hdmi-phy
+  - reg: base address and size of memory-mapped region
+  - clocks: phandles to the clocks feeding the HDMI PHY
+    * bus: the HDMI PHY interface clock
+    * mod: the HDMI PHY module clock
+  - clock-names: the clock names mentioned above
+  - resets: phandle to the reset controller driving the PHY
+  - reset-names: must be "phy"
+
 TV Encoder
 ----------
 
@@ -94,24 +140,26 @@ Required properties:
    * allwinner,sun7i-a20-tcon
    * allwinner,sun8i-a33-tcon
    * allwinner,sun8i-a83t-tcon-lcd
+   * allwinner,sun8i-a83t-tcon-tv
    * allwinner,sun8i-v3s-tcon
  - reg: base address and size of memory-mapped region
  - interrupts: interrupt associated to this IP
- - clocks: phandles to the clocks feeding the TCON. Three are needed:
+ - clocks: phandles to the clocks feeding the TCON.
    - 'ahb': the interface clocks
-   - 'tcon-ch0': The clock driving the TCON channel 0
+   - 'tcon-ch0': The clock driving the TCON channel 0, except for A83T TV TCON
  - resets: phandles to the reset controllers driving the encoder
    - "lcd": the reset line for the TCON channel 0
 
  - clock-names: the clock names mentioned above
  - reset-names: the reset names mentioned above
- - clock-output-names: Name of the pixel clock created
+ - clock-output-names: Name of the pixel clock created, if TCON supports
+   channel 0.
 
 - 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 one the output
 
-  The output may have multiple endpoints. The TCON has two channels,
+  The output may have multiple endpoints. TCON can have 1 or 2 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
@@ -122,8 +170,8 @@ Required properties:
 On SoCs other than the A33 and V3s, there is one more clock required:
    - 'tcon-ch1': The clock driving the TCON channel 1
 
-On SoCs that support LVDS (all SoCs but the A13, H3, H5 and V3s), you
-need one more reset line:
+When TCON support LVDS (all TCONs except TV TCON on A83T and those found
+in A13, H3, H5 and V3s SoCs), you need one more reset line:
    - 'lvds': The reset line driving the LVDS logic
 
 And on the A23, A31, A31s and A33, you need one more clock line:
@@ -226,6 +274,7 @@ supported.
 Required properties:
   - compatible: value must be one of:
     * allwinner,sun8i-a83t-de2-mixer-0
+    * allwinner,sun8i-a83t-de2-mixer-1
     * allwinner,sun8i-v3s-de2-mixer
   - reg: base address and size of the memory-mapped region.
   - clocks: phandles to the clocks feeding the mixer
index a38db40ce990de2c572fd0b0c2aca42d8273611c..f9802399cc0de27dae4131a20ceab434b370114f 100644 (file)
@@ -1037,19 +1037,21 @@ static void dw_hdmi_phy_enable_svsret(struct dw_hdmi *hdmi, u8 enable)
                         HDMI_PHY_CONF0_SVSRET_MASK);
 }
 
-static void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable)
+void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable)
 {
        hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
                         HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET,
                         HDMI_PHY_CONF0_GEN2_PDDQ_MASK);
 }
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_pddq);
 
-static void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable)
+void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable)
 {
        hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
                         HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET,
                         HDMI_PHY_CONF0_GEN2_TXPWRON_MASK);
 }
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_txpwron);
 
 static void dw_hdmi_phy_sel_data_en_pol(struct dw_hdmi *hdmi, u8 enable)
 {
@@ -1065,6 +1067,22 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable)
                         HDMI_PHY_CONF0_SELDIPIF_MASK);
 }
 
+void dw_hdmi_phy_reset(struct dw_hdmi *hdmi)
+{
+       /* PHY reset. The reset signal is active high on Gen2 PHYs. */
+       hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
+       hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
+
+void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address)
+{
+       hdmi_phy_test_clear(hdmi, 1);
+       hdmi_writeb(hdmi, address, HDMI_PHY_I2CM_SLAVE_ADDR);
+       hdmi_phy_test_clear(hdmi, 0);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_i2c_set_addr);
+
 static void dw_hdmi_phy_power_off(struct dw_hdmi *hdmi)
 {
        const struct dw_hdmi_phy_data *phy = hdmi->phy.data;
@@ -1203,16 +1221,11 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
        if (phy->has_svsret)
                dw_hdmi_phy_enable_svsret(hdmi, 1);
 
-       /* PHY reset. The reset signal is active high on Gen2 PHYs. */
-       hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
-       hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
+       dw_hdmi_phy_reset(hdmi);
 
        hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
 
-       hdmi_phy_test_clear(hdmi, 1);
-       hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
-                   HDMI_PHY_I2CM_SLAVE_ADDR);
-       hdmi_phy_test_clear(hdmi, 0);
+       dw_hdmi_phy_i2c_set_addr(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2);
 
        /* Write to the PHY as configured by the platform */
        if (pdata->configure_phy)
@@ -1251,15 +1264,16 @@ static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
        dw_hdmi_phy_power_off(hdmi);
 }
 
-static enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
-                                                     void *data)
+enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
+                                              void *data)
 {
        return hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_HPD ?
                connector_status_connected : connector_status_disconnected;
 }
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_read_hpd);
 
-static void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
-                                  bool force, bool disabled, bool rxsense)
+void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
+                           bool force, bool disabled, bool rxsense)
 {
        u8 old_mask = hdmi->phy_mask;
 
@@ -1271,8 +1285,9 @@ static void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
        if (old_mask != hdmi->phy_mask)
                hdmi_writeb(hdmi, hdmi->phy_mask, HDMI_PHY_MASK0);
 }
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_update_hpd);
 
-static void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
+void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
 {
        /*
         * Configure the PHY RX SENSE and HPD interrupts polarities and clear
@@ -1291,6 +1306,7 @@ static void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data)
        hdmi_writeb(hdmi, ~(HDMI_IH_PHY_STAT0_HPD | HDMI_IH_PHY_STAT0_RX_SENSE),
                    HDMI_IH_MUTE_PHY_STAT0);
 }
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_setup_hpd);
 
 static const struct dw_hdmi_phy_ops dw_hdmi_synopsys_phy_ops = {
        .init = dw_hdmi_phy_init,
@@ -1634,9 +1650,10 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
         * then write one of the FC registers several times.
         *
         * The number of iterations matters and depends on the HDMI TX revision
-        * (and possibly on the platform). So far only i.MX6Q (v1.30a) and
-        * i.MX6DL (v1.31a) have been identified as needing the workaround, with
-        * 4 and 1 iterations respectively.
+        * (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL
+        * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified
+        * as needing the workaround, with 4 iterations for v1.30a and 1
+        * iteration for others.
         */
 
        switch (hdmi->version) {
@@ -1644,6 +1661,7 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
                count = 4;
                break;
        case 0x131a:
+       case 0x132a:
                count = 1;
                break;
        default:
@@ -2525,8 +2543,6 @@ __dw_hdmi_probe(struct platform_device *pdev,
        if (hdmi->i2c)
                dw_hdmi_i2c_init(hdmi);
 
-       platform_set_drvdata(pdev, hdmi);
-
        return hdmi;
 
 err_iahb:
@@ -2576,25 +2592,23 @@ static void __dw_hdmi_remove(struct dw_hdmi *hdmi)
 /* -----------------------------------------------------------------------------
  * Probe/remove API, used from platforms based on the DRM bridge API.
  */
-int dw_hdmi_probe(struct platform_device *pdev,
-                 const struct dw_hdmi_plat_data *plat_data)
+struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
+                             const struct dw_hdmi_plat_data *plat_data)
 {
        struct dw_hdmi *hdmi;
 
        hdmi = __dw_hdmi_probe(pdev, plat_data);
        if (IS_ERR(hdmi))
-               return PTR_ERR(hdmi);
+               return hdmi;
 
        drm_bridge_add(&hdmi->bridge);
 
-       return 0;
+       return hdmi;
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_probe);
 
-void dw_hdmi_remove(struct platform_device *pdev)
+void dw_hdmi_remove(struct dw_hdmi *hdmi)
 {
-       struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
-
        drm_bridge_remove(&hdmi->bridge);
 
        __dw_hdmi_remove(hdmi);
@@ -2604,31 +2618,30 @@ EXPORT_SYMBOL_GPL(dw_hdmi_remove);
 /* -----------------------------------------------------------------------------
  * Bind/unbind API, used from platforms based on the component framework.
  */
-int dw_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder,
-                const struct dw_hdmi_plat_data *plat_data)
+struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
+                            struct drm_encoder *encoder,
+                            const struct dw_hdmi_plat_data *plat_data)
 {
        struct dw_hdmi *hdmi;
        int ret;
 
        hdmi = __dw_hdmi_probe(pdev, plat_data);
        if (IS_ERR(hdmi))
-               return PTR_ERR(hdmi);
+               return hdmi;
 
        ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL);
        if (ret) {
-               dw_hdmi_remove(pdev);
+               dw_hdmi_remove(hdmi);
                DRM_ERROR("Failed to initialize bridge with drm\n");
-               return ret;
+               return ERR_PTR(ret);
        }
 
-       return 0;
+       return hdmi;
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_bind);
 
-void dw_hdmi_unbind(struct device *dev)
+void dw_hdmi_unbind(struct dw_hdmi *hdmi)
 {
-       struct dw_hdmi *hdmi = dev_get_drvdata(dev);
-
        __dw_hdmi_remove(hdmi);
 }
 EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
index b62763aa87069fb3eccb9787dfe73f6ae33a45e2..fe6becdcc29edc8dc4fd105c932375a53c5e5865 100644 (file)
@@ -25,6 +25,7 @@
 struct imx_hdmi {
        struct device *dev;
        struct drm_encoder encoder;
+       struct dw_hdmi *hdmi;
        struct regmap *regmap;
 };
 
@@ -239,14 +240,18 @@ static int dw_hdmi_imx_bind(struct device *dev, struct device *master,
        drm_encoder_init(drm, encoder, &dw_hdmi_imx_encoder_funcs,
                         DRM_MODE_ENCODER_TMDS, NULL);
 
-       ret = dw_hdmi_bind(pdev, encoder, plat_data);
+       platform_set_drvdata(pdev, hdmi);
+
+       hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
 
        /*
         * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
         * which would have called the encoder cleanup.  Do it manually.
         */
-       if (ret)
+       if (IS_ERR(hdmi->hdmi)) {
+               ret = PTR_ERR(hdmi->hdmi);
                drm_encoder_cleanup(encoder);
+       }
 
        return ret;
 }
@@ -254,7 +259,9 @@ static int dw_hdmi_imx_bind(struct device *dev, struct device *master,
 static void dw_hdmi_imx_unbind(struct device *dev, struct device *master,
                               void *data)
 {
-       return dw_hdmi_unbind(dev);
+       struct imx_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dw_hdmi_unbind(hdmi->hdmi);
 }
 
 static const struct component_ops dw_hdmi_imx_ops = {
index 17de3afd98f6a6a3cae133944024c0ad2c41aa6c..d49af17310c99229a6af937f4f5adc3b5eccf212 100644 (file)
@@ -140,6 +140,7 @@ struct meson_dw_hdmi {
        struct clk *venci_clk;
        struct regulator *hdmi_supply;
        u32 irq_stat;
+       struct dw_hdmi *hdmi;
 };
 #define encoder_to_meson_dw_hdmi(x) \
        container_of(x, struct meson_dw_hdmi, encoder)
@@ -302,7 +303,7 @@ static void meson_hdmi_phy_setup_mode(struct meson_dw_hdmi *dw_hdmi,
        }
 }
 
-static inline void dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi)
+static inline void meson_dw_hdmi_phy_reset(struct meson_dw_hdmi *dw_hdmi)
 {
        struct meson_drm *priv = dw_hdmi->priv;
 
@@ -409,9 +410,9 @@ static int dw_hdmi_phy_init(struct dw_hdmi *hdmi, void *data,
        msleep(100);
 
        /* Reset PHY 3 times in a row */
-       dw_hdmi_phy_reset(dw_hdmi);
-       dw_hdmi_phy_reset(dw_hdmi);
-       dw_hdmi_phy_reset(dw_hdmi);
+       meson_dw_hdmi_phy_reset(dw_hdmi);
+       meson_dw_hdmi_phy_reset(dw_hdmi);
+       meson_dw_hdmi_phy_reset(dw_hdmi);
 
        /* Temporary Disable VENC video stream */
        if (priv->venc.hdmi_use_enci)
@@ -878,9 +879,12 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master,
        dw_plat_data->input_bus_format = MEDIA_BUS_FMT_YUV8_1X24;
        dw_plat_data->input_bus_encoding = V4L2_YCBCR_ENC_709;
 
-       ret = dw_hdmi_bind(pdev, encoder, &meson_dw_hdmi->dw_plat_data);
-       if (ret)
-               return ret;
+       platform_set_drvdata(pdev, meson_dw_hdmi);
+
+       meson_dw_hdmi->hdmi = dw_hdmi_bind(pdev, encoder,
+                                          &meson_dw_hdmi->dw_plat_data);
+       if (IS_ERR(meson_dw_hdmi->hdmi))
+               return PTR_ERR(meson_dw_hdmi->hdmi);
 
        DRM_DEBUG_DRIVER("HDMI controller initialized\n");
 
@@ -890,7 +894,9 @@ static int meson_dw_hdmi_bind(struct device *dev, struct device *master,
 static void meson_dw_hdmi_unbind(struct device *dev, struct device *master,
                                   void *data)
 {
-       dw_hdmi_unbind(dev);
+       struct meson_dw_hdmi *meson_dw_hdmi = dev_get_drvdata(dev);
+
+       dw_hdmi_unbind(meson_dw_hdmi->hdmi);
 }
 
 static const struct component_ops meson_dw_hdmi_ops = {
index dc85b53d58ef2e8fb640e90afdac701e6397a1af..76210ae25094d4ba9b62d7dacbc12f03254d79ae 100644 (file)
@@ -68,12 +68,22 @@ static const struct dw_hdmi_plat_data rcar_dw_hdmi_plat_data = {
 
 static int rcar_dw_hdmi_probe(struct platform_device *pdev)
 {
-       return dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data);
+       struct dw_hdmi *hdmi;
+
+       hdmi = dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data);
+       if (IS_ERR(hdmi))
+               return PTR_ERR(hdmi);
+
+       platform_set_drvdata(pdev, hdmi);
+
+       return 0;
 }
 
 static int rcar_dw_hdmi_remove(struct platform_device *pdev)
 {
-       dw_hdmi_remove(pdev);
+       struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
+
+       dw_hdmi_remove(hdmi);
 
        return 0;
 }
index 1eb02a82fd9188533bfd1debce378071b9eab540..3574b0ae2ad1923e39911ac32acabdbd9b3f7613 100644 (file)
@@ -48,6 +48,7 @@ struct rockchip_hdmi {
        const struct rockchip_hdmi_chip_data *chip_data;
        struct clk *vpll_clk;
        struct clk *grf_clk;
+       struct dw_hdmi *hdmi;
 };
 
 #define to_rockchip_hdmi(x)    container_of(x, struct rockchip_hdmi, x)
@@ -377,14 +378,18 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
        drm_encoder_init(drm, encoder, &dw_hdmi_rockchip_encoder_funcs,
                         DRM_MODE_ENCODER_TMDS, NULL);
 
-       ret = dw_hdmi_bind(pdev, encoder, plat_data);
+       platform_set_drvdata(pdev, hdmi);
+
+       hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
 
        /*
         * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
         * which would have called the encoder cleanup.  Do it manually.
         */
-       if (ret)
+       if (IS_ERR(hdmi->hdmi)) {
+               ret = PTR_ERR(hdmi->hdmi);
                drm_encoder_cleanup(encoder);
+       }
 
        return ret;
 }
@@ -392,7 +397,9 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
 static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
                                    void *data)
 {
-       return dw_hdmi_unbind(dev);
+       struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dw_hdmi_unbind(hdmi->hdmi);
 }
 
 static const struct component_ops dw_hdmi_rockchip_ops = {
index 882d85db90539ae1b00e194a0cae43bf8fb4918d..7327da3bc94fc3990743995d9fd13c5efa34d03d 100644 (file)
@@ -40,6 +40,15 @@ config DRM_SUN4I_BACKEND
          do some alpha blending and feed graphics to TCON. If M is
          selected the module will be called sun4i-backend.
 
+config DRM_SUN8I_DW_HDMI
+       tristate "Support for Allwinner version of DesignWare HDMI"
+       depends on DRM_SUN4I
+       select DRM_DW_HDMI
+       help
+         Choose this option if you have an Allwinner SoC with the
+         DesignWare HDMI controller with custom HDMI PHY. If M is
+         selected the module will be called sun8i_dw_hdmi.
+
 config DRM_SUN8I_MIXER
        tristate "Support for Allwinner Display Engine 2.0 Mixer"
        default MACH_SUN8I
index 582607c0c48817a98538c85100282d611d7fca9a..1610e748119b6ff3f253f7edf78525e2d0b616fe 100644 (file)
@@ -10,6 +10,9 @@ sun4i-drm-hdmi-y              += sun4i_hdmi_enc.o
 sun4i-drm-hdmi-y               += sun4i_hdmi_i2c.o
 sun4i-drm-hdmi-y               += sun4i_hdmi_tmds_clk.o
 
+sun8i-drm-hdmi-y               += sun8i_dw_hdmi.o
+sun8i-drm-hdmi-y               += sun8i_hdmi_phy.o
+
 sun8i-mixer-y                  += sun8i_mixer.o sun8i_ui_layer.o \
                                   sun8i_vi_layer.o sun8i_ui_scaler.o \
                                   sun8i_vi_scaler.o sun8i_csc.o
@@ -27,4 +30,5 @@ obj-$(CONFIG_DRM_SUN4I)               += sun6i_drc.o
 
 obj-$(CONFIG_DRM_SUN4I_BACKEND)        += sun4i-backend.o sun4i-frontend.o
 obj-$(CONFIG_DRM_SUN4I_HDMI)   += sun4i-drm-hdmi.o
+obj-$(CONFIG_DRM_SUN8I_DW_HDMI)        += sun8i-drm-hdmi.o
 obj-$(CONFIG_DRM_SUN8I_MIXER)  += sun8i-mixer.o
index 4b7340ee629091515db27957f8d215c0b996179b..0d6c5ed44795c411574910153d45c7829d1d56e9 100644 (file)
@@ -84,6 +84,7 @@ static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
 
        switch (channel) {
        case 0:
+               WARN_ON(!tcon->quirks->has_channel_0);
                regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
                                   SUN4I_TCON0_CTL_TCON_ENABLE,
                                   enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0);
@@ -276,6 +277,8 @@ static void sun4i_tcon0_mode_set_lvds(struct sun4i_tcon *tcon,
        u8 clk_delay;
        u32 reg, val = 0;
 
+       WARN_ON(!tcon->quirks->has_channel_0);
+
        tcon->dclk_min_div = 7;
        tcon->dclk_max_div = 7;
        sun4i_tcon0_mode_set_common(tcon, mode);
@@ -344,6 +347,8 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
        u8 clk_delay;
        u32 val = 0;
 
+       WARN_ON(!tcon->quirks->has_channel_0);
+
        tcon->dclk_min_div = 6;
        tcon->dclk_max_div = 127;
        sun4i_tcon0_mode_set_common(tcon, mode);
@@ -389,10 +394,10 @@ static void sun4i_tcon0_mode_set_rgb(struct sun4i_tcon *tcon,
                     SUN4I_TCON0_BASIC3_H_SYNC(hsync));
 
        /* Setup the polarity of the various signals */
-       if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+       if (mode->flags & DRM_MODE_FLAG_PHSYNC)
                val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE;
 
-       if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+       if (mode->flags & DRM_MODE_FLAG_PVSYNC)
                val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE;
 
        regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG,
@@ -574,10 +579,12 @@ static int sun4i_tcon_init_clocks(struct device *dev,
        }
        clk_prepare_enable(tcon->clk);
 
-       tcon->sclk0 = devm_clk_get(dev, "tcon-ch0");
-       if (IS_ERR(tcon->sclk0)) {
-               dev_err(dev, "Couldn't get the TCON channel 0 clock\n");
-               return PTR_ERR(tcon->sclk0);
+       if (tcon->quirks->has_channel_0) {
+               tcon->sclk0 = devm_clk_get(dev, "tcon-ch0");
+               if (IS_ERR(tcon->sclk0)) {
+                       dev_err(dev, "Couldn't get the TCON channel 0 clock\n");
+                       return PTR_ERR(tcon->sclk0);
+               }
        }
 
        if (tcon->quirks->has_channel_1) {
@@ -934,10 +941,12 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
                goto err_free_clocks;
        }
 
-       ret = sun4i_dclk_create(dev, tcon);
-       if (ret) {
-               dev_err(dev, "Couldn't create our TCON dot clock\n");
-               goto err_free_clocks;
+       if (tcon->quirks->has_channel_0) {
+               ret = sun4i_dclk_create(dev, tcon);
+               if (ret) {
+                       dev_err(dev, "Couldn't create our TCON dot clock\n");
+                       goto err_free_clocks;
+               }
        }
 
        ret = sun4i_tcon_init_irq(dev, tcon);
@@ -995,7 +1004,8 @@ static int sun4i_tcon_bind(struct device *dev, struct device *master,
        return 0;
 
 err_free_dotclock:
-       sun4i_dclk_free(tcon);
+       if (tcon->quirks->has_channel_0)
+               sun4i_dclk_free(tcon);
 err_free_clocks:
        sun4i_tcon_free_clocks(tcon);
 err_assert_reset:
@@ -1009,7 +1019,8 @@ static void sun4i_tcon_unbind(struct device *dev, struct device *master,
        struct sun4i_tcon *tcon = dev_get_drvdata(dev);
 
        list_del(&tcon->list);
-       sun4i_dclk_free(tcon);
+       if (tcon->quirks->has_channel_0)
+               sun4i_dclk_free(tcon);
        sun4i_tcon_free_clocks(tcon);
 }
 
@@ -1106,16 +1117,19 @@ static int sun6i_tcon_set_mux(struct sun4i_tcon *tcon,
 }
 
 static const struct sun4i_tcon_quirks sun4i_a10_quirks = {
+       .has_channel_0          = true,
        .has_channel_1          = true,
        .set_mux                = sun4i_a10_tcon_set_mux,
 };
 
 static const struct sun4i_tcon_quirks sun5i_a13_quirks = {
+       .has_channel_0          = true,
        .has_channel_1          = true,
        .set_mux                = sun5i_a13_tcon_set_mux,
 };
 
 static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
+       .has_channel_0          = true,
        .has_channel_1          = true,
        .has_lvds_alt           = true,
        .needs_de_be_mux        = true,
@@ -1123,26 +1137,33 @@ static const struct sun4i_tcon_quirks sun6i_a31_quirks = {
 };
 
 static const struct sun4i_tcon_quirks sun6i_a31s_quirks = {
+       .has_channel_0          = true,
        .has_channel_1          = true,
        .needs_de_be_mux        = true,
 };
 
 static const struct sun4i_tcon_quirks sun7i_a20_quirks = {
+       .has_channel_0          = true,
        .has_channel_1          = true,
        /* Same display pipeline structure as A10 */
        .set_mux                = sun4i_a10_tcon_set_mux,
 };
 
 static const struct sun4i_tcon_quirks sun8i_a33_quirks = {
+       .has_channel_0          = true,
        .has_lvds_alt           = true,
 };
 
 static const struct sun4i_tcon_quirks sun8i_a83t_lcd_quirks = {
-       /* nothing is supported */
+       .has_channel_0          = true,
+};
+
+static const struct sun4i_tcon_quirks sun8i_a83t_tv_quirks = {
+       .has_channel_1          = true,
 };
 
 static const struct sun4i_tcon_quirks sun8i_v3s_quirks = {
-       /* nothing is supported */
+       .has_channel_0          = true,
 };
 
 /* sun4i_drv uses this list to check if a device node is a TCON */
@@ -1154,6 +1175,7 @@ const struct of_device_id sun4i_tcon_of_table[] = {
        { .compatible = "allwinner,sun7i-a20-tcon", .data = &sun7i_a20_quirks },
        { .compatible = "allwinner,sun8i-a33-tcon", .data = &sun8i_a33_quirks },
        { .compatible = "allwinner,sun8i-a83t-tcon-lcd", .data = &sun8i_a83t_lcd_quirks },
+       { .compatible = "allwinner,sun8i-a83t-tcon-tv", .data = &sun8i_a83t_tv_quirks },
        { .compatible = "allwinner,sun8i-v3s-tcon", .data = &sun8i_v3s_quirks },
        { }
 };
index b761c7b823c560536b0f44a3c6eab6337d3e32d7..78d55e7cd2b3d286b6d16c19a94c28e55a50c91c 100644 (file)
 struct sun4i_tcon;
 
 struct sun4i_tcon_quirks {
+       bool    has_channel_0;  /* a83t does not have channel 0 on second TCON */
        bool    has_channel_1;  /* a33 does not have channel 1 */
        bool    has_lvds_alt;   /* Does the LVDS clock have a parent other than the TCON clock? */
        bool    needs_de_be_mux; /* sun6i needs mux to select backend */
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.c
new file mode 100644 (file)
index 0000000..9f40a44
--- /dev/null
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_of.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "sun8i_dw_hdmi.h"
+
+static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+                                          struct drm_display_mode *mode,
+                                          struct drm_display_mode *adj_mode)
+{
+       struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder);
+
+       clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000);
+}
+
+static const struct drm_encoder_helper_funcs
+sun8i_dw_hdmi_encoder_helper_funcs = {
+       .mode_set = sun8i_dw_hdmi_encoder_mode_set,
+};
+
+static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
+       .destroy = drm_encoder_cleanup,
+};
+
+static enum drm_mode_status
+sun8i_dw_hdmi_mode_valid(struct drm_connector *connector,
+                        const struct drm_display_mode *mode)
+{
+       if (mode->clock > 297000)
+               return MODE_CLOCK_HIGH;
+
+       return MODE_OK;
+}
+
+static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
+                             void *data)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct dw_hdmi_plat_data *plat_data;
+       struct drm_device *drm = data;
+       struct device_node *phy_node;
+       struct drm_encoder *encoder;
+       struct sun8i_dw_hdmi *hdmi;
+       int ret;
+
+       if (!pdev->dev.of_node)
+               return -ENODEV;
+
+       hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
+       if (!hdmi)
+               return -ENOMEM;
+
+       plat_data = &hdmi->plat_data;
+       hdmi->dev = &pdev->dev;
+       encoder = &hdmi->encoder;
+
+       encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+       /*
+        * If we failed to find the CRTC(s) which this encoder is
+        * supposed to be connected to, it's because the CRTC has
+        * not been registered yet.  Defer probing, and hope that
+        * the required CRTC is added later.
+        */
+       if (encoder->possible_crtcs == 0)
+               return -EPROBE_DEFER;
+
+       hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl");
+       if (IS_ERR(hdmi->rst_ctrl)) {
+               dev_err(dev, "Could not get ctrl reset control\n");
+               return PTR_ERR(hdmi->rst_ctrl);
+       }
+
+       hdmi->clk_tmds = devm_clk_get(dev, "tmds");
+       if (IS_ERR(hdmi->clk_tmds)) {
+               dev_err(dev, "Couldn't get the tmds clock\n");
+               return PTR_ERR(hdmi->clk_tmds);
+       }
+
+       ret = reset_control_deassert(hdmi->rst_ctrl);
+       if (ret) {
+               dev_err(dev, "Could not deassert ctrl reset control\n");
+               return ret;
+       }
+
+       ret = clk_prepare_enable(hdmi->clk_tmds);
+       if (ret) {
+               dev_err(dev, "Could not enable tmds clock\n");
+               goto err_assert_ctrl_reset;
+       }
+
+       phy_node = of_parse_phandle(dev->of_node, "phys", 0);
+       if (!phy_node) {
+               dev_err(dev, "Can't found PHY phandle\n");
+               goto err_disable_clk_tmds;
+       }
+
+       ret = sun8i_hdmi_phy_probe(hdmi, phy_node);
+       of_node_put(phy_node);
+       if (ret) {
+               dev_err(dev, "Couldn't get the HDMI PHY\n");
+               goto err_disable_clk_tmds;
+       }
+
+       drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
+       drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
+                        DRM_MODE_ENCODER_TMDS, NULL);
+
+       sun8i_hdmi_phy_init(hdmi->phy);
+
+       plat_data->mode_valid = &sun8i_dw_hdmi_mode_valid;
+       plat_data->phy_ops = sun8i_hdmi_phy_get_ops();
+       plat_data->phy_name = "sun8i_dw_hdmi_phy";
+       plat_data->phy_data = hdmi->phy;
+
+       platform_set_drvdata(pdev, hdmi);
+
+       hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
+
+       /*
+        * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
+        * which would have called the encoder cleanup.  Do it manually.
+        */
+       if (IS_ERR(hdmi->hdmi)) {
+               ret = PTR_ERR(hdmi->hdmi);
+               goto cleanup_encoder;
+       }
+
+       return 0;
+
+cleanup_encoder:
+       drm_encoder_cleanup(encoder);
+       sun8i_hdmi_phy_remove(hdmi);
+err_disable_clk_tmds:
+       clk_disable_unprepare(hdmi->clk_tmds);
+err_assert_ctrl_reset:
+       reset_control_assert(hdmi->rst_ctrl);
+
+       return ret;
+}
+
+static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
+                                void *data)
+{
+       struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev);
+
+       dw_hdmi_unbind(hdmi->hdmi);
+       sun8i_hdmi_phy_remove(hdmi);
+       clk_disable_unprepare(hdmi->clk_tmds);
+       reset_control_assert(hdmi->rst_ctrl);
+}
+
+static const struct component_ops sun8i_dw_hdmi_ops = {
+       .bind   = sun8i_dw_hdmi_bind,
+       .unbind = sun8i_dw_hdmi_unbind,
+};
+
+static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
+{
+       return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
+}
+
+static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
+
+       return 0;
+}
+
+static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
+       { .compatible = "allwinner,sun8i-a83t-dw-hdmi" },
+       { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
+
+struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
+       .probe  = sun8i_dw_hdmi_probe,
+       .remove = sun8i_dw_hdmi_remove,
+       .driver = {
+               .name = "sun8i-dw-hdmi",
+               .of_match_table = sun8i_dw_hdmi_dt_ids,
+       },
+};
+module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
+
+MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
+MODULE_DESCRIPTION("Allwinner DW HDMI bridge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h b/drivers/gpu/drm/sun4i/sun8i_dw_hdmi.h
new file mode 100644 (file)
index 0000000..d8d0684
--- /dev/null
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#ifndef _SUN8I_DW_HDMI_H_
+#define _SUN8I_DW_HDMI_H_
+
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_encoder.h>
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+struct sun8i_hdmi_phy {
+       struct clk              *clk_bus;
+       struct clk              *clk_mod;
+       struct regmap           *regs;
+       struct reset_control    *rst_phy;
+};
+
+struct sun8i_dw_hdmi {
+       struct clk                      *clk_tmds;
+       struct device                   *dev;
+       struct dw_hdmi                  *hdmi;
+       struct drm_encoder              encoder;
+       struct sun8i_hdmi_phy           *phy;
+       struct dw_hdmi_plat_data        plat_data;
+       struct reset_control            *rst_ctrl;
+};
+
+static inline struct sun8i_dw_hdmi *
+encoder_to_sun8i_dw_hdmi(struct drm_encoder *encoder)
+{
+       return container_of(encoder, struct sun8i_dw_hdmi, encoder);
+}
+
+int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node);
+void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi);
+
+void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy);
+const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void);
+
+#endif /* _SUN8I_DW_HDMI_H_ */
diff --git a/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c b/drivers/gpu/drm/sun4i/sun8i_hdmi_phy.c
new file mode 100644 (file)
index 0000000..e5bfcdd
--- /dev/null
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
+ */
+
+#include <linux/of_address.h>
+
+#include "sun8i_dw_hdmi.h"
+
+#define SUN8I_HDMI_PHY_DBG_CTRL_REG    0x0000
+#define SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK                BIT(0)
+#define SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK       GENMASK(15, 8)
+#define SUN8I_HDMI_PHY_DBG_CTRL_POL(val)       (val << 8)
+#define SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK      GENMASK(23, 16)
+#define SUN8I_HDMI_PHY_DBG_CTRL_ADDR(addr)     (addr << 16)
+
+#define SUN8I_HDMI_PHY_REXT_CTRL_REG   0x0004
+#define SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN       BIT(31)
+
+#define SUN8I_HDMI_PHY_READ_EN_REG     0x0010
+#define SUN8I_HDMI_PHY_READ_EN_MAGIC           0x54524545
+
+#define SUN8I_HDMI_PHY_UNSCRAMBLE_REG  0x0014
+#define SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC                0x42494E47
+
+/*
+ * Address can be actually any value. Here is set to same value as
+ * it is set in BSP driver.
+ */
+#define I2C_ADDR       0x69
+
+static int sun8i_hdmi_phy_config(struct dw_hdmi *hdmi, void *data,
+                                struct drm_display_mode *mode)
+{
+       struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
+       u32 val = 0;
+
+       if ((mode->flags & DRM_MODE_FLAG_NHSYNC) &&
+           (mode->flags & DRM_MODE_FLAG_NHSYNC)) {
+               val = 0x03;
+       }
+
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
+                          SUN8I_HDMI_PHY_DBG_CTRL_POL_MASK,
+                          SUN8I_HDMI_PHY_DBG_CTRL_POL(val));
+
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
+                          SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN,
+                          SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN);
+
+       /* power down */
+       dw_hdmi_phy_gen2_txpwron(hdmi, 0);
+       dw_hdmi_phy_gen2_pddq(hdmi, 1);
+
+       dw_hdmi_phy_reset(hdmi);
+
+       dw_hdmi_phy_gen2_pddq(hdmi, 0);
+
+       dw_hdmi_phy_i2c_set_addr(hdmi, I2C_ADDR);
+
+       /*
+        * Values are taken from BSP HDMI driver. Although AW didn't
+        * release any documentation, explanation of this values can
+        * be found in i.MX 6Dual/6Quad Reference Manual.
+        */
+       if (mode->crtc_clock <= 27000) {
+               dw_hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
+               dw_hdmi_phy_i2c_write(hdmi, 0x08da, 0x10);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0318, 0x0e);
+               dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09);
+       } else if (mode->crtc_clock <= 74250) {
+               dw_hdmi_phy_i2c_write(hdmi, 0x0540, 0x06);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0007, 0x19);
+               dw_hdmi_phy_i2c_write(hdmi, 0x02b5, 0x0e);
+               dw_hdmi_phy_i2c_write(hdmi, 0x8009, 0x09);
+       } else if (mode->crtc_clock <= 148500) {
+               dw_hdmi_phy_i2c_write(hdmi, 0x04a0, 0x06);
+               dw_hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0021, 0x0e);
+               dw_hdmi_phy_i2c_write(hdmi, 0x8029, 0x09);
+       } else {
+               dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x06);
+               dw_hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x10);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0002, 0x19);
+               dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x0e);
+               dw_hdmi_phy_i2c_write(hdmi, 0x802b, 0x09);
+       }
+
+       dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x1e);
+       dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x13);
+       dw_hdmi_phy_i2c_write(hdmi, 0x0000, 0x17);
+
+       dw_hdmi_phy_gen2_txpwron(hdmi, 1);
+
+       return 0;
+};
+
+static void sun8i_hdmi_phy_disable(struct dw_hdmi *hdmi, void *data)
+{
+       struct sun8i_hdmi_phy *phy = (struct sun8i_hdmi_phy *)data;
+
+       dw_hdmi_phy_gen2_txpwron(hdmi, 0);
+       dw_hdmi_phy_gen2_pddq(hdmi, 1);
+
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_REXT_CTRL_REG,
+                          SUN8I_HDMI_PHY_REXT_CTRL_REXT_EN, 0);
+}
+
+static const struct dw_hdmi_phy_ops sun8i_hdmi_phy_ops = {
+       .init = &sun8i_hdmi_phy_config,
+       .disable = &sun8i_hdmi_phy_disable,
+       .read_hpd = &dw_hdmi_phy_read_hpd,
+       .update_hpd = &dw_hdmi_phy_update_hpd,
+       .setup_hpd = &dw_hdmi_phy_setup_hpd,
+};
+
+void sun8i_hdmi_phy_init(struct sun8i_hdmi_phy *phy)
+{
+       /* enable read access to HDMI controller */
+       regmap_write(phy->regs, SUN8I_HDMI_PHY_READ_EN_REG,
+                    SUN8I_HDMI_PHY_READ_EN_MAGIC);
+
+       /* unscramble register offsets */
+       regmap_write(phy->regs, SUN8I_HDMI_PHY_UNSCRAMBLE_REG,
+                    SUN8I_HDMI_PHY_UNSCRAMBLE_MAGIC);
+
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
+                          SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK,
+                          SUN8I_HDMI_PHY_DBG_CTRL_PX_LOCK);
+
+       /*
+        * Set PHY I2C address. It must match to the address set by
+        * dw_hdmi_phy_set_slave_addr().
+        */
+       regmap_update_bits(phy->regs, SUN8I_HDMI_PHY_DBG_CTRL_REG,
+                          SUN8I_HDMI_PHY_DBG_CTRL_ADDR_MASK,
+                          SUN8I_HDMI_PHY_DBG_CTRL_ADDR(I2C_ADDR));
+}
+
+const struct dw_hdmi_phy_ops *sun8i_hdmi_phy_get_ops(void)
+{
+       return &sun8i_hdmi_phy_ops;
+}
+
+static struct regmap_config sun8i_hdmi_phy_regmap_config = {
+       .reg_bits       = 32,
+       .val_bits       = 32,
+       .reg_stride     = 4,
+       .max_register   = SUN8I_HDMI_PHY_UNSCRAMBLE_REG,
+       .name           = "phy"
+};
+
+static const struct of_device_id sun8i_hdmi_phy_of_table[] = {
+       { .compatible = "allwinner,sun8i-a83t-hdmi-phy" },
+       { /* sentinel */ }
+};
+
+int sun8i_hdmi_phy_probe(struct sun8i_dw_hdmi *hdmi, struct device_node *node)
+{
+       struct device *dev = hdmi->dev;
+       struct sun8i_hdmi_phy *phy;
+       struct resource res;
+       void __iomem *regs;
+       int ret;
+
+       if (!of_match_node(sun8i_hdmi_phy_of_table, node)) {
+               dev_err(dev, "Incompatible HDMI PHY\n");
+               return -EINVAL;
+       }
+
+       phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
+       if (!phy)
+               return -ENOMEM;
+
+       ret = of_address_to_resource(node, 0, &res);
+       if (ret) {
+               dev_err(dev, "phy: Couldn't get our resources\n");
+               return ret;
+       }
+
+       regs = devm_ioremap_resource(dev, &res);
+       if (IS_ERR(regs)) {
+               dev_err(dev, "Couldn't map the HDMI PHY registers\n");
+               return PTR_ERR(regs);
+       }
+
+       phy->regs = devm_regmap_init_mmio(dev, regs,
+                                         &sun8i_hdmi_phy_regmap_config);
+       if (IS_ERR(phy->regs)) {
+               dev_err(dev, "Couldn't create the HDMI PHY regmap\n");
+               return PTR_ERR(phy->regs);
+       }
+
+       phy->clk_bus = of_clk_get_by_name(node, "bus");
+       if (IS_ERR(phy->clk_bus)) {
+               dev_err(dev, "Could not get bus clock\n");
+               return PTR_ERR(phy->clk_bus);
+       }
+
+       phy->clk_mod = of_clk_get_by_name(node, "mod");
+       if (IS_ERR(phy->clk_mod)) {
+               dev_err(dev, "Could not get mod clock\n");
+               ret = PTR_ERR(phy->clk_mod);
+               goto err_put_clk_bus;
+       }
+
+       phy->rst_phy = of_reset_control_get_shared(node, "phy");
+       if (IS_ERR(phy->rst_phy)) {
+               dev_err(dev, "Could not get phy reset control\n");
+               ret = PTR_ERR(phy->rst_phy);
+               goto err_put_clk_mod;
+       }
+
+       ret = reset_control_deassert(phy->rst_phy);
+       if (ret) {
+               dev_err(dev, "Cannot deassert phy reset control: %d\n", ret);
+               goto err_put_rst_phy;
+       }
+
+       ret = clk_prepare_enable(phy->clk_bus);
+       if (ret) {
+               dev_err(dev, "Cannot enable bus clock: %d\n", ret);
+               goto err_deassert_rst_phy;
+       }
+
+       ret = clk_prepare_enable(phy->clk_mod);
+       if (ret) {
+               dev_err(dev, "Cannot enable mod clock: %d\n", ret);
+               goto err_disable_clk_bus;
+       }
+
+       hdmi->phy = phy;
+
+       return 0;
+
+err_disable_clk_bus:
+       clk_disable_unprepare(phy->clk_bus);
+err_deassert_rst_phy:
+       reset_control_assert(phy->rst_phy);
+err_put_rst_phy:
+       reset_control_put(phy->rst_phy);
+err_put_clk_mod:
+       clk_put(phy->clk_mod);
+err_put_clk_bus:
+       clk_put(phy->clk_bus);
+
+       return ret;
+}
+
+void sun8i_hdmi_phy_remove(struct sun8i_dw_hdmi *hdmi)
+{
+       struct sun8i_hdmi_phy *phy = hdmi->phy;
+
+       clk_disable_unprepare(phy->clk_mod);
+       clk_disable_unprepare(phy->clk_bus);
+
+       reset_control_assert(phy->rst_phy);
+
+       reset_control_put(phy->rst_phy);
+
+       clk_put(phy->clk_mod);
+       clk_put(phy->clk_bus);
+}
index 2cbb2de6d39c83c372cc4db7e346c1b10ca4e823..9b0256d31a61ba8fc346af440032f9a578da6d79 100644 (file)
@@ -485,6 +485,13 @@ static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = {
        .vi_num         = 1,
 };
 
+static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = {
+       .ccsc           = 1,
+       .scaler_mask    = 0x3,
+       .ui_num         = 1,
+       .vi_num         = 1,
+};
+
 static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
        .vi_num = 2,
        .ui_num = 1,
@@ -498,6 +505,10 @@ static const struct of_device_id sun8i_mixer_of_table[] = {
                .compatible = "allwinner,sun8i-a83t-de2-mixer-0",
                .data = &sun8i_a83t_mixer0_cfg,
        },
+       {
+               .compatible = "allwinner,sun8i-a83t-de2-mixer-1",
+               .data = &sun8i_a83t_mixer1_cfg,
+       },
        {
                .compatible = "allwinner,sun8i-v3s-de2-mixer",
                .data = &sun8i_v3s_mixer_cfg,
index 182f83283e247d70a2fa592c71ec5c484a551d66..dd2a8cf7d20b8d6becb6b65bcbd38efb0fd35854 100644 (file)
@@ -143,12 +143,13 @@ struct dw_hdmi_plat_data {
                             unsigned long mpixelclock);
 };
 
-int dw_hdmi_probe(struct platform_device *pdev,
-                 const struct dw_hdmi_plat_data *plat_data);
-void dw_hdmi_remove(struct platform_device *pdev);
-void dw_hdmi_unbind(struct device *dev);
-int dw_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder,
-                const struct dw_hdmi_plat_data *plat_data);
+struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
+                             const struct dw_hdmi_plat_data *plat_data);
+void dw_hdmi_remove(struct dw_hdmi *hdmi);
+void dw_hdmi_unbind(struct dw_hdmi *hdmi);
+struct dw_hdmi *dw_hdmi_bind(struct platform_device *pdev,
+                            struct drm_encoder *encoder,
+                            const struct dw_hdmi_plat_data *plat_data);
 
 void dw_hdmi_setup_rx_sense(struct device *dev, bool hpd, bool rx_sense);
 
@@ -157,7 +158,18 @@ void dw_hdmi_audio_enable(struct dw_hdmi *hdmi);
 void dw_hdmi_audio_disable(struct dw_hdmi *hdmi);
 
 /* PHY configuration */
+void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address);
 void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
                           unsigned char addr);
 
+void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable);
+void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable);
+void dw_hdmi_phy_reset(struct dw_hdmi *hdmi);
+
+enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
+                                              void *data);
+void dw_hdmi_phy_update_hpd(struct dw_hdmi *hdmi, void *data,
+                           bool force, bool disabled, bool rxsense);
+void dw_hdmi_phy_setup_hpd(struct dw_hdmi *hdmi, void *data);
+
 #endif /* __IMX_HDMI_H__ */
index c239e6e24a1014504cb0a04d17a9b194782c6d41..4de97e94ef9dd980de0ae15030ca70f5fee54add 100644 (file)
 # define DP_DS_12BPC                       2
 # define DP_DS_16BPC                       3
 
+/* DP Forward error Correction Registers */
+#define DP_FEC_CAPABILITY                  0x090    /* 1.4 */
+# define DP_FEC_CAPABLE                            (1 << 0)
+# define DP_FEC_UNCORR_BLK_ERROR_COUNT_CAP  (1 << 1)
+# define DP_FEC_CORR_BLK_ERROR_COUNT_CAP    (1 << 2)
+# define DP_FEC_BIT_ERROR_COUNT_CAP        (1 << 3)
+
 /* link configuration */
 #define        DP_LINK_BW_SET                      0x100
 # define DP_LINK_RATE_TABLE                0x00    /* eDP 1.4 */
 #define DP_UPSTREAM_DEVICE_DP_PWR_NEED     0x118   /* 1.2 */
 # define DP_PWR_NOT_NEEDED                 (1 << 0)
 
+#define DP_FEC_CONFIGURATION               0x120    /* 1.4 */
+# define DP_FEC_READY                      (1 << 0)
+# define DP_FEC_ERR_COUNT_SEL_MASK         (7 << 1)
+# define DP_FEC_ERR_COUNT_DIS              (0 << 1)
+# define DP_FEC_UNCORR_BLK_ERROR_COUNT     (1 << 1)
+# define DP_FEC_CORR_BLK_ERROR_COUNT       (2 << 1)
+# define DP_FEC_BIT_ERROR_COUNT                    (3 << 1)
+# define DP_FEC_LANE_SELECT_MASK           (3 << 4)
+# define DP_FEC_LANE_0_SELECT              (0 << 4)
+# define DP_FEC_LANE_1_SELECT              (1 << 4)
+# define DP_FEC_LANE_2_SELECT              (2 << 4)
+# define DP_FEC_LANE_3_SELECT              (3 << 4)
+
 #define DP_AUX_FRAME_SYNC_VALUE                    0x15c   /* eDP 1.4 */
 # define DP_AUX_FRAME_SYNC_VALID           (1 << 0)
 
 #define DP_TEST_SINK                       0x270
 # define DP_TEST_SINK_START                (1 << 0)
 
+#define DP_FEC_STATUS                      0x280    /* 1.4 */
+# define DP_FEC_DECODE_EN_DETECTED         (1 << 0)
+# define DP_FEC_DECODE_DIS_DETECTED        (1 << 1)
+
+#define DP_FEC_ERROR_COUNT_LSB             0x0281    /* 1.4 */
+
+#define DP_FEC_ERROR_COUNT_MSB             0x0282    /* 1.4 */
+# define DP_FEC_ERROR_COUNT_MASK           0x7F
+# define DP_FEC_ERR_COUNT_VALID                    (1 << 7)
+
 #define DP_PAYLOAD_TABLE_UPDATE_STATUS      0x2c0   /* 1.2 MST */
 # define DP_PAYLOAD_TABLE_UPDATED           (1 << 0)
 # define DP_PAYLOAD_ACT_HANDLED             (1 << 1)