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
"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"
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 =
<&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>;
};
};
};
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>;
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>;
};
};
};
================================
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"
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"
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 {
};
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.
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;
}
}
-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);
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);
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,
}
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,
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");
}
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) {
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)) {
nwl_dsi_disable_clocks(dsi, CLK_PHY_REF | CLK_TX_ESC);
phy_power_off(dsi->phy);
+ phy_exit(dsi->phy);
dsi->enabled = false;
}
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,
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);
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);
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);
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;
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)) {
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)) {
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)) {
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) {
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;
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");
* 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;
u32 tx_ulps_reg;
u32 pxl2dpi_reg;
+ unsigned long bit_clk;
u32 phyref_rate;
u32 instance;
bool enabled;
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 {
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);
.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 },
.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 },
.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);
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++) {
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);
}
}
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;
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);
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];
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)
.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)) {
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)) {
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,
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,
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 = {
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);
}
*/
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;
/* 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;
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);
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,
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__ */
#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;
}