MLK-16918-5: drm: Implement NWL MIPI-DSI as a real drm_bridge
authorRobert Chiras <robert.chiras@nxp.com>
Thu, 9 Nov 2017 12:01:35 +0000 (14:01 +0200)
committerNitin Garg <nitin.garg@nxp.com>
Mon, 19 Mar 2018 20:47:07 +0000 (15:47 -0500)
Currently, the Northwest Logic MIPI-DSI controller host specific code
resides under drm/bridge, but is not a real drm_bridge. It creates a
drm_bridge and adds itself to the drm_encoder that handles this file,
but this is wrong, since it does not implement the drm_bridge_funcs.

The correct way to implement a drm_bridge is to add the drm_bridge and
let other components (another bridge or a drm_encoder) to attach to this
bridge.
Since we are doing this, a new compatible strings can be used for this
driver: "nwl,mipi-dsi".

Since this was used by nwl_dsi-imx.c, update that driver to use this
bridge correctly.

This is needed in order to add support for MIPI-DSI on 8MQ. The IMX_NWL
driver will either add a DSI encoder to DRM, or a DSI bridge.
The encoder will be used by imx-drm-core driver, while the bridge
will be used by MXSFB driver (which creates a simple display pipe).

Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
Documentation/devicetree/bindings/display/bridge/nwl_dsi.txt
Documentation/devicetree/bindings/display/imx/dsi_nwl.txt
drivers/gpu/drm/bridge/nwl-dsi.c
drivers/gpu/drm/imx/nwl_dsi-imx.c
drivers/phy/phy-mixel-mipi-dsi.c
include/drm/bridge/nwl_dsi.h
include/linux/phy/phy-mixel-mipi-dsi.h

index ecef675..5d4242b 100644 (file)
@@ -2,9 +2,11 @@ Northwest Logic MIPI-DSI bridge bindings
 
 The MIPI-DSI host controller drives the video signals from
 display controller to video peripherals using DSI protocol.
+This is an un-managed DSI bridge. In order to use this bridge, an encoder
+or bridge must be implemented to manage the platform specific initializations.
 
 Required properties:
-- compatible:          "<vendor>,<chip>-mipi-dsi"
+- compatible:          "nwl,mipi-dsi"
 - reg:                         the register range of the MIPI-DSI controller
 - interrupts:          the interrupt number for this module
 - clock, clock-names:  phandles to the MIPI-DSI clocks
@@ -17,9 +19,10 @@ Required properties:
        "tx_esc" and "rx_esc"
 - port:                input and output port nodes with endpoint definitions as
                        defined in Documentation/devicetree/bindings/graph.txt;
-                       the input port should be connected to a display
-                       interface and the output port should be connected to a
-                       panel or a bridge input port
+                       the input port should be connected to an encoder or a
+                       bridge that manages this MIPI-DSI host and the output
+                       port should be connected to a panel or a bridge input
+                       port
 - phys:                phandle to the phy module representing the DPHY
                        inside MIPI-DSI IP block
 - phy-names:           should be "dphy"
@@ -37,9 +40,11 @@ Optional properties:
 Documentation/devicetree/bindings/clock/clock-bindings.txt
 
 Example:
-       mipi_dsi1: mipi_dsi@56228000 {
-               compatible = "fsl,imx8qm-mipi-dsi";
-               reg = <0x0 0x56228000 0x0 0x1000>;
+       mipi_dsi_bridge1: mipi_dsi_bridge@56228000 {
+               #address-cells = <1>;
+               #size-cells = <0>;
+               compatible = "nwl,mipi-dsi";
+               reg = <0x0 0x56228000 0x0 0x300>;
                interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
                interrupt-parent = <&irqsteer_dsi0>;
                clocks =
@@ -48,15 +53,15 @@ Example:
                        <&clk IMX8QM_MIPI0_DSI_RX_ESC_CLK>;
                clock-names = "phy_ref", "tx_esc", "rx_esc";
                assigned-clocks = <&clk IMX8QM_MIPI0_DSI_TX_ESC_DIV>,
-                                  <&clk IMX8QM_MIPI0_DSI_RX_ESC_DIV>;
+                                 <&clk IMX8QM_MIPI0_DSI_RX_ESC_DIV>;
                assigned-clock-rates = <18000000>, <72000000>;
                power-domains = <&pd_mipi0>;
                phys = <&mipi_dsi_phy1>;
                phy-names = "dphy";
 
                port@0 {
-                       mipi_dsi0_in: endpoint {
-                               remote-endpoint = <&dpu1_disp0_mipi_dsi>;
+                       mipi_dsi_bridge1_in: endpoint {
+                               remote-endpoint = <&mipi_dsi1_out>;
                        };
                };
 
@@ -68,8 +73,10 @@ Example:
        };
 
 Another example, for a platform with a complex clock tree, like 8QXP:
-       mipi_dsi1: mipi_dsi@56228000 {
-               compatible = "fsl,imx8qxp-mipi-dsi";
+       mipi_dsi_bridge1: mipi_dsi_bridge@56228000 {
+               #address-cells = <1>;
+               #size-cells = <0>;
+               compatible = "nwl,mipi-dsi";
                reg = <0x0 0x56228000 0x0 0x300>;
                interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
                interrupt-parent = <&irqsteer_mipi_lvds0>;
@@ -92,8 +99,14 @@ Another example, for a platform with a complex clock tree, like 8QXP:
                phy-names = "dphy";
 
                port@0 {
-                       mipi_dsi1_in: endpoint {
-                               remote-endpoint = <&dpu_disp0_mipi_dsi>;
+                       mipi_dsi_bridge1_in: endpoint {
+                               remote-endpoint = <&mipi_dsi1_out>;
+                       };
+               };
+
+               port@1 {
+                       mipi_dsi0_out: endpoint {
+                               remote-endpoint = <&adv7535_0_in>;
                        };
                };
        };
index 6ff608f..296b01b 100644 (file)
@@ -2,7 +2,8 @@ NXP specific extensions to the Northwest Logic MIPI-DSI
 ================================
 
 Platform specific extentions for the NWL MIPI-DSI host controller found in
-MX8 platforms.
+MX8 platforms. This is an encoder/bridge that manages the platform specific
+initializations required for the NWL MIPI-DSI host.
 
 Required properties:
 - compatible:          "fsl,<chip>-mipi-dsi"
@@ -27,7 +28,7 @@ Required properties:
                        defined in Documentation/devicetree/bindings/graph.txt;
                        the input port should be connected to a display
                        interface and the output port should be connected to a
-                       panel or a bridge input port
+                       NWL MIPI-DSI host
 - phys:                phandle to the phy module representing the DPHY
                        inside MIPI-DSI IP block
 - phy-names:           should be "dphy"
@@ -43,65 +44,18 @@ Optional properties:
                        parents to clocks defined in assigned-clocks
 
 Example:
-       mipi_dsi1: mipi_dsi@56228000 {
-                compatible = "fsl,imx8qm-mipi-dsi";
-                reg = <0x0 0x56228000 0x0 0x1000>;
-                interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
-                interrupt-parent = <&irqsteer_dsi0>;
-                clocks =
-                        <&clk IMX8QM_MIPI0_PXL_CLK>,
-                        <&clk IMX8QM_MIPI0_BYPASS_CLK>,
-                       <&clk IMX8QM_CLK_DUMMY>,
-                        <&clk IMX8QM_MIPI0_DSI_TX_ESC_CLK>,
-                        <&clk IMX8QM_MIPI0_DSI_RX_ESC_CLK>;
-                clock-names = "pixel", "bypass", "phy_ref", "tx_esc", "rx_esc";
-               assigned-clocks = <&clk IMX8QM_MIPI0_DSI_TX_ESC_DIV>,
-                                  <&clk IMX8QM_MIPI0_DSI_RX_ESC_DIV>;
-               assigned-clock-rates = <18000000>, <72000000>;
-                power-domains = <&pd_mipi0>;
-                csr = <&mipi_dsi_csr1>;
-                phys = <&mipi_dsi_phy1>;
-                phy-names = "dphy";
-
-                port@0 {
-                        mipi_dsi1_in: endpoint {
-                                remote-endpoint = <&dpu1_disp0_mipi_dsi>;
-                        };
-                };
-
-               port@1 {
-                        mipi_dsi1_out: endpoint {
-                                remote-endpoint = <&adv7535_1_in>;
-                        };
-                };
-        };
-
-Another example, for a platform with a complex clock tree, like 8QXP:
-       mipi_dsi1: mipi_dsi@56228000 {
+       mipi_dsi1: mipi_dsi {
                compatible = "fsl,imx8qxp-mipi-dsi";
-               reg = <0x0 0x56228000 0x0 0x300>;
-               interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
-               interrupt-parent = <&irqsteer_mipi_lvds0>;
                clocks =
                        <&clk IMX8QXP_MIPI0_PIXEL_CLK>,
                        <&clk IMX8QXP_MIPI0_BYPASS_CLK>,
-                       <&clk IMX8QXP_CLK_DUMMY>,
-                       <&clk IMX8QXP_MIPI0_DSI_TX_ESC_CLK>,
-                       <&clk IMX8QXP_MIPI0_DSI_RX_ESC_CLK>;
-               clock-names = "pixel", "bypass", "phy_ref", "tx_esc", "rx_esc";
-               assigned-clocks =
-                       <&clk IMX8QXP_MIPI0_DSI_TX_ESC_SEL>,
-                       <&clk IMX8QXP_MIPI0_DSI_RX_ESC_SEL>,
-                       <&clk IMX8QXP_MIPI0_DSI_TX_ESC_CLK>,
-                       <&clk IMX8QXP_MIPI0_DSI_RX_ESC_CLK>;
-               assigned-clock-rates = <0>, <0>, <18000000>, <72000000>;
-               assigned-clock-parents =
-                       <&clk IMX8QXP_MIPI0_DSI_PLL_DIV2_CLK>,
-                       <&clk IMX8QXP_MIPI0_DSI_PLL_DIV2_CLK>;
+                       <&clk IMX8QXP_CLK_DUMMY>;
+               clock-names = "pixel", "bypass", "phy_ref";
                power-domains = <&pd_mipi_dsi0>;
                csr = <&mipi_dsi_csr1>;
                phys = <&mipi_dsi_phy1>;
                phy-names = "dphy";
+               status = "disabled";
 
                port@0 {
                        mipi_dsi1_in: endpoint {
@@ -110,11 +64,8 @@ Another example, for a platform with a complex clock tree, like 8QXP:
                };
 
                port@1 {
-                        mipi_dsi1_out: endpoint {
-                                remote-endpoint = <&adv7535_1_in>;
-                        };
-                };
+                       mipi_dsi1_out: endpoint {
+                               remote-endpoint = <&mipi_dsi_bridge1_in>;
+                       };
+               };
        };
-
-* Here, we set the clock parents for the *_SEL clocks (which are the sources of
-the *_CLK clocks) and also the clock rate of the *_CLK clocks.
index 3eb58b0..740e996 100644 (file)
@@ -177,15 +177,15 @@ struct mipi_dsi_transfer {
 
 struct clk_config {
        struct clk *clk;
-       u32 rate;
+       unsigned long rate;
        bool enabled;
 };
 
 struct nwl_mipi_dsi {
-       struct drm_encoder              *encoder;
-       struct device_node              *panel_node;
+       struct device                   *dev;
        struct drm_panel                *panel;
-       struct drm_bridge               *bridge;
+       struct drm_bridge               *next_bridge;
+       struct drm_bridge               bridge;
        struct drm_connector            connector;
        struct mipi_dsi_host            host;
 
@@ -236,18 +236,74 @@ static u32 nwl_dsi_get_dpi_pixel_format(enum mipi_dsi_pixel_format format)
        }
 }
 
-unsigned long nwl_dsi_get_bit_clock(struct drm_encoder *encoder,
+/* Adds a bridge to encoder bridge chain */
+bool nwl_dsi_add_bridge(struct drm_encoder *encoder,
+                       struct drm_bridge *next_bridge)
+{
+       struct drm_bridge *bridge = encoder->bridge;
+
+       if (!next_bridge)
+               return false;
+
+       next_bridge->encoder = encoder;
+       if (!bridge) {
+               encoder->bridge = bridge;
+               return true;
+       }
+
+       while (bridge != next_bridge && bridge->next)
+               bridge = bridge->next;
+
+       /* Avoid adding an existing bridge to the chain */
+       if (bridge == next_bridge) {
+               next_bridge->encoder = NULL;
+               return false;
+       }
+
+       bridge->next = next_bridge;
+       return true;
+}
+EXPORT_SYMBOL_GPL(nwl_dsi_add_bridge);
+
+/* Removes last bridge from encoder bridge chain */
+bool nwl_dsi_del_bridge(struct drm_encoder *encoder,
+                       struct drm_bridge *bridge)
+{
+       struct drm_bridge *b = encoder->bridge;
+       struct drm_bridge *prev = NULL;
+
+       if (!b || !bridge)
+               return false;
+
+       while (b->next) {
+               prev = b;
+               b = b->next;
+       }
+
+       bridge->encoder = NULL;
+       if (prev)
+               prev->next = NULL;
+       else
+               encoder->bridge = NULL;
+
+       return true;
+}
+EXPORT_SYMBOL_GPL(nwl_dsi_del_bridge);
+
+unsigned long nwl_dsi_get_bit_clock(struct drm_bridge *bridge,
        unsigned long pixclock)
 {
-       struct nwl_mipi_dsi *dsi = encoder->bridge->driver_private;
+       struct nwl_mipi_dsi *dsi;
        int bpp;
 
        /* Make sure the bridge is correctly initialized */
-       if (!encoder->bridge || !encoder->bridge->driver_private ||
-               dsi->lanes < 1 || dsi->lanes > 4)
+       if (!bridge || !bridge->driver_private)
                return 0;
 
-       dsi = encoder->bridge->driver_private;
+       dsi = bridge->driver_private;
+
+       if (dsi->lanes < 1 || dsi->lanes > 4)
+               return 0;
 
        bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
 
@@ -262,15 +318,17 @@ static void nwl_dsi_config_host(struct nwl_mipi_dsi *dsi)
 
        nwl_dsi_write(dsi, CFG_NUM_LANES, dsi->lanes - 1);
 
-       if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
+       if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
                nwl_dsi_write(dsi, CFG_NONCONTINUOUS_CLK, 0x01);
-       else
+               nwl_dsi_write(dsi, CFG_AUTOINSERT_EOTP, 0x01);
+       } else {
                nwl_dsi_write(dsi, CFG_NONCONTINUOUS_CLK, 0x00);
+               nwl_dsi_write(dsi, CFG_AUTOINSERT_EOTP, 0x00);
+       }
 
        nwl_dsi_write(dsi, CFG_T_PRE, 0x01);
        nwl_dsi_write(dsi, CFG_T_POST, 0x34);
        nwl_dsi_write(dsi, CFG_TX_GAP, 0x0D);
-       nwl_dsi_write(dsi, CFG_AUTOINSERT_EOTP, 0x01);
        nwl_dsi_write(dsi, CFG_EXTRA_CMDS_AFTER_EOTP, 0x00);
        nwl_dsi_write(dsi, CFG_HTX_TO_COUNT, 0x00);
        nwl_dsi_write(dsi, CFG_LRX_H_TO_COUNT, 0x00);
@@ -319,11 +377,11 @@ static void nwl_dsi_config_dpi(struct nwl_mipi_dsi *dsi)
 
 static void nwl_dsi_enable_clocks(struct nwl_mipi_dsi *dsi, u32 clks)
 {
-       struct device *dev = dsi->host.dev;
+       struct device *dev = dsi->dev;
        unsigned long rate;
 
        if (clks & CLK_PHY_REF && !dsi->phy_ref.enabled) {
-               clk_enable(dsi->phy_ref.clk);
+               clk_prepare_enable(dsi->phy_ref.clk);
                dsi->phy_ref.enabled = true;
                rate = clk_get_rate(dsi->phy_ref.clk);
                DRM_DEV_DEBUG_DRIVER(dev,
@@ -331,12 +389,15 @@ static void nwl_dsi_enable_clocks(struct nwl_mipi_dsi *dsi, u32 clks)
        }
 
        if (clks & CLK_RX_ESC && !dsi->rx_esc.enabled) {
-               clk_enable(dsi->rx_esc.clk);
+               clk_set_rate(dsi->rx_esc.clk, dsi->rx_esc.rate);
+               clk_prepare_enable(dsi->rx_esc.clk);
                dsi->rx_esc.enabled = true;
+               rate = clk_get_rate(dsi->rx_esc.clk);
        }
 
        if (clks & CLK_TX_ESC && !dsi->tx_esc.enabled) {
-               clk_enable(dsi->tx_esc.clk);
+               clk_set_rate(dsi->tx_esc.clk, dsi->tx_esc.rate);
+               clk_prepare_enable(dsi->tx_esc.clk);
                dsi->tx_esc.enabled = true;
                rate = clk_get_rate(dsi->tx_esc.clk);
                DRM_DEV_DEBUG_DRIVER(dev,
@@ -346,21 +407,21 @@ static void nwl_dsi_enable_clocks(struct nwl_mipi_dsi *dsi, u32 clks)
 
 static void nwl_dsi_disable_clocks(struct nwl_mipi_dsi *dsi, u32 clks)
 {
-       struct device *dev = dsi->host.dev;
+       struct device *dev = dsi->dev;
 
        if (clks & CLK_PHY_REF && dsi->phy_ref.enabled) {
-               clk_disable(dsi->phy_ref.clk);
+               clk_disable_unprepare(dsi->phy_ref.clk);
                dsi->phy_ref.enabled = false;
                DRM_DEV_DEBUG_DRIVER(dev, "Disabled phy_ref clk\n");
        }
 
        if (clks & CLK_RX_ESC && dsi->rx_esc.enabled) {
-               clk_disable(dsi->rx_esc.clk);
+               clk_disable_unprepare(dsi->rx_esc.clk);
                dsi->rx_esc.enabled = false;
        }
 
        if (clks & CLK_TX_ESC && dsi->tx_esc.enabled) {
-               clk_disable(dsi->tx_esc.clk);
+               clk_disable_unprepare(dsi->tx_esc.clk);
                dsi->tx_esc.enabled = false;
                DRM_DEV_DEBUG_DRIVER(dev, "Disabled tx_esc clk\n");
        }
@@ -383,15 +444,23 @@ static void nwl_dsi_init_interrupts(struct nwl_mipi_dsi *dsi)
 static void nwl_dsi_bridge_enable(struct drm_bridge *bridge)
 {
        struct nwl_mipi_dsi *dsi = bridge->driver_private;
-       struct device *dev = dsi->host.dev;
+       struct device *dev = dsi->dev;
        int ret;
 
+       if (dsi->enabled)
+               return;
+
        if (!dsi->lanes) {
                DRM_DEV_ERROR(dev, "Bridge not set up properly!\n");
                return;
        }
 
-       nwl_dsi_enable_clocks(dsi, CLK_PHY_REF);
+       nwl_dsi_enable_clocks(dsi, CLK_PHY_REF | CLK_TX_ESC);
+
+       nwl_dsi_config_host(dsi);
+       nwl_dsi_config_dpi(dsi);
+
+       phy_init(dsi->phy);
 
        ret = phy_power_on(dsi->phy);
        if (ret < 0) {
@@ -421,7 +490,10 @@ static void nwl_dsi_bridge_enable(struct drm_bridge *bridge)
 static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
 {
        struct nwl_mipi_dsi *dsi = bridge->driver_private;
-       struct device *dev = dsi->host.dev;
+       struct device *dev = dsi->dev;
+
+       if (!dsi->enabled)
+               return;
 
        if (dsi->panel) {
                if (drm_panel_disable(dsi->panel)) {
@@ -434,6 +506,7 @@ static void nwl_dsi_bridge_disable(struct drm_bridge *bridge)
        nwl_dsi_disable_clocks(dsi, CLK_PHY_REF | CLK_TX_ESC);
 
        phy_power_off(dsi->phy);
+       phy_exit(dsi->phy);
 
        dsi->enabled = false;
 }
@@ -468,100 +541,8 @@ static void nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
 
        drm_display_mode_to_videomode(adjusted, &dsi->vm);
 
-       DRM_DEV_DEBUG_DRIVER(dsi->host.dev, "\n");
+       DRM_DEV_DEBUG_DRIVER(dsi->dev, "\n");
        drm_mode_debug_printmodeline(adjusted);
-
-       nwl_dsi_enable_clocks(dsi, CLK_TX_ESC);
-
-       nwl_dsi_config_host(dsi);
-       nwl_dsi_config_dpi(dsi);
-
-       phy_init(dsi->phy);
-}
-
-static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
-       .enable = nwl_dsi_bridge_enable,
-       .disable = nwl_dsi_bridge_disable,
-       .mode_fixup = nwl_dsi_bridge_mode_fixup,
-       .mode_set = nwl_dsi_bridge_mode_set,
-};
-
-static enum drm_connector_status nwl_dsi_connector_detect(
-       struct drm_connector *connector, bool force)
-{
-       struct nwl_mipi_dsi *dsi = container_of(connector,
-                                               struct nwl_mipi_dsi,
-                                               connector);
-
-       if (dsi->panel)
-               return connector_status_connected;
-
-       return connector_status_unknown;
-}
-
-static int nwl_dsi_connector_get_modes(struct drm_connector *connector)
-{
-       struct nwl_mipi_dsi *dsi = container_of(connector,
-                                               struct nwl_mipi_dsi,
-                                               connector);
-
-       if (dsi->panel)
-               return drm_panel_get_modes(dsi->panel);
-
-       return 0;
-}
-
-static const struct drm_connector_funcs nwl_dsi_connector_funcs = {
-       .dpms = drm_atomic_helper_connector_dpms,
-       .detect = nwl_dsi_connector_detect,
-       .fill_modes = drm_helper_probe_single_connector_modes,
-       .destroy = drm_connector_cleanup,
-       .reset = drm_atomic_helper_connector_reset,
-       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-static const struct drm_connector_helper_funcs
-       nwl_dsi_connector_helper_funcs = {
-       .get_modes = nwl_dsi_connector_get_modes,
-};
-
-static int nwl_dsi_create_connector(struct drm_device *drm,
-                                   struct nwl_mipi_dsi *dsi)
-{
-       struct device *dev = dsi->host.dev;
-       int ret;
-
-       ret = drm_connector_init(drm, &dsi->connector,
-                                &nwl_dsi_connector_funcs,
-                                DRM_MODE_CONNECTOR_DSI);
-       if (ret) {
-               DRM_DEV_ERROR(dev, "Failed to init drm connector: %d\n", ret);
-               return ret;
-       }
-
-       drm_connector_helper_add(&dsi->connector,
-                                &nwl_dsi_connector_helper_funcs);
-
-       dsi->connector.dpms = DRM_MODE_DPMS_OFF;
-       drm_mode_connector_attach_encoder(&dsi->connector, dsi->encoder);
-
-       if (!dsi->panel)
-               dsi->panel = of_drm_find_panel(dsi->panel_node);
-
-       if (dsi->panel) {
-               ret = drm_panel_attach(dsi->panel, &dsi->connector);
-               if (ret) {
-                       DRM_DEV_ERROR(dev, "Failed to attach panel: %d\n", ret);
-                       goto err_connector;
-               }
-       }
-
-       return 0;
-
-err_connector:
-       drm_connector_cleanup(&dsi->connector);
-       return ret;
 }
 
 static int nwl_dsi_host_attach(struct mipi_dsi_host *host,
@@ -570,7 +551,7 @@ static int nwl_dsi_host_attach(struct mipi_dsi_host *host,
        struct nwl_mipi_dsi *dsi = container_of(host,
                                                struct nwl_mipi_dsi,
                                                host);
-       struct device *dev = dsi->host.dev;
+       struct device *dev = dsi->dev;
 
        DRM_DEV_INFO(dev, "lanes=%u, format=0x%x flags=0x%lx\n",
                     device->lanes, device->format, device->mode_flags);
@@ -578,10 +559,35 @@ static int nwl_dsi_host_attach(struct mipi_dsi_host *host,
        if (device->lanes < 1 || device->lanes > 4)
                return -EINVAL;
 
+       /*
+        * Someone has attached to us; it could be a panel or another bridge.
+        * Check to is if this is a panel or not.
+        */
+       if (!dsi->next_bridge ||
+           device->dev.of_node != dsi->next_bridge->of_node)
+               dsi->panel = of_drm_find_panel(device->dev.of_node);
+
+       /*
+        * Bridge has priority in front of panel.
+        * Since the panel driver cannot tell if there is a physical
+        * panel connected, we'll asume that there is no physical panel if there
+        * is a bridge registered.
+        */
+       if (dsi->next_bridge &&
+           device->dev.of_node != NULL &&
+           device->dev.of_node != dsi->next_bridge->of_node) {
+               dsi->panel = NULL;
+               return -EPERM;
+       }
+
+       if (dsi->panel)
+               DRM_DEV_DEBUG_DRIVER(dsi->dev, "Panel attached\n");
+       else
+               DRM_DEV_DEBUG_DRIVER(dsi->dev, "Bridge attached\n");
+
        dsi->lanes = device->lanes;
        dsi->format = device->format;
        dsi->dsi_mode_flags = device->mode_flags;
-       dsi->panel_node = device->dev.of_node;
 
        if (dsi->connector.dev)
                drm_helper_hpd_irq_event(dsi->connector.dev);
@@ -595,8 +601,8 @@ static int nwl_dsi_host_detach(struct mipi_dsi_host *host,
        struct nwl_mipi_dsi *dsi = container_of(host,
                                                struct nwl_mipi_dsi,
                                                host);
-
-       dsi->panel_node = NULL;
+       if (dsi->panel)
+               dsi->panel = NULL;
 
        if (dsi->connector.dev)
                drm_helper_hpd_irq_event(dsi->connector.dev);
@@ -657,7 +663,7 @@ static void nwl_dsi_print_error(struct device *dev, u16 error)
 
 static bool nwl_dsi_read_packet(struct nwl_mipi_dsi *dsi, u32 status)
 {
-       struct device *dev = dsi->host.dev;
+       struct device *dev = dsi->dev;
        struct mipi_dsi_transfer *xfer = dsi->xfer;
        u8 *payload = xfer->msg->rx_buf;
        u32 val;
@@ -919,54 +925,185 @@ static irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
        return IRQ_HANDLED;
 }
 
-static int nwl_dsi_register(struct drm_device *drm, struct nwl_mipi_dsi *dsi)
+static enum drm_connector_status nwl_dsi_connector_detect(
+       struct drm_connector *connector, bool force)
 {
-       struct drm_encoder *encoder = dsi->encoder;
-       struct drm_bridge *bridge;
-       struct device *dev = dsi->host.dev;
-       int ret = 0;
+       struct nwl_mipi_dsi *dsi = container_of(connector,
+                                               struct nwl_mipi_dsi,
+                                               connector);
 
-       bridge = devm_kzalloc(drm->dev, sizeof(*bridge), GFP_KERNEL);
-       if (!bridge) {
-               DRM_DEV_ERROR(dev, "failed to allocate drm bridge\n");
-               return -ENOMEM;
+       if (dsi->panel)
+               return connector_status_connected;
+
+       return connector_status_unknown;
+}
+
+static int nwl_dsi_connector_get_modes(struct drm_connector *connector)
+{
+       struct nwl_mipi_dsi *dsi = container_of(connector,
+                                               struct nwl_mipi_dsi,
+                                               connector);
+
+       if (dsi->panel)
+               return drm_panel_get_modes(dsi->panel);
+
+       return 0;
+}
+
+static const struct drm_connector_funcs nwl_dsi_connector_funcs = {
+       .dpms = drm_atomic_helper_connector_dpms,
+       .detect = nwl_dsi_connector_detect,
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = drm_connector_cleanup,
+       .reset = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+       nwl_dsi_connector_helper_funcs = {
+       .get_modes = nwl_dsi_connector_get_modes,
+};
+
+static int nwl_dsi_create_connector(struct drm_device *drm,
+                                   struct nwl_mipi_dsi *dsi)
+{
+       struct device *dev = dsi->dev;
+       int ret;
+
+       ret = drm_connector_init(drm, &dsi->connector,
+                                &nwl_dsi_connector_funcs,
+                                DRM_MODE_CONNECTOR_DSI);
+       if (ret) {
+               DRM_DEV_ERROR(dev, "Failed to init drm connector: %d\n", ret);
+               return ret;
        }
 
-       dsi->bridge = bridge;
-       bridge->driver_private = dsi;
-       bridge->funcs = &nwl_dsi_bridge_funcs;
+       drm_connector_helper_add(&dsi->connector,
+                                &nwl_dsi_connector_helper_funcs);
 
-       ret = drm_bridge_attach(drm, bridge);
+       dsi->connector.dpms = DRM_MODE_DPMS_OFF;
+       drm_mode_connector_attach_encoder(&dsi->connector, dsi->bridge.encoder);
+
+       ret = drm_panel_attach(dsi->panel, &dsi->connector);
        if (ret) {
-               DRM_DEV_ERROR(dev, "Failed to initialize bridge (%d)\n", ret);
+               DRM_DEV_ERROR(dev, "Failed to attach panel: %d\n", ret);
+               drm_connector_cleanup(&dsi->connector);
                return ret;
        }
 
-       encoder->bridge = bridge;
-       bridge->encoder = encoder;
+       return 0;
+}
+
+static int nwl_dsi_attach_next_bridge(struct drm_encoder *encoder,
+                                     struct drm_bridge *bridge)
+{
+       int ret = 0;
+
+       /* Attach the next bridge in chain */
+       if (!nwl_dsi_add_bridge(encoder, bridge))
+               return -EEXIST;
+       ret = drm_bridge_attach(encoder->dev, bridge);
+       if (ret)
+               nwl_dsi_del_bridge(encoder, bridge);
 
        return ret;
 }
 
-int nwl_dsi_bind(struct device *dev,
-               struct drm_encoder *encoder,
-               struct phy *phy,
-               struct resource *res,
-               int irq)
+static int nwl_dsi_bridge_attach(struct drm_bridge *bridge)
 {
-       struct drm_device *drm = encoder->dev;
-       struct drm_bridge *next_bridge = NULL;
+       struct nwl_mipi_dsi *dsi = bridge->driver_private;
+       struct device *dev = dsi->dev;
+       struct drm_encoder *encoder = bridge->encoder;
        struct device_node *np = dev->of_node;
        struct device_node *remote_node, *endpoint;
+
+       int ret = 0;
+
+       DRM_DEV_DEBUG_DRIVER(dsi->dev, "\n");
+       if (!encoder) {
+               DRM_DEV_ERROR(dev, "Parent encoder object not found\n");
+               return -ENODEV;
+       }
+
+       dsi->host.ops = &nwl_dsi_host_ops;
+       dsi->host.dev = dev;
+       ret = mipi_dsi_host_register(&dsi->host);
+       if (ret < 0) {
+               dev_err(dev, "failed to register DSI host (%d)\n", ret);
+               return ret;
+       }
+
+       endpoint = of_graph_get_next_endpoint(np, NULL);
+       while (endpoint && !dsi->next_bridge) {
+               remote_node = of_graph_get_remote_port_parent(endpoint);
+               if (!remote_node) {
+                       DRM_DEV_ERROR(dev, "No endpoint found!\n");
+                       return -ENODEV;
+               }
+
+               dsi->next_bridge = of_drm_find_bridge(remote_node);
+               ret = nwl_dsi_attach_next_bridge(encoder, dsi->next_bridge);
+               if (ret)
+                       dsi->next_bridge = NULL;
+               of_node_put(remote_node);
+               endpoint = of_graph_get_next_endpoint(np, endpoint);
+       };
+
+       /*
+        * Create the connector. If we have a bridge, attach it and let the
+        * bridge create the connector.
+        */
+       if (dsi->panel)
+               ret = nwl_dsi_create_connector(encoder->dev, dsi);
+       else if (!dsi->next_bridge)
+               ret = -ENODEV;
+
+       return ret;
+}
+
+static void nwl_dsi_bridge_detach(struct drm_bridge *bridge)
+{
+       struct nwl_mipi_dsi *dsi = bridge->driver_private;
+
+       DRM_DEV_DEBUG_DRIVER(dsi->dev, "\n");
+       if (dsi->panel) {
+               drm_panel_detach(dsi->panel);
+               drm_connector_cleanup(&dsi->connector);
+       } else if (dsi->next_bridge) {
+               drm_bridge_detach(dsi->next_bridge);
+               nwl_dsi_del_bridge(dsi->next_bridge->encoder, dsi->next_bridge);
+       }
+}
+
+static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
+       .enable = nwl_dsi_bridge_enable,
+       .disable = nwl_dsi_bridge_disable,
+       .mode_fixup = nwl_dsi_bridge_mode_fixup,
+       .mode_set = nwl_dsi_bridge_mode_set,
+       .attach = nwl_dsi_bridge_attach,
+       .detach = nwl_dsi_bridge_detach,
+};
+
+static int nwl_dsi_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
        struct nwl_mipi_dsi *dsi;
        struct clk *clk;
+       struct resource *res;
+       int irq;
        int ret;
 
        dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
        if (!dsi)
                return -ENOMEM;
 
-       dsi->phy = phy;
+       dsi->phy = devm_phy_get(dev, "dphy");
+       if (IS_ERR(dsi->phy)) {
+               ret = PTR_ERR(dsi->phy);
+               dev_err(dev, "Could not get PHY (%d)\n", ret);
+               return ret;
+       }
 
        clk = devm_clk_get(dev, "phy_ref");
        if (IS_ERR(clk)) {
@@ -975,8 +1112,8 @@ int nwl_dsi_bind(struct device *dev,
                return ret;
        }
        dsi->phy_ref.clk = clk;
+       dsi->phy_ref.rate = clk_get_rate(clk);
        dsi->phy_ref.enabled = false;
-       clk_prepare(clk);
 
        clk = devm_clk_get(dev, "rx_esc");
        if (IS_ERR(clk)) {
@@ -985,8 +1122,8 @@ int nwl_dsi_bind(struct device *dev,
                return ret;
        }
        dsi->rx_esc.clk = clk;
+       dsi->rx_esc.rate = clk_get_rate(clk);
        dsi->rx_esc.enabled = false;
-       clk_prepare(clk);
 
        clk = devm_clk_get(dev, "tx_esc");
        if (IS_ERR(clk)) {
@@ -995,16 +1132,28 @@ int nwl_dsi_bind(struct device *dev,
                return ret;
        }
        dsi->tx_esc.clk = clk;
+       dsi->tx_esc.rate = clk_get_rate(clk);
        dsi->tx_esc.enabled = false;
-       clk_prepare(clk);
+       /* TX clk rate must be RX clk rate divided by 4 */
+       if (dsi->tx_esc.rate != (dsi->rx_esc.rate / 4))
+               dsi->tx_esc.rate = dsi->rx_esc.rate / 4;
 
-       dsi->encoder = encoder;
        dsi->enabled = false;
 
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res)
+               return -EBUSY;
+
        dsi->base = devm_ioremap_resource(dev, res);
        if (IS_ERR(dsi->base))
                return PTR_ERR(dsi->base);
 
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               DRM_DEV_ERROR(dev, "Failed to get device IRQ!\n");
+               return -EINVAL;
+       }
+
        ret = devm_request_irq(dev, irq,
                        nwl_dsi_irq_handler, 0, IRQ_NAME, dsi);
        if (ret < 0) {
@@ -1012,49 +1161,17 @@ int nwl_dsi_bind(struct device *dev,
                return ret;
        }
 
-       ret = nwl_dsi_register(drm, dsi);
-       if (ret)
-               return ret;
+       dsi->dev = dev;
+       platform_set_drvdata(pdev, dsi);
 
-       endpoint = of_graph_get_next_endpoint(np, NULL);
-       while (endpoint && !next_bridge) {
-               remote_node = of_graph_get_remote_port_parent(endpoint);
-               if (!remote_node) {
-                       dev_err(dev, "No endpoint found!\n");
-                       return -ENODEV;
-               }
-
-               next_bridge = of_drm_find_bridge(remote_node);
-               of_node_put(remote_node);
-               endpoint = of_graph_get_next_endpoint(np, endpoint);
-       };
+       dsi->bridge.driver_private = dsi;
+       dsi->bridge.funcs = &nwl_dsi_bridge_funcs;
+       dsi->bridge.of_node = dev->of_node;
 
-       dsi->host.ops = &nwl_dsi_host_ops;
-       dsi->host.dev = dev;
-       ret = mipi_dsi_host_register(&dsi->host);
+       ret = drm_bridge_add(&dsi->bridge);
        if (ret < 0) {
-               dev_err(dev, "failed to register DSI host (%d)\n", ret);
-               return ret;
-       }
-
-       /*
-        * Create the connector. If we have a bridge, attach it and let the
-        * bridge create the connector.
-        */
-       if (next_bridge != NULL) {
-               /* Link bridge with encoder */
-               dsi->bridge->next = next_bridge;
-               next_bridge->encoder = encoder;
-               encoder->bridge->next = next_bridge;
-               if (drm_bridge_attach(encoder->dev, next_bridge)) {
-                       DRM_DEV_ERROR(dev, "Failed to attach bridge to drm!\n");
-                       next_bridge->encoder = NULL;
-                       encoder->bridge->next = NULL;
-               }
-       } else {
-               ret = nwl_dsi_create_connector(drm, dsi);
-               if (ret)
-                       goto err_host;
+               dev_err(dev, "Failed to add nwl-dsi bridge (%d)\n", ret);
+               goto err_host;
        }
 
        return 0;
@@ -1064,24 +1181,32 @@ err_host:
        return ret;
 
 }
-EXPORT_SYMBOL_GPL(nwl_dsi_bind);
 
-void nwl_dsi_unbind(struct drm_bridge *bridge)
+static int nwl_dsi_remove(struct platform_device *pdev)
 {
-       struct nwl_mipi_dsi *dsi;
+       struct nwl_mipi_dsi *dsi = platform_get_drvdata(pdev);
 
-       if (!bridge)
-               return;
+       mipi_dsi_host_unregister(&dsi->host);
 
-       dsi = bridge->driver_private;
+       return 0;
+}
 
-       /* Skip connector cleanup if creation was delegated to the bridge */
-       if (dsi->connector.dev)
-               drm_connector_cleanup(&dsi->connector);
+static const struct of_device_id nwl_dsi_dt_ids[] = {
+       { .compatible = "nwl,mipi-dsi" },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
+
+static struct platform_driver imx_nwl_dsi_driver = {
+       .probe          = nwl_dsi_probe,
+       .remove         = nwl_dsi_remove,
+       .driver         = {
+               .of_match_table = nwl_dsi_dt_ids,
+               .name   = "nwl-mipi-dsi",
+       },
+};
 
-       mipi_dsi_host_unregister(&dsi->host);
-}
-EXPORT_SYMBOL_GPL(nwl_dsi_unbind);
+module_platform_driver(imx_nwl_dsi_driver);
 
 MODULE_AUTHOR("NXP Semiconductor");
 MODULE_DESCRIPTION("NWL MIPI-DSI transmitter driver");
index f352e6a..7443167 100644 (file)
  * GNU General Public License for more details.
  */
 
-
 #include <drm/bridge/nwl_dsi.h>
+#include <drm/drmP.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_of.h>
-#include <drm/drmP.h>
 #include <linux/clk.h>
 #include <linux/component.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx8mq-iomuxc-gpr.h>
 #include <linux/module.h>
-#include <linux/of_graph.h>
 #include <linux/of.h>
+#include <linux/of_graph.h>
 #include <linux/of_platform.h>
-#include <linux/phy/phy.h>
 #include <linux/phy/phy-mixel-mipi-dsi.h>
+#include <linux/phy/phy.h>
 #include <linux/regmap.h>
 #include <soc/imx8/sc/sci.h>
 #include <video/videomode.h>
 
 #define DRIVER_NAME "nwl_dsi-imx"
 
+/* 8MQ SRC specific registers */
+#define SRC_MIPIPHY_RCR                                0x28
+#define RESET_BYTE_N                           BIT(1)
+#define RESET_N                                        BIT(2)
+#define DPI_RESET_N                            BIT(3)
+#define ESC_RESET_N                            BIT(4)
+#define PCLK_RESET_N                           BIT(5)
+
 #define DC_ID(x)       SC_R_DC_ ## x
 #define MIPI_ID(x)     SC_R_MIPI_ ## x
 #define SYNC_CTRL(x)   SC_C_SYNC_CTRL ## x
 
 /* Possible clocks */
 #define CLK_PIXEL      "pixel"
+#define CLK_CORE       "core"
 #define CLK_BYPASS     "bypass"
 #define CLK_PHYREF     "phy_ref"
 
 /* Possible valid PHY reference clock rates*/
-u32 phyref_rates[] =
-{
+u32 phyref_rates[] = {
+       24000000,
+       25000000,
        27000000,
-       24000000
 };
 
 struct imx_mipi_dsi {
        struct drm_encoder              encoder;
+       struct drm_bridge               bridge;
+       struct drm_bridge               *next_bridge;
        struct device                   *dev;
        struct phy                      *phy;
 
        /* Optional external regs */
        struct regmap                   *csr;
+       struct regmap                   *reset;
+       struct regmap                   *mux_sel;
 
        /* Optional clocks */
        struct clk_config               *clk_config;
@@ -72,6 +85,7 @@ struct imx_mipi_dsi {
        u32 tx_ulps_reg;
        u32 pxl2dpi_reg;
 
+       unsigned long                   bit_clk;
        u32                             phyref_rate;
        u32                             instance;
        bool                            enabled;
@@ -81,11 +95,14 @@ struct clk_config {
        const char *id;
        struct clk *clk;
        bool present;
+       bool enabled;
        u32 rate;
 };
 
 enum imx_ext_regs {
-       IMX_REG_CSR = BIT(0),
+       IMX_REG_CSR = BIT(1),
+       IMX_REG_SRC = BIT(2),
+       IMX_REG_GPR = BIT(3),
 };
 
 struct devtype {
@@ -95,7 +112,7 @@ struct devtype {
        u32 tx_ulps_reg;
        u32 pxl2dpi_reg;
        u8 max_instances;
-       struct clk_config clk_config[3];
+       struct clk_config clk_config[4];
 };
 
 static int imx8qm_dsi_poweron(struct imx_mipi_dsi *dsi);
@@ -104,6 +121,7 @@ static struct devtype imx8qm_dev = {
        .poweron = &imx8qm_dsi_poweron,
        .poweroff = &imx8qm_dsi_poweroff,
        .clk_config = {
+               { .id = CLK_CORE,   .present = false },
                { .id = CLK_PIXEL,  .present = true },
                { .id = CLK_BYPASS, .present = true },
                { .id = CLK_PHYREF, .present = true },
@@ -120,6 +138,7 @@ static struct devtype imx8qxp_dev = {
        .poweron = &imx8qxp_dsi_poweron,
        .poweroff = &imx8qxp_dsi_poweroff,
        .clk_config = {
+               { .id = CLK_CORE,   .present = false },
                { .id = CLK_PIXEL,  .present = true },
                { .id = CLK_BYPASS, .present = true },
                { .id = CLK_PHYREF, .present = true },
@@ -130,9 +149,25 @@ static struct devtype imx8qxp_dev = {
        .max_instances =    2,
 };
 
+static int imx8mq_dsi_poweron(struct imx_mipi_dsi *dsi);
+static int imx8mq_dsi_poweroff(struct imx_mipi_dsi *dsi);
+static struct devtype imx8mq_dev = {
+       .poweron = &imx8mq_dsi_poweron,
+       .poweroff = &imx8mq_dsi_poweroff,
+       .clk_config = {
+               { .id = CLK_CORE,   .present = true },
+               { .id = CLK_PIXEL,  .present = false },
+               { .id = CLK_BYPASS, .present = false },
+               { .id = CLK_PHYREF, .present = true },
+       },
+       .ext_regs = IMX_REG_SRC | IMX_REG_GPR,
+       .max_instances = 1,
+};
+
 static const struct of_device_id imx_nwl_dsi_dt_ids[] = {
        { .compatible = "fsl,imx8qm-mipi-dsi", .data = &imx8qm_dev, },
        { .compatible = "fsl,imx8qxp-mipi-dsi", .data = &imx8qxp_dev, },
+       { .compatible = "fsl,imx8mq-mipi-dsi_drm", .data = &imx8mq_dev, },
        { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, imx_nwl_dsi_dt_ids);
@@ -147,7 +182,8 @@ static void imx_nwl_dsi_set_clocks(struct imx_mipi_dsi *dsi, bool enable)
        struct device *dev = dsi->dev;
        const char *id;
        struct clk *clk;
-       unsigned long rate;
+       unsigned long new_rate, cur_rate;
+       bool enabled;
        size_t i;
 
        for (i = 0; i < dsi->clk_num; i++) {
@@ -155,21 +191,30 @@ static void imx_nwl_dsi_set_clocks(struct imx_mipi_dsi *dsi, bool enable)
                        continue;
                id = dsi->clk_config[i].id;
                clk = dsi->clk_config[i].clk;
-               rate = dsi->clk_config[i].rate;
+               new_rate = dsi->clk_config[i].rate;
+               cur_rate = clk_get_rate(clk);
+               enabled = dsi->clk_config[i].enabled;
 
                /* BYPASS clk must have the same rate as PHY_REF clk */
-               if (!strcmp(id, CLK_BYPASS))
-                       rate = dsi->phyref_rate;
+               if (!strcmp(id, CLK_BYPASS) || !strcmp(id, CLK_PHYREF))
+                       new_rate = dsi->phyref_rate;
 
                if (enable) {
-                       if (rate > 0)
-                               clk_set_rate(clk, rate);
-                       clk_enable(clk);
-                       rate = clk_get_rate(clk);
+                       if (enabled && new_rate != cur_rate)
+                               clk_disable_unprepare(clk);
+                       else if (enabled && new_rate == cur_rate)
+                               continue;
+                       if (new_rate > 0)
+                               clk_set_rate(clk, new_rate);
+                       clk_prepare_enable(clk);
+                       dsi->clk_config[i].enabled = true;
+                       cur_rate = clk_get_rate(clk);
                        DRM_DEV_DEBUG_DRIVER(dev,
-                               "Enabled %s clk (rate=%lu)\n", id, rate);
-               } else {
-                       clk_disable(clk);
+                               "Enabled %s clk (rate: req=%lu act=%lu)\n",
+                               id, new_rate, cur_rate);
+               } else if (enabled) {
+                       clk_disable_unprepare(clk);
+                       dsi->clk_config[i].enabled = false;
                        DRM_DEV_DEBUG_DRIVER(dev, "Disabled %s clk\n", id);
                }
        }
@@ -384,16 +429,42 @@ static int imx8qxp_dsi_poweroff(struct imx_mipi_dsi *dsi)
        return imx8q_dsi_poweroff(dsi, true);
 }
 
-static void imx_nwl_dsi_encoder_enable(struct drm_encoder *encoder)
+static int imx8mq_dsi_poweron(struct imx_mipi_dsi *dsi)
+{
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          PCLK_RESET_N, PCLK_RESET_N);
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          ESC_RESET_N, ESC_RESET_N);
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          RESET_BYTE_N, RESET_BYTE_N);
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          DPI_RESET_N, DPI_RESET_N);
+
+       return 0;
+}
+
+static int imx8mq_dsi_poweroff(struct imx_mipi_dsi *dsi)
+{
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          PCLK_RESET_N, 0);
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          ESC_RESET_N, 0);
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          RESET_BYTE_N, 0);
+       regmap_update_bits(dsi->reset, SRC_MIPIPHY_RCR,
+                          DPI_RESET_N, 0);
+
+       return 0;
+}
+
+static void imx_nwl_dsi_enable(struct imx_mipi_dsi *dsi)
 {
-       struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
        struct device *dev = dsi->dev;
        const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
                                                           dev);
        const struct devtype *devtype = of_id->data;
        int ret;
 
-
        if (dsi->enabled)
                return;
 
@@ -410,9 +481,8 @@ static void imx_nwl_dsi_encoder_enable(struct drm_encoder *encoder)
        dsi->enabled = true;
 }
 
-static void imx_nwl_dsi_encoder_disable(struct drm_encoder *encoder)
+static void imx_nwl_dsi_disable(struct imx_mipi_dsi *dsi)
 {
-       struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
        struct device *dev = dsi->dev;
        const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
                                                           dev);
@@ -445,7 +515,15 @@ static int imx_nwl_try_phy_speed(struct imx_mipi_dsi *dsi,
        int ret = 0;
 
        pixclock = mode->clock * 1000;
-       bit_clk = nwl_dsi_get_bit_clock(&dsi->encoder, pixclock);
+       /*
+        * DSI host should know the required bit clock, since it has info
+        * about bits-per-pixel and number of lanes from DSI device
+        */
+       bit_clk = nwl_dsi_get_bit_clock(dsi->next_bridge, pixclock);
+
+       /* If bit_clk is the same with current, we're good */
+       if (bit_clk == dsi->bit_clk)
+               return 0;
 
        for (i = 0; i < num_rates; i++) {
                dsi->phyref_rate = phyref_rates[i];
@@ -453,24 +531,40 @@ static int imx_nwl_try_phy_speed(struct imx_mipi_dsi *dsi,
                        dsi->phyref_rate);
                ret = mixel_phy_mipi_set_phy_speed(dsi->phy,
                        bit_clk,
-                       dsi->phyref_rate);
+                       dsi->phyref_rate,
+                       false);
                /* Pick the first non-failing rate */
                if (!ret)
                        break;
        }
        if (ret < 0) {
-               DRM_DEV_DEBUG_DRIVER(dev,
+               DRM_DEV_ERROR(dev,
                        "Cannot setup PHY for mode: %ux%u @%d kHz\n",
                        mode->hdisplay,
                        mode->vdisplay,
                        mode->clock);
-               DRM_DEV_DEBUG_DRIVER(dev, "PHY_REF clk: %u, bit clk: %lu\n",
+               DRM_DEV_ERROR(dev, "PHY_REF clk: %u, bit clk: %lu\n",
                        dsi->phyref_rate, bit_clk);
-       }
+       } else
+               dsi->bit_clk = bit_clk;
 
        return ret;
 }
 
+static void imx_nwl_dsi_encoder_enable(struct drm_encoder *encoder)
+{
+       struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
+
+       imx_nwl_dsi_enable(dsi);
+}
+
+static void imx_nwl_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+       struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
+
+       imx_nwl_dsi_disable(dsi);
+}
+
 static int imx_nwl_dsi_encoder_atomic_check(struct drm_encoder *encoder,
                                        struct drm_crtc_state *crtc_state,
                                        struct drm_connector_state *conn_state)
@@ -500,45 +594,96 @@ static const struct drm_encoder_funcs imx_nwl_dsi_encoder_funcs = {
        .destroy = imx_nwl_dsi_encoder_destroy,
 };
 
-static int imx_nwl_dsi_bind(struct device *dev,
-                       struct device *master,
-                       void *data)
+
+static void imx_nwl_dsi_bridge_enable(struct drm_bridge *bridge)
+{
+       struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+       imx_nwl_dsi_enable(dsi);
+}
+
+static void imx_nwl_dsi_bridge_disable(struct drm_bridge *bridge)
+{
+       struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+       imx_nwl_dsi_disable(dsi);
+}
+
+static bool imx_nwl_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
+                          const struct drm_display_mode *mode,
+                          struct drm_display_mode *adjusted_mode)
+{
+       struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+       return (imx_nwl_try_phy_speed(dsi, adjusted_mode) == 0);
+}
+
+static int imx_nwl_dsi_bridge_attach(struct drm_bridge *bridge)
+{
+       struct imx_mipi_dsi *dsi = bridge->driver_private;
+       struct drm_encoder *encoder = bridge->encoder;
+       int ret = 0;
+
+       DRM_DEV_INFO(dsi->dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+       if (!encoder) {
+               DRM_DEV_ERROR(dsi->dev, "Parent encoder object not found\n");
+               return -ENODEV;
+       }
+
+       /* Attach the next bridge in chain */
+       nwl_dsi_add_bridge(encoder, dsi->next_bridge);
+       ret = drm_bridge_attach(encoder->dev, dsi->next_bridge);
+       if (ret) {
+               DRM_DEV_ERROR(dsi->dev, "Failed to attach bridge! (%d)\n",
+                             ret);
+               nwl_dsi_del_bridge(encoder, dsi->next_bridge);
+       }
+
+       return ret;
+}
+
+static void imx_nwl_dsi_bridge_detach(struct drm_bridge *bridge)
+{
+       struct imx_mipi_dsi *dsi = bridge->driver_private;
+
+       DRM_DEV_INFO(dsi->dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+       drm_bridge_detach(dsi->next_bridge);
+       nwl_dsi_del_bridge(dsi->next_bridge->encoder, dsi->next_bridge);
+}
+
+static const struct drm_bridge_funcs imx_nwl_dsi_bridge_funcs = {
+       .enable = imx_nwl_dsi_bridge_enable,
+       .disable = imx_nwl_dsi_bridge_disable,
+       .mode_fixup = imx_nwl_dsi_bridge_mode_fixup,
+       .attach = imx_nwl_dsi_bridge_attach,
+       .detach = imx_nwl_dsi_bridge_detach,
+};
+
+static int imx_nwl_dsi_parse_of(struct device *dev, bool as_bridge)
 {
-       struct platform_device *pdev = to_platform_device(dev);
-       struct drm_device *drm = data;
-       struct imx_mipi_dsi *dsi;
        struct device_node *np = dev->of_node;
        const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
                                                           dev);
        const struct devtype *devtype = of_id->data;
-       struct resource *res;
-       int irq;
+       struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
        struct clk *clk;
        const char *clk_id;
        size_t i, clk_config_sz;
-       int ret;
-
-       if (!np)
-               return -ENODEV;
-
-       dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
-       if (!dsi)
-               return -ENOMEM;
-
-       dsi->dev = dev;
+       int id;
+       u32 mux_val;
+       int ret = 0;
 
-       dsi->instance = of_alias_get_id(np, "mipi_dsi");
-       if (dsi->instance < 0) {
+       id = of_alias_get_id(np, "mipi_dsi");
+       if (id < 0) {
                dev_err(dev, "No mipi_dsi alias found!");
-               return dsi->instance;
+               return id;
        }
-       if (dsi->instance > devtype->max_instances - 1) {
+       if (id > devtype->max_instances - 1) {
                dev_err(dev, "Too many instances! (cur: %d, max: %d)\n",
-                       dsi->instance, devtype->max_instances);
+                       id, devtype->max_instances);
                return -ENODEV;
        }
-
-       DRM_DEV_INFO(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+       dsi->instance = id;
 
        dsi->phy = devm_phy_get(dev, "dphy");
        if (IS_ERR(dsi->phy)) {
@@ -568,11 +713,14 @@ static int imx_nwl_dsi_bind(struct device *dev,
                                clk_id, ret);
                        return ret;
                }
-               clk_prepare(clk);
-
+               dev_dbg(dev, "Setup clk %s (rate: %lu)\n",
+                       clk_id, clk_get_rate(clk));
                dsi->clk_config[i].clk = clk;
        }
 
+       dsi->tx_ulps_reg = devtype->tx_ulps_reg;
+       dsi->pxl2dpi_reg = devtype->pxl2dpi_reg;
+
        /* Look for optional regmaps */
        dsi->csr = syscon_regmap_lookup_by_phandle(np, "csr");
        if (IS_ERR(dsi->csr) && (devtype->ext_regs & IMX_REG_CSR)) {
@@ -580,26 +728,57 @@ static int imx_nwl_dsi_bind(struct device *dev,
                dev_err(dev, "Failed to get CSR regmap (%d)\n", ret);
                return ret;
        }
+       dsi->reset = syscon_regmap_lookup_by_phandle(np, "src");
+       if (IS_ERR(dsi->reset) && (devtype->ext_regs & IMX_REG_SRC)) {
+               ret = PTR_ERR(dsi->reset);
+               dev_err(dev, "Failed to get SRC regmap (%d)\n", ret);
+               return ret;
+       }
+       dsi->mux_sel = syscon_regmap_lookup_by_phandle(np, "mux-sel");
+       if (IS_ERR(dsi->mux_sel) && (devtype->ext_regs & IMX_REG_GPR)) {
+               ret = PTR_ERR(dsi->mux_sel);
+               dev_err(dev, "Failed to get GPR regmap (%d)\n", ret);
+               return ret;
+       }
+       if (IS_ERR(dsi->mux_sel))
+               return 0;
+
+       mux_val = IMX8MQ_GPR13_MIPI_MUX_SEL;
+       if (as_bridge)
+               mux_val = 0;
+       dev_info(dev, "Using %s as input source\n",
+               (mux_val)?"DCSS":"LCDIF");
+       regmap_update_bits(dsi->mux_sel,
+                  IOMUXC_GPR13,
+                  IMX8MQ_GPR13_MIPI_MUX_SEL,
+                  mux_val);
 
-       dsi->tx_ulps_reg = devtype->tx_ulps_reg;
-       dsi->pxl2dpi_reg = devtype->pxl2dpi_reg;
+       return 0;
+}
 
-       platform_set_drvdata(pdev, dsi);
+static int imx_nwl_dsi_bind(struct device *dev,
+                       struct device *master,
+                       void *data)
+{
+       struct drm_device *drm = data;
+       struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
+       int ret = 0;
 
-       ret = imx_drm_encoder_parse_of(drm, &dsi->encoder, dev->of_node);
+       ret = imx_nwl_dsi_parse_of(dev, false);
        if (ret)
                return ret;
 
-       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       if (!res)
-               return -EBUSY;
+       DRM_DEV_INFO(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
 
-       irq = platform_get_irq(pdev, 0);
-       if (irq < 0) {
-               DRM_DEV_ERROR(dev, "Failed to get device IRQ!\n");
-               return -EINVAL;
+       if (!dsi->next_bridge) {
+               dev_warn(dev, "No bridge found, skipping encoder creation\n");
+               return ret;
        }
 
+       ret = imx_drm_encoder_parse_of(drm, &dsi->encoder, dev->of_node);
+       if (ret)
+               return ret;
+
        drm_encoder_helper_add(&dsi->encoder,
                               &imx_nwl_dsi_encoder_helper_funcs);
        ret = drm_encoder_init(drm,
@@ -612,13 +791,12 @@ static int imx_nwl_dsi_bind(struct device *dev,
                return ret;
        }
 
-       /* Now, bind our NWL MIPI-DSI bridge */
-       ret = nwl_dsi_bind(dev, &dsi->encoder, dsi->phy, res, irq);
-
-       if (ret)
+       dsi->next_bridge->encoder = &dsi->encoder;
+       dsi->encoder.bridge = dsi->next_bridge;
+       if (drm_bridge_attach(dsi->encoder.dev, dsi->next_bridge))
                drm_encoder_cleanup(&dsi->encoder);
 
-       return ret;
+       return 0;
 }
 
 static void imx_nwl_dsi_unbind(struct device *dev,
@@ -633,7 +811,7 @@ static void imx_nwl_dsi_unbind(struct device *dev,
 
        drm_encoder_cleanup(&dsi->encoder);
 
-       nwl_dsi_unbind(dsi->encoder.bridge);
+       drm_bridge_detach(dsi->next_bridge);
 }
 
 static const struct component_ops imx_nwl_dsi_component_ops = {
@@ -643,6 +821,60 @@ static const struct component_ops imx_nwl_dsi_component_ops = {
 
 static int imx_nwl_dsi_probe(struct platform_device *pdev)
 {
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
+       struct device_node *remote_node, *endpoint;
+       struct imx_mipi_dsi *dsi;
+       int ret = 0;
+
+       if (!np)
+               return -ENODEV;
+
+       dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+       if (!dsi)
+               return -ENOMEM;
+
+       /* Search for next bridge (usually the DSI HOST bridge) */
+       endpoint = of_graph_get_next_endpoint(np, NULL);
+       while (endpoint && !dsi->next_bridge) {
+               remote_node = of_graph_get_remote_port_parent(endpoint);
+               if (!remote_node) {
+                       dev_err(dev, "No endpoint found!\n");
+                       return -ENODEV;
+               }
+
+               dsi->next_bridge = of_drm_find_bridge(remote_node);
+               of_node_put(remote_node);
+               endpoint = of_graph_get_next_endpoint(np, endpoint);
+       };
+
+       if (!dsi->next_bridge) {
+               dev_warn(dev, "Waiting for DSI host bridge\n");
+               return -EPROBE_DEFER;
+       }
+
+       dsi->dev = dev;
+       dev_set_drvdata(dev, dsi);
+
+       if (of_property_read_bool(dev->of_node, "as_bridge")) {
+               ret = imx_nwl_dsi_parse_of(dev, true);
+               if (ret)
+                       return ret;
+               /* Create our bridge */
+               dsi->bridge.driver_private = dsi;
+               dsi->bridge.funcs = &imx_nwl_dsi_bridge_funcs;
+               dsi->bridge.of_node = np;
+
+               ret = drm_bridge_add(&dsi->bridge);
+               if (ret) {
+                       dev_err(dev, "Failed to add imx-nwl-dsi bridge (%d)\n",
+                               ret);
+                       return ret;
+               }
+               dev_info(dev, "Added drm bridge!");
+               return 0;
+       }
+
        return component_add(&pdev->dev, &imx_nwl_dsi_component_ops);
 }
 
index d62f439..934456e 100644 (file)
@@ -107,7 +107,8 @@ static inline void phy_write(struct phy *phy, u32 value, unsigned int reg)
  */
 int mixel_phy_mipi_set_phy_speed(struct phy *phy,
                                 unsigned long bit_clk,
-                                unsigned long ref_clk)
+                                unsigned long ref_clk,
+                                bool best_match)
 {
        struct mixel_mipi_phy_priv *priv = dev_get_drvdata(phy->dev.parent);
        u32 div_rate;
@@ -132,6 +133,15 @@ int mixel_phy_mipi_set_phy_speed(struct phy *phy,
        /* CM ranges between 16 and 255 */
        /* CN ranges between 1 and 32 */
        /* CO is power of 2: 1, 2, 4, 8 */
+       if (best_match && numerator < 16)
+               numerator = div_rate / 1000;
+
+       if (best_match && numerator > 255) {
+               while (numerator > 255 && denominator > 1) {
+                       numerator = DIV_ROUND_UP(numerator, 2);
+                       denominator = denominator >> 1;
+               }
+       }
 
        if (numerator < 16 || numerator > 255)
                return -EINVAL;
@@ -269,6 +279,15 @@ int mixel_mipi_phy_init(struct phy *phy)
        return 0;
 }
 
+int mixel_mipi_phy_exit(struct phy *phy)
+{
+       phy_write(phy, 0, DPHY_CM);
+       phy_write(phy, 0, DPHY_CN);
+       phy_write(phy, 0, DPHY_CO);
+
+       return 0;
+}
+
 static int mixel_mipi_phy_power_on(struct phy *phy)
 {
        struct mixel_mipi_phy_priv *priv = phy_get_drvdata(phy);
@@ -318,6 +337,7 @@ static int mixel_mipi_phy_power_off(struct phy *phy)
 
 static const struct phy_ops mixel_mipi_phy_ops = {
        .init = mixel_mipi_phy_init,
+       .exit = mixel_mipi_phy_exit,
        .power_on = mixel_mipi_phy_power_on,
        .power_off = mixel_mipi_phy_power_off,
        .owner = THIS_MODULE,
index ae20541..11a7bef 100644 (file)
@@ -28,11 +28,17 @@ enum dpi_pixel_format {
        DPI_24_BIT
 };
 
-unsigned long nwl_dsi_get_bit_clock(struct drm_encoder *encoder,
-       unsigned long pixclock);
+/*
+ * Just some helper functions to add/remove a bridge into/from encoder bridge
+ * chain.
+ */
+bool nwl_dsi_add_bridge(struct drm_encoder *encoder,
+                       struct drm_bridge *next_bridge);
 
-int nwl_dsi_bind(struct device *dev, struct drm_encoder *encoder,
-                struct phy *phy, struct resource *res, int irq);
-void nwl_dsi_unbind(struct drm_bridge *bridge);
+bool nwl_dsi_del_bridge(struct drm_encoder *encoder,
+                       struct drm_bridge *bridge);
+
+unsigned long nwl_dsi_get_bit_clock(struct drm_bridge *bridge,
+       unsigned long pixclock);
 
 #endif /* __NWL_DSI_H__ */
index f941903..a76eea4 100644 (file)
 #if IS_ENABLED(CONFIG_PHY_MIXEL_MIPI_DSI)
 int mixel_phy_mipi_set_phy_speed(struct phy *phy,
                unsigned long bit_clk,
-               unsigned long ref_clk);
+               unsigned long ref_clk,
+               bool best_match);
 #else
 int mixel_phy_mipi_set_phy_speed(struct phy *phy,
                unsigned long bit_clk,
-               unsigned long ref_clk)
+               unsigned long ref_clk,
+               bool best_match)
 {
        return -ENODEV;
 }