This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10 to 13) and finally expose the API, split the code and move the platform independent driver to the bridges (patches 14 - 16). Hopefully this simplifies testing/bisecting and helps me to understand potential error reports.
Also I added host_ops for attach/detach and the tearing effect handler to make the calls into the platform code more visible.
Furthermore, the series should now apply to linux-next and correctly build the exynos_defconfig.
Thanks,
Michael
Changelog:
v2: - rebase on linux-next - verify with exynos_defconfig - fix crashes reported by Marek Szyprowski Exynos3250-based Rinato - reorder patches - add host_ops for platform specific code - roughly test component framework integration with dummy
Michael Tretter (16): drm/encoder: remove obsolete documentation of bridge drm/exynos: remove in_bridge_node from exynos_dsi drm/exynos: use exynos_dsi as drvdata drm/exynos: extract helper functions for probe drm/exynos: move dsi host registration to probe drm/exynos: shift register values to fields on write drm/exynos: use identifier instead of register offsets drm/exynos: add host_ops callback for platform drivers drm/exynos: add callback for tearing effect handler drm/exynos: implement a drm bridge drm/exynos: convert encoder functions to bridge function drm/exynos: configure mode on drm bridge drm/exynos: get encoder from bridge whenever possible drm/exynos: add API functions for platform drivers drm/exynos: split out platform specific code drm/exynos: move bridge driver to bridges
drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1790 +++++++++++++++++++++ drivers/gpu/drm/exynos/Kconfig | 5 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 1927 ++--------------------- include/drm/bridge/samsung-dsim.h | 64 + include/drm/drm_encoder.h | 1 - 7 files changed, 2027 insertions(+), 1770 deletions(-) create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c create mode 100644 include/drm/bridge/samsung-dsim.h
In commit 05193dc38197 ("drm/bridge: Make the bridge chain a double-linked list") the bridge has been removed and replaced by a private field. Remove the leftover documentation of the removed field.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de Reviewed-by: Laurent Pinchart laurent.pinchart@ideasonboard.com --- v2: none --- include/drm/drm_encoder.h | 1 - 1 file changed, 1 deletion(-)
diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h index a60f5f1555ac..5dfa5f7a80a7 100644 --- a/include/drm/drm_encoder.h +++ b/include/drm/drm_encoder.h @@ -89,7 +89,6 @@ struct drm_encoder_funcs { * @head: list management * @base: base KMS object * @name: human readable name, can be overwritten by the driver - * @bridge: bridge associated to the encoder * @funcs: control functions * @helper_private: mid-layer private data *
On Fri, Sep 11, 2020 at 8:54 AM Michael Tretter m.tretter@pengutronix.de wrote:
In commit 05193dc38197 ("drm/bridge: Make the bridge chain a double-linked list") the bridge has been removed and replaced by a private field. Remove the leftover documentation of the removed field.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de Reviewed-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
What is the status of this series? I know of at least one other patch series depending on this.
adam
v2: none
include/drm/drm_encoder.h | 1 - 1 file changed, 1 deletion(-)
diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h index a60f5f1555ac..5dfa5f7a80a7 100644 --- a/include/drm/drm_encoder.h +++ b/include/drm/drm_encoder.h @@ -89,7 +89,6 @@ struct drm_encoder_funcs {
- @head: list management
- @base: base KMS object
- @name: human readable name, can be overwritten by the driver
- @bridge: bridge associated to the encoder
- @funcs: control functions
- @helper_private: mid-layer private data
-- 2.20.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Sat, 07 Nov 2020 09:07:19 -0600, Adam Ford wrote:
On Fri, Sep 11, 2020 at 8:54 AM Michael Tretter m.tretter@pengutronix.de wrote:
In commit 05193dc38197 ("drm/bridge: Make the bridge chain a double-linked list") the bridge has been removed and replaced by a private field. Remove the leftover documentation of the removed field.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de Reviewed-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
What is the status of this series? I know of at least one other patch series depending on this.
Sorry for that. I lately didn't have time to work on the series.
There are two big open TODOs:
- How should this bridge react if there is no out bridge available, yet? This series implements a static approach by returning EPROBE_DEFER from bridge_attach if there isn't a next bridge, connector or display. Andrezej suggested a dynamic approach which allows to attach this bridge without a next bridge and dynamically add further bridges/panels. The latter approach didn't work with the mxsfb driver, but I didn't have time to look into this.
- The component framework stuff that allows to use the bridge with the Exynos driver should not go into the bridge driver, but stay in the platform part.
Michael
adam
v2: none
include/drm/drm_encoder.h | 1 - 1 file changed, 1 deletion(-)
diff --git a/include/drm/drm_encoder.h b/include/drm/drm_encoder.h index a60f5f1555ac..5dfa5f7a80a7 100644 --- a/include/drm/drm_encoder.h +++ b/include/drm/drm_encoder.h @@ -89,7 +89,6 @@ struct drm_encoder_funcs {
- @head: list management
- @base: base KMS object
- @name: human readable name, can be overwritten by the driver
- @bridge: bridge associated to the encoder
- @funcs: control functions
- @helper_private: mid-layer private data
-- 2.20.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Fri, Sep 11, 2020 at 03:53:58PM +0200, Michael Tretter wrote:
In commit 05193dc38197 ("drm/bridge: Make the bridge chain a double-linked list") the bridge has been removed and replaced by a private field. Remove the leftover documentation of the removed field.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de Reviewed-by: Laurent Pinchart laurent.pinchart@ideasonboard.com
Hi Michael.
Applied to drm-misc-next, thanks. The rest of this patch-set is exynos stuff, that the maintainer needs to deal with but I will try to take a look at some of the patches.
Sam
We do not need to keep a reference to the in_bridge_node, but we can simply drop it, once we found and attached the previous bridge.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 1a1a2853a842..29f941b02210 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -282,7 +282,6 @@ struct exynos_dsi { struct list_head transfer_list;
const struct exynos_dsi_driver_data *driver_data; - struct device_node *in_bridge_node; };
#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) @@ -1684,8 +1683,6 @@ static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) if (ret < 0) return ret;
- dsi->in_bridge_node = of_graph_get_remote_node(node, DSI_PORT_IN, 0); - return 0; }
@@ -1695,6 +1692,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, struct drm_encoder *encoder = dev_get_drvdata(dev); struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_device *drm_dev = data; + struct device_node *in_bridge_node; struct drm_bridge *in_bridge; int ret;
@@ -1706,10 +1704,12 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, if (ret < 0) return ret;
- if (dsi->in_bridge_node) { - in_bridge = of_drm_find_bridge(dsi->in_bridge_node); + in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); + if (in_bridge_node) { + in_bridge = of_drm_find_bridge(in_bridge_node); if (in_bridge) drm_bridge_attach(encoder, in_bridge, NULL, 0); + of_node_put(in_bridge_node); }
return mipi_dsi_host_register(&dsi->dsi_host); @@ -1830,17 +1830,12 @@ static int exynos_dsi_probe(struct platform_device *pdev)
err_disable_runtime: pm_runtime_disable(dev); - of_node_put(dsi->in_bridge_node);
return ret; }
static int exynos_dsi_remove(struct platform_device *pdev) { - struct exynos_dsi *dsi = platform_get_drvdata(pdev); - - of_node_put(dsi->in_bridge_node); - pm_runtime_disable(&pdev->dev);
component_del(&pdev->dev, &exynos_dsi_component_ops);
On Fri, Sep 11, 2020 at 03:53:59PM +0200, Michael Tretter wrote:
We do not need to keep a reference to the in_bridge_node, but we can simply drop it, once we found and attached the previous bridge.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
Reviewed-by: Sam Ravnborg sam@ravnborg.org
Note: I expect exynos people to pick it up.
Sam
20. 11. 8. 오전 7:19에 Sam Ravnborg 이(가) 쓴 글:
On Fri, Sep 11, 2020 at 03:53:59PM +0200, Michael Tretter wrote:
We do not need to keep a reference to the in_bridge_node, but we can simply drop it, once we found and attached the previous bridge.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
Reviewed-by: Sam Ravnborg sam@ravnborg.org
Note: I expect exynos people to pick it up.
Cleanup patch so picked it up.
Thanks, Inki Dae
Sam _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=0ba8a77f-54339e66-0ba92c30-000babff379...
Use the exynos_dsi as drvdata instead of the encoder to further decouple the driver from the encoder.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: drop removal of encoder_to_dsi --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 29f941b02210..ed04064afbb2 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -1689,8 +1689,8 @@ static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) static int exynos_dsi_bind(struct device *dev, struct device *master, void *data) { - struct drm_encoder *encoder = dev_get_drvdata(dev); - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder; struct drm_device *drm_dev = data; struct device_node *in_bridge_node; struct drm_bridge *in_bridge; @@ -1718,8 +1718,8 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, static void exynos_dsi_unbind(struct device *dev, struct device *master, void *data) { - struct drm_encoder *encoder = dev_get_drvdata(dev); - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder;
exynos_dsi_disable(encoder);
@@ -1818,7 +1818,7 @@ static int exynos_dsi_probe(struct platform_device *pdev) if (ret) return ret;
- platform_set_drvdata(pdev, &dsi->encoder); + platform_set_drvdata(pdev, dsi);
pm_runtime_enable(dev);
@@ -1845,8 +1845,7 @@ static int exynos_dsi_remove(struct platform_device *pdev)
static int __maybe_unused exynos_dsi_suspend(struct device *dev) { - struct drm_encoder *encoder = dev_get_drvdata(dev); - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct exynos_dsi *dsi = dev_get_drvdata(dev); const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; int ret, i;
@@ -1876,8 +1875,7 @@ static int __maybe_unused exynos_dsi_suspend(struct device *dev)
static int __maybe_unused exynos_dsi_resume(struct device *dev) { - struct drm_encoder *encoder = dev_get_drvdata(dev); - struct exynos_dsi *dsi = encoder_to_dsi(encoder); + struct exynos_dsi *dsi = dev_get_drvdata(dev); const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; int ret, i;
On Fri, Sep 11, 2020 at 03:54:00PM +0200, Michael Tretter wrote:
Use the exynos_dsi as drvdata instead of the encoder to further decouple the driver from the encoder.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
Reviewed-by: Sam Ravnborg sam@ravnborg.org
Likewise, the exynos people are expected to pick this up.
Sam
20. 11. 8. 오전 7:24에 Sam Ravnborg 이(가) 쓴 글:
On Fri, Sep 11, 2020 at 03:54:00PM +0200, Michael Tretter wrote:
Use the exynos_dsi as drvdata instead of the encoder to further decouple the driver from the encoder.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
Reviewed-by: Sam Ravnborg sam@ravnborg.org
Likewise, the exynos people are expected to pick this up.
Cleanup patch so picked it up.
Thanks, Inki Dae
Sam
As the driver shall be usable with drivers that use the component framework and drivers that don't, split the common probing code into a separate function that can be called from different locations.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - move pm_runtime_enable from helper to calling function --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 40 ++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index ed04064afbb2..f8e64b74a6dd 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -1731,7 +1731,7 @@ static const struct component_ops exynos_dsi_component_ops = { .unbind = exynos_dsi_unbind, };
-static int exynos_dsi_probe(struct platform_device *pdev) +static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; @@ -1740,7 +1740,7 @@ static int exynos_dsi_probe(struct platform_device *pdev)
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); if (!dsi) - return -ENOMEM; + return ERR_PTR(-ENOMEM);
/* To be checked as invalid one */ dsi->te_gpio = -ENOENT; @@ -1763,14 +1763,14 @@ static int exynos_dsi_probe(struct platform_device *pdev) if (ret) { if (ret != -EPROBE_DEFER) dev_info(dev, "failed to get regulators: %d\n", ret); - return ret; + return ERR_PTR(ret); }
dsi->clks = devm_kcalloc(dev, dsi->driver_data->num_clks, sizeof(*dsi->clks), GFP_KERNEL); if (!dsi->clks) - return -ENOMEM; + return ERR_PTR(-ENOMEM);
for (i = 0; i < dsi->driver_data->num_clks; i++) { dsi->clks[i] = devm_clk_get(dev, clk_names[i]); @@ -1784,7 +1784,7 @@ static int exynos_dsi_probe(struct platform_device *pdev)
dev_info(dev, "failed to get the clock: %s\n", clk_names[i]); - return PTR_ERR(dsi->clks[i]); + return ERR_PTR(PTR_ERR(dsi->clks[i])); } }
@@ -1792,18 +1792,18 @@ static int exynos_dsi_probe(struct platform_device *pdev) dsi->reg_base = devm_ioremap_resource(dev, res); if (IS_ERR(dsi->reg_base)) { dev_err(dev, "failed to remap io region\n"); - return PTR_ERR(dsi->reg_base); + return dsi->reg_base; }
dsi->phy = devm_phy_get(dev, "dsim"); if (IS_ERR(dsi->phy)) { dev_info(dev, "failed to get dsim phy\n"); - return PTR_ERR(dsi->phy); + return ERR_PTR(PTR_ERR(dsi->phy)); }
dsi->irq = platform_get_irq(pdev, 0); if (dsi->irq < 0) - return dsi->irq; + return ERR_PTR(dsi->irq);
irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN); ret = devm_request_threaded_irq(dev, dsi->irq, NULL, @@ -1811,13 +1811,29 @@ static int exynos_dsi_probe(struct platform_device *pdev) dev_name(dev), dsi); if (ret) { dev_err(dev, "failed to request dsi irq\n"); - return ret; + return ERR_PTR(ret); }
ret = exynos_dsi_parse_dt(dsi); if (ret) - return ret; + return ERR_PTR(ret); + + return dsi; +} + +static void __exynos_dsi_remove(struct exynos_dsi *dsi) +{ +}
+static int exynos_dsi_probe(struct platform_device *pdev) +{ + struct exynos_dsi *dsi; + struct device *dev = &pdev->dev; + int ret; + + dsi = __exynos_dsi_probe(pdev); + if (IS_ERR(dsi)) + return PTR_ERR(dsi); platform_set_drvdata(pdev, dsi);
pm_runtime_enable(dev); @@ -1836,8 +1852,12 @@ static int exynos_dsi_probe(struct platform_device *pdev)
static int exynos_dsi_remove(struct platform_device *pdev) { + struct exynos_dsi *dsi = platform_get_drvdata(pdev); + pm_runtime_disable(&pdev->dev);
+ __exynos_dsi_remove(dsi); + component_del(&pdev->dev, &exynos_dsi_component_ops);
return 0;
On Fri, Sep 11, 2020 at 03:54:01PM +0200, Michael Tretter wrote:
As the driver shall be usable with drivers that use the component framework and drivers that don't, split the common probing code into a separate function that can be called from different locations.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
Reviewed-by: Sam Ravnborg sam@ravnborg.org
20. 11. 8. 오전 7:27에 Sam Ravnborg 이(가) 쓴 글:
On Fri, Sep 11, 2020 at 03:54:01PM +0200, Michael Tretter wrote:
As the driver shall be usable with drivers that use the component framework and drivers that don't, split the common probing code into a separate function that can be called from different locations.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
Reviewed-by: Sam Ravnborg sam@ravnborg.org
This patch and other are related to what this patch series do. However, this patch makes Exynos board not working yet. So excepting patches 2 to 3, I will wait for that this patch series will be fixed and reviewed more.
Thanks, Inki Dae
Once the driver implements a drm_bridge, the bridge will be attached during bind. The driver has to register the mipi dsi host before making the driver available at the component framework, because the bridge is only initialized when a mipi dsi device attaches.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index f8e64b74a6dd..41000214a5fe 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -1712,7 +1712,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, of_node_put(in_bridge_node); }
- return mipi_dsi_host_register(&dsi->dsi_host); + return 0; }
static void exynos_dsi_unbind(struct device *dev, struct device *master, @@ -1722,8 +1722,6 @@ static void exynos_dsi_unbind(struct device *dev, struct device *master, struct drm_encoder *encoder = &dsi->encoder;
exynos_dsi_disable(encoder); - - mipi_dsi_host_unregister(&dsi->dsi_host); }
static const struct component_ops exynos_dsi_component_ops = { @@ -1818,11 +1816,16 @@ static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) if (ret) return ERR_PTR(ret);
+ ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) + return ERR_PTR(ret); + return dsi; }
static void __exynos_dsi_remove(struct exynos_dsi *dsi) { + mipi_dsi_host_unregister(&dsi->dsi_host); }
static int exynos_dsi_probe(struct platform_device *pdev)
The phy timings are already shifted to the field position. If the driver is reused on multiple platforms, this exposes the field positions to the platform code.
Store only the timing values in the platform data and shift the value to the field when writing the fields to the registers.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 88 +++++++++++++------------ 1 file changed, 46 insertions(+), 42 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 41000214a5fe..0f2cac7ed944 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -399,54 +399,54 @@ static const unsigned int reg_values[] = { [RESET_TYPE] = DSIM_SWRST, [PLL_TIMER] = 500, [STOP_STATE_CNT] = 0xf, - [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0x0af), + [PHYCTRL_ULPS_EXIT] = 0x0af, [PHYCTRL_VREG_LP] = 0, [PHYCTRL_SLEW_UP] = 0, - [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x06), - [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0b), - [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x07), - [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x27), - [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0d), - [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x08), - [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x09), - [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x0d), - [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0b), + [PHYTIMING_LPX] = 0x06, + [PHYTIMING_HS_EXIT] = 0x0b, + [PHYTIMING_CLK_PREPARE] = 0x07, + [PHYTIMING_CLK_ZERO] = 0x27, + [PHYTIMING_CLK_POST] = 0x0d, + [PHYTIMING_CLK_TRAIL] = 0x08, + [PHYTIMING_HS_PREPARE] = 0x09, + [PHYTIMING_HS_ZERO] = 0x0d, + [PHYTIMING_HS_TRAIL] = 0x0b, };
static const unsigned int exynos5422_reg_values[] = { [RESET_TYPE] = DSIM_SWRST, [PLL_TIMER] = 500, [STOP_STATE_CNT] = 0xf, - [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0xaf), + [PHYCTRL_ULPS_EXIT] = 0xaf, [PHYCTRL_VREG_LP] = 0, [PHYCTRL_SLEW_UP] = 0, - [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x08), - [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0d), - [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x09), - [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x30), - [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0e), - [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x0a), - [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x0c), - [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x11), - [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0d), + [PHYTIMING_LPX] = 0x08, + [PHYTIMING_HS_EXIT] = 0x0d, + [PHYTIMING_CLK_PREPARE] = 0x09, + [PHYTIMING_CLK_ZERO] = 0x30, + [PHYTIMING_CLK_POST] = 0x0e, + [PHYTIMING_CLK_TRAIL] = 0x0a, + [PHYTIMING_HS_PREPARE] = 0x0c, + [PHYTIMING_HS_ZERO] = 0x11, + [PHYTIMING_HS_TRAIL] = 0x0d, };
static const unsigned int exynos5433_reg_values[] = { [RESET_TYPE] = DSIM_FUNCRST, [PLL_TIMER] = 22200, [STOP_STATE_CNT] = 0xa, - [PHYCTRL_ULPS_EXIT] = DSIM_PHYCTRL_ULPS_EXIT(0x190), - [PHYCTRL_VREG_LP] = DSIM_PHYCTRL_B_DPHYCTL_VREG_LP, - [PHYCTRL_SLEW_UP] = DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP, - [PHYTIMING_LPX] = DSIM_PHYTIMING_LPX(0x07), - [PHYTIMING_HS_EXIT] = DSIM_PHYTIMING_HS_EXIT(0x0c), - [PHYTIMING_CLK_PREPARE] = DSIM_PHYTIMING1_CLK_PREPARE(0x09), - [PHYTIMING_CLK_ZERO] = DSIM_PHYTIMING1_CLK_ZERO(0x2d), - [PHYTIMING_CLK_POST] = DSIM_PHYTIMING1_CLK_POST(0x0e), - [PHYTIMING_CLK_TRAIL] = DSIM_PHYTIMING1_CLK_TRAIL(0x09), - [PHYTIMING_HS_PREPARE] = DSIM_PHYTIMING2_HS_PREPARE(0x0b), - [PHYTIMING_HS_ZERO] = DSIM_PHYTIMING2_HS_ZERO(0x10), - [PHYTIMING_HS_TRAIL] = DSIM_PHYTIMING2_HS_TRAIL(0x0c), + [PHYCTRL_ULPS_EXIT] = 0x190, + [PHYCTRL_VREG_LP] = 1, + [PHYCTRL_SLEW_UP] = 1, + [PHYTIMING_LPX] = 0x07, + [PHYTIMING_HS_EXIT] = 0x0c, + [PHYTIMING_CLK_PREPARE] = 0x09, + [PHYTIMING_CLK_ZERO] = 0x2d, + [PHYTIMING_CLK_POST] = 0x0e, + [PHYTIMING_CLK_TRAIL] = 0x09, + [PHYTIMING_HS_PREPARE] = 0x0b, + [PHYTIMING_HS_ZERO] = 0x10, + [PHYTIMING_HS_TRAIL] = 0x0c, };
static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { @@ -698,8 +698,11 @@ static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) return;
/* B D-PHY: D-PHY Master & Slave Analog Block control */ - reg = reg_values[PHYCTRL_ULPS_EXIT] | reg_values[PHYCTRL_VREG_LP] | - reg_values[PHYCTRL_SLEW_UP]; + reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]); + if (reg_values[PHYCTRL_VREG_LP]) + reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP; + if (reg_values[PHYCTRL_SLEW_UP]) + reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP; exynos_dsi_write(dsi, DSIM_PHYCTRL_REG, reg);
/* @@ -707,7 +710,8 @@ static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) * T HS-EXIT: Time that the transmitter drives LP-11 following a HS * burst */ - reg = reg_values[PHYTIMING_LPX] | reg_values[PHYTIMING_HS_EXIT]; + reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) | + DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]); exynos_dsi_write(dsi, DSIM_PHYTIMING_REG, reg);
/* @@ -723,11 +727,10 @@ static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) * T CLK-TRAIL: Time that the transmitter drives the HS-0 state after * the last payload clock bit of a HS transmission burst */ - reg = reg_values[PHYTIMING_CLK_PREPARE] | - reg_values[PHYTIMING_CLK_ZERO] | - reg_values[PHYTIMING_CLK_POST] | - reg_values[PHYTIMING_CLK_TRAIL]; - + reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) | + DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) | + DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) | + DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]); exynos_dsi_write(dsi, DSIM_PHYTIMING1_REG, reg);
/* @@ -739,8 +742,9 @@ static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) * T HS-TRAIL: Time that the transmitter drives the flipped differential * state after last payload data bit of a HS transmission burst */ - reg = reg_values[PHYTIMING_HS_PREPARE] | reg_values[PHYTIMING_HS_ZERO] | - reg_values[PHYTIMING_HS_TRAIL]; + reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) | + DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) | + DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]); exynos_dsi_write(dsi, DSIM_PHYTIMING2_REG, reg); }
Hi Michael.
On Fri, Sep 11, 2020 at 03:54:03PM +0200, Michael Tretter wrote:
The phy timings are already shifted to the field position. If the driver is reused on multiple platforms, this exposes the field positions to the platform code.
Store only the timing values in the platform data and shift the value to the field when writing the fields to the registers.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This and the following patch smells like the regmap functionality is partly open coded. regmaps supports defining different register layouts and select the correct layout at runtime.
See for example: https://www.collabora.com/news-and-blog/blog/2020/05/27/using-regmaps-to-mak... or https://www.youtube.com/watch?v=0RPDGANArFc
Some parts is not a perfect fit - but using regmaps will make it better as a general and well-known solution is used.
@Adrian - see https://lore.kernel.org/dri-devel/20200911135413.3654800-1-m.tretter@pengutr... for the original patch.
Sam
On Sat, 07 Nov 2020 23:39:30 +0100, Sam Ravnborg wrote:
On Fri, Sep 11, 2020 at 03:54:03PM +0200, Michael Tretter wrote:
The phy timings are already shifted to the field position. If the driver is reused on multiple platforms, this exposes the field positions to the platform code.
Store only the timing values in the platform data and shift the value to the field when writing the fields to the registers.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This and the following patch smells like the regmap functionality is partly open coded. regmaps supports defining different register layouts and select the correct layout at runtime.
See for example: https://www.collabora.com/news-and-blog/blog/2020/05/27/using-regmaps-to-mak... or https://www.youtube.com/watch?v=0RPDGANArFc
Some parts is not a perfect fit - but using regmaps will make it better as a general and well-known solution is used.
I considered using regmaps, but there was something that didn't work out. Unfortunately, I don't remember, what it actually was. Therefore, it is probably best to use regmaps here.
Michael
@Adrian - see https://lore.kernel.org/dri-devel/20200911135413.3654800-1-m.tretter@pengutr... for the original patch.
Sam
Different revisions of the MIPI-DSI PHY have slightly different register layouts. Currently, the register layout was stored per platform, which makes it necessary to define the layout for each new platform.
Keep the register layout in the driver and use identifiers to specify which register layout shall be used on a platform.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 54 ++++++++++++++++--------- 1 file changed, 36 insertions(+), 18 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 0f2cac7ed944..1a15ae71205d 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -239,8 +239,13 @@ struct exynos_dsi_transfer { #define DSIM_STATE_CMD_LPM BIT(2) #define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+enum exynos_reg_offset { + EXYNOS_REG_OFS, + EXYNOS5433_REG_OFS +}; + struct exynos_dsi_driver_data { - const unsigned int *reg_ofs; + enum exynos_reg_offset reg_ofs; unsigned int plltmr_reg; unsigned int has_freqband:1; unsigned int has_clklane_stop:1; @@ -317,18 +322,6 @@ enum reg_idx { NUM_REGS };
-static inline void exynos_dsi_write(struct exynos_dsi *dsi, enum reg_idx idx, - u32 val) -{ - - writel(val, dsi->reg_base + dsi->driver_data->reg_ofs[idx]); -} - -static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) -{ - return readl(dsi->reg_base + dsi->driver_data->reg_ofs[idx]); -} - static const unsigned int exynos_reg_ofs[] = { [DSIM_STATUS_REG] = 0x00, [DSIM_SWRST_REG] = 0x04, @@ -377,6 +370,31 @@ static const unsigned int exynos5433_reg_ofs[] = { [DSIM_PHYTIMING2_REG] = 0xBC, };
+static inline void exynos_dsi_write(struct exynos_dsi *dsi, enum reg_idx idx, + u32 val) +{ + const unsigned int *reg_ofs; + + if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS) + reg_ofs = exynos5433_reg_ofs; + else + reg_ofs = exynos_reg_ofs; + + writel(val, dsi->reg_base + reg_ofs[idx]); +} + +static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) +{ + const unsigned int *reg_ofs; + + if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS) + reg_ofs = exynos5433_reg_ofs; + else + reg_ofs = exynos_reg_ofs; + + return readl(dsi->reg_base + reg_ofs[idx]); +} + enum reg_value_idx { RESET_TYPE, PLL_TIMER, @@ -450,7 +468,7 @@ static const unsigned int exynos5433_reg_values[] = { };
static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { - .reg_ofs = exynos_reg_ofs, + .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x50, .has_freqband = 1, .has_clklane_stop = 1, @@ -462,7 +480,7 @@ static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { };
static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { - .reg_ofs = exynos_reg_ofs, + .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x50, .has_freqband = 1, .has_clklane_stop = 1, @@ -474,7 +492,7 @@ static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { };
static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { - .reg_ofs = exynos_reg_ofs, + .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x58, .num_clks = 2, .max_freq = 1000, @@ -484,7 +502,7 @@ static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { };
static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { - .reg_ofs = exynos5433_reg_ofs, + .reg_ofs = EXYNOS5433_REG_OFS, .plltmr_reg = 0xa0, .has_clklane_stop = 1, .num_clks = 5, @@ -495,7 +513,7 @@ static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { };
static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { - .reg_ofs = exynos5433_reg_ofs, + .reg_ofs = EXYNOS5433_REG_OFS, .plltmr_reg = 0xa0, .has_clklane_stop = 1, .num_clks = 2,
Platform drivers need to be aware if a mipi dsi device attaches or detaches. Add host_ops to the driver_data for attach and detach callbacks and move the i80 mode selection and the hotplug handling into the callback, because these depend on the drm driver.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - new patch --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 64 ++++++++++++++++++++----- 1 file changed, 53 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 1a15ae71205d..684a2fbef08a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -239,6 +239,12 @@ struct exynos_dsi_transfer { #define DSIM_STATE_CMD_LPM BIT(2) #define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct exynos_dsi; +struct exynos_dsi_host_ops { + int (*attach)(struct device *dev, struct mipi_dsi_device *device); + int (*detach)(struct device *dev, struct mipi_dsi_device *device); +}; + enum exynos_reg_offset { EXYNOS_REG_OFS, EXYNOS5433_REG_OFS @@ -254,6 +260,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values; + const struct exynos_dsi_host_ops *host_ops; };
struct exynos_dsi { @@ -467,6 +474,41 @@ static const unsigned int exynos5433_reg_values[] = { [PHYTIMING_HS_TRAIL] = 0x0c, };
+static int __exynos_dsi_host_attach(struct device *dev, + struct mipi_dsi_device *device) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_device *drm = dsi->encoder.dev; + struct exynos_drm_crtc *crtc; + + mutex_lock(&drm->mode_config.mutex); + crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD); + crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO); + mutex_unlock(&drm->mode_config.mutex); + + if (drm->mode_config.poll_enabled) + drm_kms_helper_hotplug_event(drm); + + return 0; +} + +static int __exynos_dsi_host_detach(struct device *dev, + struct mipi_dsi_device *device) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_device *drm = dsi->encoder.dev; + + if (drm->mode_config.poll_enabled) + drm_kms_helper_hotplug_event(drm); + + return 0; +} + +static const struct exynos_dsi_host_ops exynos_dsi_host_ops = { + .attach = __exynos_dsi_host_attach, + .detach = __exynos_dsi_host_detach, +}; + static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x50, @@ -477,6 +519,7 @@ static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { @@ -489,6 +532,7 @@ static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { @@ -499,6 +543,7 @@ static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { @@ -510,6 +555,7 @@ static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { .wait_for_reset = 0, .num_bits_resol = 12, .reg_values = exynos5433_reg_values, + .host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { @@ -521,6 +567,7 @@ static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 12, .reg_values = exynos5422_reg_values, + .host_ops = &exynos_dsi_host_ops, };
static const struct of_device_id exynos_dsi_of_match[] = { @@ -1551,8 +1598,8 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host); + const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_encoder *encoder = &dsi->encoder; - struct drm_device *drm = encoder->dev; struct drm_bridge *out_bridge;
out_bridge = of_drm_find_bridge(device->dev.of_node); @@ -1590,18 +1637,12 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, return ret; }
- mutex_lock(&drm->mode_config.mutex); - dsi->lanes = device->lanes; dsi->format = device->format; dsi->mode_flags = device->mode_flags; - exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD)->i80_mode = - !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex); - - if (drm->mode_config.poll_enabled) - drm_kms_helper_hotplug_event(drm); + if (ops && ops->attach) + ops->attach(dsi->dsi_host.dev, device);
return 0; } @@ -1610,6 +1651,7 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host); + const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_device *drm = dsi->encoder.dev;
if (dsi->panel) { @@ -1625,8 +1667,8 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, INIT_LIST_HEAD(&dsi->bridge_chain); }
- if (drm->mode_config.poll_enabled) - drm_kms_helper_hotplug_event(drm); + if (ops && ops->detach) + ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
W dniu 11.09.2020 o 15:54, Michael Tretter pisze:
Platform drivers need to be aware if a mipi dsi device attaches or detaches. Add host_ops to the driver_data for attach and detach callbacks and move the i80 mode selection and the hotplug handling into the callback, because these depend on the drm driver.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
v2:
- new patch
drivers/gpu/drm/exynos/exynos_drm_dsi.c | 64 ++++++++++++++++++++----- 1 file changed, 53 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 1a15ae71205d..684a2fbef08a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -239,6 +239,12 @@ struct exynos_dsi_transfer { #define DSIM_STATE_CMD_LPM BIT(2) #define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct exynos_dsi; +struct exynos_dsi_host_ops {
- int (*attach)(struct device *dev, struct mipi_dsi_device *device);
- int (*detach)(struct device *dev, struct mipi_dsi_device *device);
+};
- enum exynos_reg_offset { EXYNOS_REG_OFS, EXYNOS5433_REG_OFS
@@ -254,6 +260,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values;
const struct exynos_dsi_host_ops *host_ops; };
struct exynos_dsi {
@@ -467,6 +474,41 @@ static const unsigned int exynos5433_reg_values[] = { [PHYTIMING_HS_TRAIL] = 0x0c, };
+static int __exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
+{
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
As I wrote yesterday drm device was guaranteed to be present here only if mipi dsi host registration was performed in component bind. In case it is performed in probe it will be always NULL, and the code does not make sense.
Regards
Andrzej
- struct exynos_drm_crtc *crtc;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
+}
+static int __exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
+{
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
+}
+static const struct exynos_dsi_host_ops exynos_dsi_host_ops = {
- .attach = __exynos_dsi_host_attach,
- .detach = __exynos_dsi_host_detach,
+};
- static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x50,
@@ -477,6 +519,7 @@ static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = {
@@ -489,6 +532,7 @@ static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = {
@@ -499,6 +543,7 @@ static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = {
@@ -510,6 +555,7 @@ static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { .wait_for_reset = 0, .num_bits_resol = 12, .reg_values = exynos5433_reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = {
@@ -521,6 +567,7 @@ static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 12, .reg_values = exynos5422_reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct of_device_id exynos_dsi_of_match[] = {
@@ -1551,8 +1598,8 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host);
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_encoder *encoder = &dsi->encoder;
struct drm_device *drm = encoder->dev; struct drm_bridge *out_bridge;
out_bridge = of_drm_find_bridge(device->dev.of_node);
@@ -1590,18 +1637,12 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, return ret; }
mutex_lock(&drm->mode_config.mutex);
dsi->lanes = device->lanes; dsi->format = device->format; dsi->mode_flags = device->mode_flags;
exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD)->i80_mode =
!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
mutex_unlock(&drm->mode_config.mutex);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
return 0; }
@@ -1610,6 +1651,7 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host);
const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_device *drm = dsi->encoder.dev;
if (dsi->panel) {
@@ -1625,8 +1667,8 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, INIT_LIST_HEAD(&dsi->bridge_chain); }
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
On Tue, 15 Sep 2020 19:07:59 +0200, Andrzej Hajda wrote:
W dniu 11.09.2020 o 15:54, Michael Tretter pisze:
Platform drivers need to be aware if a mipi dsi device attaches or detaches. Add host_ops to the driver_data for attach and detach callbacks and move the i80 mode selection and the hotplug handling into the callback, because these depend on the drm driver.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
v2:
- new patch
drivers/gpu/drm/exynos/exynos_drm_dsi.c | 64 ++++++++++++++++++++----- 1 file changed, 53 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 1a15ae71205d..684a2fbef08a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -239,6 +239,12 @@ struct exynos_dsi_transfer { #define DSIM_STATE_CMD_LPM BIT(2) #define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct exynos_dsi; +struct exynos_dsi_host_ops {
- int (*attach)(struct device *dev, struct mipi_dsi_device *device);
- int (*detach)(struct device *dev, struct mipi_dsi_device *device);
+};
- enum exynos_reg_offset { EXYNOS_REG_OFS, EXYNOS5433_REG_OFS
@@ -254,6 +260,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values;
const struct exynos_dsi_host_ops *host_ops; };
struct exynos_dsi {
@@ -467,6 +474,41 @@ static const unsigned int exynos5433_reg_values[] = { [PHYTIMING_HS_TRAIL] = 0x0c, };
+static int __exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
+{
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
As I wrote yesterday drm device was guaranteed to be present here only if mipi dsi host registration was performed in component bind. In case it is performed in probe it will be always NULL, and the code does not make sense.
Correct, but if the driver is used as a drm bridge, there won't be an encoder until bridge_attach. Mipi dsi devices defer their probe until the mipi dsi host is available. If I move the mipi dsi host registration into bridge_attach, the driver does not know if the next device is another bridge or a panel during bridge_attach, but the driver cannot successfully return from bridge_attach, because then the drm driver expects a connector.
I guess that the encoder should be initialized before registering the mipi dsi host during probe instead of bind. The probe won't be rolled back on PROBE_DEFER during bind and the encoder will be available in host_attach. When splitting the driver into the exynos platform specific code and the more generic code, there won't be an encoder during host_attach in the generic code, but the host_ops callback could (and will) use the platform specific encoder, which is available before bridge_attach.
Does this make sense to you?
Michael
Regards
Andrzej
- struct exynos_drm_crtc *crtc;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
+}
+static int __exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
+{
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
+}
+static const struct exynos_dsi_host_ops exynos_dsi_host_ops = {
- .attach = __exynos_dsi_host_attach,
- .detach = __exynos_dsi_host_detach,
+};
- static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x50,
@@ -477,6 +519,7 @@ static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = {
@@ -489,6 +532,7 @@ static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = {
@@ -499,6 +543,7 @@ static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = {
@@ -510,6 +555,7 @@ static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { .wait_for_reset = 0, .num_bits_resol = 12, .reg_values = exynos5433_reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = {
@@ -521,6 +567,7 @@ static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 12, .reg_values = exynos5422_reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct of_device_id exynos_dsi_of_match[] = {
@@ -1551,8 +1598,8 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host);
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_encoder *encoder = &dsi->encoder;
struct drm_device *drm = encoder->dev; struct drm_bridge *out_bridge;
out_bridge = of_drm_find_bridge(device->dev.of_node);
@@ -1590,18 +1637,12 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, return ret; }
mutex_lock(&drm->mode_config.mutex);
dsi->lanes = device->lanes; dsi->format = device->format; dsi->mode_flags = device->mode_flags;
exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD)->i80_mode =
!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
mutex_unlock(&drm->mode_config.mutex);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
return 0; }
@@ -1610,6 +1651,7 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host);
const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_device *drm = dsi->encoder.dev;
if (dsi->panel) {
@@ -1625,8 +1667,8 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, INIT_LIST_HEAD(&dsi->bridge_chain); }
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
W dniu 15.09.2020 o 20:02, Michael Tretter pisze:
On Tue, 15 Sep 2020 19:07:59 +0200, Andrzej Hajda wrote:
W dniu 11.09.2020 o 15:54, Michael Tretter pisze:
Platform drivers need to be aware if a mipi dsi device attaches or detaches. Add host_ops to the driver_data for attach and detach callbacks and move the i80 mode selection and the hotplug handling into the callback, because these depend on the drm driver.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
v2:
- new patch
drivers/gpu/drm/exynos/exynos_drm_dsi.c | 64 ++++++++++++++++++++----- 1 file changed, 53 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 1a15ae71205d..684a2fbef08a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -239,6 +239,12 @@ struct exynos_dsi_transfer { #define DSIM_STATE_CMD_LPM BIT(2) #define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct exynos_dsi; +struct exynos_dsi_host_ops {
- int (*attach)(struct device *dev, struct mipi_dsi_device *device);
- int (*detach)(struct device *dev, struct mipi_dsi_device *device);
+};
- enum exynos_reg_offset { EXYNOS_REG_OFS, EXYNOS5433_REG_OFS
@@ -254,6 +260,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values;
const struct exynos_dsi_host_ops *host_ops; };
struct exynos_dsi {
@@ -467,6 +474,41 @@ static const unsigned int exynos5433_reg_values[] = { [PHYTIMING_HS_TRAIL] = 0x0c, };
+static int __exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
+{
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
As I wrote yesterday drm device was guaranteed to be present here only if mipi dsi host registration was performed in component bind. In case it is performed in probe it will be always NULL, and the code does not make sense.
Correct, but if the driver is used as a drm bridge, there won't be an encoder until bridge_attach. Mipi dsi devices defer their probe until the mipi dsi host is available. If I move the mipi dsi host registration into bridge_attach, the driver does not know if the next device is another bridge or a panel during bridge_attach, but the driver cannot successfully return from bridge_attach, because then the drm driver expects a connector.
Hmm, I am not sure why do you think driver expects connector on return of briddge_attach.
I guess that the encoder should be initialized before registering the mipi dsi host during probe instead of bind.
But you should have already drm dev on encoder init which in probe is unavailable.
The probe won't be rolled back on PROBE_DEFER during bind and the encoder will be available in host_attach. When splitting the driver into the exynos platform specific code and the more generic code, there won't be an encoder during host_attach in the generic code, but the host_ops callback could (and will) use the platform specific encoder, which is available before bridge_attach.
Does this make sense to you?
Nope :) But maybe I am missing sth.
Generally I see two ways (which I have already described in different e-mail, in different words):
A. Static - we wait for every part of display stack to be probed, then create drm_dev - typical approach, but slow (deferred probe causes late drm creation), and racy - only(?) component framework and DSI bus have possibility to signal driver unbind, so we can react on it properly.
B. Dynamic - drm framework requires only crtcs and encoders to be attached to drm on init, connectors, and hidden parts (drm_bridges, drm_panels) can be created/destroyed and attached/detached at any time (almost), so lets take advantage of it - create drm_dev ASAP and attach other parts when they become available, the only issue is that there is no generic way to be notified when given parts becomes available - in interesting area only mipi devices have such notifications via attach callbacks.
So either we convert exynos_dsi to A either we continue B approach. In second case we should assure mipi_dsi host is created if drm_dev is available.
Regards
Andrzej
Michael
Regards
Andrzej
- struct exynos_drm_crtc *crtc;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
+}
+static int __exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
+{
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
+}
+static const struct exynos_dsi_host_ops exynos_dsi_host_ops = {
- .attach = __exynos_dsi_host_attach,
- .detach = __exynos_dsi_host_detach,
+};
- static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .reg_ofs = EXYNOS_REG_OFS, .plltmr_reg = 0x50,
@@ -477,6 +519,7 @@ static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = {
@@ -489,6 +532,7 @@ static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = {
@@ -499,6 +543,7 @@ static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 11, .reg_values = reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = {
@@ -510,6 +555,7 @@ static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { .wait_for_reset = 0, .num_bits_resol = 12, .reg_values = exynos5433_reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = {
@@ -521,6 +567,7 @@ static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { .wait_for_reset = 1, .num_bits_resol = 12, .reg_values = exynos5422_reg_values,
.host_ops = &exynos_dsi_host_ops, };
static const struct of_device_id exynos_dsi_of_match[] = {
@@ -1551,8 +1598,8 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host);
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_encoder *encoder = &dsi->encoder;
struct drm_device *drm = encoder->dev; struct drm_bridge *out_bridge;
out_bridge = of_drm_find_bridge(device->dev.of_node);
@@ -1590,18 +1637,12 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, return ret; }
mutex_lock(&drm->mode_config.mutex);
dsi->lanes = device->lanes; dsi->format = device->format; dsi->mode_flags = device->mode_flags;
exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD)->i80_mode =
!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
mutex_unlock(&drm->mode_config.mutex);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
return 0; }
@@ -1610,6 +1651,7 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { struct exynos_dsi *dsi = host_to_dsi(host);
const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; struct drm_device *drm = dsi->encoder.dev;
if (dsi->panel) {
@@ -1625,8 +1667,8 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, INIT_LIST_HEAD(&dsi->bridge_chain); }
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=80c78954-dd15920c-80c6021b-0cc47a31c8b...
The tearing effect interrupts are not handled by the MIPI DSI bridge driver, but the display controller driver.
Allow platforms to register a callback for the tearing effect interrupt.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - add handler as callback in host_ops --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 684a2fbef08a..2d75f9877dc0 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -243,6 +243,7 @@ struct exynos_dsi; struct exynos_dsi_host_ops { int (*attach)(struct device *dev, struct mipi_dsi_device *device); int (*detach)(struct device *dev, struct mipi_dsi_device *device); + void (*te_handler)(struct device *dev); };
enum exynos_reg_offset { @@ -504,9 +505,17 @@ static int __exynos_dsi_host_detach(struct device *dev, return 0; }
+static void __exynos_dsi_te_handler(struct device *dev) +{ + struct exynos_dsi *dsi = dev_get_drvdata(dev); + + exynos_drm_crtc_te_handler(dsi->encoder.crtc); +} + static const struct exynos_dsi_host_ops exynos_dsi_host_ops = { .attach = __exynos_dsi_host_attach, .detach = __exynos_dsi_host_detach, + .te_handler = __exynos_dsi_te_handler, };
static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { @@ -1354,11 +1363,12 @@ static irqreturn_t exynos_dsi_irq(int irq, void *dev_id)
static irqreturn_t exynos_dsi_te_irq_handler(int irq, void *dev_id) { - struct exynos_dsi *dsi = (struct exynos_dsi *)dev_id; - struct drm_encoder *encoder = &dsi->encoder; + struct exynos_dsi *dsi = dev_id; + const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- if (dsi->state & DSIM_STATE_VIDOUT_AVAILABLE) - exynos_drm_crtc_te_handler(encoder->crtc); + if (ops && ops->te_handler && + (dsi->state & DSIM_STATE_VIDOUT_AVAILABLE)) + ops->te_handler(dsi->dsi_host.dev);
return IRQ_HANDLED; }
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - move attach of out_bridge to bridge_attach - add bridge_detach - don't cleanup encoder if create_connector failed --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 103 +++++++++++++++++------- 1 file changed, 75 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 2d75f9877dc0..5e7c1a99a3ee 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -266,6 +266,7 @@ struct exynos_dsi_driver_data {
struct exynos_dsi { struct drm_encoder encoder; + struct drm_bridge bridge; struct mipi_dsi_host dsi_host; struct drm_connector connector; struct drm_panel *panel; @@ -1602,6 +1603,60 @@ static const struct drm_encoder_helper_funcs exynos_dsi_encoder_helper_funcs = { .disable = exynos_dsi_disable, };
+static int exynos_dsi_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct exynos_dsi *dsi = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + int ret; + + if (!dsi->out_bridge && !dsi->panel) + return -EPROBE_DEFER; + + if (dsi->out_bridge) { + ret = drm_bridge_attach(encoder, dsi->out_bridge, + bridge, flags); + if (ret) + return ret; + list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); + } else { + ret = exynos_dsi_create_connector(encoder); + if (ret) + return ret; + + if (dsi->panel) { + dsi->connector.status = connector_status_connected; + } + } + + return 0; +} + +static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) +{ + struct exynos_dsi *dsi = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + struct drm_device *drm = encoder->dev; + + if (dsi->panel) { + mutex_lock(&drm->mode_config.mutex); + exynos_dsi_disable(&dsi->encoder); + dsi->panel = NULL; + dsi->connector.status = connector_status_disconnected; + mutex_unlock(&drm->mode_config.mutex); + } else { + if (dsi->out_bridge->funcs->detach) + dsi->out_bridge->funcs->detach(dsi->out_bridge); + dsi->out_bridge = NULL; + INIT_LIST_HEAD(&dsi->bridge_chain); + } +} + +static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { + .attach = exynos_dsi_bridge_attach, + .detach = exynos_dsi_bridge_detach, +}; + MODULE_DEVICE_TABLE(of, exynos_dsi_of_match);
static int exynos_dsi_host_attach(struct mipi_dsi_host *host, @@ -1609,25 +1664,12 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, { struct exynos_dsi *dsi = host_to_dsi(host); const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; - struct drm_encoder *encoder = &dsi->encoder; struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node); + out_bridge = of_drm_find_bridge(device->dev.of_node); if (out_bridge) { - drm_bridge_attach(encoder, out_bridge, NULL, 0); dsi->out_bridge = out_bridge; - list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); } else { - int ret = exynos_dsi_create_connector(encoder); - - if (ret) { - DRM_DEV_ERROR(dsi->dev, - "failed to create connector ret = %d\n", - ret); - drm_encoder_cleanup(encoder); - return ret; - } - dsi->panel = of_drm_find_panel(device->dev.of_node); if (IS_ERR(dsi->panel)) dsi->panel = NULL; @@ -1662,20 +1704,6 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, { struct exynos_dsi *dsi = host_to_dsi(host); const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; - struct drm_device *drm = dsi->encoder.dev; - - if (dsi->panel) { - mutex_lock(&drm->mode_config.mutex); - exynos_dsi_disable(&dsi->encoder); - dsi->panel = NULL; - dsi->connector.status = connector_status_disconnected; - mutex_unlock(&drm->mode_config.mutex); - } else { - if (dsi->out_bridge->funcs->detach) - dsi->out_bridge->funcs->detach(dsi->out_bridge); - dsi->out_bridge = NULL; - INIT_LIST_HEAD(&dsi->bridge_chain); - }
if (ops && ops->detach) ops->detach(dsi->dsi_host.dev, device); @@ -1786,7 +1814,15 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, of_node_put(in_bridge_node); }
+ ret = drm_bridge_attach(encoder, &dsi->bridge, in_bridge, 0); + if (ret) + goto err; + return 0; + +err: + drm_encoder_cleanup(encoder); + return ret; }
static void exynos_dsi_unbind(struct device *dev, struct device *master, @@ -1796,6 +1832,8 @@ static void exynos_dsi_unbind(struct device *dev, struct device *master, struct drm_encoder *encoder = &dsi->encoder;
exynos_dsi_disable(encoder); + + drm_encoder_cleanup(encoder); }
static const struct component_ops exynos_dsi_component_ops = { @@ -1806,6 +1844,7 @@ static const struct component_ops exynos_dsi_component_ops = { static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct drm_bridge *bridge; struct resource *res; struct exynos_dsi *dsi; int ret, i; @@ -1894,11 +1933,19 @@ static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) if (ret) return ERR_PTR(ret);
+ bridge = &dsi->bridge; + bridge->driver_private = dsi; + bridge->funcs = &exynos_dsi_bridge_funcs; + bridge->of_node = dev->of_node; + drm_bridge_add(bridge); + return dsi; }
static void __exynos_dsi_remove(struct exynos_dsi *dsi) { + drm_bridge_remove(&dsi->bridge); + mipi_dsi_host_unregister(&dsi->dsi_host); }
Hi Michael,
On 11.09.2020 15:54, Michael Tretter wrote:
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
v2:
- move attach of out_bridge to bridge_attach
- add bridge_detach
- don't cleanup encoder if create_connector failed
drivers/gpu/drm/exynos/exynos_drm_dsi.c | 103 +++++++++++++++++------- 1 file changed, 75 insertions(+), 28 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 2d75f9877dc0..5e7c1a99a3ee 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -266,6 +266,7 @@ struct exynos_dsi_driver_data {
struct exynos_dsi { struct drm_encoder encoder;
- struct drm_bridge bridge; struct mipi_dsi_host dsi_host; struct drm_connector connector; struct drm_panel *panel;
@@ -1602,6 +1603,60 @@ static const struct drm_encoder_helper_funcs exynos_dsi_encoder_helper_funcs = { .disable = exynos_dsi_disable, };
+static int exynos_dsi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
+{
- struct exynos_dsi *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- int ret;
- if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
- if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
- } else {
ret = exynos_dsi_create_connector(encoder);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
- }
- return 0;
+}
+static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) +{
- struct exynos_dsi *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
exynos_dsi_disable(&dsi->encoder);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- }
+}
+static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = {
- .attach = exynos_dsi_bridge_attach,
- .detach = exynos_dsi_bridge_detach,
+};
MODULE_DEVICE_TABLE(of, exynos_dsi_of_match);
static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
@@ -1609,25 +1664,12 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, { struct exynos_dsi *dsi = host_to_dsi(host); const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
struct drm_encoder *encoder = &dsi->encoder; struct drm_bridge *out_bridge;
out_bridge = of_drm_find_bridge(device->dev.of_node);
- out_bridge = of_drm_find_bridge(device->dev.of_node); if (out_bridge) {
dsi->out_bridge = out_bridge;drm_bridge_attach(encoder, out_bridge, NULL, 0);
} else {list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
int ret = exynos_dsi_create_connector(encoder);
if (ret) {
DRM_DEV_ERROR(dsi->dev,
"failed to create connector ret = %d\n",
ret);
drm_encoder_cleanup(encoder);
return ret;
}
- dsi->panel = of_drm_find_panel(device->dev.of_node); if (IS_ERR(dsi->panel)) dsi->panel = NULL;
@@ -1662,20 +1704,6 @@ static int exynos_dsi_host_detach(struct mipi_dsi_host *host, { struct exynos_dsi *dsi = host_to_dsi(host); const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
struct drm_device *drm = dsi->encoder.dev;
if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
exynos_dsi_disable(&dsi->encoder);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
} else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
}
if (ops && ops->detach) ops->detach(dsi->dsi_host.dev, device);
@@ -1786,7 +1814,15 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, of_node_put(in_bridge_node); }
- ret = drm_bridge_attach(encoder, &dsi->bridge, in_bridge, 0);
- if (ret)
goto err;
- return 0;
+err:
drm_encoder_cleanup(encoder);
return ret; }
static void exynos_dsi_unbind(struct device *dev, struct device *master,
@@ -1796,6 +1832,8 @@ static void exynos_dsi_unbind(struct device *dev, struct device *master, struct drm_encoder *encoder = &dsi->encoder;
exynos_dsi_disable(encoder);
drm_encoder_cleanup(encoder); }
static const struct component_ops exynos_dsi_component_ops = {
@@ -1806,6 +1844,7 @@ static const struct component_ops exynos_dsi_component_ops = { static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev;
- struct drm_bridge *bridge; struct resource *res; struct exynos_dsi *dsi; int ret, i;
@@ -1894,11 +1933,19 @@ static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) if (ret) return ERR_PTR(ret);
bridge = &dsi->bridge;
bridge->driver_private = dsi;
bridge->funcs = &exynos_dsi_bridge_funcs;
bridge->of_node = dev->of_node;
drm_bridge_add(bridge);
return dsi; }
static void __exynos_dsi_remove(struct exynos_dsi *dsi) {
drm_bridge_remove(&dsi->bridge);
mipi_dsi_host_unregister(&dsi->dsi_host); }
Best regards
Hi,
On 14.09.2020 10:29, Marek Szyprowski wrote:
On 11.09.2020 15:54, Michael Tretter wrote:
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
Best regards
Hi,
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote:
On 14.09.2020 10:29, Marek Szyprowski wrote:
On 11.09.2020 15:54, Michael Tretter wrote:
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
Michael
Hi Marek, Michael,
On 14.09.2020 22:01, Michael Tretter wrote:
Hi,
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote:
On 14.09.2020 10:29, Marek Szyprowski wrote:
On 11.09.2020 15:54, Michael Tretter wrote:
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
Regards
Andrzej
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=4f0be936-129547ac-4f0a6279-0cc47a336fa...
Hi again,
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze:
Hi Marek, Michael,
On 14.09.2020 22:01, Michael Tretter wrote:
Hi,
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote:
On 14.09.2020 10:29, Marek Szyprowski wrote:
On 11.09.2020 15:54, Michael Tretter wrote:
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
1. Safely bind/unbind different device drivers at any time and at any order, without killing exynos_drm and/or crashing system.
2. Avoid issues with late drm init - on some platforms exynos_drm device appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Regards
Andrzej
Regards
Andrzej
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=4f0be936-129547ac-4f0a6279-0cc47a336fa...
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote:
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze:
On 14.09.2020 22:01, Michael Tretter wrote:
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote:
On 14.09.2020 10:29, Marek Szyprowski wrote:
On 11.09.2020 15:54, Michael Tretter wrote:
Make the exynos_dsi driver a full drm bridge that can be found and used from other drivers.
Other drivers can only attach to the bridge, if a mipi dsi device already attached to the bridge. This allows to defer the probe of the display pipe until the downstream bridges are available, too.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
- Safely bind/unbind different device drivers at any time and at any
order, without killing exynos_drm and/or crashing system.
- Avoid issues with late drm init - on some platforms exynos_drm device
appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Michael
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote:
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote:
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze:
On 14.09.2020 22:01, Michael Tretter wrote:
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote:
On 14.09.2020 10:29, Marek Szyprowski wrote:
On 11.09.2020 15:54, Michael Tretter wrote: > Make the exynos_dsi driver a full drm bridge that can be found and > used > from other drivers. > > Other drivers can only attach to the bridge, if a mipi dsi device > already attached to the bridge. This allows to defer the probe of the > display pipe until the downstream bridges are available, too. > > Signed-off-by: Michael Tretter m.tretter@pengutronix.de This one (and the whole series applied) still fails on Exynos boards:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000084 pgd = (ptrval) [00000084] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 1 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at drm_bridge_attach+0x18/0x164 LR is at exynos_dsi_bind+0x88/0xa8 pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 sp : ef0dfca8 ip : 00000002 fp : c13190e0 r10: 00000000 r9 : ee46d580 r8 : c13190e0 r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfca8 to 0xef0e0000) ... [<c0628c08>] (drm_bridge_attach) from [<c064d560>] (exynos_dsi_bind+0x88/0xa8) [<c064d560>] (exynos_dsi_bind) from [<c066a800>] (component_bind_all+0xfc/0x290) [<c066a800>] (component_bind_all) from [<c0649dc0>] (exynos_drm_bind+0xe4/0x19c) [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] (try_to_bring_up_master+0x1e4/0x2c4) [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] (component_master_add_with_match+0xd4/0x108) [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] (exynos_drm_platform_probe+0xe4/0x110) [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] (platform_drv_probe+0x6c/0xa4) [<c0674e6c>] (platform_drv_probe) from [<c067242c>] (really_probe+0x200/0x4fc) [<c067242c>] (really_probe) from [<c06728f0>] (driver_probe_device+0x78/0x1fc) [<c06728f0>] (driver_probe_device) from [<c0672cd8>] (device_driver_attach+0x58/0x60) [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] (__driver_attach+0xdc/0x174) [<c0672dbc>] (__driver_attach) from [<c06701b4>] (bus_for_each_dev+0x68/0xb4) [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] (bus_add_driver+0x158/0x214) [<c06714e8>] (bus_add_driver) from [<c0673c1c>] (driver_register+0x78/0x110) [<c0673c1c>] (driver_register) from [<c0649ca8>] (exynos_drm_init+0xe4/0x118) [<c0649ca8>] (exynos_drm_init) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] (kernel_init+0x8/0x118) [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace ee27f313f9ed9da1 ]---
# arm-linux-gnueabi-addr2line -e vmlinux c0628c08 drivers/gpu/drm/drm_bridge.c:184 (discriminator 1)
I will try to debug it a bit more today.
The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
- Safely bind/unbind different device drivers at any time and at any
order, without killing exynos_drm and/or crashing system.
- Avoid issues with late drm init - on some platforms exynos_drm device
appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
Michael
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter m.tretter@pengutronix.de wrote:
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote:
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote:
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze:
On 14.09.2020 22:01, Michael Tretter wrote:
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote:
On 14.09.2020 10:29, Marek Szyprowski wrote: > On 11.09.2020 15:54, Michael Tretter wrote: >> Make the exynos_dsi driver a full drm bridge that can be found and >> used >> from other drivers. >> >> Other drivers can only attach to the bridge, if a mipi dsi device >> already attached to the bridge. This allows to defer the probe of the >> display pipe until the downstream bridges are available, too. >> >> Signed-off-by: Michael Tretter m.tretter@pengutronix.de > This one (and the whole series applied) still fails on Exynos boards: > > [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping > operations > exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) > OF: graph: no port node found in /soc/dsi@11c80000 > 8<--- cut here --- > Unable to handle kernel NULL pointer dereference at virtual address > 00000084 > pgd = (ptrval) > [00000084] *pgd=00000000 > Internal error: Oops: 5 [#1] PREEMPT SMP ARM > Modules linked in: > CPU: 1 PID: 1 Comm: swapper/0 Not tainted > 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 > Hardware name: Samsung Exynos (Flattened Device Tree) > PC is at drm_bridge_attach+0x18/0x164 > LR is at exynos_dsi_bind+0x88/0xa8 > pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 > sp : ef0dfca8 ip : 00000002 fp : c13190e0 > r10: 00000000 r9 : ee46d580 r8 : c13190e0 > r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 > r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 > Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none > Control: 10c5387d Table: 4000404a DAC: 00000051 > Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) > Stack: (0xef0dfca8 to 0xef0e0000) > ... > [<c0628c08>] (drm_bridge_attach) from [<c064d560>] > (exynos_dsi_bind+0x88/0xa8) > [<c064d560>] (exynos_dsi_bind) from [<c066a800>] > (component_bind_all+0xfc/0x290) > [<c066a800>] (component_bind_all) from [<c0649dc0>] > (exynos_drm_bind+0xe4/0x19c) > [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] > (try_to_bring_up_master+0x1e4/0x2c4) > [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] > (component_master_add_with_match+0xd4/0x108) > [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] > (exynos_drm_platform_probe+0xe4/0x110) > [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] > (platform_drv_probe+0x6c/0xa4) > [<c0674e6c>] (platform_drv_probe) from [<c067242c>] > (really_probe+0x200/0x4fc) > [<c067242c>] (really_probe) from [<c06728f0>] > (driver_probe_device+0x78/0x1fc) > [<c06728f0>] (driver_probe_device) from [<c0672cd8>] > (device_driver_attach+0x58/0x60) > [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] > (__driver_attach+0xdc/0x174) > [<c0672dbc>] (__driver_attach) from [<c06701b4>] > (bus_for_each_dev+0x68/0xb4) > [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] > (bus_add_driver+0x158/0x214) > [<c06714e8>] (bus_add_driver) from [<c0673c1c>] > (driver_register+0x78/0x110) > [<c0673c1c>] (driver_register) from [<c0649ca8>] > (exynos_drm_init+0xe4/0x118) > [<c0649ca8>] (exynos_drm_init) from [<c0102484>] > (do_one_initcall+0x8c/0x42c) > [<c0102484>] (do_one_initcall) from [<c11011c0>] > (kernel_init_freeable+0x190/0x1dc) > [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] > (kernel_init+0x8/0x118) > [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) > Exception stack(0xef0dffb0 to 0xef0dfff8) > ... > ---[ end trace ee27f313f9ed9da1 ]--- > > # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 > drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) > > I will try to debug it a bit more today. The above crash has been caused by lack of in_bridge initialization to NULL in exynos_dsi_bind() in this patch. However, fixing it reveals another issue:
[drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) OF: graph: no port node found in /soc/dsi@11c80000 8<--- cut here --- Unable to handle kernel NULL pointer dereference at virtual address 00000280 pgd = (ptrval) [00000280] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 Hardware name: Samsung Exynos (Flattened Device Tree) PC is at __mutex_lock+0x54/0xb18 LR is at lock_is_held_type+0x80/0x138 pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 sp : ef0dfd30 ip : 33937b74 fp : c13193c8 r10: c1208eec r9 : 00000000 r8 : ee45f808 r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none Control: 10c5387d Table: 4000404a DAC: 00000051 Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) Stack: (0xef0dfd30 to 0xef0e0000) ... [<c0afc920>] (__mutex_lock) from [<c0afd400>] (mutex_lock_nested+0x1c/0x24) [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] (__exynos_dsi_host_attach+0x20/0x6c) [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] (exynos_dsi_host_attach+0x70/0x194) [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] (s6e8aa0_probe+0x1b0/0x218) [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] (really_probe+0x200/0x4fc) [<c0672530>] (really_probe) from [<c06729f4>] (driver_probe_device+0x78/0x1fc) [<c06729f4>] (driver_probe_device) from [<c0672ddc>] (device_driver_attach+0x58/0x60) [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] (__driver_attach+0xdc/0x174) [<c0672ec0>] (__driver_attach) from [<c06702b8>] (bus_for_each_dev+0x68/0xb4) [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] (bus_add_driver+0x158/0x214) [<c06715ec>] (bus_add_driver) from [<c0673d20>] (driver_register+0x78/0x110) [<c0673d20>] (driver_register) from [<c0102484>] (do_one_initcall+0x8c/0x42c) [<c0102484>] (do_one_initcall) from [<c11011c0>] (kernel_init_freeable+0x190/0x1dc) [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] (kernel_init+0x8/0x118) [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) Exception stack(0xef0dffb0 to 0xef0dfff8) ... ---[ end trace c06e996ec2e8234d ]---
This means that dsi->encoder.dev is not initialized in __exynos_dsi_host_attach().
This happens, because drm_bridge_attach() in exynos_dsi_bind() returned earlier -517 (deferred probe), what causes cleanup of encoder and release of all drm resources.
Then however, the panel tries to register itself and exynos_dsi_host_attach() tries to access the released encoder (which is zeroed in drm_encoder_release) and rest of resources, what causes failure.
It looks that something is missing. Maybe mipi host has to be registered later, when bridge is ready? I have no idea how it is handled before this patch. Andrzej, could you comment it a bit?
I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
- Safely bind/unbind different device drivers at any time and at any
order, without killing exynos_drm and/or crashing system.
- Avoid issues with late drm init - on some platforms exynos_drm device
appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion. -Daniel
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote:
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter m.tretter@pengutronix.de wrote:
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote:
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote:
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze:
On 14.09.2020 22:01, Michael Tretter wrote:
On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: > On 14.09.2020 10:29, Marek Szyprowski wrote: >> On 11.09.2020 15:54, Michael Tretter wrote: >>> Make the exynos_dsi driver a full drm bridge that can be found and >>> used >>> from other drivers. >>> >>> Other drivers can only attach to the bridge, if a mipi dsi device >>> already attached to the bridge. This allows to defer the probe of the >>> display pipe until the downstream bridges are available, too. >>> >>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >> This one (and the whole series applied) still fails on Exynos boards: >> >> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >> operations >> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >> OF: graph: no port node found in /soc/dsi@11c80000 >> 8<--- cut here --- >> Unable to handle kernel NULL pointer dereference at virtual address >> 00000084 >> pgd = (ptrval) >> [00000084] *pgd=00000000 >> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >> Modules linked in: >> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >> Hardware name: Samsung Exynos (Flattened Device Tree) >> PC is at drm_bridge_attach+0x18/0x164 >> LR is at exynos_dsi_bind+0x88/0xa8 >> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >> Control: 10c5387d Table: 4000404a DAC: 00000051 >> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >> Stack: (0xef0dfca8 to 0xef0e0000) >> ... >> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >> (exynos_dsi_bind+0x88/0xa8) >> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >> (component_bind_all+0xfc/0x290) >> [<c066a800>] (component_bind_all) from [<c0649dc0>] >> (exynos_drm_bind+0xe4/0x19c) >> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >> (try_to_bring_up_master+0x1e4/0x2c4) >> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >> (component_master_add_with_match+0xd4/0x108) >> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >> (exynos_drm_platform_probe+0xe4/0x110) >> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >> (platform_drv_probe+0x6c/0xa4) >> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >> (really_probe+0x200/0x4fc) >> [<c067242c>] (really_probe) from [<c06728f0>] >> (driver_probe_device+0x78/0x1fc) >> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >> (device_driver_attach+0x58/0x60) >> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >> (__driver_attach+0xdc/0x174) >> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >> (bus_for_each_dev+0x68/0xb4) >> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >> (bus_add_driver+0x158/0x214) >> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >> (driver_register+0x78/0x110) >> [<c0673c1c>] (driver_register) from [<c0649ca8>] >> (exynos_drm_init+0xe4/0x118) >> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >> (do_one_initcall+0x8c/0x42c) >> [<c0102484>] (do_one_initcall) from [<c11011c0>] >> (kernel_init_freeable+0x190/0x1dc) >> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >> (kernel_init+0x8/0x118) >> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >> Exception stack(0xef0dffb0 to 0xef0dfff8) >> ... >> ---[ end trace ee27f313f9ed9da1 ]--- >> >> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >> >> I will try to debug it a bit more today. > The above crash has been caused by lack of in_bridge initialization to > NULL in exynos_dsi_bind() in this patch. However, fixing it reveals > another issue: > > [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations > exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) > OF: graph: no port node found in /soc/dsi@11c80000 > 8<--- cut here --- > Unable to handle kernel NULL pointer dereference at virtual address > 00000280 > pgd = (ptrval) > [00000280] *pgd=00000000 > Internal error: Oops: 5 [#1] PREEMPT SMP ARM > Modules linked in: > CPU: 0 PID: 1 Comm: swapper/0 Not tainted > 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 > Hardware name: Samsung Exynos (Flattened Device Tree) > PC is at __mutex_lock+0x54/0xb18 > LR is at lock_is_held_type+0x80/0x138 > pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 > sp : ef0dfd30 ip : 33937b74 fp : c13193c8 > r10: c1208eec r9 : 00000000 r8 : ee45f808 > r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c > r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 > Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none > Control: 10c5387d Table: 4000404a DAC: 00000051 > Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) > Stack: (0xef0dfd30 to 0xef0e0000) > ... > [<c0afc920>] (__mutex_lock) from [<c0afd400>] > (mutex_lock_nested+0x1c/0x24) > [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] > (__exynos_dsi_host_attach+0x20/0x6c) > [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] > (exynos_dsi_host_attach+0x70/0x194) > [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] > (s6e8aa0_probe+0x1b0/0x218) > [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] > (really_probe+0x200/0x4fc) > [<c0672530>] (really_probe) from [<c06729f4>] > (driver_probe_device+0x78/0x1fc) > [<c06729f4>] (driver_probe_device) from [<c0672ddc>] > (device_driver_attach+0x58/0x60) > [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] > (__driver_attach+0xdc/0x174) > [<c0672ec0>] (__driver_attach) from [<c06702b8>] > (bus_for_each_dev+0x68/0xb4) > [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] > (bus_add_driver+0x158/0x214) > [<c06715ec>] (bus_add_driver) from [<c0673d20>] > (driver_register+0x78/0x110) > [<c0673d20>] (driver_register) from [<c0102484>] > (do_one_initcall+0x8c/0x42c) > [<c0102484>] (do_one_initcall) from [<c11011c0>] > (kernel_init_freeable+0x190/0x1dc) > [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] > (kernel_init+0x8/0x118) > [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) > Exception stack(0xef0dffb0 to 0xef0dfff8) > ... > ---[ end trace c06e996ec2e8234d ]--- > > This means that dsi->encoder.dev is not initialized in > __exynos_dsi_host_attach(). > > This happens, because drm_bridge_attach() in exynos_dsi_bind() returned > earlier -517 (deferred probe), what causes cleanup of encoder and > release of all drm resources. > > Then however, the panel tries to register itself and > exynos_dsi_host_attach() tries to access the released encoder (which is > zeroed in drm_encoder_release) and rest of resources, what causes > failure. > > It looks that something is missing. Maybe mipi host has to be > registered > later, when bridge is ready? I have no idea how it is handled before > this patch. Andrzej, could you comment it a bit? I intentionally changed the order, because if another bridge follows in the pipeline, the probe of the drm driver has to be deferred until some bridge provides a connector. The next bridge registers itself via the host_attach function and the deferral is ensured via the bind for the bind/unbind API or the bridge_attach function otherwise.
On the other hand, the bridge does not have an encoder until the mipi device has been attached.
As a solution, the exynos dsi driver must initialize the encoder in exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder via exynos_dsi instead of the bridge.
Can you try to move everything except samsung_dsim_bind from exynos_dsi_bind to exynos_dsi_probe (respectively for unbind) and report if it fixes the crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
- Safely bind/unbind different device drivers at any time and at any
order, without killing exynos_drm and/or crashing system.
- Avoid issues with late drm init - on some platforms exynos_drm device
appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion.
Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Michael
-Daniel
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote:
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote:
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter m.tretter@pengutronix.de wrote:
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote:
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote:
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze:
On 14.09.2020 22:01, Michael Tretter wrote: > On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >> On 14.09.2020 10:29, Marek Szyprowski wrote: >>> On 11.09.2020 15:54, Michael Tretter wrote: >>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>> used >>>> from other drivers. >>>> >>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>> already attached to the bridge. This allows to defer the probe of the >>>> display pipe until the downstream bridges are available, too. >>>> >>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>> This one (and the whole series applied) still fails on Exynos boards: >>> >>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>> operations >>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>> OF: graph: no port node found in /soc/dsi@11c80000 >>> 8<--- cut here --- >>> Unable to handle kernel NULL pointer dereference at virtual address >>> 00000084 >>> pgd = (ptrval) >>> [00000084] *pgd=00000000 >>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>> Modules linked in: >>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>> Hardware name: Samsung Exynos (Flattened Device Tree) >>> PC is at drm_bridge_attach+0x18/0x164 >>> LR is at exynos_dsi_bind+0x88/0xa8 >>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>> Stack: (0xef0dfca8 to 0xef0e0000) >>> ... >>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>> (exynos_dsi_bind+0x88/0xa8) >>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>> (component_bind_all+0xfc/0x290) >>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>> (exynos_drm_bind+0xe4/0x19c) >>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>> (try_to_bring_up_master+0x1e4/0x2c4) >>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>> (component_master_add_with_match+0xd4/0x108) >>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>> (exynos_drm_platform_probe+0xe4/0x110) >>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>> (platform_drv_probe+0x6c/0xa4) >>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>> (really_probe+0x200/0x4fc) >>> [<c067242c>] (really_probe) from [<c06728f0>] >>> (driver_probe_device+0x78/0x1fc) >>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>> (device_driver_attach+0x58/0x60) >>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>> (__driver_attach+0xdc/0x174) >>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>> (bus_for_each_dev+0x68/0xb4) >>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>> (bus_add_driver+0x158/0x214) >>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>> (driver_register+0x78/0x110) >>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>> (exynos_drm_init+0xe4/0x118) >>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>> (do_one_initcall+0x8c/0x42c) >>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>> (kernel_init_freeable+0x190/0x1dc) >>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>> (kernel_init+0x8/0x118) >>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>> ... >>> ---[ end trace ee27f313f9ed9da1 ]--- >>> >>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>> >>> I will try to debug it a bit more today. >> The above crash has been caused by lack of in_bridge initialization to >> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >> another issue: >> >> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >> OF: graph: no port node found in /soc/dsi@11c80000 >> 8<--- cut here --- >> Unable to handle kernel NULL pointer dereference at virtual address >> 00000280 >> pgd = (ptrval) >> [00000280] *pgd=00000000 >> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >> Modules linked in: >> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >> Hardware name: Samsung Exynos (Flattened Device Tree) >> PC is at __mutex_lock+0x54/0xb18 >> LR is at lock_is_held_type+0x80/0x138 >> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >> r10: c1208eec r9 : 00000000 r8 : ee45f808 >> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >> Control: 10c5387d Table: 4000404a DAC: 00000051 >> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >> Stack: (0xef0dfd30 to 0xef0e0000) >> ... >> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >> (mutex_lock_nested+0x1c/0x24) >> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >> (__exynos_dsi_host_attach+0x20/0x6c) >> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >> (exynos_dsi_host_attach+0x70/0x194) >> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >> (s6e8aa0_probe+0x1b0/0x218) >> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >> (really_probe+0x200/0x4fc) >> [<c0672530>] (really_probe) from [<c06729f4>] >> (driver_probe_device+0x78/0x1fc) >> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >> (device_driver_attach+0x58/0x60) >> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >> (__driver_attach+0xdc/0x174) >> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >> (bus_for_each_dev+0x68/0xb4) >> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >> (bus_add_driver+0x158/0x214) >> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >> (driver_register+0x78/0x110) >> [<c0673d20>] (driver_register) from [<c0102484>] >> (do_one_initcall+0x8c/0x42c) >> [<c0102484>] (do_one_initcall) from [<c11011c0>] >> (kernel_init_freeable+0x190/0x1dc) >> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >> (kernel_init+0x8/0x118) >> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >> Exception stack(0xef0dffb0 to 0xef0dfff8) >> ... >> ---[ end trace c06e996ec2e8234d ]--- >> >> This means that dsi->encoder.dev is not initialized in >> __exynos_dsi_host_attach(). >> >> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >> earlier -517 (deferred probe), what causes cleanup of encoder and >> release of all drm resources. >> >> Then however, the panel tries to register itself and >> exynos_dsi_host_attach() tries to access the released encoder (which is >> zeroed in drm_encoder_release) and rest of resources, what causes >> failure. >> >> It looks that something is missing. Maybe mipi host has to be >> registered >> later, when bridge is ready? I have no idea how it is handled before >> this patch. Andrzej, could you comment it a bit? > I intentionally changed the order, because if another bridge follows > in the > pipeline, the probe of the drm driver has to be deferred until some > bridge > provides a connector. The next bridge registers itself via the > host_attach > function and the deferral is ensured via the bind for the bind/unbind > API or > the bridge_attach function otherwise. > > On the other hand, the bridge does not have an encoder until the mipi > device > has been attached. > > As a solution, the exynos dsi driver must initialize the encoder in > exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder > via > exynos_dsi instead of the bridge. > > Can you try to move everything except samsung_dsim_bind from > exynos_dsi_bind > to exynos_dsi_probe (respectively for unbind) and report if it fixes the > crash.
The original behaviour is that encoder (exynos_dsi) is registered regardless of sink presence (initially panel, later also bridge) - it avoids multiple issues with deferred probe, device driver bind/unbind and module load/unload. Appearance or disappearance of sink is reported to host nicely via DSI attach/detach callbacks - and it is reflected in drm world as change state of the connector.
Registering DSI host in bind and unregistering in unbind assures that if mipi_dsi device is attached/detached the drm device is always present - it makes device/driver binding race free and allows to avoid additional locking.
Moving DSI host registration to probe changes everything, for sure it breaks the nice feature of DSI attach/detach callbacks and apparently can cause different issues depending on device bind order.
I will try to look at the patches tomorrow and maybe I can find more constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
- Safely bind/unbind different device drivers at any time and at any
order, without killing exynos_drm and/or crashing system.
- Avoid issues with late drm init - on some platforms exynos_drm device
appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion.
Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register. -Daniel
Michael
-Daniel
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze:
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote:
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote:
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter m.tretter@pengutronix.de wrote:
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote:
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote:
W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: > On 14.09.2020 22:01, Michael Tretter wrote: >> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>> used >>>>> from other drivers. >>>>> >>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>> already attached to the bridge. This allows to defer the probe of the >>>>> display pipe until the downstream bridges are available, too. >>>>> >>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>> This one (and the whole series applied) still fails on Exynos boards: >>>> >>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>> operations >>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>> 8<--- cut here --- >>>> Unable to handle kernel NULL pointer dereference at virtual address >>>> 00000084 >>>> pgd = (ptrval) >>>> [00000084] *pgd=00000000 >>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>> Modules linked in: >>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>> PC is at drm_bridge_attach+0x18/0x164 >>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>> ... >>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>> (exynos_dsi_bind+0x88/0xa8) >>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>> (component_bind_all+0xfc/0x290) >>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>> (exynos_drm_bind+0xe4/0x19c) >>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>> (component_master_add_with_match+0xd4/0x108) >>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>> (exynos_drm_platform_probe+0xe4/0x110) >>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>> (platform_drv_probe+0x6c/0xa4) >>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>> (really_probe+0x200/0x4fc) >>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>> (driver_probe_device+0x78/0x1fc) >>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>> (device_driver_attach+0x58/0x60) >>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>> (__driver_attach+0xdc/0x174) >>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>> (bus_for_each_dev+0x68/0xb4) >>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>> (bus_add_driver+0x158/0x214) >>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>> (driver_register+0x78/0x110) >>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>> (exynos_drm_init+0xe4/0x118) >>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>> (do_one_initcall+0x8c/0x42c) >>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>> (kernel_init_freeable+0x190/0x1dc) >>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>> (kernel_init+0x8/0x118) >>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>> ... >>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>> >>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>> >>>> I will try to debug it a bit more today. >>> The above crash has been caused by lack of in_bridge initialization to >>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>> another issue: >>> >>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>> OF: graph: no port node found in /soc/dsi@11c80000 >>> 8<--- cut here --- >>> Unable to handle kernel NULL pointer dereference at virtual address >>> 00000280 >>> pgd = (ptrval) >>> [00000280] *pgd=00000000 >>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>> Modules linked in: >>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>> Hardware name: Samsung Exynos (Flattened Device Tree) >>> PC is at __mutex_lock+0x54/0xb18 >>> LR is at lock_is_held_type+0x80/0x138 >>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>> Stack: (0xef0dfd30 to 0xef0e0000) >>> ... >>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>> (mutex_lock_nested+0x1c/0x24) >>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>> (__exynos_dsi_host_attach+0x20/0x6c) >>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>> (exynos_dsi_host_attach+0x70/0x194) >>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>> (s6e8aa0_probe+0x1b0/0x218) >>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>> (really_probe+0x200/0x4fc) >>> [<c0672530>] (really_probe) from [<c06729f4>] >>> (driver_probe_device+0x78/0x1fc) >>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>> (device_driver_attach+0x58/0x60) >>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>> (__driver_attach+0xdc/0x174) >>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>> (bus_for_each_dev+0x68/0xb4) >>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>> (bus_add_driver+0x158/0x214) >>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>> (driver_register+0x78/0x110) >>> [<c0673d20>] (driver_register) from [<c0102484>] >>> (do_one_initcall+0x8c/0x42c) >>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>> (kernel_init_freeable+0x190/0x1dc) >>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>> (kernel_init+0x8/0x118) >>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>> ... >>> ---[ end trace c06e996ec2e8234d ]--- >>> >>> This means that dsi->encoder.dev is not initialized in >>> __exynos_dsi_host_attach(). >>> >>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>> earlier -517 (deferred probe), what causes cleanup of encoder and >>> release of all drm resources. >>> >>> Then however, the panel tries to register itself and >>> exynos_dsi_host_attach() tries to access the released encoder (which is >>> zeroed in drm_encoder_release) and rest of resources, what causes >>> failure. >>> >>> It looks that something is missing. Maybe mipi host has to be >>> registered >>> later, when bridge is ready? I have no idea how it is handled before >>> this patch. Andrzej, could you comment it a bit? >> I intentionally changed the order, because if another bridge follows >> in the >> pipeline, the probe of the drm driver has to be deferred until some >> bridge >> provides a connector. The next bridge registers itself via the >> host_attach >> function and the deferral is ensured via the bind for the bind/unbind >> API or >> the bridge_attach function otherwise. >> >> On the other hand, the bridge does not have an encoder until the mipi >> device >> has been attached. >> >> As a solution, the exynos dsi driver must initialize the encoder in >> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >> via >> exynos_dsi instead of the bridge. >> >> Can you try to move everything except samsung_dsim_bind from >> exynos_dsi_bind >> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >> crash. > > The original behaviour is that encoder (exynos_dsi) is registered > regardless of sink presence (initially panel, later also bridge) - it > avoids multiple issues with deferred probe, device driver bind/unbind > and module load/unload. Appearance or disappearance of sink is > reported to host nicely via DSI attach/detach callbacks - and it is > reflected in drm world as change state of the connector. > > Registering DSI host in bind and unregistering in unbind assures that > if mipi_dsi device is attached/detached the drm device is always > present - it makes device/driver binding race free and allows to avoid > additional locking. > > Moving DSI host registration to probe changes everything, for sure it > breaks the nice feature of DSI attach/detach callbacks and apparently > can cause different issues depending on device bind order. > > I will try to look at the patches tomorrow and maybe I can find more > constructive comments :)
As I said yesterday, exynos_dsi driver uses dsi host attach/detach callbacks to control appearance/disappearance of downstream device. It allows to:
- Safely bind/unbind different device drivers at any time and at any
order, without killing exynos_drm and/or crashing system.
- Avoid issues with late drm init - on some platforms exynos_drm device
appeared too late, due to deferred probe, and resulted in black screen in userspace.
Now if we want to convert exynos_dsi to drm_bridge I see following options:
A. Forgot about callbacks and make the exynos_drm to defer probing until exynos_dsi bridge is available, probably it will cause later exynos_drm appearance, thus probably black screen on some targets. So for sure it will be suboptimal. Making it bridge unbind safe would be another problem, but most developers do not care about it so why should we? :)
B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, even if downstream devices are not yet attached, on attach/detach notify drm about it via connector status change, for this dsi_host registration should be performed from drm_bridge attach, I guess.
Option A is more standard, but is unsafe and causes other issues.
Option B keeps current behaviour.
Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion.
Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register. -Daniel
I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Regards
Andrzej
Michael
-Daniel
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=9b91fda0-c40ac4d5-9b9076ef-0cc47a31ce4...
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda a.hajda@samsung.com wrote:
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze:
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote:
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote:
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter m.tretter@pengutronix.de wrote:
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote:
On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: > W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >> On 14.09.2020 22:01, Michael Tretter wrote: >>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>> used >>>>>> from other drivers. >>>>>> >>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>> display pipe until the downstream bridges are available, too. >>>>>> >>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>> >>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>> operations >>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>> 8<--- cut here --- >>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>> 00000084 >>>>> pgd = (ptrval) >>>>> [00000084] *pgd=00000000 >>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>> Modules linked in: >>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>> ... >>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>> (exynos_dsi_bind+0x88/0xa8) >>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>> (component_bind_all+0xfc/0x290) >>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>> (exynos_drm_bind+0xe4/0x19c) >>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>> (component_master_add_with_match+0xd4/0x108) >>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>> (platform_drv_probe+0x6c/0xa4) >>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>> (really_probe+0x200/0x4fc) >>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>> (driver_probe_device+0x78/0x1fc) >>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>> (device_driver_attach+0x58/0x60) >>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>> (__driver_attach+0xdc/0x174) >>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>> (bus_for_each_dev+0x68/0xb4) >>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>> (bus_add_driver+0x158/0x214) >>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>> (driver_register+0x78/0x110) >>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>> (exynos_drm_init+0xe4/0x118) >>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>> (do_one_initcall+0x8c/0x42c) >>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>> (kernel_init_freeable+0x190/0x1dc) >>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>> (kernel_init+0x8/0x118) >>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>> ... >>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>> >>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>> >>>>> I will try to debug it a bit more today. >>>> The above crash has been caused by lack of in_bridge initialization to >>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>> another issue: >>>> >>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>> 8<--- cut here --- >>>> Unable to handle kernel NULL pointer dereference at virtual address >>>> 00000280 >>>> pgd = (ptrval) >>>> [00000280] *pgd=00000000 >>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>> Modules linked in: >>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>> PC is at __mutex_lock+0x54/0xb18 >>>> LR is at lock_is_held_type+0x80/0x138 >>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>> ... >>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>> (mutex_lock_nested+0x1c/0x24) >>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>> (exynos_dsi_host_attach+0x70/0x194) >>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>> (s6e8aa0_probe+0x1b0/0x218) >>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>> (really_probe+0x200/0x4fc) >>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>> (driver_probe_device+0x78/0x1fc) >>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>> (device_driver_attach+0x58/0x60) >>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>> (__driver_attach+0xdc/0x174) >>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>> (bus_for_each_dev+0x68/0xb4) >>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>> (bus_add_driver+0x158/0x214) >>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>> (driver_register+0x78/0x110) >>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>> (do_one_initcall+0x8c/0x42c) >>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>> (kernel_init_freeable+0x190/0x1dc) >>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>> (kernel_init+0x8/0x118) >>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>> ... >>>> ---[ end trace c06e996ec2e8234d ]--- >>>> >>>> This means that dsi->encoder.dev is not initialized in >>>> __exynos_dsi_host_attach(). >>>> >>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>> release of all drm resources. >>>> >>>> Then however, the panel tries to register itself and >>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>> failure. >>>> >>>> It looks that something is missing. Maybe mipi host has to be >>>> registered >>>> later, when bridge is ready? I have no idea how it is handled before >>>> this patch. Andrzej, could you comment it a bit? >>> I intentionally changed the order, because if another bridge follows >>> in the >>> pipeline, the probe of the drm driver has to be deferred until some >>> bridge >>> provides a connector. The next bridge registers itself via the >>> host_attach >>> function and the deferral is ensured via the bind for the bind/unbind >>> API or >>> the bridge_attach function otherwise. >>> >>> On the other hand, the bridge does not have an encoder until the mipi >>> device >>> has been attached. >>> >>> As a solution, the exynos dsi driver must initialize the encoder in >>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>> via >>> exynos_dsi instead of the bridge. >>> >>> Can you try to move everything except samsung_dsim_bind from >>> exynos_dsi_bind >>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>> crash. >> >> The original behaviour is that encoder (exynos_dsi) is registered >> regardless of sink presence (initially panel, later also bridge) - it >> avoids multiple issues with deferred probe, device driver bind/unbind >> and module load/unload. Appearance or disappearance of sink is >> reported to host nicely via DSI attach/detach callbacks - and it is >> reflected in drm world as change state of the connector. >> >> Registering DSI host in bind and unregistering in unbind assures that >> if mipi_dsi device is attached/detached the drm device is always >> present - it makes device/driver binding race free and allows to avoid >> additional locking. >> >> Moving DSI host registration to probe changes everything, for sure it >> breaks the nice feature of DSI attach/detach callbacks and apparently >> can cause different issues depending on device bind order. >> >> I will try to look at the patches tomorrow and maybe I can find more >> constructive comments :) > > As I said yesterday, exynos_dsi driver uses dsi host attach/detach > callbacks to control appearance/disappearance of downstream device. It > allows to: > > 1. Safely bind/unbind different device drivers at any time and at any > order, without killing exynos_drm and/or crashing system. > > 2. Avoid issues with late drm init - on some platforms exynos_drm device > appeared too late, due to deferred probe, and resulted in black screen > in userspace. > > > Now if we want to convert exynos_dsi to drm_bridge I see following options: > > A. Forgot about callbacks and make the exynos_drm to defer probing until > exynos_dsi bridge is available, probably it will cause later exynos_drm > appearance, thus probably black screen on some targets. So for sure it > will be suboptimal. Making it bridge unbind safe would be another > problem, but most developers do not care about it so why should we? :) > > B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, > even if downstream devices are not yet attached, on attach/detach notify > drm about it via connector status change, for this dsi_host registration > should be performed from drm_bridge attach, I guess. > > > Option A is more standard, but is unsafe and causes other issues. > > Option B keeps current behaviour. Maybe we can have both, but I am not sure, if I am missing something:
I still prefer option A for the samsung-dsim driver, because it is more standard, simpler and avoids issues with encoders, connectors or handling hotplug.
The idea is to use two bridges in the exynos-dsi driver: One bridge in the samsung-dsim driver which implements option A and defers probing of the drm driver until the next bridge is attached. And a second bridge in the exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm device to appear) and implements the hotplug handling for notifying drm via connector status change.
The driver for the i.MX8M would use the samsung-dsim bridge without an additional bridge.
This allows the samsung-dsim driver to expose the standard behavior while the exynos_dsi may stick to the existing behavior for the exynos_drm driver.
I hope this makes sense and does not sound too crazy. It might be difficult to get the probing and mipi host/device registration correct, but I will try, if this can work.
Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion.
Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register. -Daniel
I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
There's also a bit the issue that most userspace handles panels in a special way, and if they don't find the panel at first, that doesn't work. Stuff like "which is the main screen" on laptops.
So yeah please fix this properly. -Daniel
Regards
Andrzej
Michael
-Daniel
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=9b91fda0-c40ac4d5-9b9076ef-0cc47a31ce4...
Hi Daniel,
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote:
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze:
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote:
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote:
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote:
On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: > On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>> On 14.09.2020 22:01, Michael Tretter wrote: >>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>> used >>>>>>> from other drivers. >>>>>>> >>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>> display pipe until the downstream bridges are available, too. >>>>>>> >>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>> >>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>> operations >>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>> 8<--- cut here --- >>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>> 00000084 >>>>>> pgd = (ptrval) >>>>>> [00000084] *pgd=00000000 >>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>> Modules linked in: >>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>> ... >>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>> (component_bind_all+0xfc/0x290) >>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>> (really_probe+0x200/0x4fc) >>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>> (driver_probe_device+0x78/0x1fc) >>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>> (device_driver_attach+0x58/0x60) >>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>> (__driver_attach+0xdc/0x174) >>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>> (bus_add_driver+0x158/0x214) >>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>> (driver_register+0x78/0x110) >>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>> (exynos_drm_init+0xe4/0x118) >>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>> (do_one_initcall+0x8c/0x42c) >>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>> (kernel_init+0x8/0x118) >>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>> ... >>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>> >>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>> >>>>>> I will try to debug it a bit more today. >>>>> The above crash has been caused by lack of in_bridge initialization to >>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>> another issue: >>>>> >>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>> 8<--- cut here --- >>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>> 00000280 >>>>> pgd = (ptrval) >>>>> [00000280] *pgd=00000000 >>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>> Modules linked in: >>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>> PC is at __mutex_lock+0x54/0xb18 >>>>> LR is at lock_is_held_type+0x80/0x138 >>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>> ... >>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>> (mutex_lock_nested+0x1c/0x24) >>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>> (really_probe+0x200/0x4fc) >>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>> (driver_probe_device+0x78/0x1fc) >>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>> (device_driver_attach+0x58/0x60) >>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>> (__driver_attach+0xdc/0x174) >>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>> (bus_for_each_dev+0x68/0xb4) >>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>> (bus_add_driver+0x158/0x214) >>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>> (driver_register+0x78/0x110) >>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>> (do_one_initcall+0x8c/0x42c) >>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>> (kernel_init_freeable+0x190/0x1dc) >>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>> (kernel_init+0x8/0x118) >>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>> ... >>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>> >>>>> This means that dsi->encoder.dev is not initialized in >>>>> __exynos_dsi_host_attach(). >>>>> >>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>> release of all drm resources. >>>>> >>>>> Then however, the panel tries to register itself and >>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>> failure. >>>>> >>>>> It looks that something is missing. Maybe mipi host has to be >>>>> registered >>>>> later, when bridge is ready? I have no idea how it is handled before >>>>> this patch. Andrzej, could you comment it a bit? >>>> I intentionally changed the order, because if another bridge follows >>>> in the >>>> pipeline, the probe of the drm driver has to be deferred until some >>>> bridge >>>> provides a connector. The next bridge registers itself via the >>>> host_attach >>>> function and the deferral is ensured via the bind for the bind/unbind >>>> API or >>>> the bridge_attach function otherwise. >>>> >>>> On the other hand, the bridge does not have an encoder until the mipi >>>> device >>>> has been attached. >>>> >>>> As a solution, the exynos dsi driver must initialize the encoder in >>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>> via >>>> exynos_dsi instead of the bridge. >>>> >>>> Can you try to move everything except samsung_dsim_bind from >>>> exynos_dsi_bind >>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>> crash. >>> >>> The original behaviour is that encoder (exynos_dsi) is registered >>> regardless of sink presence (initially panel, later also bridge) - it >>> avoids multiple issues with deferred probe, device driver bind/unbind >>> and module load/unload. Appearance or disappearance of sink is >>> reported to host nicely via DSI attach/detach callbacks - and it is >>> reflected in drm world as change state of the connector. >>> >>> Registering DSI host in bind and unregistering in unbind assures that >>> if mipi_dsi device is attached/detached the drm device is always >>> present - it makes device/driver binding race free and allows to avoid >>> additional locking. >>> >>> Moving DSI host registration to probe changes everything, for sure it >>> breaks the nice feature of DSI attach/detach callbacks and apparently >>> can cause different issues depending on device bind order. >>> >>> I will try to look at the patches tomorrow and maybe I can find more >>> constructive comments :) >> >> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >> callbacks to control appearance/disappearance of downstream device. It >> allows to: >> >> 1. Safely bind/unbind different device drivers at any time and at any >> order, without killing exynos_drm and/or crashing system. >> >> 2. Avoid issues with late drm init - on some platforms exynos_drm device >> appeared too late, due to deferred probe, and resulted in black screen >> in userspace. >> >> >> Now if we want to convert exynos_dsi to drm_bridge I see following options: >> >> A. Forgot about callbacks and make the exynos_drm to defer probing until >> exynos_dsi bridge is available, probably it will cause later exynos_drm >> appearance, thus probably black screen on some targets. So for sure it >> will be suboptimal. Making it bridge unbind safe would be another >> problem, but most developers do not care about it so why should we? :) >> >> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >> even if downstream devices are not yet attached, on attach/detach notify >> drm about it via connector status change, for this dsi_host registration >> should be performed from drm_bridge attach, I guess. >> >> >> Option A is more standard, but is unsafe and causes other issues. >> >> Option B keeps current behaviour. > Maybe we can have both, but I am not sure, if I am missing something: > > I still prefer option A for the samsung-dsim driver, because it is more > standard, simpler and avoids issues with encoders, connectors or handling > hotplug. > > The idea is to use two bridges in the exynos-dsi driver: One bridge in the > samsung-dsim driver which implements option A and defers probing of the drm > driver until the next bridge is attached. And a second bridge in the > exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm > device to appear) and implements the hotplug handling for notifying drm via > connector status change. > > The driver for the i.MX8M would use the samsung-dsim bridge without an > additional bridge. > > This allows the samsung-dsim driver to expose the standard behavior while the > exynos_dsi may stick to the existing behavior for the exynos_drm driver. > > I hope this makes sense and does not sound too crazy. It might be difficult to > get the probing and mipi host/device registration correct, but I will try, if > this can work. Adding two bridges for being able to support hotplugging adds many special cases to the bridge driver and still requires more custom API to correctly add the second bridge. I don't think that this a viable path to go.
Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion.
Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register.
I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
There's also a bit the issue that most userspace handles panels in a special way, and if they don't find the panel at first, that doesn't work. Stuff like "which is the main screen" on laptops.
So yeah please fix this properly.
This leaves us with:
Option A) Standard drm_bridge behavior, which is currently implemented, but incompatible with the currently expected behavior of exynos_drm.
Option B) Creating the drm device without all bridges being attached, which would work with the exynos_drm driver, but breaks for the standard drm_bridge behavior, especially, if the encoder/connector is created at the beginning of the pipeline and passed downwards when the bridges are attached.
Option C) Extracting only low level register accesses into shared code, adding a custom interface and implementing the drm_bridge handling in the platform specific code.
None of the options really convinces me.
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Daniel,
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote:
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze:
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote:
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote:
On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: > On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: >> On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >>> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>>> On 14.09.2020 22:01, Michael Tretter wrote: >>>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>>> used >>>>>>>> from other drivers. >>>>>>>> >>>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>>> display pipe until the downstream bridges are available, too. >>>>>>>> >>>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>>> >>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>>> operations >>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>> 8<--- cut here --- >>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>> 00000084 >>>>>>> pgd = (ptrval) >>>>>>> [00000084] *pgd=00000000 >>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>> Modules linked in: >>>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>>> ... >>>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>>> (component_bind_all+0xfc/0x290) >>>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>>> (really_probe+0x200/0x4fc) >>>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>>> (device_driver_attach+0x58/0x60) >>>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>>> (__driver_attach+0xdc/0x174) >>>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>>> (bus_add_driver+0x158/0x214) >>>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>>> (driver_register+0x78/0x110) >>>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>>> (exynos_drm_init+0xe4/0x118) >>>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>>> (kernel_init+0x8/0x118) >>>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>> ... >>>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>>> >>>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>>> >>>>>>> I will try to debug it a bit more today. >>>>>> The above crash has been caused by lack of in_bridge initialization to >>>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>>> another issue: >>>>>> >>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>> 8<--- cut here --- >>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>> 00000280 >>>>>> pgd = (ptrval) >>>>>> [00000280] *pgd=00000000 >>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>> Modules linked in: >>>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>> PC is at __mutex_lock+0x54/0xb18 >>>>>> LR is at lock_is_held_type+0x80/0x138 >>>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>>> ... >>>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>>> (mutex_lock_nested+0x1c/0x24) >>>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>>> (really_probe+0x200/0x4fc) >>>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>>> (driver_probe_device+0x78/0x1fc) >>>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>>> (device_driver_attach+0x58/0x60) >>>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>>> (__driver_attach+0xdc/0x174) >>>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>>> (bus_add_driver+0x158/0x214) >>>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>>> (driver_register+0x78/0x110) >>>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>>> (do_one_initcall+0x8c/0x42c) >>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>>> (kernel_init+0x8/0x118) >>>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>> ... >>>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>>> >>>>>> This means that dsi->encoder.dev is not initialized in >>>>>> __exynos_dsi_host_attach(). >>>>>> >>>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>>> release of all drm resources. >>>>>> >>>>>> Then however, the panel tries to register itself and >>>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>>> failure. >>>>>> >>>>>> It looks that something is missing. Maybe mipi host has to be >>>>>> registered >>>>>> later, when bridge is ready? I have no idea how it is handled before >>>>>> this patch. Andrzej, could you comment it a bit? >>>>> I intentionally changed the order, because if another bridge follows >>>>> in the >>>>> pipeline, the probe of the drm driver has to be deferred until some >>>>> bridge >>>>> provides a connector. The next bridge registers itself via the >>>>> host_attach >>>>> function and the deferral is ensured via the bind for the bind/unbind >>>>> API or >>>>> the bridge_attach function otherwise. >>>>> >>>>> On the other hand, the bridge does not have an encoder until the mipi >>>>> device >>>>> has been attached. >>>>> >>>>> As a solution, the exynos dsi driver must initialize the encoder in >>>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>>> via >>>>> exynos_dsi instead of the bridge. >>>>> >>>>> Can you try to move everything except samsung_dsim_bind from >>>>> exynos_dsi_bind >>>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>>> crash. >>>> >>>> The original behaviour is that encoder (exynos_dsi) is registered >>>> regardless of sink presence (initially panel, later also bridge) - it >>>> avoids multiple issues with deferred probe, device driver bind/unbind >>>> and module load/unload. Appearance or disappearance of sink is >>>> reported to host nicely via DSI attach/detach callbacks - and it is >>>> reflected in drm world as change state of the connector. >>>> >>>> Registering DSI host in bind and unregistering in unbind assures that >>>> if mipi_dsi device is attached/detached the drm device is always >>>> present - it makes device/driver binding race free and allows to avoid >>>> additional locking. >>>> >>>> Moving DSI host registration to probe changes everything, for sure it >>>> breaks the nice feature of DSI attach/detach callbacks and apparently >>>> can cause different issues depending on device bind order. >>>> >>>> I will try to look at the patches tomorrow and maybe I can find more >>>> constructive comments :) >>> >>> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >>> callbacks to control appearance/disappearance of downstream device. It >>> allows to: >>> >>> 1. Safely bind/unbind different device drivers at any time and at any >>> order, without killing exynos_drm and/or crashing system. >>> >>> 2. Avoid issues with late drm init - on some platforms exynos_drm device >>> appeared too late, due to deferred probe, and resulted in black screen >>> in userspace. >>> >>> >>> Now if we want to convert exynos_dsi to drm_bridge I see following options: >>> >>> A. Forgot about callbacks and make the exynos_drm to defer probing until >>> exynos_dsi bridge is available, probably it will cause later exynos_drm >>> appearance, thus probably black screen on some targets. So for sure it >>> will be suboptimal. Making it bridge unbind safe would be another >>> problem, but most developers do not care about it so why should we? :) >>> >>> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >>> even if downstream devices are not yet attached, on attach/detach notify >>> drm about it via connector status change, for this dsi_host registration >>> should be performed from drm_bridge attach, I guess. >>> >>> >>> Option A is more standard, but is unsafe and causes other issues. >>> >>> Option B keeps current behaviour. >> Maybe we can have both, but I am not sure, if I am missing something: >> >> I still prefer option A for the samsung-dsim driver, because it is more >> standard, simpler and avoids issues with encoders, connectors or handling >> hotplug. >> >> The idea is to use two bridges in the exynos-dsi driver: One bridge in the >> samsung-dsim driver which implements option A and defers probing of the drm >> driver until the next bridge is attached. And a second bridge in the >> exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm >> device to appear) and implements the hotplug handling for notifying drm via >> connector status change. >> >> The driver for the i.MX8M would use the samsung-dsim bridge without an >> additional bridge. >> >> This allows the samsung-dsim driver to expose the standard behavior while the >> exynos_dsi may stick to the existing behavior for the exynos_drm driver. >> >> I hope this makes sense and does not sound too crazy. It might be difficult to >> get the probing and mipi host/device registration correct, but I will try, if >> this can work. > Adding two bridges for being able to support hotplugging adds many special > cases to the bridge driver and still requires more custom API to correctly add > the second bridge. I don't think that this a viable path to go. Just jumping in here: You cannot hotplug/hotremove anything from a drm_device after drm_dev_register has been called, except drm_connector. I didn't dig into details here so not sure whether you want to late-bind your bridge after drm_dev_register is called or not, so might just be fyi and not relevant to the discussion.
Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register.
I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
There's also a bit the issue that most userspace handles panels in a special way, and if they don't find the panel at first, that doesn't work. Stuff like "which is the main screen" on laptops.
So yeah please fix this properly.
> This leaves us with: > > Option A) Standard drm_bridge behavior, which is currently implemented, but > incompatible with the currently expected behavior of exynos_drm. > > Option B) Creating the drm device without all bridges being attached, which > would work with the exynos_drm driver, but breaks for the standard drm_bridge > behavior, especially, if the encoder/connector is created at the beginning of > the pipeline and passed downwards when the bridges are attached. > > Option C) Extracting only low level register accesses into shared code, adding > a custom interface and implementing the drm_bridge handling in the platform > specific code. > > None of the options really convinces me.
-- Regards,
Laurent Pinchart
On 04.02.21 18:46, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
Hi Daniel,
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote:
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze:
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote:
On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote: > On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: >> On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: >>> On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >>>> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>>>> On 14.09.2020 22:01, Michael Tretter wrote: >>>>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>>>> used >>>>>>>>> from other drivers. >>>>>>>>> >>>>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>>>> display pipe until the downstream bridges are available, too. >>>>>>>>> >>>>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>>>> >>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>>>> operations >>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>> 8<--- cut here --- >>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>> 00000084 >>>>>>>> pgd = (ptrval) >>>>>>>> [00000084] *pgd=00000000 >>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>> Modules linked in: >>>>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>>>> ... >>>>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>>>> (component_bind_all+0xfc/0x290) >>>>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>>>> (driver_register+0x78/0x110) >>>>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>>>> (exynos_drm_init+0xe4/0x118) >>>>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>>>> (kernel_init+0x8/0x118) >>>>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>> ... >>>>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>>>> >>>>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>>>> >>>>>>>> I will try to debug it a bit more today. >>>>>>> The above crash has been caused by lack of in_bridge initialization to >>>>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>>>> another issue: >>>>>>> >>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>> 8<--- cut here --- >>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>> 00000280 >>>>>>> pgd = (ptrval) >>>>>>> [00000280] *pgd=00000000 >>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>> Modules linked in: >>>>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>> PC is at __mutex_lock+0x54/0xb18 >>>>>>> LR is at lock_is_held_type+0x80/0x138 >>>>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>>>> ... >>>>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>>>> (mutex_lock_nested+0x1c/0x24) >>>>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>>>> (really_probe+0x200/0x4fc) >>>>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>>>> (device_driver_attach+0x58/0x60) >>>>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>>>> (__driver_attach+0xdc/0x174) >>>>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>>>> (bus_add_driver+0x158/0x214) >>>>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>>>> (driver_register+0x78/0x110) >>>>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>>>> (kernel_init+0x8/0x118) >>>>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>> ... >>>>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>>>> >>>>>>> This means that dsi->encoder.dev is not initialized in >>>>>>> __exynos_dsi_host_attach(). >>>>>>> >>>>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>>>> release of all drm resources. >>>>>>> >>>>>>> Then however, the panel tries to register itself and >>>>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>>>> failure. >>>>>>> >>>>>>> It looks that something is missing. Maybe mipi host has to be >>>>>>> registered >>>>>>> later, when bridge is ready? I have no idea how it is handled before >>>>>>> this patch. Andrzej, could you comment it a bit? >>>>>> I intentionally changed the order, because if another bridge follows >>>>>> in the >>>>>> pipeline, the probe of the drm driver has to be deferred until some >>>>>> bridge >>>>>> provides a connector. The next bridge registers itself via the >>>>>> host_attach >>>>>> function and the deferral is ensured via the bind for the bind/unbind >>>>>> API or >>>>>> the bridge_attach function otherwise. >>>>>> >>>>>> On the other hand, the bridge does not have an encoder until the mipi >>>>>> device >>>>>> has been attached. >>>>>> >>>>>> As a solution, the exynos dsi driver must initialize the encoder in >>>>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>>>> via >>>>>> exynos_dsi instead of the bridge. >>>>>> >>>>>> Can you try to move everything except samsung_dsim_bind from >>>>>> exynos_dsi_bind >>>>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>>>> crash. >>>>> >>>>> The original behaviour is that encoder (exynos_dsi) is registered >>>>> regardless of sink presence (initially panel, later also bridge) - it >>>>> avoids multiple issues with deferred probe, device driver bind/unbind >>>>> and module load/unload. Appearance or disappearance of sink is >>>>> reported to host nicely via DSI attach/detach callbacks - and it is >>>>> reflected in drm world as change state of the connector. >>>>> >>>>> Registering DSI host in bind and unregistering in unbind assures that >>>>> if mipi_dsi device is attached/detached the drm device is always >>>>> present - it makes device/driver binding race free and allows to avoid >>>>> additional locking. >>>>> >>>>> Moving DSI host registration to probe changes everything, for sure it >>>>> breaks the nice feature of DSI attach/detach callbacks and apparently >>>>> can cause different issues depending on device bind order. >>>>> >>>>> I will try to look at the patches tomorrow and maybe I can find more >>>>> constructive comments :) >>>> >>>> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >>>> callbacks to control appearance/disappearance of downstream device. It >>>> allows to: >>>> >>>> 1. Safely bind/unbind different device drivers at any time and at any >>>> order, without killing exynos_drm and/or crashing system. >>>> >>>> 2. Avoid issues with late drm init - on some platforms exynos_drm device >>>> appeared too late, due to deferred probe, and resulted in black screen >>>> in userspace. >>>> >>>> >>>> Now if we want to convert exynos_dsi to drm_bridge I see following options: >>>> >>>> A. Forgot about callbacks and make the exynos_drm to defer probing until >>>> exynos_dsi bridge is available, probably it will cause later exynos_drm >>>> appearance, thus probably black screen on some targets. So for sure it >>>> will be suboptimal. Making it bridge unbind safe would be another >>>> problem, but most developers do not care about it so why should we? :) >>>> >>>> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >>>> even if downstream devices are not yet attached, on attach/detach notify >>>> drm about it via connector status change, for this dsi_host registration >>>> should be performed from drm_bridge attach, I guess. >>>> >>>> >>>> Option A is more standard, but is unsafe and causes other issues. >>>> >>>> Option B keeps current behaviour. >>> Maybe we can have both, but I am not sure, if I am missing something: >>> >>> I still prefer option A for the samsung-dsim driver, because it is more >>> standard, simpler and avoids issues with encoders, connectors or handling >>> hotplug. >>> >>> The idea is to use two bridges in the exynos-dsi driver: One bridge in the >>> samsung-dsim driver which implements option A and defers probing of the drm >>> driver until the next bridge is attached. And a second bridge in the >>> exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm >>> device to appear) and implements the hotplug handling for notifying drm via >>> connector status change. >>> >>> The driver for the i.MX8M would use the samsung-dsim bridge without an >>> additional bridge. >>> >>> This allows the samsung-dsim driver to expose the standard behavior while the >>> exynos_dsi may stick to the existing behavior for the exynos_drm driver. >>> >>> I hope this makes sense and does not sound too crazy. It might be difficult to >>> get the probing and mipi host/device registration correct, but I will try, if >>> this can work. >> Adding two bridges for being able to support hotplugging adds many special >> cases to the bridge driver and still requires more custom API to correctly add >> the second bridge. I don't think that this a viable path to go. > Just jumping in here: You cannot hotplug/hotremove anything from a > drm_device after drm_dev_register has been called, except > drm_connector. I didn't dig into details here so not sure whether you > want to late-bind your bridge after drm_dev_register is called or not, > so might just be fyi and not relevant to the discussion. Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm driver (i.e. Option B)
exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI device might attach to the DSI host and call exynos_dsi_host_attach. In exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and attaches this bridge to the encoder _after_ drm_dev_register has been called. This is invalid behavior, right?
Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register.
I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
I think it is quite clear that replacing or reworking the deferral mechanism is out of scope for this discussion, which is why I would like to come back to the original issue and sum this up as far as I understand it (which is not really far when it comes to the details):
We have the existing exynos driver that avoids the standard deferral mechanism in favor of something that works but Daniel describes as "definitely not supported".
We have a proposal from Michael for converting the driver to the standard drm_bridge behavior and more work from Michael and Marek based on this to implement the platform specific parts for i.MX8MM.
From the i.MX8MM POV this approach already received some testing and looks good as far as I can judge. Upstreaming this solution is blocked because of objections from the Samsung maintainers.
Sorry if I'm being blunt or naive, but where to go from here?
On Wed, 10 Feb 2021 10:10:37 +0100, Frieder Schrempf wrote:
On 04.02.21 18:46, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote:
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze:
On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote: > On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote: > > On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: > > > On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: > > > > On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: > > > > > W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: > > > > > > On 14.09.2020 22:01, Michael Tretter wrote: > > > > > > > On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: > > > > > > > > On 14.09.2020 10:29, Marek Szyprowski wrote: > > > > > > > > > On 11.09.2020 15:54, Michael Tretter wrote: > > > > > > > > > > Make the exynos_dsi driver a full drm bridge that can be found and > > > > > > > > > > used > > > > > > > > > > from other drivers. > > > > > > > > > > > > > > > > > > > > Other drivers can only attach to the bridge, if a mipi dsi device > > > > > > > > > > already attached to the bridge. This allows to defer the probe of the > > > > > > > > > > display pipe until the downstream bridges are available, too. > > > > > > > > > > > > > > > > > > > > Signed-off-by: Michael Tretter m.tretter@pengutronix.de > > > > > > > > > This one (and the whole series applied) still fails on Exynos boards: > > > > > > > > > > > > > > > > > > [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping > > > > > > > > > operations > > > > > > > > > exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) > > > > > > > > > OF: graph: no port node found in /soc/dsi@11c80000 > > > > > > > > > 8<--- cut here --- > > > > > > > > > Unable to handle kernel NULL pointer dereference at virtual address > > > > > > > > > 00000084 > > > > > > > > > pgd = (ptrval) > > > > > > > > > [00000084] *pgd=00000000 > > > > > > > > > Internal error: Oops: 5 [#1] PREEMPT SMP ARM > > > > > > > > > Modules linked in: > > > > > > > > > CPU: 1 PID: 1 Comm: swapper/0 Not tainted > > > > > > > > > 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 > > > > > > > > > Hardware name: Samsung Exynos (Flattened Device Tree) > > > > > > > > > PC is at drm_bridge_attach+0x18/0x164 > > > > > > > > > LR is at exynos_dsi_bind+0x88/0xa8 > > > > > > > > > pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 > > > > > > > > > sp : ef0dfca8 ip : 00000002 fp : c13190e0 > > > > > > > > > r10: 00000000 r9 : ee46d580 r8 : c13190e0 > > > > > > > > > r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 > > > > > > > > > r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 > > > > > > > > > Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none > > > > > > > > > Control: 10c5387d Table: 4000404a DAC: 00000051 > > > > > > > > > Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) > > > > > > > > > Stack: (0xef0dfca8 to 0xef0e0000) > > > > > > > > > ... > > > > > > > > > [<c0628c08>] (drm_bridge_attach) from [<c064d560>] > > > > > > > > > (exynos_dsi_bind+0x88/0xa8) > > > > > > > > > [<c064d560>] (exynos_dsi_bind) from [<c066a800>] > > > > > > > > > (component_bind_all+0xfc/0x290) > > > > > > > > > [<c066a800>] (component_bind_all) from [<c0649dc0>] > > > > > > > > > (exynos_drm_bind+0xe4/0x19c) > > > > > > > > > [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] > > > > > > > > > (try_to_bring_up_master+0x1e4/0x2c4) > > > > > > > > > [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] > > > > > > > > > (component_master_add_with_match+0xd4/0x108) > > > > > > > > > [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] > > > > > > > > > (exynos_drm_platform_probe+0xe4/0x110) > > > > > > > > > [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] > > > > > > > > > (platform_drv_probe+0x6c/0xa4) > > > > > > > > > [<c0674e6c>] (platform_drv_probe) from [<c067242c>] > > > > > > > > > (really_probe+0x200/0x4fc) > > > > > > > > > [<c067242c>] (really_probe) from [<c06728f0>] > > > > > > > > > (driver_probe_device+0x78/0x1fc) > > > > > > > > > [<c06728f0>] (driver_probe_device) from [<c0672cd8>] > > > > > > > > > (device_driver_attach+0x58/0x60) > > > > > > > > > [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] > > > > > > > > > (__driver_attach+0xdc/0x174) > > > > > > > > > [<c0672dbc>] (__driver_attach) from [<c06701b4>] > > > > > > > > > (bus_for_each_dev+0x68/0xb4) > > > > > > > > > [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] > > > > > > > > > (bus_add_driver+0x158/0x214) > > > > > > > > > [<c06714e8>] (bus_add_driver) from [<c0673c1c>] > > > > > > > > > (driver_register+0x78/0x110) > > > > > > > > > [<c0673c1c>] (driver_register) from [<c0649ca8>] > > > > > > > > > (exynos_drm_init+0xe4/0x118) > > > > > > > > > [<c0649ca8>] (exynos_drm_init) from [<c0102484>] > > > > > > > > > (do_one_initcall+0x8c/0x42c) > > > > > > > > > [<c0102484>] (do_one_initcall) from [<c11011c0>] > > > > > > > > > (kernel_init_freeable+0x190/0x1dc) > > > > > > > > > [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] > > > > > > > > > (kernel_init+0x8/0x118) > > > > > > > > > [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) > > > > > > > > > Exception stack(0xef0dffb0 to 0xef0dfff8) > > > > > > > > > ... > > > > > > > > > ---[ end trace ee27f313f9ed9da1 ]--- > > > > > > > > > > > > > > > > > > # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 > > > > > > > > > drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) > > > > > > > > > > > > > > > > > > I will try to debug it a bit more today. > > > > > > > > The above crash has been caused by lack of in_bridge initialization to > > > > > > > > NULL in exynos_dsi_bind() in this patch. However, fixing it reveals > > > > > > > > another issue: > > > > > > > > > > > > > > > > [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations > > > > > > > > exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) > > > > > > > > OF: graph: no port node found in /soc/dsi@11c80000 > > > > > > > > 8<--- cut here --- > > > > > > > > Unable to handle kernel NULL pointer dereference at virtual address > > > > > > > > 00000280 > > > > > > > > pgd = (ptrval) > > > > > > > > [00000280] *pgd=00000000 > > > > > > > > Internal error: Oops: 5 [#1] PREEMPT SMP ARM > > > > > > > > Modules linked in: > > > > > > > > CPU: 0 PID: 1 Comm: swapper/0 Not tainted > > > > > > > > 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 > > > > > > > > Hardware name: Samsung Exynos (Flattened Device Tree) > > > > > > > > PC is at __mutex_lock+0x54/0xb18 > > > > > > > > LR is at lock_is_held_type+0x80/0x138 > > > > > > > > pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 > > > > > > > > sp : ef0dfd30 ip : 33937b74 fp : c13193c8 > > > > > > > > r10: c1208eec r9 : 00000000 r8 : ee45f808 > > > > > > > > r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c > > > > > > > > r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 > > > > > > > > Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none > > > > > > > > Control: 10c5387d Table: 4000404a DAC: 00000051 > > > > > > > > Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) > > > > > > > > Stack: (0xef0dfd30 to 0xef0e0000) > > > > > > > > ... > > > > > > > > [<c0afc920>] (__mutex_lock) from [<c0afd400>] > > > > > > > > (mutex_lock_nested+0x1c/0x24) > > > > > > > > [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] > > > > > > > > (__exynos_dsi_host_attach+0x20/0x6c) > > > > > > > > [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] > > > > > > > > (exynos_dsi_host_attach+0x70/0x194) > > > > > > > > [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] > > > > > > > > (s6e8aa0_probe+0x1b0/0x218) > > > > > > > > [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] > > > > > > > > (really_probe+0x200/0x4fc) > > > > > > > > [<c0672530>] (really_probe) from [<c06729f4>] > > > > > > > > (driver_probe_device+0x78/0x1fc) > > > > > > > > [<c06729f4>] (driver_probe_device) from [<c0672ddc>] > > > > > > > > (device_driver_attach+0x58/0x60) > > > > > > > > [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] > > > > > > > > (__driver_attach+0xdc/0x174) > > > > > > > > [<c0672ec0>] (__driver_attach) from [<c06702b8>] > > > > > > > > (bus_for_each_dev+0x68/0xb4) > > > > > > > > [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] > > > > > > > > (bus_add_driver+0x158/0x214) > > > > > > > > [<c06715ec>] (bus_add_driver) from [<c0673d20>] > > > > > > > > (driver_register+0x78/0x110) > > > > > > > > [<c0673d20>] (driver_register) from [<c0102484>] > > > > > > > > (do_one_initcall+0x8c/0x42c) > > > > > > > > [<c0102484>] (do_one_initcall) from [<c11011c0>] > > > > > > > > (kernel_init_freeable+0x190/0x1dc) > > > > > > > > [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] > > > > > > > > (kernel_init+0x8/0x118) > > > > > > > > [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) > > > > > > > > Exception stack(0xef0dffb0 to 0xef0dfff8) > > > > > > > > ... > > > > > > > > ---[ end trace c06e996ec2e8234d ]--- > > > > > > > > > > > > > > > > This means that dsi->encoder.dev is not initialized in > > > > > > > > __exynos_dsi_host_attach(). > > > > > > > > > > > > > > > > This happens, because drm_bridge_attach() in exynos_dsi_bind() returned > > > > > > > > earlier -517 (deferred probe), what causes cleanup of encoder and > > > > > > > > release of all drm resources. > > > > > > > > > > > > > > > > Then however, the panel tries to register itself and > > > > > > > > exynos_dsi_host_attach() tries to access the released encoder (which is > > > > > > > > zeroed in drm_encoder_release) and rest of resources, what causes > > > > > > > > failure. > > > > > > > > > > > > > > > > It looks that something is missing. Maybe mipi host has to be > > > > > > > > registered > > > > > > > > later, when bridge is ready? I have no idea how it is handled before > > > > > > > > this patch. Andrzej, could you comment it a bit? > > > > > > > I intentionally changed the order, because if another bridge follows > > > > > > > in the > > > > > > > pipeline, the probe of the drm driver has to be deferred until some > > > > > > > bridge > > > > > > > provides a connector. The next bridge registers itself via the > > > > > > > host_attach > > > > > > > function and the deferral is ensured via the bind for the bind/unbind > > > > > > > API or > > > > > > > the bridge_attach function otherwise. > > > > > > > > > > > > > > On the other hand, the bridge does not have an encoder until the mipi > > > > > > > device > > > > > > > has been attached. > > > > > > > > > > > > > > As a solution, the exynos dsi driver must initialize the encoder in > > > > > > > exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder > > > > > > > via > > > > > > > exynos_dsi instead of the bridge. > > > > > > > > > > > > > > Can you try to move everything except samsung_dsim_bind from > > > > > > > exynos_dsi_bind > > > > > > > to exynos_dsi_probe (respectively for unbind) and report if it fixes the > > > > > > > crash. > > > > > > > > > > > > The original behaviour is that encoder (exynos_dsi) is registered > > > > > > regardless of sink presence (initially panel, later also bridge) - it > > > > > > avoids multiple issues with deferred probe, device driver bind/unbind > > > > > > and module load/unload. Appearance or disappearance of sink is > > > > > > reported to host nicely via DSI attach/detach callbacks - and it is > > > > > > reflected in drm world as change state of the connector. > > > > > > > > > > > > Registering DSI host in bind and unregistering in unbind assures that > > > > > > if mipi_dsi device is attached/detached the drm device is always > > > > > > present - it makes device/driver binding race free and allows to avoid > > > > > > additional locking. > > > > > > > > > > > > Moving DSI host registration to probe changes everything, for sure it > > > > > > breaks the nice feature of DSI attach/detach callbacks and apparently > > > > > > can cause different issues depending on device bind order. > > > > > > > > > > > > I will try to look at the patches tomorrow and maybe I can find more > > > > > > constructive comments :) > > > > > > > > > > As I said yesterday, exynos_dsi driver uses dsi host attach/detach > > > > > callbacks to control appearance/disappearance of downstream device. It > > > > > allows to: > > > > > > > > > > 1. Safely bind/unbind different device drivers at any time and at any > > > > > order, without killing exynos_drm and/or crashing system. > > > > > > > > > > 2. Avoid issues with late drm init - on some platforms exynos_drm device > > > > > appeared too late, due to deferred probe, and resulted in black screen > > > > > in userspace. > > > > > > > > > > > > > > > Now if we want to convert exynos_dsi to drm_bridge I see following options: > > > > > > > > > > A. Forgot about callbacks and make the exynos_drm to defer probing until > > > > > exynos_dsi bridge is available, probably it will cause later exynos_drm > > > > > appearance, thus probably black screen on some targets. So for sure it > > > > > will be suboptimal. Making it bridge unbind safe would be another > > > > > problem, but most developers do not care about it so why should we? :) > > > > > > > > > > B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, > > > > > even if downstream devices are not yet attached, on attach/detach notify > > > > > drm about it via connector status change, for this dsi_host registration > > > > > should be performed from drm_bridge attach, I guess. > > > > > > > > > > > > > > > Option A is more standard, but is unsafe and causes other issues. > > > > > > > > > > Option B keeps current behaviour. > > > > Maybe we can have both, but I am not sure, if I am missing something: > > > > > > > > I still prefer option A for the samsung-dsim driver, because it is more > > > > standard, simpler and avoids issues with encoders, connectors or handling > > > > hotplug. > > > > > > > > The idea is to use two bridges in the exynos-dsi driver: One bridge in the > > > > samsung-dsim driver which implements option A and defers probing of the drm > > > > driver until the next bridge is attached. And a second bridge in the > > > > exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm > > > > device to appear) and implements the hotplug handling for notifying drm via > > > > connector status change. > > > > > > > > The driver for the i.MX8M would use the samsung-dsim bridge without an > > > > additional bridge. > > > > > > > > This allows the samsung-dsim driver to expose the standard behavior while the > > > > exynos_dsi may stick to the existing behavior for the exynos_drm driver. > > > > > > > > I hope this makes sense and does not sound too crazy. It might be difficult to > > > > get the probing and mipi host/device registration correct, but I will try, if > > > > this can work. > > > Adding two bridges for being able to support hotplugging adds many special > > > cases to the bridge driver and still requires more custom API to correctly add > > > the second bridge. I don't think that this a viable path to go. > > Just jumping in here: You cannot hotplug/hotremove anything from a > > drm_device after drm_dev_register has been called, except > > drm_connector. I didn't dig into details here so not sure whether you > > want to late-bind your bridge after drm_dev_register is called or not, > > so might just be fyi and not relevant to the discussion. > Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm > driver (i.e. Option B) > > exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, > exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI > device might attach to the DSI host and call exynos_dsi_host_attach. In > exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and > attaches this bridge to the encoder _after_ drm_dev_register has been called. > This is invalid behavior, right? Definitely not supported, I don't think we have the right locks in place to make sure this works.
Now if your _only_ adding a drm_bridge (and not an encoder or anything like that), and you are adding the drm_connector correctly (like a hotplugged DP MST sink), then that would at least work from a uapi pov. Because drm_bridge isn't exposed as an uapi object.
But yeah, as-is, don't :-)
The solution here is a bunch of EPROBE_DEFER handling until all your bridges are loaded, with or without the assistance of component.c framework. Only then call drm_dev_register.
I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
I think it is quite clear that replacing or reworking the deferral mechanism is out of scope for this discussion, which is why I would like to come back to the original issue and sum this up as far as I understand it (which is not really far when it comes to the details):
We have the existing exynos driver that avoids the standard deferral mechanism in favor of something that works but Daniel describes as "definitely not supported".
We have a proposal from Michael for converting the driver to the standard drm_bridge behavior and more work from Michael and Marek based on this to implement the platform specific parts for i.MX8MM.
From the i.MX8MM POV this approach already received some testing and looks good as far as I can judge. Upstreaming this solution is blocked because of objections from the Samsung maintainers.
Sorry if I'm being blunt or naive, but where to go from here?
Maybe some more information by the Samsung maintainers would help:
If I understand correctly, the main reason for the non-standard behavior is a userspace application that runs into a timeout if the drm-device does not appear in time. Correct? Is there something we can do about that?
The other reason is the convenience of binding and unbinding a bridge driver, while the drm device is kept available. Correct? Is this used in development, testing, or production?
Is there anything else that prevents the exynos drm from switching to the standard behavior?
Would a exynos drm specific wrapper, which uses a standard bridge driver but exposes the non-standard behavior, be acceptable? (Unfortunately, my first try on something like that felt really awkward and didn't really work.)
Michael
Hi Michael,
W dniu 18.02.2021 o 09:04, Michael Tretter pisze:
On Wed, 10 Feb 2021 10:10:37 +0100, Frieder Schrempf wrote:
On 04.02.21 18:46, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote:
W dniu 04.02.2021 o 17:05, Daniel Vetter pisze: > On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote: >> On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote: >>> On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: >>>> On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: >>>>> On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >>>>>> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>>>>>> On 14.09.2020 22:01, Michael Tretter wrote: >>>>>>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>>>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>>>>>> used >>>>>>>>>>> from other drivers. >>>>>>>>>>> >>>>>>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>>>>>> display pipe until the downstream bridges are available, too. >>>>>>>>>>> >>>>>>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>>>>>> >>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>>>>>> operations >>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>> 8<--- cut here --- >>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>> 00000084 >>>>>>>>>> pgd = (ptrval) >>>>>>>>>> [00000084] *pgd=00000000 >>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>> Modules linked in: >>>>>>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>>>>>> ... >>>>>>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>>>>>> (component_bind_all+0xfc/0x290) >>>>>>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>>>>>> (exynos_drm_init+0xe4/0x118) >>>>>>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>> ... >>>>>>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>>>>>> >>>>>>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>>>>>> >>>>>>>>>> I will try to debug it a bit more today. >>>>>>>>> The above crash has been caused by lack of in_bridge initialization to >>>>>>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>>>>>> another issue: >>>>>>>>> >>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>> 8<--- cut here --- >>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>> 00000280 >>>>>>>>> pgd = (ptrval) >>>>>>>>> [00000280] *pgd=00000000 >>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>> Modules linked in: >>>>>>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>> PC is at __mutex_lock+0x54/0xb18 >>>>>>>>> LR is at lock_is_held_type+0x80/0x138 >>>>>>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>>>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>>>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>>>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>>>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>>>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>>>>>> ... >>>>>>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>>>>>> (mutex_lock_nested+0x1c/0x24) >>>>>>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>>>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>>>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>>>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>>>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>>>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>>>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>> ... >>>>>>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>>>>>> >>>>>>>>> This means that dsi->encoder.dev is not initialized in >>>>>>>>> __exynos_dsi_host_attach(). >>>>>>>>> >>>>>>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>>>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>>>>>> release of all drm resources. >>>>>>>>> >>>>>>>>> Then however, the panel tries to register itself and >>>>>>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>>>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>>>>>> failure. >>>>>>>>> >>>>>>>>> It looks that something is missing. Maybe mipi host has to be >>>>>>>>> registered >>>>>>>>> later, when bridge is ready? I have no idea how it is handled before >>>>>>>>> this patch. Andrzej, could you comment it a bit? >>>>>>>> I intentionally changed the order, because if another bridge follows >>>>>>>> in the >>>>>>>> pipeline, the probe of the drm driver has to be deferred until some >>>>>>>> bridge >>>>>>>> provides a connector. The next bridge registers itself via the >>>>>>>> host_attach >>>>>>>> function and the deferral is ensured via the bind for the bind/unbind >>>>>>>> API or >>>>>>>> the bridge_attach function otherwise. >>>>>>>> >>>>>>>> On the other hand, the bridge does not have an encoder until the mipi >>>>>>>> device >>>>>>>> has been attached. >>>>>>>> >>>>>>>> As a solution, the exynos dsi driver must initialize the encoder in >>>>>>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>>>>>> via >>>>>>>> exynos_dsi instead of the bridge. >>>>>>>> >>>>>>>> Can you try to move everything except samsung_dsim_bind from >>>>>>>> exynos_dsi_bind >>>>>>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>>>>>> crash. >>>>>>> The original behaviour is that encoder (exynos_dsi) is registered >>>>>>> regardless of sink presence (initially panel, later also bridge) - it >>>>>>> avoids multiple issues with deferred probe, device driver bind/unbind >>>>>>> and module load/unload. Appearance or disappearance of sink is >>>>>>> reported to host nicely via DSI attach/detach callbacks - and it is >>>>>>> reflected in drm world as change state of the connector. >>>>>>> >>>>>>> Registering DSI host in bind and unregistering in unbind assures that >>>>>>> if mipi_dsi device is attached/detached the drm device is always >>>>>>> present - it makes device/driver binding race free and allows to avoid >>>>>>> additional locking. >>>>>>> >>>>>>> Moving DSI host registration to probe changes everything, for sure it >>>>>>> breaks the nice feature of DSI attach/detach callbacks and apparently >>>>>>> can cause different issues depending on device bind order. >>>>>>> >>>>>>> I will try to look at the patches tomorrow and maybe I can find more >>>>>>> constructive comments :) >>>>>> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >>>>>> callbacks to control appearance/disappearance of downstream device. It >>>>>> allows to: >>>>>> >>>>>> 1. Safely bind/unbind different device drivers at any time and at any >>>>>> order, without killing exynos_drm and/or crashing system. >>>>>> >>>>>> 2. Avoid issues with late drm init - on some platforms exynos_drm device >>>>>> appeared too late, due to deferred probe, and resulted in black screen >>>>>> in userspace. >>>>>> >>>>>> >>>>>> Now if we want to convert exynos_dsi to drm_bridge I see following options: >>>>>> >>>>>> A. Forgot about callbacks and make the exynos_drm to defer probing until >>>>>> exynos_dsi bridge is available, probably it will cause later exynos_drm >>>>>> appearance, thus probably black screen on some targets. So for sure it >>>>>> will be suboptimal. Making it bridge unbind safe would be another >>>>>> problem, but most developers do not care about it so why should we? :) >>>>>> >>>>>> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >>>>>> even if downstream devices are not yet attached, on attach/detach notify >>>>>> drm about it via connector status change, for this dsi_host registration >>>>>> should be performed from drm_bridge attach, I guess. >>>>>> >>>>>> >>>>>> Option A is more standard, but is unsafe and causes other issues. >>>>>> >>>>>> Option B keeps current behaviour. >>>>> Maybe we can have both, but I am not sure, if I am missing something: >>>>> >>>>> I still prefer option A for the samsung-dsim driver, because it is more >>>>> standard, simpler and avoids issues with encoders, connectors or handling >>>>> hotplug. >>>>> >>>>> The idea is to use two bridges in the exynos-dsi driver: One bridge in the >>>>> samsung-dsim driver which implements option A and defers probing of the drm >>>>> driver until the next bridge is attached. And a second bridge in the >>>>> exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm >>>>> device to appear) and implements the hotplug handling for notifying drm via >>>>> connector status change. >>>>> >>>>> The driver for the i.MX8M would use the samsung-dsim bridge without an >>>>> additional bridge. >>>>> >>>>> This allows the samsung-dsim driver to expose the standard behavior while the >>>>> exynos_dsi may stick to the existing behavior for the exynos_drm driver. >>>>> >>>>> I hope this makes sense and does not sound too crazy. It might be difficult to >>>>> get the probing and mipi host/device registration correct, but I will try, if >>>>> this can work. >>>> Adding two bridges for being able to support hotplugging adds many special >>>> cases to the bridge driver and still requires more custom API to correctly add >>>> the second bridge. I don't think that this a viable path to go. >>> Just jumping in here: You cannot hotplug/hotremove anything from a >>> drm_device after drm_dev_register has been called, except >>> drm_connector. I didn't dig into details here so not sure whether you >>> want to late-bind your bridge after drm_dev_register is called or not, >>> so might just be fyi and not relevant to the discussion. >> Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm >> driver (i.e. Option B) >> >> exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, >> exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI >> device might attach to the DSI host and call exynos_dsi_host_attach. In >> exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and >> attaches this bridge to the encoder _after_ drm_dev_register has been called. >> This is invalid behavior, right? > Definitely not supported, I don't think we have the right locks in place > to make sure this works. > > Now if your _only_ adding a drm_bridge (and not an encoder or anything > like that), and you are adding the drm_connector correctly (like a > hotplugged DP MST sink), then that would at least work from a uapi pov. > Because drm_bridge isn't exposed as an uapi object. > > But yeah, as-is, don't :-) > > The solution here is a bunch of EPROBE_DEFER handling until all your > bridges are loaded, with or without the assistance of component.c > framework. Only then call drm_dev_register. I have impression we have similar conversation already.
As you stated drm_bridge and drm_panel are not exposed to userspace so there shouldn't be problem with them from uapi PoV.
On the other side drm_panel or drm_bridge are not used until pipeline enters connected state (at least they were not some time ago :) ). The issue is that bridge exposes drm_connector, but as you stated (again :) ) connectors can be hotplugged, so in theory it should work. Practical tests shows that it also works, but bugs can be still there.
Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and decided there is no display), and does not handle unbinding/re-binding drivers.
Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
I think it is quite clear that replacing or reworking the deferral mechanism is out of scope for this discussion, which is why I would like to come back to the original issue and sum this up as far as I understand it (which is not really far when it comes to the details):
We have the existing exynos driver that avoids the standard deferral mechanism in favor of something that works but Daniel describes as "definitely not supported".
We have a proposal from Michael for converting the driver to the standard drm_bridge behavior and more work from Michael and Marek based on this to implement the platform specific parts for i.MX8MM.
From the i.MX8MM POV this approach already received some testing and looks good as far as I can judge. Upstreaming this solution is blocked because of objections from the Samsung maintainers.
Sorry if I'm being blunt or naive, but where to go from here?
Maybe some more information by the Samsung maintainers would help:
If I understand correctly, the main reason for the non-standard behavior is a userspace application that runs into a timeout if the drm-device does not appear in time. Correct? Is there something we can do about that?
The other reason is the convenience of binding and unbinding a bridge driver, while the drm device is kept available. Correct? Is this used in development, testing, or production?
Is there anything else that prevents the exynos drm from switching to the standard behavior?
Would a exynos drm specific wrapper, which uses a standard bridge driver but exposes the non-standard behavior, be acceptable? (Unfortunately, my first try on something like that felt really awkward and didn't really work.)
Even if we drop this 'non-standard' behaviour, your task will be still quite difficult to fulfil - you are trying to completely rewrite core component of Exynos display pipeline without hardware to test.
ExynosDSI is used in almost all Exynos platforms supported mainline (ls -1 arch/arm*/boot/dts/exynos*.dts | wc shows 35). It has different hw versions (4 compatibles) and is used in different configurations (video mode, command mode, with hw/sw trigger, connected to panels/bridges) and for sure with big heritage, since it was one of the 1st DSI drivers.
Rewriting such driver is challenging, even with access to hw.
So maybe it would be better to move common parts in your and exynos driver to 'shared library' and use it in both drivers - this way you have bigger chances to avoid traps.
Regards
Andrzej
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=08365e03-57ad66fe-0837d54c-000babff317...
On Thu, Feb 18, 2021 at 5:02 PM Andrzej Hajda a.hajda@samsung.com wrote:
Hi Michael,
W dniu 18.02.2021 o 09:04, Michael Tretter pisze:
On Wed, 10 Feb 2021 10:10:37 +0100, Frieder Schrempf wrote:
On 04.02.21 18:46, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote: > W dniu 04.02.2021 o 17:05, Daniel Vetter pisze: >> On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote: >>> On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote: >>>> On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: >>>>> On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: >>>>>> On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >>>>>>> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>>>>>>> On 14.09.2020 22:01, Michael Tretter wrote: >>>>>>>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>>>>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>>>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>>>>>>> used >>>>>>>>>>>> from other drivers. >>>>>>>>>>>> >>>>>>>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>>>>>>> display pipe until the downstream bridges are available, too. >>>>>>>>>>>> >>>>>>>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>>>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>>>>>>> >>>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>>>>>>> operations >>>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>>> 8<--- cut here --- >>>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>>> 00000084 >>>>>>>>>>> pgd = (ptrval) >>>>>>>>>>> [00000084] *pgd=00000000 >>>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>>> Modules linked in: >>>>>>>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>>>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>>>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>>>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>>>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>>>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>>>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>>>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>>>>>>> ... >>>>>>>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>>>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>>>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>>>>>>> (component_bind_all+0xfc/0x290) >>>>>>>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>>>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>>>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>>>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>>>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>>>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>>>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>>>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>>>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>>>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>>>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>>>>>>> (exynos_drm_init+0xe4/0x118) >>>>>>>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>>> ... >>>>>>>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>>>>>>> >>>>>>>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>>>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>>>>>>> >>>>>>>>>>> I will try to debug it a bit more today. >>>>>>>>>> The above crash has been caused by lack of in_bridge initialization to >>>>>>>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>>>>>>> another issue: >>>>>>>>>> >>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>> 8<--- cut here --- >>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>> 00000280 >>>>>>>>>> pgd = (ptrval) >>>>>>>>>> [00000280] *pgd=00000000 >>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>> Modules linked in: >>>>>>>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>> PC is at __mutex_lock+0x54/0xb18 >>>>>>>>>> LR is at lock_is_held_type+0x80/0x138 >>>>>>>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>>>>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>>>>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>>>>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>>>>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>>>>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>>>>>>> ... >>>>>>>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>>>>>>> (mutex_lock_nested+0x1c/0x24) >>>>>>>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>>>>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>>>>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>>>>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>>>>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>>>>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>>>>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>> ... >>>>>>>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>>>>>>> >>>>>>>>>> This means that dsi->encoder.dev is not initialized in >>>>>>>>>> __exynos_dsi_host_attach(). >>>>>>>>>> >>>>>>>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>>>>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>>>>>>> release of all drm resources. >>>>>>>>>> >>>>>>>>>> Then however, the panel tries to register itself and >>>>>>>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>>>>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>>>>>>> failure. >>>>>>>>>> >>>>>>>>>> It looks that something is missing. Maybe mipi host has to be >>>>>>>>>> registered >>>>>>>>>> later, when bridge is ready? I have no idea how it is handled before >>>>>>>>>> this patch. Andrzej, could you comment it a bit? >>>>>>>>> I intentionally changed the order, because if another bridge follows >>>>>>>>> in the >>>>>>>>> pipeline, the probe of the drm driver has to be deferred until some >>>>>>>>> bridge >>>>>>>>> provides a connector. The next bridge registers itself via the >>>>>>>>> host_attach >>>>>>>>> function and the deferral is ensured via the bind for the bind/unbind >>>>>>>>> API or >>>>>>>>> the bridge_attach function otherwise. >>>>>>>>> >>>>>>>>> On the other hand, the bridge does not have an encoder until the mipi >>>>>>>>> device >>>>>>>>> has been attached. >>>>>>>>> >>>>>>>>> As a solution, the exynos dsi driver must initialize the encoder in >>>>>>>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>>>>>>> via >>>>>>>>> exynos_dsi instead of the bridge. >>>>>>>>> >>>>>>>>> Can you try to move everything except samsung_dsim_bind from >>>>>>>>> exynos_dsi_bind >>>>>>>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>>>>>>> crash. >>>>>>>> The original behaviour is that encoder (exynos_dsi) is registered >>>>>>>> regardless of sink presence (initially panel, later also bridge) - it >>>>>>>> avoids multiple issues with deferred probe, device driver bind/unbind >>>>>>>> and module load/unload. Appearance or disappearance of sink is >>>>>>>> reported to host nicely via DSI attach/detach callbacks - and it is >>>>>>>> reflected in drm world as change state of the connector. >>>>>>>> >>>>>>>> Registering DSI host in bind and unregistering in unbind assures that >>>>>>>> if mipi_dsi device is attached/detached the drm device is always >>>>>>>> present - it makes device/driver binding race free and allows to avoid >>>>>>>> additional locking. >>>>>>>> >>>>>>>> Moving DSI host registration to probe changes everything, for sure it >>>>>>>> breaks the nice feature of DSI attach/detach callbacks and apparently >>>>>>>> can cause different issues depending on device bind order. >>>>>>>> >>>>>>>> I will try to look at the patches tomorrow and maybe I can find more >>>>>>>> constructive comments :) >>>>>>> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >>>>>>> callbacks to control appearance/disappearance of downstream device. It >>>>>>> allows to: >>>>>>> >>>>>>> 1. Safely bind/unbind different device drivers at any time and at any >>>>>>> order, without killing exynos_drm and/or crashing system. >>>>>>> >>>>>>> 2. Avoid issues with late drm init - on some platforms exynos_drm device >>>>>>> appeared too late, due to deferred probe, and resulted in black screen >>>>>>> in userspace. >>>>>>> >>>>>>> >>>>>>> Now if we want to convert exynos_dsi to drm_bridge I see following options: >>>>>>> >>>>>>> A. Forgot about callbacks and make the exynos_drm to defer probing until >>>>>>> exynos_dsi bridge is available, probably it will cause later exynos_drm >>>>>>> appearance, thus probably black screen on some targets. So for sure it >>>>>>> will be suboptimal. Making it bridge unbind safe would be another >>>>>>> problem, but most developers do not care about it so why should we? :) >>>>>>> >>>>>>> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >>>>>>> even if downstream devices are not yet attached, on attach/detach notify >>>>>>> drm about it via connector status change, for this dsi_host registration >>>>>>> should be performed from drm_bridge attach, I guess. >>>>>>> >>>>>>> >>>>>>> Option A is more standard, but is unsafe and causes other issues. >>>>>>> >>>>>>> Option B keeps current behaviour. >>>>>> Maybe we can have both, but I am not sure, if I am missing something: >>>>>> >>>>>> I still prefer option A for the samsung-dsim driver, because it is more >>>>>> standard, simpler and avoids issues with encoders, connectors or handling >>>>>> hotplug. >>>>>> >>>>>> The idea is to use two bridges in the exynos-dsi driver: One bridge in the >>>>>> samsung-dsim driver which implements option A and defers probing of the drm >>>>>> driver until the next bridge is attached. And a second bridge in the >>>>>> exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm >>>>>> device to appear) and implements the hotplug handling for notifying drm via >>>>>> connector status change. >>>>>> >>>>>> The driver for the i.MX8M would use the samsung-dsim bridge without an >>>>>> additional bridge. >>>>>> >>>>>> This allows the samsung-dsim driver to expose the standard behavior while the >>>>>> exynos_dsi may stick to the existing behavior for the exynos_drm driver. >>>>>> >>>>>> I hope this makes sense and does not sound too crazy. It might be difficult to >>>>>> get the probing and mipi host/device registration correct, but I will try, if >>>>>> this can work. >>>>> Adding two bridges for being able to support hotplugging adds many special >>>>> cases to the bridge driver and still requires more custom API to correctly add >>>>> the second bridge. I don't think that this a viable path to go. >>>> Just jumping in here: You cannot hotplug/hotremove anything from a >>>> drm_device after drm_dev_register has been called, except >>>> drm_connector. I didn't dig into details here so not sure whether you >>>> want to late-bind your bridge after drm_dev_register is called or not, >>>> so might just be fyi and not relevant to the discussion. >>> Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm >>> driver (i.e. Option B) >>> >>> exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, >>> exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI >>> device might attach to the DSI host and call exynos_dsi_host_attach. In >>> exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and >>> attaches this bridge to the encoder _after_ drm_dev_register has been called. >>> This is invalid behavior, right? >> Definitely not supported, I don't think we have the right locks in place >> to make sure this works. >> >> Now if your _only_ adding a drm_bridge (and not an encoder or anything >> like that), and you are adding the drm_connector correctly (like a >> hotplugged DP MST sink), then that would at least work from a uapi pov. >> Because drm_bridge isn't exposed as an uapi object. >> >> But yeah, as-is, don't :-) >> >> The solution here is a bunch of EPROBE_DEFER handling until all your >> bridges are loaded, with or without the assistance of component.c >> framework. Only then call drm_dev_register. > I have impression we have similar conversation already. > > As you stated drm_bridge and drm_panel are not exposed to userspace so > there shouldn't be problem with them from uapi PoV. > > On the other side drm_panel or drm_bridge are not used until pipeline > enters connected state (at least they were not some time ago :) ). The > issue is that bridge exposes drm_connector, but as you stated (again :) > ) connectors can be hotplugged, so in theory it should work. Practical > tests shows that it also works, but bugs can be still there. > > Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and > decided there is no display), and does not handle unbinding/re-binding > drivers. Rebinding drivers should be fixed now, with a bunch of fixes in driver core. If not, we need to fix this more.
Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never seem to go anywhere), not paper over it with bad architecture in drivers.
I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
I think it is quite clear that replacing or reworking the deferral mechanism is out of scope for this discussion, which is why I would like to come back to the original issue and sum this up as far as I understand it (which is not really far when it comes to the details):
We have the existing exynos driver that avoids the standard deferral mechanism in favor of something that works but Daniel describes as "definitely not supported".
We have a proposal from Michael for converting the driver to the standard drm_bridge behavior and more work from Michael and Marek based on this to implement the platform specific parts for i.MX8MM.
From the i.MX8MM POV this approach already received some testing and looks good as far as I can judge. Upstreaming this solution is blocked because of objections from the Samsung maintainers.
Sorry if I'm being blunt or naive, but where to go from here?
Maybe some more information by the Samsung maintainers would help:
If I understand correctly, the main reason for the non-standard behavior is a userspace application that runs into a timeout if the drm-device does not appear in time. Correct? Is there something we can do about that?
The other reason is the convenience of binding and unbinding a bridge driver, while the drm device is kept available. Correct? Is this used in development, testing, or production?
Is there anything else that prevents the exynos drm from switching to the standard behavior?
Would a exynos drm specific wrapper, which uses a standard bridge driver but exposes the non-standard behavior, be acceptable? (Unfortunately, my first try on something like that felt really awkward and didn't really work.)
Even if we drop this 'non-standard' behaviour, your task will be still quite difficult to fulfil - you are trying to completely rewrite core component of Exynos display pipeline without hardware to test.
ExynosDSI is used in almost all Exynos platforms supported mainline (ls -1 arch/arm*/boot/dts/exynos*.dts | wc shows 35). It has different hw versions (4 compatibles) and is used in different configurations (video mode, command mode, with hw/sw trigger, connected to panels/bridges) and for sure with big heritage, since it was one of the 1st DSI drivers.
Rewriting such driver is challenging, even with access to hw.
So maybe it would be better to move common parts in your and exynos driver to 'shared library' and use it in both drivers - this way you have bigger chances to avoid traps.
If exynos really can't be fixed up in a reasonable way, then I think sharing code doesn't make much sense - you drag the new driver down with the old one that's just hanging in there the wrong way round. For that case just copypaste the exynos code into a new clean drm_bridge driver, and done.
That would also mean that new exynos support in drm/exynos would need to be stalled until this is sorted out (at least for new platforms), since continuing the old way really doesn't sound so great. Wouldn't be the first time we just end up with a driver fork because the old one has too much heritage and is too hard to change.
Note that this can also be done within one driver codebase, e.g. nouveau has still legacy modeset code for nv04-nv4x, and atomic from nv50+ going forward.
Should be possible to find a pragmatic solution here going forward, despite tons of hw and heritage. If we use existing hard to retest hw support to stop new driver submissions from doing the right thing, that's a clear failure, we need a better approach here. -Daniel
Regards
Andrzej
Michael _______________________________________________ dri-devel mailing list dri-devel@lists.freedesktop.org https://protect2.fireeye.com/v1/url?k=08365e03-57ad66fe-0837d54c-000babff317...
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On 23.02.21 13:07, Daniel Vetter wrote:
On Thu, Feb 18, 2021 at 5:02 PM Andrzej Hajda a.hajda@samsung.com wrote:
Hi Michael,
W dniu 18.02.2021 o 09:04, Michael Tretter pisze:
On Wed, 10 Feb 2021 10:10:37 +0100, Frieder Schrempf wrote:
On 04.02.21 18:46, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote:
On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote: > On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote: >> W dniu 04.02.2021 o 17:05, Daniel Vetter pisze: >>> On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote: >>>> On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote: >>>>> On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: >>>>>> On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: >>>>>>> On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >>>>>>>> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>>>>>>>> On 14.09.2020 22:01, Michael Tretter wrote: >>>>>>>>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>>>>>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>>>>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>>>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>>>>>>>> used >>>>>>>>>>>>> from other drivers. >>>>>>>>>>>>> >>>>>>>>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>>>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>>>>>>>> display pipe until the downstream bridges are available, too. >>>>>>>>>>>>> >>>>>>>>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>>>>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>>>>>>>> >>>>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>>>>>>>> operations >>>>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>>>> 8<--- cut here --- >>>>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>>>> 00000084 >>>>>>>>>>>> pgd = (ptrval) >>>>>>>>>>>> [00000084] *pgd=00000000 >>>>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>>>> Modules linked in: >>>>>>>>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>>>>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>>>>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>>>>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>>>>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>>>>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>>>>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>>>>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>>>>>>>> ... >>>>>>>>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>>>>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>>>>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>>>>>>>> (component_bind_all+0xfc/0x290) >>>>>>>>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>>>>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>>>>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>>>>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>>>>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>>>>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>>>>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>>>>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>>>>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>>>>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>>>>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>>>>>>>> (exynos_drm_init+0xe4/0x118) >>>>>>>>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>>>> ... >>>>>>>>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>>>>>>>> >>>>>>>>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>>>>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>>>>>>>> >>>>>>>>>>>> I will try to debug it a bit more today. >>>>>>>>>>> The above crash has been caused by lack of in_bridge initialization to >>>>>>>>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>>>>>>>> another issue: >>>>>>>>>>> >>>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>>> 8<--- cut here --- >>>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>>> 00000280 >>>>>>>>>>> pgd = (ptrval) >>>>>>>>>>> [00000280] *pgd=00000000 >>>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>>> Modules linked in: >>>>>>>>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>>> PC is at __mutex_lock+0x54/0xb18 >>>>>>>>>>> LR is at lock_is_held_type+0x80/0x138 >>>>>>>>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>>>>>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>>>>>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>>>>>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>>>>>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>>>>>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>>>>>>>> ... >>>>>>>>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>>>>>>>> (mutex_lock_nested+0x1c/0x24) >>>>>>>>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>>>>>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>>>>>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>>>>>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>>>>>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>>>>>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>>>>>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>>> ... >>>>>>>>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>>>>>>>> >>>>>>>>>>> This means that dsi->encoder.dev is not initialized in >>>>>>>>>>> __exynos_dsi_host_attach(). >>>>>>>>>>> >>>>>>>>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>>>>>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>>>>>>>> release of all drm resources. >>>>>>>>>>> >>>>>>>>>>> Then however, the panel tries to register itself and >>>>>>>>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>>>>>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>>>>>>>> failure. >>>>>>>>>>> >>>>>>>>>>> It looks that something is missing. Maybe mipi host has to be >>>>>>>>>>> registered >>>>>>>>>>> later, when bridge is ready? I have no idea how it is handled before >>>>>>>>>>> this patch. Andrzej, could you comment it a bit? >>>>>>>>>> I intentionally changed the order, because if another bridge follows >>>>>>>>>> in the >>>>>>>>>> pipeline, the probe of the drm driver has to be deferred until some >>>>>>>>>> bridge >>>>>>>>>> provides a connector. The next bridge registers itself via the >>>>>>>>>> host_attach >>>>>>>>>> function and the deferral is ensured via the bind for the bind/unbind >>>>>>>>>> API or >>>>>>>>>> the bridge_attach function otherwise. >>>>>>>>>> >>>>>>>>>> On the other hand, the bridge does not have an encoder until the mipi >>>>>>>>>> device >>>>>>>>>> has been attached. >>>>>>>>>> >>>>>>>>>> As a solution, the exynos dsi driver must initialize the encoder in >>>>>>>>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>>>>>>>> via >>>>>>>>>> exynos_dsi instead of the bridge. >>>>>>>>>> >>>>>>>>>> Can you try to move everything except samsung_dsim_bind from >>>>>>>>>> exynos_dsi_bind >>>>>>>>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>>>>>>>> crash. >>>>>>>>> The original behaviour is that encoder (exynos_dsi) is registered >>>>>>>>> regardless of sink presence (initially panel, later also bridge) - it >>>>>>>>> avoids multiple issues with deferred probe, device driver bind/unbind >>>>>>>>> and module load/unload. Appearance or disappearance of sink is >>>>>>>>> reported to host nicely via DSI attach/detach callbacks - and it is >>>>>>>>> reflected in drm world as change state of the connector. >>>>>>>>> >>>>>>>>> Registering DSI host in bind and unregistering in unbind assures that >>>>>>>>> if mipi_dsi device is attached/detached the drm device is always >>>>>>>>> present - it makes device/driver binding race free and allows to avoid >>>>>>>>> additional locking. >>>>>>>>> >>>>>>>>> Moving DSI host registration to probe changes everything, for sure it >>>>>>>>> breaks the nice feature of DSI attach/detach callbacks and apparently >>>>>>>>> can cause different issues depending on device bind order. >>>>>>>>> >>>>>>>>> I will try to look at the patches tomorrow and maybe I can find more >>>>>>>>> constructive comments :) >>>>>>>> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >>>>>>>> callbacks to control appearance/disappearance of downstream device. It >>>>>>>> allows to: >>>>>>>> >>>>>>>> 1. Safely bind/unbind different device drivers at any time and at any >>>>>>>> order, without killing exynos_drm and/or crashing system. >>>>>>>> >>>>>>>> 2. Avoid issues with late drm init - on some platforms exynos_drm device >>>>>>>> appeared too late, due to deferred probe, and resulted in black screen >>>>>>>> in userspace. >>>>>>>> >>>>>>>> >>>>>>>> Now if we want to convert exynos_dsi to drm_bridge I see following options: >>>>>>>> >>>>>>>> A. Forgot about callbacks and make the exynos_drm to defer probing until >>>>>>>> exynos_dsi bridge is available, probably it will cause later exynos_drm >>>>>>>> appearance, thus probably black screen on some targets. So for sure it >>>>>>>> will be suboptimal. Making it bridge unbind safe would be another >>>>>>>> problem, but most developers do not care about it so why should we? :) >>>>>>>> >>>>>>>> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >>>>>>>> even if downstream devices are not yet attached, on attach/detach notify >>>>>>>> drm about it via connector status change, for this dsi_host registration >>>>>>>> should be performed from drm_bridge attach, I guess. >>>>>>>> >>>>>>>> >>>>>>>> Option A is more standard, but is unsafe and causes other issues. >>>>>>>> >>>>>>>> Option B keeps current behaviour. >>>>>>> Maybe we can have both, but I am not sure, if I am missing something: >>>>>>> >>>>>>> I still prefer option A for the samsung-dsim driver, because it is more >>>>>>> standard, simpler and avoids issues with encoders, connectors or handling >>>>>>> hotplug. >>>>>>> >>>>>>> The idea is to use two bridges in the exynos-dsi driver: One bridge in the >>>>>>> samsung-dsim driver which implements option A and defers probing of the drm >>>>>>> driver until the next bridge is attached. And a second bridge in the >>>>>>> exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm >>>>>>> device to appear) and implements the hotplug handling for notifying drm via >>>>>>> connector status change. >>>>>>> >>>>>>> The driver for the i.MX8M would use the samsung-dsim bridge without an >>>>>>> additional bridge. >>>>>>> >>>>>>> This allows the samsung-dsim driver to expose the standard behavior while the >>>>>>> exynos_dsi may stick to the existing behavior for the exynos_drm driver. >>>>>>> >>>>>>> I hope this makes sense and does not sound too crazy. It might be difficult to >>>>>>> get the probing and mipi host/device registration correct, but I will try, if >>>>>>> this can work. >>>>>> Adding two bridges for being able to support hotplugging adds many special >>>>>> cases to the bridge driver and still requires more custom API to correctly add >>>>>> the second bridge. I don't think that this a viable path to go. >>>>> Just jumping in here: You cannot hotplug/hotremove anything from a >>>>> drm_device after drm_dev_register has been called, except >>>>> drm_connector. I didn't dig into details here so not sure whether you >>>>> want to late-bind your bridge after drm_dev_register is called or not, >>>>> so might just be fyi and not relevant to the discussion. >>>> Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm >>>> driver (i.e. Option B) >>>> >>>> exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, >>>> exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI >>>> device might attach to the DSI host and call exynos_dsi_host_attach. In >>>> exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and >>>> attaches this bridge to the encoder _after_ drm_dev_register has been called. >>>> This is invalid behavior, right? >>> Definitely not supported, I don't think we have the right locks in place >>> to make sure this works. >>> >>> Now if your _only_ adding a drm_bridge (and not an encoder or anything >>> like that), and you are adding the drm_connector correctly (like a >>> hotplugged DP MST sink), then that would at least work from a uapi pov. >>> Because drm_bridge isn't exposed as an uapi object. >>> >>> But yeah, as-is, don't :-) >>> >>> The solution here is a bunch of EPROBE_DEFER handling until all your >>> bridges are loaded, with or without the assistance of component.c >>> framework. Only then call drm_dev_register. >> I have impression we have similar conversation already. >> >> As you stated drm_bridge and drm_panel are not exposed to userspace so >> there shouldn't be problem with them from uapi PoV. >> >> On the other side drm_panel or drm_bridge are not used until pipeline >> enters connected state (at least they were not some time ago :) ). The >> issue is that bridge exposes drm_connector, but as you stated (again :) >> ) connectors can be hotplugged, so in theory it should work. Practical >> tests shows that it also works, but bugs can be still there. >> >> Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and >> decided there is no display), and does not handle unbinding/re-binding >> drivers. > Rebinding drivers should be fixed now, with a bunch of fixes in driver > core. If not, we need to fix this more. > > Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, > we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never > seem to go anywhere), not paper over it with bad architecture in > drivers. I've heard this argument multiple times, but it sounds more like an attempt to ignore the problem and hope it will fall on someone else's plate :-) Improvement in the probe deferral mechanism are certainly an option to explore, but as far as I can tell nobody has proven that this mechanism is or will be able to solve all problems related to probe ordering dependencies. I wouldn't rule out the need for different solutions for some of the issues.
Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
I think it is quite clear that replacing or reworking the deferral mechanism is out of scope for this discussion, which is why I would like to come back to the original issue and sum this up as far as I understand it (which is not really far when it comes to the details):
We have the existing exynos driver that avoids the standard deferral mechanism in favor of something that works but Daniel describes as "definitely not supported".
We have a proposal from Michael for converting the driver to the standard drm_bridge behavior and more work from Michael and Marek based on this to implement the platform specific parts for i.MX8MM.
From the i.MX8MM POV this approach already received some testing and looks good as far as I can judge. Upstreaming this solution is blocked because of objections from the Samsung maintainers.
Sorry if I'm being blunt or naive, but where to go from here?
Maybe some more information by the Samsung maintainers would help:
If I understand correctly, the main reason for the non-standard behavior is a userspace application that runs into a timeout if the drm-device does not appear in time. Correct? Is there something we can do about that?
The other reason is the convenience of binding and unbinding a bridge driver, while the drm device is kept available. Correct? Is this used in development, testing, or production?
Is there anything else that prevents the exynos drm from switching to the standard behavior?
Would a exynos drm specific wrapper, which uses a standard bridge driver but exposes the non-standard behavior, be acceptable? (Unfortunately, my first try on something like that felt really awkward and didn't really work.)
Even if we drop this 'non-standard' behaviour, your task will be still quite difficult to fulfil - you are trying to completely rewrite core component of Exynos display pipeline without hardware to test.
ExynosDSI is used in almost all Exynos platforms supported mainline (ls -1 arch/arm*/boot/dts/exynos*.dts | wc shows 35). It has different hw versions (4 compatibles) and is used in different configurations (video mode, command mode, with hw/sw trigger, connected to panels/bridges) and for sure with big heritage, since it was one of the 1st DSI drivers.
Rewriting such driver is challenging, even with access to hw.
So maybe it would be better to move common parts in your and exynos driver to 'shared library' and use it in both drivers - this way you have bigger chances to avoid traps.
If exynos really can't be fixed up in a reasonable way, then I think sharing code doesn't make much sense - you drag the new driver down with the old one that's just hanging in there the wrong way round. For that case just copypaste the exynos code into a new clean drm_bridge driver, and done.
That would also mean that new exynos support in drm/exynos would need to be stalled until this is sorted out (at least for new platforms), since continuing the old way really doesn't sound so great. Wouldn't be the first time we just end up with a driver fork because the old one has too much heritage and is too hard to change.
Note that this can also be done within one driver codebase, e.g. nouveau has still legacy modeset code for nv04-nv4x, and atomic from nv50+ going forward.
Should be possible to find a pragmatic solution here going forward, despite tons of hw and heritage. If we use existing hard to retest hw support to stop new driver submissions from doing the right thing, that's a clear failure, we need a better approach here. -Daniel
Right, and I just wanted to add that there seems to be a similar (maybe less complex?) situation for the CSIS CSI controller. In that case we already have two separate drivers for pretty much the same hardware in the media subsystem, media/platform/exynos4-is/mipi-csis.c for the exynos and staging/media/imx/imx7-mipi-csis.c for the imx.
I don't know the history for this, but it just came to my mind that this case is related and it might be interesting for the scope of this discussion.
Hi Frieder,
On Tue, Apr 20, 2021 at 01:42:05PM +0200, Frieder Schrempf wrote:
On 23.02.21 13:07, Daniel Vetter wrote:
On Thu, Feb 18, 2021 at 5:02 PM Andrzej Hajda a.hajda@samsung.com wrote:
W dniu 18.02.2021 o 09:04, Michael Tretter pisze:
On Wed, 10 Feb 2021 10:10:37 +0100, Frieder Schrempf wrote:
On 04.02.21 18:46, Daniel Vetter wrote:
On Thu, Feb 4, 2021 at 6:26 PM Laurent Pinchart laurent.pinchart@ideasonboard.com wrote: > On Thu, Feb 04, 2021 at 06:19:22PM +0100, Daniel Vetter wrote: >> On Thu, Feb 4, 2021 at 5:28 PM Andrzej Hajda wrote: >>> W dniu 04.02.2021 o 17:05, Daniel Vetter pisze: >>>> On Thu, Feb 04, 2021 at 11:56:32AM +0100, Michael Tretter wrote: >>>>> On Thu, 04 Feb 2021 11:17:49 +0100, Daniel Vetter wrote: >>>>>> On Wed, Feb 3, 2021 at 9:32 PM Michael Tretter wrote: >>>>>>> On Mon, 01 Feb 2021 17:33:14 +0100, Michael Tretter wrote: >>>>>>>> On Tue, 15 Sep 2020 21:40:40 +0200, Andrzej Hajda wrote: >>>>>>>>> W dniu 14.09.2020 o 23:19, Andrzej Hajda pisze: >>>>>>>>>> On 14.09.2020 22:01, Michael Tretter wrote: >>>>>>>>>>> On Mon, 14 Sep 2020 14:31:19 +0200, Marek Szyprowski wrote: >>>>>>>>>>>> On 14.09.2020 10:29, Marek Szyprowski wrote: >>>>>>>>>>>>> On 11.09.2020 15:54, Michael Tretter wrote: >>>>>>>>>>>>>> Make the exynos_dsi driver a full drm bridge that can be found and >>>>>>>>>>>>>> used >>>>>>>>>>>>>> from other drivers. >>>>>>>>>>>>>> >>>>>>>>>>>>>> Other drivers can only attach to the bridge, if a mipi dsi device >>>>>>>>>>>>>> already attached to the bridge. This allows to defer the probe of the >>>>>>>>>>>>>> display pipe until the downstream bridges are available, too. >>>>>>>>>>>>>> >>>>>>>>>>>>>> Signed-off-by: Michael Tretter m.tretter@pengutronix.de >>>>>>>>>>>>> This one (and the whole series applied) still fails on Exynos boards: >>>>>>>>>>>>> >>>>>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping >>>>>>>>>>>>> operations >>>>>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>>>>> 8<--- cut here --- >>>>>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>>>>> 00000084 >>>>>>>>>>>>> pgd = (ptrval) >>>>>>>>>>>>> [00000084] *pgd=00000000 >>>>>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>>>>> Modules linked in: >>>>>>>>>>>>> CPU: 1 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec #1608 >>>>>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>>>>> PC is at drm_bridge_attach+0x18/0x164 >>>>>>>>>>>>> LR is at exynos_dsi_bind+0x88/0xa8 >>>>>>>>>>>>> pc : [<c0628c08>] lr : [<c064d560>] psr: 20000013 >>>>>>>>>>>>> sp : ef0dfca8 ip : 00000002 fp : c13190e0 >>>>>>>>>>>>> r10: 00000000 r9 : ee46d580 r8 : c13190e0 >>>>>>>>>>>>> r7 : ee438800 r6 : 00000018 r5 : ef253810 r4 : ef39e840 >>>>>>>>>>>>> r3 : 00000000 r2 : 00000018 r1 : ef39e888 r0 : ef39e840 >>>>>>>>>>>>> Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>>>>> Stack: (0xef0dfca8 to 0xef0e0000) >>>>>>>>>>>>> ... >>>>>>>>>>>>> [<c0628c08>] (drm_bridge_attach) from [<c064d560>] >>>>>>>>>>>>> (exynos_dsi_bind+0x88/0xa8) >>>>>>>>>>>>> [<c064d560>] (exynos_dsi_bind) from [<c066a800>] >>>>>>>>>>>>> (component_bind_all+0xfc/0x290) >>>>>>>>>>>>> [<c066a800>] (component_bind_all) from [<c0649dc0>] >>>>>>>>>>>>> (exynos_drm_bind+0xe4/0x19c) >>>>>>>>>>>>> [<c0649dc0>] (exynos_drm_bind) from [<c066ad74>] >>>>>>>>>>>>> (try_to_bring_up_master+0x1e4/0x2c4) >>>>>>>>>>>>> [<c066ad74>] (try_to_bring_up_master) from [<c066b2b4>] >>>>>>>>>>>>> (component_master_add_with_match+0xd4/0x108) >>>>>>>>>>>>> [<c066b2b4>] (component_master_add_with_match) from [<c0649ae8>] >>>>>>>>>>>>> (exynos_drm_platform_probe+0xe4/0x110) >>>>>>>>>>>>> [<c0649ae8>] (exynos_drm_platform_probe) from [<c0674e6c>] >>>>>>>>>>>>> (platform_drv_probe+0x6c/0xa4) >>>>>>>>>>>>> [<c0674e6c>] (platform_drv_probe) from [<c067242c>] >>>>>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>>>>> [<c067242c>] (really_probe) from [<c06728f0>] >>>>>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>>>>> [<c06728f0>] (driver_probe_device) from [<c0672cd8>] >>>>>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>>>>> [<c0672cd8>] (device_driver_attach) from [<c0672dbc>] >>>>>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>>>>> [<c0672dbc>] (__driver_attach) from [<c06701b4>] >>>>>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>>>>> [<c06701b4>] (bus_for_each_dev) from [<c06714e8>] >>>>>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>>>>> [<c06714e8>] (bus_add_driver) from [<c0673c1c>] >>>>>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>>>>> [<c0673c1c>] (driver_register) from [<c0649ca8>] >>>>>>>>>>>>> (exynos_drm_init+0xe4/0x118) >>>>>>>>>>>>> [<c0649ca8>] (exynos_drm_init) from [<c0102484>] >>>>>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7880>] >>>>>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>>>>> [<c0af7880>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>>>>> ... >>>>>>>>>>>>> ---[ end trace ee27f313f9ed9da1 ]--- >>>>>>>>>>>>> >>>>>>>>>>>>> # arm-linux-gnueabi-addr2line -e vmlinux c0628c08 >>>>>>>>>>>>> drivers/gpu/drm/drm_bridge.c:184 (discriminator 1) >>>>>>>>>>>>> >>>>>>>>>>>>> I will try to debug it a bit more today. >>>>>>>>>>>> The above crash has been caused by lack of in_bridge initialization to >>>>>>>>>>>> NULL in exynos_dsi_bind() in this patch. However, fixing it reveals >>>>>>>>>>>> another issue: >>>>>>>>>>>> >>>>>>>>>>>> [drm] Exynos DRM: using 11c00000.fimd device for DMA mapping operations >>>>>>>>>>>> exynos-drm exynos-drm: bound 11c00000.fimd (ops fimd_component_ops) >>>>>>>>>>>> OF: graph: no port node found in /soc/dsi@11c80000 >>>>>>>>>>>> 8<--- cut here --- >>>>>>>>>>>> Unable to handle kernel NULL pointer dereference at virtual address >>>>>>>>>>>> 00000280 >>>>>>>>>>>> pgd = (ptrval) >>>>>>>>>>>> [00000280] *pgd=00000000 >>>>>>>>>>>> Internal error: Oops: 5 [#1] PREEMPT SMP ARM >>>>>>>>>>>> Modules linked in: >>>>>>>>>>>> CPU: 0 PID: 1 Comm: swapper/0 Not tainted >>>>>>>>>>>> 5.9.0-rc4-next-20200911-00010-g417dc70d70ec-dirty #1613 >>>>>>>>>>>> Hardware name: Samsung Exynos (Flattened Device Tree) >>>>>>>>>>>> PC is at __mutex_lock+0x54/0xb18 >>>>>>>>>>>> LR is at lock_is_held_type+0x80/0x138 >>>>>>>>>>>> pc : [<c0afc920>] lr : [<c0af63e8>] psr: 60000013 >>>>>>>>>>>> sp : ef0dfd30 ip : 33937b74 fp : c13193c8 >>>>>>>>>>>> r10: c1208eec r9 : 00000000 r8 : ee45f808 >>>>>>>>>>>> r7 : c19561a4 r6 : 00000000 r5 : 00000000 r4 : 0000024c >>>>>>>>>>>> r3 : 00000000 r2 : 00204140 r1 : c124f13c r0 : 00000000 >>>>>>>>>>>> Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none >>>>>>>>>>>> Control: 10c5387d Table: 4000404a DAC: 00000051 >>>>>>>>>>>> Process swapper/0 (pid: 1, stack limit = 0x(ptrval)) >>>>>>>>>>>> Stack: (0xef0dfd30 to 0xef0e0000) >>>>>>>>>>>> ... >>>>>>>>>>>> [<c0afc920>] (__mutex_lock) from [<c0afd400>] >>>>>>>>>>>> (mutex_lock_nested+0x1c/0x24) >>>>>>>>>>>> [<c0afd400>] (mutex_lock_nested) from [<c064d4b8>] >>>>>>>>>>>> (__exynos_dsi_host_attach+0x20/0x6c) >>>>>>>>>>>> [<c064d4b8>] (__exynos_dsi_host_attach) from [<c064d914>] >>>>>>>>>>>> (exynos_dsi_host_attach+0x70/0x194) >>>>>>>>>>>> [<c064d914>] (exynos_dsi_host_attach) from [<c0656b64>] >>>>>>>>>>>> (s6e8aa0_probe+0x1b0/0x218) >>>>>>>>>>>> [<c0656b64>] (s6e8aa0_probe) from [<c0672530>] >>>>>>>>>>>> (really_probe+0x200/0x4fc) >>>>>>>>>>>> [<c0672530>] (really_probe) from [<c06729f4>] >>>>>>>>>>>> (driver_probe_device+0x78/0x1fc) >>>>>>>>>>>> [<c06729f4>] (driver_probe_device) from [<c0672ddc>] >>>>>>>>>>>> (device_driver_attach+0x58/0x60) >>>>>>>>>>>> [<c0672ddc>] (device_driver_attach) from [<c0672ec0>] >>>>>>>>>>>> (__driver_attach+0xdc/0x174) >>>>>>>>>>>> [<c0672ec0>] (__driver_attach) from [<c06702b8>] >>>>>>>>>>>> (bus_for_each_dev+0x68/0xb4) >>>>>>>>>>>> [<c06702b8>] (bus_for_each_dev) from [<c06715ec>] >>>>>>>>>>>> (bus_add_driver+0x158/0x214) >>>>>>>>>>>> [<c06715ec>] (bus_add_driver) from [<c0673d20>] >>>>>>>>>>>> (driver_register+0x78/0x110) >>>>>>>>>>>> [<c0673d20>] (driver_register) from [<c0102484>] >>>>>>>>>>>> (do_one_initcall+0x8c/0x42c) >>>>>>>>>>>> [<c0102484>] (do_one_initcall) from [<c11011c0>] >>>>>>>>>>>> (kernel_init_freeable+0x190/0x1dc) >>>>>>>>>>>> [<c11011c0>] (kernel_init_freeable) from [<c0af7988>] >>>>>>>>>>>> (kernel_init+0x8/0x118) >>>>>>>>>>>> [<c0af7988>] (kernel_init) from [<c0100114>] (ret_from_fork+0x14/0x20) >>>>>>>>>>>> Exception stack(0xef0dffb0 to 0xef0dfff8) >>>>>>>>>>>> ... >>>>>>>>>>>> ---[ end trace c06e996ec2e8234d ]--- >>>>>>>>>>>> >>>>>>>>>>>> This means that dsi->encoder.dev is not initialized in >>>>>>>>>>>> __exynos_dsi_host_attach(). >>>>>>>>>>>> >>>>>>>>>>>> This happens, because drm_bridge_attach() in exynos_dsi_bind() returned >>>>>>>>>>>> earlier -517 (deferred probe), what causes cleanup of encoder and >>>>>>>>>>>> release of all drm resources. >>>>>>>>>>>> >>>>>>>>>>>> Then however, the panel tries to register itself and >>>>>>>>>>>> exynos_dsi_host_attach() tries to access the released encoder (which is >>>>>>>>>>>> zeroed in drm_encoder_release) and rest of resources, what causes >>>>>>>>>>>> failure. >>>>>>>>>>>> >>>>>>>>>>>> It looks that something is missing. Maybe mipi host has to be >>>>>>>>>>>> registered >>>>>>>>>>>> later, when bridge is ready? I have no idea how it is handled before >>>>>>>>>>>> this patch. Andrzej, could you comment it a bit? >>>>>>>>>>> I intentionally changed the order, because if another bridge follows >>>>>>>>>>> in the >>>>>>>>>>> pipeline, the probe of the drm driver has to be deferred until some >>>>>>>>>>> bridge >>>>>>>>>>> provides a connector. The next bridge registers itself via the >>>>>>>>>>> host_attach >>>>>>>>>>> function and the deferral is ensured via the bind for the bind/unbind >>>>>>>>>>> API or >>>>>>>>>>> the bridge_attach function otherwise. >>>>>>>>>>> >>>>>>>>>>> On the other hand, the bridge does not have an encoder until the mipi >>>>>>>>>>> device >>>>>>>>>>> has been attached. >>>>>>>>>>> >>>>>>>>>>> As a solution, the exynos dsi driver must initialize the encoder in >>>>>>>>>>> exynos_dsi_probe instead of in exynos_dsi_bind and access the encoder >>>>>>>>>>> via >>>>>>>>>>> exynos_dsi instead of the bridge. >>>>>>>>>>> >>>>>>>>>>> Can you try to move everything except samsung_dsim_bind from >>>>>>>>>>> exynos_dsi_bind >>>>>>>>>>> to exynos_dsi_probe (respectively for unbind) and report if it fixes the >>>>>>>>>>> crash. >>>>>>>>>> The original behaviour is that encoder (exynos_dsi) is registered >>>>>>>>>> regardless of sink presence (initially panel, later also bridge) - it >>>>>>>>>> avoids multiple issues with deferred probe, device driver bind/unbind >>>>>>>>>> and module load/unload. Appearance or disappearance of sink is >>>>>>>>>> reported to host nicely via DSI attach/detach callbacks - and it is >>>>>>>>>> reflected in drm world as change state of the connector. >>>>>>>>>> >>>>>>>>>> Registering DSI host in bind and unregistering in unbind assures that >>>>>>>>>> if mipi_dsi device is attached/detached the drm device is always >>>>>>>>>> present - it makes device/driver binding race free and allows to avoid >>>>>>>>>> additional locking. >>>>>>>>>> >>>>>>>>>> Moving DSI host registration to probe changes everything, for sure it >>>>>>>>>> breaks the nice feature of DSI attach/detach callbacks and apparently >>>>>>>>>> can cause different issues depending on device bind order. >>>>>>>>>> >>>>>>>>>> I will try to look at the patches tomorrow and maybe I can find more >>>>>>>>>> constructive comments :) >>>>>>>>> As I said yesterday, exynos_dsi driver uses dsi host attach/detach >>>>>>>>> callbacks to control appearance/disappearance of downstream device. It >>>>>>>>> allows to: >>>>>>>>> >>>>>>>>> 1. Safely bind/unbind different device drivers at any time and at any >>>>>>>>> order, without killing exynos_drm and/or crashing system. >>>>>>>>> >>>>>>>>> 2. Avoid issues with late drm init - on some platforms exynos_drm device >>>>>>>>> appeared too late, due to deferred probe, and resulted in black screen >>>>>>>>> in userspace. >>>>>>>>> >>>>>>>>> >>>>>>>>> Now if we want to convert exynos_dsi to drm_bridge I see following options: >>>>>>>>> >>>>>>>>> A. Forgot about callbacks and make the exynos_drm to defer probing until >>>>>>>>> exynos_dsi bridge is available, probably it will cause later exynos_drm >>>>>>>>> appearance, thus probably black screen on some targets. So for sure it >>>>>>>>> will be suboptimal. Making it bridge unbind safe would be another >>>>>>>>> problem, but most developers do not care about it so why should we? :) >>>>>>>>> >>>>>>>>> B. Try to mimic current behaviour - exynos_dsi register bridge ASAP, >>>>>>>>> even if downstream devices are not yet attached, on attach/detach notify >>>>>>>>> drm about it via connector status change, for this dsi_host registration >>>>>>>>> should be performed from drm_bridge attach, I guess. >>>>>>>>> >>>>>>>>> >>>>>>>>> Option A is more standard, but is unsafe and causes other issues. >>>>>>>>> >>>>>>>>> Option B keeps current behaviour. >>>>>>>> Maybe we can have both, but I am not sure, if I am missing something: >>>>>>>> >>>>>>>> I still prefer option A for the samsung-dsim driver, because it is more >>>>>>>> standard, simpler and avoids issues with encoders, connectors or handling >>>>>>>> hotplug. >>>>>>>> >>>>>>>> The idea is to use two bridges in the exynos-dsi driver: One bridge in the >>>>>>>> samsung-dsim driver which implements option A and defers probing of the drm >>>>>>>> driver until the next bridge is attached. And a second bridge in the >>>>>>>> exynos_dsi that attaches to the first bridge (thus, allowing the exynos_drm >>>>>>>> device to appear) and implements the hotplug handling for notifying drm via >>>>>>>> connector status change. >>>>>>>> >>>>>>>> The driver for the i.MX8M would use the samsung-dsim bridge without an >>>>>>>> additional bridge. >>>>>>>> >>>>>>>> This allows the samsung-dsim driver to expose the standard behavior while the >>>>>>>> exynos_dsi may stick to the existing behavior for the exynos_drm driver. >>>>>>>> >>>>>>>> I hope this makes sense and does not sound too crazy. It might be difficult to >>>>>>>> get the probing and mipi host/device registration correct, but I will try, if >>>>>>>> this can work. >>>>>>> Adding two bridges for being able to support hotplugging adds many special >>>>>>> cases to the bridge driver and still requires more custom API to correctly add >>>>>>> the second bridge. I don't think that this a viable path to go. >>>>>> Just jumping in here: You cannot hotplug/hotremove anything from a >>>>>> drm_device after drm_dev_register has been called, except >>>>>> drm_connector. I didn't dig into details here so not sure whether you >>>>>> want to late-bind your bridge after drm_dev_register is called or not, >>>>>> so might just be fyi and not relevant to the discussion. >>>>> Thanks. AFAIC that is exactly what is currently implemented in the exynos_drm >>>>> driver (i.e. Option B) >>>>> >>>>> exynos_dsi_bind configures the encoder and registers a DSI host. Afterwards, >>>>> exynos_drm_bind (as component_master_ops) calls drm_dev_register. Later, a DSI >>>>> device might attach to the DSI host and call exynos_dsi_host_attach. In >>>>> exynos_dsi_host_attach, the driver finds the drm_bridge for the DSI device and >>>>> attaches this bridge to the encoder _after_ drm_dev_register has been called. >>>>> This is invalid behavior, right? >>>> Definitely not supported, I don't think we have the right locks in place >>>> to make sure this works. >>>> >>>> Now if your _only_ adding a drm_bridge (and not an encoder or anything >>>> like that), and you are adding the drm_connector correctly (like a >>>> hotplugged DP MST sink), then that would at least work from a uapi pov. >>>> Because drm_bridge isn't exposed as an uapi object. >>>> >>>> But yeah, as-is, don't :-) >>>> >>>> The solution here is a bunch of EPROBE_DEFER handling until all your >>>> bridges are loaded, with or without the assistance of component.c >>>> framework. Only then call drm_dev_register. >>> I have impression we have similar conversation already. >>> >>> As you stated drm_bridge and drm_panel are not exposed to userspace so >>> there shouldn't be problem with them from uapi PoV. >>> >>> On the other side drm_panel or drm_bridge are not used until pipeline >>> enters connected state (at least they were not some time ago :) ). The >>> issue is that bridge exposes drm_connector, but as you stated (again :) >>> ) connectors can be hotplugged, so in theory it should work. Practical >>> tests shows that it also works, but bugs can be still there. >>> >>> Bunch of EPROBE_DEFER was very slow (as a result userspace timeouted and >>> decided there is no display), and does not handle unbinding/re-binding >>> drivers. >> Rebinding drivers should be fixed now, with a bunch of fixes in driver >> core. If not, we need to fix this more. >> >> Also, EPROBE_DEFER is how this is supposed to work. If it's too slow, >> we need to fix EPROBE_DEFER (there's ideas for pre-sorting that never >> seem to go anywhere), not paper over it with bad architecture in >> drivers. > I've heard this argument multiple times, but it sounds more like an > attempt to ignore the problem and hope it will fall on someone else's > plate :-) Improvement in the probe deferral mechanism are certainly an > option to explore, but as far as I can tell nobody has proven that this > mechanism is or will be able to solve all problems related to probe > ordering dependencies. I wouldn't rule out the need for different > solutions for some of the issues. Then build another one. But adding hotplug for stuff that is there, and shouldn't be hotplugged, just because it's easier on driver writers and harder on userspace isn't really a good approach. -Daniel
I think it is quite clear that replacing or reworking the deferral mechanism is out of scope for this discussion, which is why I would like to come back to the original issue and sum this up as far as I understand it (which is not really far when it comes to the details):
We have the existing exynos driver that avoids the standard deferral mechanism in favor of something that works but Daniel describes as "definitely not supported".
We have a proposal from Michael for converting the driver to the standard drm_bridge behavior and more work from Michael and Marek based on this to implement the platform specific parts for i.MX8MM.
From the i.MX8MM POV this approach already received some testing and looks good as far as I can judge. Upstreaming this solution is blocked because of objections from the Samsung maintainers.
Sorry if I'm being blunt or naive, but where to go from here?
Maybe some more information by the Samsung maintainers would help:
If I understand correctly, the main reason for the non-standard behavior is a userspace application that runs into a timeout if the drm-device does not appear in time. Correct? Is there something we can do about that?
The other reason is the convenience of binding and unbinding a bridge driver, while the drm device is kept available. Correct? Is this used in development, testing, or production?
Is there anything else that prevents the exynos drm from switching to the standard behavior?
Would a exynos drm specific wrapper, which uses a standard bridge driver but exposes the non-standard behavior, be acceptable? (Unfortunately, my first try on something like that felt really awkward and didn't really work.)
Even if we drop this 'non-standard' behaviour, your task will be still quite difficult to fulfil - you are trying to completely rewrite core component of Exynos display pipeline without hardware to test.
ExynosDSI is used in almost all Exynos platforms supported mainline (ls -1 arch/arm*/boot/dts/exynos*.dts | wc shows 35). It has different hw versions (4 compatibles) and is used in different configurations (video mode, command mode, with hw/sw trigger, connected to panels/bridges) and for sure with big heritage, since it was one of the 1st DSI drivers.
Rewriting such driver is challenging, even with access to hw.
So maybe it would be better to move common parts in your and exynos driver to 'shared library' and use it in both drivers - this way you have bigger chances to avoid traps.
If exynos really can't be fixed up in a reasonable way, then I think sharing code doesn't make much sense - you drag the new driver down with the old one that's just hanging in there the wrong way round. For that case just copypaste the exynos code into a new clean drm_bridge driver, and done.
That would also mean that new exynos support in drm/exynos would need to be stalled until this is sorted out (at least for new platforms), since continuing the old way really doesn't sound so great. Wouldn't be the first time we just end up with a driver fork because the old one has too much heritage and is too hard to change.
Note that this can also be done within one driver codebase, e.g. nouveau has still legacy modeset code for nv04-nv4x, and atomic from nv50+ going forward.
Should be possible to find a pragmatic solution here going forward, despite tons of hw and heritage. If we use existing hard to retest hw support to stop new driver submissions from doing the right thing, that's a clear failure, we need a better approach here. -Daniel
Right, and I just wanted to add that there seems to be a similar (maybe less complex?) situation for the CSIS CSI controller. In that case we already have two separate drivers for pretty much the same hardware in the media subsystem, media/platform/exynos4-is/mipi-csis.c for the exynos and staging/media/imx/imx7-mipi-csis.c for the imx.
And we would have at least a third on in staging/media/imx/imx8-mipi-csi2-sam.c if we followed the NXP BSP :-) I've added support for i.MX8 to the imx7-mipi-csis driver recently, and I'm half-tempted to merge it with the media/platform/exynos4-is/mipi-csis.c driver at some point. Lack of Exynos test hardware and documentation, as well as of time, will likely prevent that from happening, but if someone wanted to give it a go, it would be nice.
I don't know the history for this, but it just came to my mind that this case is related and it might be interesting for the scope of this discussion.
I think staging/media/imx/imx7-mipi-csis.c was developed in the NXP BSP, and we merged it upstream without realizing it was the same IP core as media/platform/exynos4-is/mipi-csis.c.
If other drivers use the exynos_dsi driver as a bridge, they might bring their own encoder. Enable and disable the MIPI-DSI bridge using the bridge functions instead of the encoder functions.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 5e7c1a99a3ee..a4f17d50d1d8 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -1455,9 +1455,8 @@ static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) } }
-static void exynos_dsi_enable(struct drm_encoder *encoder) +static void exynos_dsi_enable(struct exynos_dsi *dsi) { - struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_bridge *iter; int ret;
@@ -1505,9 +1504,8 @@ static void exynos_dsi_enable(struct drm_encoder *encoder) pm_runtime_put(dsi->dev); }
-static void exynos_dsi_disable(struct drm_encoder *encoder) +static void exynos_dsi_disable(struct exynos_dsi *dsi) { - struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_bridge *iter;
if (!(dsi->state & DSIM_STATE_ENABLED)) @@ -1598,11 +1596,6 @@ static int exynos_dsi_create_connector(struct drm_encoder *encoder) return 0; }
-static const struct drm_encoder_helper_funcs exynos_dsi_encoder_helper_funcs = { - .enable = exynos_dsi_enable, - .disable = exynos_dsi_disable, -}; - static int exynos_dsi_bridge_attach(struct drm_bridge *bridge, enum drm_bridge_attach_flags flags) { @@ -1640,7 +1633,7 @@ static void exynos_dsi_bridge_detach(struct drm_bridge *bridge)
if (dsi->panel) { mutex_lock(&drm->mode_config.mutex); - exynos_dsi_disable(&dsi->encoder); + exynos_dsi_disable(dsi); dsi->panel = NULL; dsi->connector.status = connector_status_disconnected; mutex_unlock(&drm->mode_config.mutex); @@ -1652,9 +1645,25 @@ static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) } }
+static void exynos_dsi_bridge_enable(struct drm_bridge *bridge) +{ + struct exynos_dsi *dsi = bridge->driver_private; + + exynos_dsi_enable(dsi); +} + +static void exynos_dsi_bridge_disable(struct drm_bridge *bridge) +{ + struct exynos_dsi *dsi = bridge->driver_private; + + exynos_dsi_disable(dsi); +} + static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { .attach = exynos_dsi_bridge_attach, .detach = exynos_dsi_bridge_detach, + .enable = exynos_dsi_bridge_enable, + .disable = exynos_dsi_bridge_disable, };
MODULE_DEVICE_TABLE(of, exynos_dsi_of_match); @@ -1800,8 +1809,6 @@ static int exynos_dsi_bind(struct device *dev, struct device *master,
drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- drm_encoder_helper_add(encoder, &exynos_dsi_encoder_helper_funcs); - ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); if (ret < 0) return ret; @@ -1831,7 +1838,7 @@ static void exynos_dsi_unbind(struct device *dev, struct device *master, struct exynos_dsi *dsi = dev_get_drvdata(dev); struct drm_encoder *encoder = &dsi->encoder;
- exynos_dsi_disable(encoder); + exynos_dsi_disable(dsi);
drm_encoder_cleanup(encoder); }
The driver uses the encoder to get the mode that shall be configured. This is not possible, if the driver is used as a bridge, because the encoder might not be used.
Use the mode_set function to set the display mode.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index a4f17d50d1d8..988447812333 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -288,6 +288,8 @@ struct exynos_dsi { u32 mode_flags; u32 format;
+ struct drm_display_mode mode; + int state; struct drm_property *brightness; struct completion completed; @@ -961,7 +963,7 @@ static int exynos_dsi_init_link(struct exynos_dsi *dsi)
static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) { - struct drm_display_mode *m = &dsi->encoder.crtc->state->adjusted_mode; + struct drm_display_mode *m = &dsi->mode; unsigned int num_bits_resol = dsi->driver_data->num_bits_resol; u32 reg;
@@ -1659,11 +1661,22 @@ static void exynos_dsi_bridge_disable(struct drm_bridge *bridge) exynos_dsi_disable(dsi); }
+static void exynos_dsi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct exynos_dsi *dsi = bridge->driver_private; + + /* The mode is set when actually enabling the device. */ + drm_mode_copy(&dsi->mode, adjusted_mode); +} + static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { .attach = exynos_dsi_bridge_attach, .detach = exynos_dsi_bridge_detach, .enable = exynos_dsi_bridge_enable, .disable = exynos_dsi_bridge_disable, + .mode_set = exynos_dsi_bridge_mode_set, };
MODULE_DEVICE_TABLE(of, exynos_dsi_of_match);
The bridge will not necessarily use the encoder of struct exynos_dsi, but might use another encoder from another drm driver. Therefore, the driver has to use the encoder from the bridge instead of the one from exynos_dsi.
In the future, the struct exynos_dsi will not have an encoder at all.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - add removal of encoder_to_dsi --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index 988447812333..b9216785b2d7 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -303,11 +303,6 @@ struct exynos_dsi { #define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) #define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector)
-static inline struct exynos_dsi *encoder_to_dsi(struct drm_encoder *e) -{ - return container_of(e, struct exynos_dsi, encoder); -} - enum reg_idx { DSIM_STATUS_REG, /* Status register */ DSIM_SWRST_REG, /* Software reset register */ @@ -1570,11 +1565,10 @@ static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs .get_modes = exynos_dsi_get_modes, };
-static int exynos_dsi_create_connector(struct drm_encoder *encoder) +static int exynos_dsi_create_connector(struct exynos_dsi *dsi) { - struct exynos_dsi *dsi = encoder_to_dsi(encoder); struct drm_connector *connector = &dsi->connector; - struct drm_device *drm = encoder->dev; + struct drm_device *drm = dsi->bridge.dev; int ret;
connector->polled = DRM_CONNECTOR_POLL_HPD; @@ -1589,7 +1583,7 @@ static int exynos_dsi_create_connector(struct drm_encoder *encoder)
connector->status = connector_status_disconnected; drm_connector_helper_add(connector, &exynos_dsi_connector_helper_funcs); - drm_connector_attach_encoder(connector, encoder); + drm_connector_attach_encoder(connector, dsi->bridge.encoder); if (!drm->registered) return 0;
@@ -1615,7 +1609,7 @@ static int exynos_dsi_bridge_attach(struct drm_bridge *bridge, return ret; list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); } else { - ret = exynos_dsi_create_connector(encoder); + ret = exynos_dsi_create_connector(dsi); if (ret) return ret;
Add new functions for the probe/remove API, the bind/unbind API and resume/suspend calls. Move everything exynos drm specific into separate functions, which can easily be moved to other files.
Also split struct exynos_dsi into a generic and a platform specific struct.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - new patch --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 114 +++++++++++++++++------- 1 file changed, 82 insertions(+), 32 deletions(-)
diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index b9216785b2d7..ad70f5ce81ad 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -265,7 +265,6 @@ struct exynos_dsi_driver_data { };
struct exynos_dsi { - struct drm_encoder encoder; struct drm_bridge bridge; struct mipi_dsi_host dsi_host; struct drm_connector connector; @@ -419,6 +418,11 @@ enum reg_value_idx { PHYTIMING_HS_TRAIL };
+struct exynos_dsi_pltfm { + struct exynos_dsi *dsi; + struct drm_encoder encoder; +}; + static const unsigned int reg_values[] = { [RESET_TYPE] = DSIM_SWRST, [PLL_TIMER] = 500, @@ -476,7 +480,7 @@ static const unsigned int exynos5433_reg_values[] = { static int __exynos_dsi_host_attach(struct device *dev, struct mipi_dsi_device *device) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); struct drm_device *drm = dsi->encoder.dev; struct exynos_drm_crtc *crtc;
@@ -494,7 +498,7 @@ static int __exynos_dsi_host_attach(struct device *dev, static int __exynos_dsi_host_detach(struct device *dev, struct mipi_dsi_device *device) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); struct drm_device *drm = dsi->encoder.dev;
if (drm->mode_config.poll_enabled) @@ -505,7 +509,7 @@ static int __exynos_dsi_host_detach(struct device *dev,
static void __exynos_dsi_te_handler(struct device *dev) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
exynos_drm_crtc_te_handler(dsi->encoder.crtc); } @@ -1804,10 +1808,12 @@ static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) return 0; }
-static int exynos_dsi_bind(struct device *dev, struct device *master, - void *data) +static int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); +static void exynos_dsi_unbind(struct exynos_dsi *dsi); + +static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); struct drm_encoder *encoder = &dsi->encoder; struct drm_device *drm_dev = data; struct device_node *in_bridge_node; @@ -1828,7 +1834,7 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, of_node_put(in_bridge_node); }
- ret = drm_bridge_attach(encoder, &dsi->bridge, in_bridge, 0); + ret = exynos_dsi_bind(dsi->dsi, encoder); if (ret) goto err;
@@ -1839,20 +1845,20 @@ static int exynos_dsi_bind(struct device *dev, struct device *master, return ret; }
-static void exynos_dsi_unbind(struct device *dev, struct device *master, - void *data) +static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master, + void *data) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); struct drm_encoder *encoder = &dsi->encoder;
- exynos_dsi_disable(dsi); + exynos_dsi_unbind(dsi->dsi);
drm_encoder_cleanup(encoder); }
-static const struct component_ops exynos_dsi_component_ops = { - .bind = exynos_dsi_bind, - .unbind = exynos_dsi_unbind, +static const struct component_ops exynos_dsi_pltfm_component_ops = { + .bind = exynos_dsi_pltfm_bind, + .unbind = exynos_dsi_pltfm_unbind, };
static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) @@ -1963,20 +1969,52 @@ static void __exynos_dsi_remove(struct exynos_dsi *dsi) mipi_dsi_host_unregister(&dsi->dsi_host); }
-static int exynos_dsi_probe(struct platform_device *pdev) +/* + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +static struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) { - struct exynos_dsi *dsi; + return __exynos_dsi_probe(pdev); +} + +static void exynos_dsi_remove(struct exynos_dsi *dsi) +{ + return __exynos_dsi_remove(dsi); +} + +/* + * Bind/unbind API, used from platforms based on the component framework. + */ +static int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) +{ + struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder); + + return drm_bridge_attach(encoder, &dsi->bridge, previous, 0); +} + +static void exynos_dsi_unbind(struct exynos_dsi *dsi) +{ + exynos_dsi_disable(dsi); +} + +static int exynos_dsi_pltfm_probe(struct platform_device *pdev) +{ + struct exynos_dsi_pltfm *dsi; struct device *dev = &pdev->dev; int ret;
- dsi = __exynos_dsi_probe(pdev); - if (IS_ERR(dsi)) - return PTR_ERR(dsi); + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; platform_set_drvdata(pdev, dsi);
+ dsi->dsi = exynos_dsi_probe(pdev); + if (IS_ERR(dsi->dsi)) + return PTR_ERR(dsi->dsi); + pm_runtime_enable(dev);
- ret = component_add(dev, &exynos_dsi_component_ops); + ret = component_add(dev, &exynos_dsi_pltfm_component_ops); if (ret) goto err_disable_runtime;
@@ -1988,22 +2026,21 @@ static int exynos_dsi_probe(struct platform_device *pdev) return ret; }
-static int exynos_dsi_remove(struct platform_device *pdev) +static int exynos_dsi_pltfm_remove(struct platform_device *pdev) { - struct exynos_dsi *dsi = platform_get_drvdata(pdev); + struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev);
pm_runtime_disable(&pdev->dev);
- __exynos_dsi_remove(dsi); + exynos_dsi_remove(dsi->dsi);
- component_del(&pdev->dev, &exynos_dsi_component_ops); + component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops);
return 0; }
-static int __maybe_unused exynos_dsi_suspend(struct device *dev) +static int exynos_dsi_suspend(struct exynos_dsi *dsi) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; int ret, i;
@@ -2031,9 +2068,8 @@ static int __maybe_unused exynos_dsi_suspend(struct device *dev) return 0; }
-static int __maybe_unused exynos_dsi_resume(struct device *dev) +static int exynos_dsi_resume(struct exynos_dsi *dsi) { - struct exynos_dsi *dsi = dev_get_drvdata(dev); const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; int ret, i;
@@ -2065,15 +2101,29 @@ static int __maybe_unused exynos_dsi_resume(struct device *dev) return ret; }
+static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + + return exynos_dsi_suspend(dsi->dsi); +} + +static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + + return exynos_dsi_resume(dsi->dsi); +} + static const struct dev_pm_ops exynos_dsi_pm_ops = { - SET_RUNTIME_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume, NULL) + SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) };
struct platform_driver dsi_driver = { - .probe = exynos_dsi_probe, - .remove = exynos_dsi_remove, + .probe = exynos_dsi_pltfm_probe, + .remove = exynos_dsi_pltfm_remove, .driver = { .name = "exynos-dsi", .owner = THIS_MODULE,
Split the driver into the drm bridge driver and the exynos platform specific driver.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: none --- drivers/gpu/drm/exynos/Makefile | 2 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 372 +----------------- drivers/gpu/drm/exynos/exynos_drm_dsi.h | 64 +++ drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c | 333 ++++++++++++++++ 4 files changed, 405 insertions(+), 366 deletions(-) create mode 100644 drivers/gpu/drm/exynos/exynos_drm_dsi.h create mode 100644 drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 2fd2f3ee4fcf..add70b336935 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -11,7 +11,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o -exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynos_drm_dsi_pltfm.o exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index ad70f5ce81ad..e8aea9d90c34 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -32,8 +32,7 @@ #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_crtc.h" -#include "exynos_drm_drv.h" +#include "exynos_drm_dsi.h"
/* returns true iff both arguments logically differs */ #define NEQV(a, b) (!(a) ^ !(b)) @@ -44,10 +43,6 @@ #define DSIM_TX_READY_HS_CLK (1 << 10) #define DSIM_PLL_STABLE (1 << 31)
-/* DSIM_SWRST */ -#define DSIM_FUNCRST (1 << 16) -#define DSIM_SWRST (1 << 0) - /* DSIM_TIMEOUT */ #define DSIM_LPDR_TIMEOUT(x) ((x) << 0) #define DSIM_BTA_TIMEOUT(x) ((x) << 16) @@ -239,31 +234,6 @@ struct exynos_dsi_transfer { #define DSIM_STATE_CMD_LPM BIT(2) #define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
-struct exynos_dsi; -struct exynos_dsi_host_ops { - int (*attach)(struct device *dev, struct mipi_dsi_device *device); - int (*detach)(struct device *dev, struct mipi_dsi_device *device); - void (*te_handler)(struct device *dev); -}; - -enum exynos_reg_offset { - EXYNOS_REG_OFS, - EXYNOS5433_REG_OFS -}; - -struct exynos_dsi_driver_data { - enum exynos_reg_offset reg_ofs; - unsigned int plltmr_reg; - unsigned int has_freqband:1; - unsigned int has_clklane_stop:1; - unsigned int num_clks; - unsigned int max_freq; - unsigned int wait_for_reset; - unsigned int num_bits_resol; - const unsigned int *reg_values; - const struct exynos_dsi_host_ops *host_ops; -}; - struct exynos_dsi { struct drm_bridge bridge; struct mipi_dsi_host dsi_host; @@ -400,201 +370,6 @@ static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) return readl(dsi->reg_base + reg_ofs[idx]); }
-enum reg_value_idx { - RESET_TYPE, - PLL_TIMER, - STOP_STATE_CNT, - PHYCTRL_ULPS_EXIT, - PHYCTRL_VREG_LP, - PHYCTRL_SLEW_UP, - PHYTIMING_LPX, - PHYTIMING_HS_EXIT, - PHYTIMING_CLK_PREPARE, - PHYTIMING_CLK_ZERO, - PHYTIMING_CLK_POST, - PHYTIMING_CLK_TRAIL, - PHYTIMING_HS_PREPARE, - PHYTIMING_HS_ZERO, - PHYTIMING_HS_TRAIL -}; - -struct exynos_dsi_pltfm { - struct exynos_dsi *dsi; - struct drm_encoder encoder; -}; - -static const unsigned int reg_values[] = { - [RESET_TYPE] = DSIM_SWRST, - [PLL_TIMER] = 500, - [STOP_STATE_CNT] = 0xf, - [PHYCTRL_ULPS_EXIT] = 0x0af, - [PHYCTRL_VREG_LP] = 0, - [PHYCTRL_SLEW_UP] = 0, - [PHYTIMING_LPX] = 0x06, - [PHYTIMING_HS_EXIT] = 0x0b, - [PHYTIMING_CLK_PREPARE] = 0x07, - [PHYTIMING_CLK_ZERO] = 0x27, - [PHYTIMING_CLK_POST] = 0x0d, - [PHYTIMING_CLK_TRAIL] = 0x08, - [PHYTIMING_HS_PREPARE] = 0x09, - [PHYTIMING_HS_ZERO] = 0x0d, - [PHYTIMING_HS_TRAIL] = 0x0b, -}; - -static const unsigned int exynos5422_reg_values[] = { - [RESET_TYPE] = DSIM_SWRST, - [PLL_TIMER] = 500, - [STOP_STATE_CNT] = 0xf, - [PHYCTRL_ULPS_EXIT] = 0xaf, - [PHYCTRL_VREG_LP] = 0, - [PHYCTRL_SLEW_UP] = 0, - [PHYTIMING_LPX] = 0x08, - [PHYTIMING_HS_EXIT] = 0x0d, - [PHYTIMING_CLK_PREPARE] = 0x09, - [PHYTIMING_CLK_ZERO] = 0x30, - [PHYTIMING_CLK_POST] = 0x0e, - [PHYTIMING_CLK_TRAIL] = 0x0a, - [PHYTIMING_HS_PREPARE] = 0x0c, - [PHYTIMING_HS_ZERO] = 0x11, - [PHYTIMING_HS_TRAIL] = 0x0d, -}; - -static const unsigned int exynos5433_reg_values[] = { - [RESET_TYPE] = DSIM_FUNCRST, - [PLL_TIMER] = 22200, - [STOP_STATE_CNT] = 0xa, - [PHYCTRL_ULPS_EXIT] = 0x190, - [PHYCTRL_VREG_LP] = 1, - [PHYCTRL_SLEW_UP] = 1, - [PHYTIMING_LPX] = 0x07, - [PHYTIMING_HS_EXIT] = 0x0c, - [PHYTIMING_CLK_PREPARE] = 0x09, - [PHYTIMING_CLK_ZERO] = 0x2d, - [PHYTIMING_CLK_POST] = 0x0e, - [PHYTIMING_CLK_TRAIL] = 0x09, - [PHYTIMING_HS_PREPARE] = 0x0b, - [PHYTIMING_HS_ZERO] = 0x10, - [PHYTIMING_HS_TRAIL] = 0x0c, -}; - -static int __exynos_dsi_host_attach(struct device *dev, - struct mipi_dsi_device *device) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_device *drm = dsi->encoder.dev; - struct exynos_drm_crtc *crtc; - - mutex_lock(&drm->mode_config.mutex); - crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD); - crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO); - mutex_unlock(&drm->mode_config.mutex); - - if (drm->mode_config.poll_enabled) - drm_kms_helper_hotplug_event(drm); - - return 0; -} - -static int __exynos_dsi_host_detach(struct device *dev, - struct mipi_dsi_device *device) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_device *drm = dsi->encoder.dev; - - if (drm->mode_config.poll_enabled) - drm_kms_helper_hotplug_event(drm); - - return 0; -} - -static void __exynos_dsi_te_handler(struct device *dev) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - - exynos_drm_crtc_te_handler(dsi->encoder.crtc); -} - -static const struct exynos_dsi_host_ops exynos_dsi_host_ops = { - .attach = __exynos_dsi_host_attach, - .detach = __exynos_dsi_host_detach, - .te_handler = __exynos_dsi_te_handler, -}; - -static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { - .reg_ofs = EXYNOS_REG_OFS, - .plltmr_reg = 0x50, - .has_freqband = 1, - .has_clklane_stop = 1, - .num_clks = 2, - .max_freq = 1000, - .wait_for_reset = 1, - .num_bits_resol = 11, - .reg_values = reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { - .reg_ofs = EXYNOS_REG_OFS, - .plltmr_reg = 0x50, - .has_freqband = 1, - .has_clklane_stop = 1, - .num_clks = 2, - .max_freq = 1000, - .wait_for_reset = 1, - .num_bits_resol = 11, - .reg_values = reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { - .reg_ofs = EXYNOS_REG_OFS, - .plltmr_reg = 0x58, - .num_clks = 2, - .max_freq = 1000, - .wait_for_reset = 1, - .num_bits_resol = 11, - .reg_values = reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { - .reg_ofs = EXYNOS5433_REG_OFS, - .plltmr_reg = 0xa0, - .has_clklane_stop = 1, - .num_clks = 5, - .max_freq = 1500, - .wait_for_reset = 0, - .num_bits_resol = 12, - .reg_values = exynos5433_reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { - .reg_ofs = EXYNOS5433_REG_OFS, - .plltmr_reg = 0xa0, - .has_clklane_stop = 1, - .num_clks = 2, - .max_freq = 1500, - .wait_for_reset = 1, - .num_bits_resol = 12, - .reg_values = exynos5422_reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct of_device_id exynos_dsi_of_match[] = { - { .compatible = "samsung,exynos3250-mipi-dsi", - .data = &exynos3_dsi_driver_data }, - { .compatible = "samsung,exynos4210-mipi-dsi", - .data = &exynos4_dsi_driver_data }, - { .compatible = "samsung,exynos5410-mipi-dsi", - .data = &exynos5_dsi_driver_data }, - { .compatible = "samsung,exynos5422-mipi-dsi", - .data = &exynos5422_dsi_driver_data }, - { .compatible = "samsung,exynos5433-mipi-dsi", - .data = &exynos5433_dsi_driver_data }, - { } -}; - static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) { if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300))) @@ -1677,8 +1452,6 @@ static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { .mode_set = exynos_dsi_bridge_mode_set, };
-MODULE_DEVICE_TABLE(of, exynos_dsi_of_match); - static int exynos_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *device) { @@ -1779,11 +1552,6 @@ static int exynos_dsi_of_read_u32(const struct device_node *np, return ret; }
-enum { - DSI_PORT_IN, - DSI_PORT_OUT -}; - static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) { struct device *dev = dsi->dev; @@ -1808,59 +1576,6 @@ static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) return 0; }
-static int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); -static void exynos_dsi_unbind(struct exynos_dsi *dsi); - -static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dsi->encoder; - struct drm_device *drm_dev = data; - struct device_node *in_bridge_node; - struct drm_bridge *in_bridge; - int ret; - - drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); - - ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); - if (ret < 0) - return ret; - - in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); - if (in_bridge_node) { - in_bridge = of_drm_find_bridge(in_bridge_node); - if (in_bridge) - drm_bridge_attach(encoder, in_bridge, NULL, 0); - of_node_put(in_bridge_node); - } - - ret = exynos_dsi_bind(dsi->dsi, encoder); - if (ret) - goto err; - - return 0; - -err: - drm_encoder_cleanup(encoder); - return ret; -} - -static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master, - void *data) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dsi->encoder; - - exynos_dsi_unbind(dsi->dsi); - - drm_encoder_cleanup(encoder); -} - -static const struct component_ops exynos_dsi_pltfm_component_ops = { - .bind = exynos_dsi_pltfm_bind, - .unbind = exynos_dsi_pltfm_unbind, -}; - static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1972,12 +1687,12 @@ static void __exynos_dsi_remove(struct exynos_dsi *dsi) /* * Probe/remove API, used from platforms based on the DRM bridge API. */ -static struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) +struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) { return __exynos_dsi_probe(pdev); }
-static void exynos_dsi_remove(struct exynos_dsi *dsi) +void exynos_dsi_remove(struct exynos_dsi *dsi) { return __exynos_dsi_remove(dsi); } @@ -1985,61 +1700,19 @@ static void exynos_dsi_remove(struct exynos_dsi *dsi) /* * Bind/unbind API, used from platforms based on the component framework. */ -static int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) +int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) { struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
return drm_bridge_attach(encoder, &dsi->bridge, previous, 0); }
-static void exynos_dsi_unbind(struct exynos_dsi *dsi) +void exynos_dsi_unbind(struct exynos_dsi *dsi) { exynos_dsi_disable(dsi); }
-static int exynos_dsi_pltfm_probe(struct platform_device *pdev) -{ - struct exynos_dsi_pltfm *dsi; - struct device *dev = &pdev->dev; - int ret; - - dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); - if (!dsi) - return -ENOMEM; - platform_set_drvdata(pdev, dsi); - - dsi->dsi = exynos_dsi_probe(pdev); - if (IS_ERR(dsi->dsi)) - return PTR_ERR(dsi->dsi); - - pm_runtime_enable(dev); - - ret = component_add(dev, &exynos_dsi_pltfm_component_ops); - if (ret) - goto err_disable_runtime; - - return 0; - -err_disable_runtime: - pm_runtime_disable(dev); - - return ret; -} - -static int exynos_dsi_pltfm_remove(struct platform_device *pdev) -{ - struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev); - - pm_runtime_disable(&pdev->dev); - - exynos_dsi_remove(dsi->dsi); - - component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops); - - return 0; -} - -static int exynos_dsi_suspend(struct exynos_dsi *dsi) +int exynos_dsi_suspend(struct exynos_dsi *dsi) { const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; int ret, i; @@ -2068,7 +1741,7 @@ static int exynos_dsi_suspend(struct exynos_dsi *dsi) return 0; }
-static int exynos_dsi_resume(struct exynos_dsi *dsi) +int exynos_dsi_resume(struct exynos_dsi *dsi) { const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; int ret, i; @@ -2101,37 +1774,6 @@ static int exynos_dsi_resume(struct exynos_dsi *dsi) return ret; }
-static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - - return exynos_dsi_suspend(dsi->dsi); -} - -static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - - return exynos_dsi_resume(dsi->dsi); -} - -static const struct dev_pm_ops exynos_dsi_pm_ops = { - SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) -}; - -struct platform_driver dsi_driver = { - .probe = exynos_dsi_pltfm_probe, - .remove = exynos_dsi_pltfm_remove, - .driver = { - .name = "exynos-dsi", - .owner = THIS_MODULE, - .pm = &exynos_dsi_pm_ops, - .of_match_table = exynos_dsi_of_match, - }, -}; - MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.h b/drivers/gpu/drm/exynos/exynos_drm_dsi.h new file mode 100644 index 000000000000..8fa3276889de --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __EXYNOS_DRM_DSI__ +#define __EXYNOS_DRM_DSI__ + +struct drm_encoder; +struct exynos_dsi; +struct platform_device; +struct mipi_dsi_device; + +enum exynos_reg_offset { + EXYNOS_REG_OFS, + EXYNOS5433_REG_OFS +}; + +struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev); +void exynos_dsi_remove(struct exynos_dsi *dsi); +int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); +void exynos_dsi_unbind(struct exynos_dsi *dsi); + +int exynos_dsi_suspend(struct exynos_dsi *dsi); +int exynos_dsi_resume(struct exynos_dsi *dsi); + +enum reg_value_idx { + RESET_TYPE, + PLL_TIMER, + STOP_STATE_CNT, + PHYCTRL_ULPS_EXIT, + PHYCTRL_VREG_LP, + PHYCTRL_SLEW_UP, + PHYTIMING_LPX, + PHYTIMING_HS_EXIT, + PHYTIMING_CLK_PREPARE, + PHYTIMING_CLK_ZERO, + PHYTIMING_CLK_POST, + PHYTIMING_CLK_TRAIL, + PHYTIMING_HS_PREPARE, + PHYTIMING_HS_ZERO, + PHYTIMING_HS_TRAIL +}; + +/* DSIM_SWRST */ +#define DSIM_FUNCRST (1 << 16) +#define DSIM_SWRST (1 << 0) + +struct exynos_dsi_host_ops { + int (*attach)(struct device *dev, struct mipi_dsi_device *device); + int (*detach)(struct device *dev, struct mipi_dsi_device *device); + void (*te_handler)(struct device *dev); +}; + +struct exynos_dsi_driver_data { + enum exynos_reg_offset reg_ofs; + unsigned int plltmr_reg; + unsigned int has_freqband:1; + unsigned int has_clklane_stop:1; + unsigned int num_clks; + unsigned int max_freq; + unsigned int wait_for_reset; + unsigned int num_bits_resol; + const unsigned int *reg_values; + const struct exynos_dsi_host_ops *host_ops; +}; + +#endif /* __EXYNOS_DRM_DSI__ */ diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c b/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c new file mode 100644 index 000000000000..79d9ec6ade45 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung SoC MIPI DSI Master driver. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * Contacts: Tomasz Figa t.figa@samsung.com + */ + +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/pm_runtime.h> + +#include <drm/drm_bridge.h> +#include <drm/drm_encoder.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include "exynos_drm_crtc.h" +#include "exynos_drm_drv.h" +#include "exynos_drm_dsi.h" + +enum { + DSI_PORT_IN, + DSI_PORT_OUT +}; + +struct exynos_dsi_pltfm { + struct exynos_dsi *dsi; + struct drm_encoder encoder; +}; + +static const unsigned int reg_values[] = { + [RESET_TYPE] = DSIM_SWRST, + [PLL_TIMER] = 500, + [STOP_STATE_CNT] = 0xf, + [PHYCTRL_ULPS_EXIT] = 0x0af, + [PHYCTRL_VREG_LP] = 0, + [PHYCTRL_SLEW_UP] = 0, + [PHYTIMING_LPX] = 0x06, + [PHYTIMING_HS_EXIT] = 0x0b, + [PHYTIMING_CLK_PREPARE] = 0x07, + [PHYTIMING_CLK_ZERO] = 0x27, + [PHYTIMING_CLK_POST] = 0x0d, + [PHYTIMING_CLK_TRAIL] = 0x08, + [PHYTIMING_HS_PREPARE] = 0x09, + [PHYTIMING_HS_ZERO] = 0x0d, + [PHYTIMING_HS_TRAIL] = 0x0b, +}; + +static const unsigned int exynos5422_reg_values[] = { + [RESET_TYPE] = DSIM_SWRST, + [PLL_TIMER] = 500, + [STOP_STATE_CNT] = 0xf, + [PHYCTRL_ULPS_EXIT] = 0xaf, + [PHYCTRL_VREG_LP] = 0, + [PHYCTRL_SLEW_UP] = 0, + [PHYTIMING_LPX] = 0x08, + [PHYTIMING_HS_EXIT] = 0x0d, + [PHYTIMING_CLK_PREPARE] = 0x09, + [PHYTIMING_CLK_ZERO] = 0x30, + [PHYTIMING_CLK_POST] = 0x0e, + [PHYTIMING_CLK_TRAIL] = 0x0a, + [PHYTIMING_HS_PREPARE] = 0x0c, + [PHYTIMING_HS_ZERO] = 0x11, + [PHYTIMING_HS_TRAIL] = 0x0d, +}; + +static const unsigned int exynos5433_reg_values[] = { + [RESET_TYPE] = DSIM_FUNCRST, + [PLL_TIMER] = 22200, + [STOP_STATE_CNT] = 0xa, + [PHYCTRL_ULPS_EXIT] = 0x190, + [PHYCTRL_VREG_LP] = 1, + [PHYCTRL_SLEW_UP] = 1, + [PHYTIMING_LPX] = 0x07, + [PHYTIMING_HS_EXIT] = 0x0c, + [PHYTIMING_CLK_PREPARE] = 0x09, + [PHYTIMING_CLK_ZERO] = 0x2d, + [PHYTIMING_CLK_POST] = 0x0e, + [PHYTIMING_CLK_TRAIL] = 0x09, + [PHYTIMING_HS_PREPARE] = 0x0b, + [PHYTIMING_HS_ZERO] = 0x10, + [PHYTIMING_HS_TRAIL] = 0x0c, +}; + +static int __exynos_dsi_host_attach(struct device *dev, + struct mipi_dsi_device *device) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + struct drm_device *drm = dsi->encoder.dev; + struct exynos_drm_crtc *crtc; + + mutex_lock(&drm->mode_config.mutex); + crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD); + crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO); + mutex_unlock(&drm->mode_config.mutex); + + if (drm->mode_config.poll_enabled) + drm_kms_helper_hotplug_event(drm); + + return 0; +} + +static int __exynos_dsi_host_detach(struct device *dev, + struct mipi_dsi_device *device) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + struct drm_device *drm = dsi->encoder.dev; + + if (drm->mode_config.poll_enabled) + drm_kms_helper_hotplug_event(drm); + + return 0; +} + +static void __exynos_dsi_te_handler(struct device *dev) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + + exynos_drm_crtc_te_handler(dsi->encoder.crtc); +} + +static const struct exynos_dsi_host_ops exynos_dsi_host_ops = { + .attach = __exynos_dsi_host_attach, + .detach = __exynos_dsi_host_detach, + .te_handler = __exynos_dsi_te_handler, +}; + +static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { + .reg_ofs = EXYNOS_REG_OFS, + .plltmr_reg = 0x50, + .has_freqband = 1, + .has_clklane_stop = 1, + .num_clks = 2, + .max_freq = 1000, + .wait_for_reset = 1, + .num_bits_resol = 11, + .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, +}; + +static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { + .reg_ofs = EXYNOS_REG_OFS, + .plltmr_reg = 0x50, + .has_freqband = 1, + .has_clklane_stop = 1, + .num_clks = 2, + .max_freq = 1000, + .wait_for_reset = 1, + .num_bits_resol = 11, + .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, +}; + +static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { + .reg_ofs = EXYNOS_REG_OFS, + .plltmr_reg = 0x58, + .num_clks = 2, + .max_freq = 1000, + .wait_for_reset = 1, + .num_bits_resol = 11, + .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, +}; + +static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { + .reg_ofs = EXYNOS5433_REG_OFS, + .plltmr_reg = 0xa0, + .has_clklane_stop = 1, + .num_clks = 5, + .max_freq = 1500, + .wait_for_reset = 0, + .num_bits_resol = 12, + .reg_values = exynos5433_reg_values, + .host_ops = &exynos_dsi_host_ops, +}; + +static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { + .reg_ofs = EXYNOS5433_REG_OFS, + .plltmr_reg = 0xa0, + .has_clklane_stop = 1, + .num_clks = 2, + .max_freq = 1500, + .wait_for_reset = 1, + .num_bits_resol = 12, + .reg_values = exynos5422_reg_values, + .host_ops = &exynos_dsi_host_ops, +}; + +static const struct of_device_id exynos_dsi_of_match[] = { + { .compatible = "samsung,exynos3250-mipi-dsi", + .data = &exynos3_dsi_driver_data }, + { .compatible = "samsung,exynos4210-mipi-dsi", + .data = &exynos4_dsi_driver_data }, + { .compatible = "samsung,exynos5410-mipi-dsi", + .data = &exynos5_dsi_driver_data }, + { .compatible = "samsung,exynos5422-mipi-dsi", + .data = &exynos5422_dsi_driver_data }, + { .compatible = "samsung,exynos5433-mipi-dsi", + .data = &exynos5433_dsi_driver_data }, + { } +}; + +static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder; + struct drm_device *drm_dev = data; + struct device_node *in_bridge_node; + struct drm_bridge *in_bridge; + int ret; + + drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); + + ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); + if (ret < 0) + return ret; + + in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); + if (in_bridge_node) { + in_bridge = of_drm_find_bridge(in_bridge_node); + if (in_bridge) + drm_bridge_attach(encoder, in_bridge, NULL, 0); + of_node_put(in_bridge_node); + } + + ret = exynos_dsi_bind(dsi->dsi, encoder); + if (ret) + goto err; + + return 0; + +err: + drm_encoder_cleanup(encoder); + return ret; +} + +static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master, + void *data) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder; + + exynos_dsi_unbind(dsi->dsi); + + drm_encoder_cleanup(encoder); +} + +static const struct component_ops exynos_dsi_pltfm_component_ops = { + .bind = exynos_dsi_pltfm_bind, + .unbind = exynos_dsi_pltfm_unbind, +}; + +static int exynos_dsi_pltfm_probe(struct platform_device *pdev) +{ + struct exynos_dsi_pltfm *dsi; + struct device *dev = &pdev->dev; + int ret; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + platform_set_drvdata(pdev, dsi); + + dsi->dsi = exynos_dsi_probe(pdev); + if (IS_ERR(dsi->dsi)) + return PTR_ERR(dsi->dsi); + + pm_runtime_enable(dev); + + ret = component_add(dev, &exynos_dsi_pltfm_component_ops); + if (ret) + goto err_disable_runtime; + + return 0; + +err_disable_runtime: + pm_runtime_disable(dev); + + return ret; +} + +static int exynos_dsi_pltfm_remove(struct platform_device *pdev) +{ + struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + exynos_dsi_remove(dsi->dsi); + + component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops); + + return 0; +} + +static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + + return exynos_dsi_suspend(dsi->dsi); +} + +static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) +{ + struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); + + return exynos_dsi_resume(dsi->dsi); +} + +static const struct dev_pm_ops exynos_dsi_pm_ops = { + SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +struct platform_driver dsi_driver = { + .probe = exynos_dsi_pltfm_probe, + .remove = exynos_dsi_pltfm_remove, + .driver = { + .name = "exynos-dsi", + .owner = THIS_MODULE, + .pm = &exynos_dsi_pm_ops, + .of_match_table = exynos_dsi_of_match, + }, +}; + +MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); +MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL v2");
As the driver is not platform dependent anymore, move it to the drm bridge driver directory.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de --- v2: - select DRM_SAMSUNG_DSIM from DRM_EXYNOS_DSI - add removal of depends on !FB_S3C --- drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1790 ++++++++++++++++ drivers/gpu/drm/exynos/Kconfig | 5 +- drivers/gpu/drm/exynos/Makefile | 2 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 1896 ++--------------- drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c | 333 --- .../drm/bridge/samsung-dsim.h | 20 +- 8 files changed, 2037 insertions(+), 2019 deletions(-) create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c delete mode 100644 drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c rename drivers/gpu/drm/exynos/exynos_drm_dsi.h => include/drm/bridge/samsung-dsim.h (69%)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3e11af4e9f63..55ab5030c6cf 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -125,6 +125,15 @@ config DRM_PARADE_PS8640 The PS8640 is a high-performance and low-power MIPI DSI to eDP converter
+config DRM_SAMSUNG_DSIM + tristate "Samsung MIPI DSI bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + Samsung MIPI DSI bridge driver. + config DRM_SIL_SII8620 tristate "Silicon Image SII8620 HDMI/MHL bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c589a6a7cbe1..5ac7a5c413dc 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o +obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII9234) += sii9234.o diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c new file mode 100644 index 000000000000..6d2d8dc027de --- /dev/null +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -0,0 +1,1790 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung SoC MIPI DSI Master driver. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * Contacts: Tomasz Figa t.figa@samsung.com +*/ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/component.h> +#include <linux/gpio/consumer.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h> + +#include <asm/unaligned.h> + +#include <video/mipi_display.h> +#include <video/videomode.h> + +#include <drm/bridge/samsung-dsim.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +/* returns true iff both arguments logically differs */ +#define NEQV(a, b) (!(a) ^ !(b)) + +/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31) + +/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) +#define DSIM_BTA_TIMEOUT(x) ((x) << 16) + +/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31) + +/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) +/* This flag is valid only for exynos3250/3472/5260/5430 */ +#define DSIM_CLKLANE_STOP (1 << 30) + +/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21) + +/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) +#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0) + +/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0) + +/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) + +/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) + +/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) + +/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14) + +/* DSIM_FIFOCTRL */ +#define DSIM_RX_DATA_FULL (1 << 25) +#define DSIM_RX_DATA_EMPTY (1 << 24) +#define DSIM_SFR_HEADER_FULL (1 << 23) +#define DSIM_SFR_HEADER_EMPTY (1 << 22) +#define DSIM_SFR_PAYLOAD_FULL (1 << 21) +#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) +#define DSIM_I80_HEADER_FULL (1 << 19) +#define DSIM_I80_HEADER_EMPTY (1 << 18) +#define DSIM_I80_PAYLOAD_FULL (1 << 17) +#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) +#define DSIM_SD_HEADER_FULL (1 << 15) +#define DSIM_SD_HEADER_EMPTY (1 << 14) +#define DSIM_SD_PAYLOAD_FULL (1 << 13) +#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) +#define DSIM_MD_HEADER_FULL (1 << 11) +#define DSIM_MD_HEADER_EMPTY (1 << 10) +#define DSIM_MD_PAYLOAD_FULL (1 << 9) +#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) +#define DSIM_RX_FIFO (1 << 4) +#define DSIM_SFR_FIFO (1 << 3) +#define DSIM_I80_FIFO (1 << 2) +#define DSIM_SD_FIFO (1 << 1) +#define DSIM_MD_FIFO (1 << 0) + +/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) + +/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1) + +/* DSIM_PHYCTRL */ +#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) +#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) +#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14) + +/* DSIM_PHYTIMING */ +#define DSIM_PHYTIMING_LPX(x) ((x) << 8) +#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0) + +/* DSIM_PHYTIMING1 */ +#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) +#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) +#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) +#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0) + +/* DSIM_PHYTIMING2 */ +#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) +#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) +#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0) + +#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002 + +#define OLD_SCLK_MIPI_CLK_NAME "pll_clk" + +static const char *const clk_names[5] = { "bus_clk", "sclk_mipi", + "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0", + "sclk_rgb_vclk_to_dsim0" }; + +enum samsung_dsim_transfer_type { + EXYNOS_DSI_TX, + EXYNOS_DSI_RX, +}; + +struct samsung_dsim_transfer { + struct list_head list; + struct completion completed; + int result; + struct mipi_dsi_packet packet; + u16 flags; + u16 tx_done; + + u8 *rx_payload; + u16 rx_len; + u16 rx_done; +}; + +#define DSIM_STATE_ENABLED BIT(0) +#define DSIM_STATE_INITIALIZED BIT(1) +#define DSIM_STATE_CMD_LPM BIT(2) +#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3) + +struct samsung_dsim { + struct drm_bridge bridge; + struct mipi_dsi_host dsi_host; + struct drm_connector connector; + struct drm_panel *panel; + struct list_head bridge_chain; + struct drm_bridge *out_bridge; + struct device *dev; + + void __iomem *reg_base; + struct phy *phy; + struct clk **clks; + struct regulator_bulk_data supplies[2]; + int irq; + int te_gpio; + + u32 pll_clk_rate; + u32 burst_clk_rate; + u32 esc_clk_rate; + u32 lanes; + u32 mode_flags; + u32 format; + + struct drm_display_mode mode; + + int state; + struct drm_property *brightness; + struct completion completed; + + spinlock_t transfer_lock; /* protects transfer_list */ + struct list_head transfer_list; + + const struct samsung_dsim_driver_data *driver_data; +}; + +#define host_to_dsi(host) container_of(host, struct samsung_dsim, dsi_host) +#define connector_to_dsi(c) container_of(c, struct samsung_dsim, connector) + +enum reg_idx { + DSIM_STATUS_REG, /* Status register */ + DSIM_SWRST_REG, /* Software reset register */ + DSIM_CLKCTRL_REG, /* Clock control register */ + DSIM_TIMEOUT_REG, /* Time out register */ + DSIM_CONFIG_REG, /* Configuration register */ + DSIM_ESCMODE_REG, /* Escape mode register */ + DSIM_MDRESOL_REG, + DSIM_MVPORCH_REG, /* Main display Vporch register */ + DSIM_MHPORCH_REG, /* Main display Hporch register */ + DSIM_MSYNC_REG, /* Main display sync area register */ + DSIM_INTSRC_REG, /* Interrupt source register */ + DSIM_INTMSK_REG, /* Interrupt mask register */ + DSIM_PKTHDR_REG, /* Packet Header FIFO register */ + DSIM_PAYLOAD_REG, /* Payload FIFO register */ + DSIM_RXFIFO_REG, /* Read FIFO register */ + DSIM_FIFOCTRL_REG, /* FIFO status and control register */ + DSIM_PLLCTRL_REG, /* PLL control register */ + DSIM_PHYCTRL_REG, + DSIM_PHYTIMING_REG, + DSIM_PHYTIMING1_REG, + DSIM_PHYTIMING2_REG, + NUM_REGS +}; + +static const unsigned int exynos_reg_ofs[] = { + [DSIM_STATUS_REG] = 0x00, + [DSIM_SWRST_REG] = 0x04, + [DSIM_CLKCTRL_REG] = 0x08, + [DSIM_TIMEOUT_REG] = 0x0c, + [DSIM_CONFIG_REG] = 0x10, + [DSIM_ESCMODE_REG] = 0x14, + [DSIM_MDRESOL_REG] = 0x18, + [DSIM_MVPORCH_REG] = 0x1c, + [DSIM_MHPORCH_REG] = 0x20, + [DSIM_MSYNC_REG] = 0x24, + [DSIM_INTSRC_REG] = 0x2c, + [DSIM_INTMSK_REG] = 0x30, + [DSIM_PKTHDR_REG] = 0x34, + [DSIM_PAYLOAD_REG] = 0x38, + [DSIM_RXFIFO_REG] = 0x3c, + [DSIM_FIFOCTRL_REG] = 0x44, + [DSIM_PLLCTRL_REG] = 0x4c, + [DSIM_PHYCTRL_REG] = 0x5c, + [DSIM_PHYTIMING_REG] = 0x64, + [DSIM_PHYTIMING1_REG] = 0x68, + [DSIM_PHYTIMING2_REG] = 0x6c, +}; + +static const unsigned int exynos5433_reg_ofs[] = { + [DSIM_STATUS_REG] = 0x04, + [DSIM_SWRST_REG] = 0x0C, + [DSIM_CLKCTRL_REG] = 0x10, + [DSIM_TIMEOUT_REG] = 0x14, + [DSIM_CONFIG_REG] = 0x18, + [DSIM_ESCMODE_REG] = 0x1C, + [DSIM_MDRESOL_REG] = 0x20, + [DSIM_MVPORCH_REG] = 0x24, + [DSIM_MHPORCH_REG] = 0x28, + [DSIM_MSYNC_REG] = 0x2C, + [DSIM_INTSRC_REG] = 0x34, + [DSIM_INTMSK_REG] = 0x38, + [DSIM_PKTHDR_REG] = 0x3C, + [DSIM_PAYLOAD_REG] = 0x40, + [DSIM_RXFIFO_REG] = 0x44, + [DSIM_FIFOCTRL_REG] = 0x4C, + [DSIM_PLLCTRL_REG] = 0x94, + [DSIM_PHYCTRL_REG] = 0xA4, + [DSIM_PHYTIMING_REG] = 0xB4, + [DSIM_PHYTIMING1_REG] = 0xB8, + [DSIM_PHYTIMING2_REG] = 0xBC, +}; + +static inline void samsung_dsim_write(struct samsung_dsim *dsi, + enum reg_idx idx, u32 val) +{ + const unsigned int *reg_ofs; + + if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS) + reg_ofs = exynos5433_reg_ofs; + else + reg_ofs = exynos_reg_ofs; + + writel(val, dsi->reg_base + reg_ofs[idx]); +} + +static inline u32 samsung_dsim_read(struct samsung_dsim *dsi, enum reg_idx idx) +{ + const unsigned int *reg_ofs; + + if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS) + reg_ofs = exynos5433_reg_ofs; + else + reg_ofs = exynos_reg_ofs; + + return readl(dsi->reg_base + reg_ofs[idx]); +} + +static void samsung_dsim_wait_for_reset(struct samsung_dsim *dsi) +{ + if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300))) + return; + + dev_err(dsi->dev, "timeout waiting for reset\n"); +} + +static void samsung_dsim_reset(struct samsung_dsim *dsi) +{ + u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE]; + + reinit_completion(&dsi->completed); + samsung_dsim_write(dsi, DSIM_SWRST_REG, reset_val); +} + +#ifndef MHZ +#define MHZ (1000*1000) +#endif + +static unsigned long samsung_dsim_pll_find_pms(struct samsung_dsim *dsi, + unsigned long fin, + unsigned long fout, + u8 *p, u16 *m, u8 *s) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + unsigned long best_freq = 0; + u32 min_delta = 0xffffffff; + u8 p_min, p_max; + u8 _p, best_p; + u16 _m, best_m; + u8 _s, best_s; + + p_min = DIV_ROUND_UP(fin, (12 * MHZ)); + p_max = fin / (6 * MHZ); + + for (_p = p_min; _p <= p_max; ++_p) { + for (_s = 0; _s <= 5; ++_s) { + u64 tmp; + u32 delta; + + tmp = (u64)fout * (_p << _s); + do_div(tmp, fin); + _m = tmp; + if (_m < 41 || _m > 125) + continue; + + tmp = (u64)_m * fin; + do_div(tmp, _p); + if (tmp < 500 * MHZ || + tmp > driver_data->max_freq * MHZ) + continue; + + tmp = (u64)_m * fin; + do_div(tmp, _p << _s); + + delta = abs(fout - tmp); + if (delta < min_delta) { + best_p = _p; + best_m = _m; + best_s = _s; + min_delta = delta; + best_freq = tmp; + } + } + } + + if (best_freq) { + *p = best_p; + *m = best_m; + *s = best_s; + } + + return best_freq; +} + +static unsigned long samsung_dsim_set_pll(struct samsung_dsim *dsi, + unsigned long freq) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + unsigned long fin, fout; + int timeout; + u8 p, s; + u16 m; + u32 reg; + + fin = dsi->pll_clk_rate; + fout = samsung_dsim_pll_find_pms(dsi, fin, freq, &p, &m, &s); + if (!fout) { + dev_err(dsi->dev, + "failed to find PLL PMS for requested frequency\n"); + return 0; + } + dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s); + + writel(driver_data->reg_values[PLL_TIMER], + dsi->reg_base + driver_data->plltmr_reg); + + reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); + + if (driver_data->has_freqband) { + static const unsigned long freq_bands[] = { + 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, + 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, + 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, + 770 * MHZ, 870 * MHZ, 950 * MHZ, + }; + int band; + + for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) + if (fout < freq_bands[band]) + break; + + dev_dbg(dsi->dev, "band %d\n", band); + + reg |= DSIM_FREQ_BAND(band); + } + + samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg); + + timeout = 1000; + do { + if (timeout-- == 0) { + dev_err(dsi->dev, "PLL failed to stabilize\n"); + return 0; + } + reg = samsung_dsim_read(dsi, DSIM_STATUS_REG); + } while ((reg & DSIM_PLL_STABLE) == 0); + + return fout; +} + +static int samsung_dsim_enable_clock(struct samsung_dsim *dsi) +{ + unsigned long hs_clk, byte_clk, esc_clk; + unsigned long esc_div; + u32 reg; + + hs_clk = samsung_dsim_set_pll(dsi, dsi->burst_clk_rate); + if (!hs_clk) { + dev_err(dsi->dev, "failed to configure DSI PLL\n"); + return -EFAULT; + } + + byte_clk = hs_clk / 8; + esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate); + esc_clk = byte_clk / esc_div; + + if (esc_clk > 20 * MHZ) { + ++esc_div; + esc_clk = byte_clk / esc_div; + } + + dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n", + hs_clk, byte_clk, esc_clk); + + reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG); + reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS + | DSIM_BYTE_CLK_SRC_MASK); + reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN + | DSIM_ESC_PRESCALER(esc_div) + | DSIM_LANE_ESC_CLK_EN_CLK + | DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1) + | DSIM_BYTE_CLK_SRC(0) + | DSIM_TX_REQUEST_HSCLK; + samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg); + + return 0; +} + +static void samsung_dsim_set_phy_ctrl(struct samsung_dsim *dsi) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + const unsigned int *reg_values = driver_data->reg_values; + u32 reg; + + if (driver_data->has_freqband) + return; + + /* B D-PHY: D-PHY Master & Slave Analog Block control */ + reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]); + if (reg_values[PHYCTRL_VREG_LP]) + reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP; + if (reg_values[PHYCTRL_SLEW_UP]) + reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP; + samsung_dsim_write(dsi, DSIM_PHYCTRL_REG, reg); + + /* + * T LPX: Transmitted length of any Low-Power state period + * T HS-EXIT: Time that the transmitter drives LP-11 following a HS + * burst + */ + reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) | + DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]); + samsung_dsim_write(dsi, DSIM_PHYTIMING_REG, reg); + + /* + * T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00 + * Line state immediately before the HS-0 Line state starting the + * HS transmission + * T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to + * transmitting the Clock. + * T CLK_POST: Time that the transmitter continues to send HS clock + * after the last associated Data Lane has transitioned to LP Mode + * Interval is defined as the period from the end of T HS-TRAIL to + * the beginning of T CLK-TRAIL + * T CLK-TRAIL: Time that the transmitter drives the HS-0 state after + * the last payload clock bit of a HS transmission burst + */ + reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) | + DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) | + DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) | + DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]); + samsung_dsim_write(dsi, DSIM_PHYTIMING1_REG, reg); + + /* + * T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00 + * Line state immediately before the HS-0 Line state starting the + * HS transmission + * T HS-ZERO: Time that the transmitter drives the HS-0 state prior to + * transmitting the Sync sequence. + * T HS-TRAIL: Time that the transmitter drives the flipped differential + * state after last payload data bit of a HS transmission burst + */ + reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) | + DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) | + DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]); + samsung_dsim_write(dsi, DSIM_PHYTIMING2_REG, reg); +} + +static void samsung_dsim_disable_clock(struct samsung_dsim *dsi) +{ + u32 reg; + + reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG); + reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK + | DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN); + samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg); + + reg = samsung_dsim_read(dsi, DSIM_PLLCTRL_REG); + reg &= ~DSIM_PLL_EN; + samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg); +} + +static void samsung_dsim_enable_lane(struct samsung_dsim *dsi, u32 lane) +{ + u32 reg = samsung_dsim_read(dsi, DSIM_CONFIG_REG); + reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK | + DSIM_LANE_EN(lane)); + samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg); +} + +static int samsung_dsim_init_link(struct samsung_dsim *dsi) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + int timeout; + u32 reg; + u32 lanes_mask; + + /* Initialize FIFO pointers */ + reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG); + reg &= ~0x1f; + samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg); + + usleep_range(9000, 11000); + + reg |= 0x1f; + samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg); + usleep_range(9000, 11000); + + /* DSI configuration */ + reg = 0; + + /* + * The first bit of mode_flags specifies display configuration. + * If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video + * mode, otherwise it will support command mode. + */ + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + reg |= DSIM_VIDEO_MODE; + + /* + * The user manual describes that following bits are ignored in + * command mode. + */ + if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH)) + reg |= DSIM_MFLUSH_VS; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) + reg |= DSIM_SYNC_INFORM; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) + reg |= DSIM_BURST_MODE; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT) + reg |= DSIM_AUTO_MODE; + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE) + reg |= DSIM_HSE_MODE; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP)) + reg |= DSIM_HFP_MODE; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP)) + reg |= DSIM_HBP_MODE; + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA)) + reg |= DSIM_HSA_MODE; + } + + if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) + reg |= DSIM_EOT_DISABLE; + + switch (dsi->format) { + case MIPI_DSI_FMT_RGB888: + reg |= DSIM_MAIN_PIX_FORMAT_RGB888; + break; + case MIPI_DSI_FMT_RGB666: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666; + break; + case MIPI_DSI_FMT_RGB666_PACKED: + reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P; + break; + case MIPI_DSI_FMT_RGB565: + reg |= DSIM_MAIN_PIX_FORMAT_RGB565; + break; + default: + dev_err(dsi->dev, "invalid pixel format\n"); + return -EINVAL; + } + + /* + * Use non-continuous clock mode if the periparal wants and + * host controller supports + * + * In non-continous clock mode, host controller will turn off + * the HS clock between high-speed transmissions to reduce + * power consumption. + */ + if (driver_data->has_clklane_stop && + dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { + reg |= DSIM_CLKLANE_STOP; + } + samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg); + + lanes_mask = BIT(dsi->lanes) - 1; + samsung_dsim_enable_lane(dsi, lanes_mask); + + /* Check clock and data lane state are stop state */ + timeout = 100; + do { + if (timeout-- == 0) { + dev_err(dsi->dev, "waiting for bus lanes timed out\n"); + return -EFAULT; + } + + reg = samsung_dsim_read(dsi, DSIM_STATUS_REG); + if ((reg & DSIM_STOP_STATE_DAT(lanes_mask)) + != DSIM_STOP_STATE_DAT(lanes_mask)) + continue; + } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK))); + + reg = samsung_dsim_read(dsi, DSIM_ESCMODE_REG); + reg &= ~DSIM_STOP_STATE_CNT_MASK; + reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]); + samsung_dsim_write(dsi, DSIM_ESCMODE_REG, reg); + + reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff); + samsung_dsim_write(dsi, DSIM_TIMEOUT_REG, reg); + + return 0; +} + +static void samsung_dsim_set_display_mode(struct samsung_dsim *dsi) +{ + struct drm_display_mode *m = &dsi->mode; + unsigned int num_bits_resol = dsi->driver_data->num_bits_resol; + u32 reg; + + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { + reg = DSIM_CMD_ALLOW(0xf) + | DSIM_STABLE_VFP(m->vsync_start - m->vdisplay) + | DSIM_MAIN_VBP(m->vtotal - m->vsync_end); + samsung_dsim_write(dsi, DSIM_MVPORCH_REG, reg); + + reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay) + | DSIM_MAIN_HBP(m->htotal - m->hsync_end); + samsung_dsim_write(dsi, DSIM_MHPORCH_REG, reg); + + reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start) + | DSIM_MAIN_HSA(m->hsync_end - m->hsync_start); + samsung_dsim_write(dsi, DSIM_MSYNC_REG, reg); + } + reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) | + DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol); + + samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg); + + dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay); +} + +static void samsung_dsim_set_display_enable(struct samsung_dsim *dsi, + bool enable) +{ + u32 reg; + + reg = samsung_dsim_read(dsi, DSIM_MDRESOL_REG); + if (enable) + reg |= DSIM_MAIN_STAND_BY; + else + reg &= ~DSIM_MAIN_STAND_BY; + samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg); +} + +static int samsung_dsim_wait_for_hdr_fifo(struct samsung_dsim *dsi) +{ + int timeout = 2000; + + do { + u32 reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG); + + if (!(reg & DSIM_SFR_HEADER_FULL)) + return 0; + + if (!cond_resched()) + usleep_range(950, 1050); + } while (--timeout); + + return -ETIMEDOUT; +} + +static void samsung_dsim_set_cmd_lpm(struct samsung_dsim *dsi, bool lpm) +{ + u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG); + + if (lpm) + v |= DSIM_CMD_LPDT_LP; + else + v &= ~DSIM_CMD_LPDT_LP; + + samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v); +} + +static void samsung_dsim_force_bta(struct samsung_dsim *dsi) +{ + u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG); + v |= DSIM_FORCE_BTA; + samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v); +} + +static void samsung_dsim_send_to_fifo(struct samsung_dsim *dsi, + struct samsung_dsim_transfer *xfer) +{ + struct device *dev = dsi->dev; + struct mipi_dsi_packet *pkt = &xfer->packet; + const u8 *payload = pkt->payload + xfer->tx_done; + u16 length = pkt->payload_length - xfer->tx_done; + bool first = !xfer->tx_done; + u32 reg; + + dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n", + xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done); + + if (length > DSI_TX_FIFO_SIZE) + length = DSI_TX_FIFO_SIZE; + + xfer->tx_done += length; + + /* Send payload */ + while (length >= 4) { + reg = get_unaligned_le32(payload); + samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg); + payload += 4; + length -= 4; + } + + reg = 0; + switch (length) { + case 3: + reg |= payload[2] << 16; + fallthrough; + case 2: + reg |= payload[1] << 8; + fallthrough; + case 1: + reg |= payload[0]; + samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg); + break; + } + + /* Send packet header */ + if (!first) + return; + + reg = get_unaligned_le32(pkt->header); + if (samsung_dsim_wait_for_hdr_fifo(dsi)) { + dev_err(dev, "waiting for header FIFO timed out\n"); + return; + } + + if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM, + dsi->state & DSIM_STATE_CMD_LPM)) { + samsung_dsim_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM); + dsi->state ^= DSIM_STATE_CMD_LPM; + } + + samsung_dsim_write(dsi, DSIM_PKTHDR_REG, reg); + + if (xfer->flags & MIPI_DSI_MSG_REQ_ACK) + samsung_dsim_force_bta(dsi); +} + +static void samsung_dsim_read_from_fifo(struct samsung_dsim *dsi, + struct samsung_dsim_transfer *xfer) +{ + u8 *payload = xfer->rx_payload + xfer->rx_done; + bool first = !xfer->rx_done; + struct device *dev = dsi->dev; + u16 length; + u32 reg; + + if (first) { + reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG); + + switch (reg & 0x3f) { + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + if (xfer->rx_len >= 2) { + payload[1] = reg >> 16; + ++xfer->rx_done; + } + fallthrough; + case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + payload[0] = reg >> 8; + ++xfer->rx_done; + xfer->rx_len = xfer->rx_done; + xfer->result = 0; + goto clear_fifo; + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + dev_err(dev, "DSI Error Report: 0x%04x\n", + (reg >> 8) & 0xffff); + xfer->result = 0; + goto clear_fifo; + } + + length = (reg >> 8) & 0xffff; + if (length > xfer->rx_len) { + dev_err(dev, + "response too long (%u > %u bytes), stripping\n", + xfer->rx_len, length); + length = xfer->rx_len; + } else if (length < xfer->rx_len) + xfer->rx_len = length; + } + + length = xfer->rx_len - xfer->rx_done; + xfer->rx_done += length; + + /* Receive payload */ + while (length >= 4) { + reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG); + payload[0] = (reg >> 0) & 0xff; + payload[1] = (reg >> 8) & 0xff; + payload[2] = (reg >> 16) & 0xff; + payload[3] = (reg >> 24) & 0xff; + payload += 4; + length -= 4; + } + + if (length) { + reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG); + switch (length) { + case 3: + payload[2] = (reg >> 16) & 0xff; + fallthrough; + case 2: + payload[1] = (reg >> 8) & 0xff; + fallthrough; + case 1: + payload[0] = reg & 0xff; + } + } + + if (xfer->rx_done == xfer->rx_len) + xfer->result = 0; + +clear_fifo: + length = DSI_RX_FIFO_SIZE / 4; + do { + reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG); + if (reg == DSI_RX_FIFO_EMPTY) + break; + } while (--length); +} + +static void samsung_dsim_transfer_start(struct samsung_dsim *dsi) +{ + unsigned long flags; + struct samsung_dsim_transfer *xfer; + bool start = false; + +again: + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct samsung_dsim_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (xfer->packet.payload_length && + xfer->tx_done == xfer->packet.payload_length) + /* waiting for RX */ + return; + + samsung_dsim_send_to_fifo(dsi, xfer); + + if (xfer->packet.payload_length || xfer->rx_len) + return; + + xfer->result = 0; + complete(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (start) + goto again; +} + +static bool samsung_dsim_transfer_finish(struct samsung_dsim *dsi) +{ + struct samsung_dsim_transfer *xfer; + unsigned long flags; + bool start = true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (list_empty(&dsi->transfer_list)) { + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + return false; + } + + xfer = list_first_entry(&dsi->transfer_list, + struct samsung_dsim_transfer, list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + dev_dbg(dsi->dev, + "> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n", + xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len, + xfer->rx_done); + + if (xfer->tx_done != xfer->packet.payload_length) + return true; + + if (xfer->rx_done != xfer->rx_len) + samsung_dsim_read_from_fifo(dsi, xfer); + + if (xfer->rx_done != xfer->rx_len) + return true; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (!xfer->rx_len) + xfer->result = 0; + complete(&xfer->completed); + + return start; +} + +static void samsung_dsim_remove_transfer(struct samsung_dsim *dsi, + struct samsung_dsim_transfer *xfer) +{ + unsigned long flags; + bool start; + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + if (!list_empty(&dsi->transfer_list) && + xfer == list_first_entry(&dsi->transfer_list, + struct samsung_dsim_transfer, list)) { + list_del_init(&xfer->list); + start = !list_empty(&dsi->transfer_list); + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + if (start) + samsung_dsim_transfer_start(dsi); + return; + } + + list_del_init(&xfer->list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); +} + +static int samsung_dsim_transfer(struct samsung_dsim *dsi, + struct samsung_dsim_transfer *xfer) +{ + unsigned long flags; + bool stopped; + + xfer->tx_done = 0; + xfer->rx_done = 0; + xfer->result = -ETIMEDOUT; + init_completion(&xfer->completed); + + spin_lock_irqsave(&dsi->transfer_lock, flags); + + stopped = list_empty(&dsi->transfer_list); + list_add_tail(&xfer->list, &dsi->transfer_list); + + spin_unlock_irqrestore(&dsi->transfer_lock, flags); + + if (stopped) + samsung_dsim_transfer_start(dsi); + + wait_for_completion_timeout(&xfer->completed, + msecs_to_jiffies(DSI_XFER_TIMEOUT_MS)); + if (xfer->result == -ETIMEDOUT) { + struct mipi_dsi_packet *pkt = &xfer->packet; + samsung_dsim_remove_transfer(dsi, xfer); + dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header, + (int)pkt->payload_length, pkt->payload); + return -ETIMEDOUT; + } + + /* Also covers hardware timeout condition */ + return xfer->result; +} + +static irqreturn_t samsung_dsim_irq(int irq, void *dev_id) +{ + struct samsung_dsim *dsi = dev_id; + u32 status; + + status = samsung_dsim_read(dsi, DSIM_INTSRC_REG); + if (!status) { + static unsigned long int j; + if (printk_timed_ratelimit(&j, 500)) + dev_warn(dsi->dev, "spurious interrupt\n"); + return IRQ_HANDLED; + } + samsung_dsim_write(dsi, DSIM_INTSRC_REG, status); + + if (status & DSIM_INT_SW_RST_RELEASE) { + u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY | + DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR | + DSIM_INT_SW_RST_RELEASE); + samsung_dsim_write(dsi, DSIM_INTMSK_REG, mask); + complete(&dsi->completed); + return IRQ_HANDLED; + } + + if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY | + DSIM_INT_PLL_STABLE))) + return IRQ_HANDLED; + + if (samsung_dsim_transfer_finish(dsi)) + samsung_dsim_transfer_start(dsi); + + return IRQ_HANDLED; +} + +static irqreturn_t samsung_dsim_te_irq_handler(int irq, void *dev_id) +{ + struct samsung_dsim *dsi = dev_id; + const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops; + + if (ops && ops->te_handler && + (dsi->state & DSIM_STATE_VIDOUT_AVAILABLE)) + ops->te_handler(dsi->dsi_host.dev); + + return IRQ_HANDLED; +} + +static void samsung_dsim_enable_irq(struct samsung_dsim *dsi) +{ + enable_irq(dsi->irq); + + if (gpio_is_valid(dsi->te_gpio)) + enable_irq(gpio_to_irq(dsi->te_gpio)); +} + +static void samsung_dsim_disable_irq(struct samsung_dsim *dsi) +{ + if (gpio_is_valid(dsi->te_gpio)) + disable_irq(gpio_to_irq(dsi->te_gpio)); + + disable_irq(dsi->irq); +} + +static int samsung_dsim_init(struct samsung_dsim *dsi) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + + samsung_dsim_reset(dsi); + samsung_dsim_enable_irq(dsi); + + if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST) + samsung_dsim_enable_lane(dsi, BIT(dsi->lanes) - 1); + + samsung_dsim_enable_clock(dsi); + if (driver_data->wait_for_reset) + samsung_dsim_wait_for_reset(dsi); + samsung_dsim_set_phy_ctrl(dsi); + samsung_dsim_init_link(dsi); + + return 0; +} + +static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi, + struct device *panel) +{ + int ret; + int te_gpio_irq; + + dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0); + if (dsi->te_gpio == -ENOENT) + return 0; + + if (!gpio_is_valid(dsi->te_gpio)) { + ret = dsi->te_gpio; + dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret); + goto out; + } + + ret = gpio_request(dsi->te_gpio, "te_gpio"); + if (ret) { + dev_err(dsi->dev, "gpio request failed with %d\n", ret); + goto out; + } + + te_gpio_irq = gpio_to_irq(dsi->te_gpio); + irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN); + + ret = request_threaded_irq(te_gpio_irq, samsung_dsim_te_irq_handler, + NULL, IRQF_TRIGGER_RISING, "TE", dsi); + if (ret) { + dev_err(dsi->dev, "request interrupt failed with %d\n", ret); + gpio_free(dsi->te_gpio); + goto out; + } + +out: + return ret; +} + +static void samsung_dsim_unregister_te_irq(struct samsung_dsim *dsi) +{ + if (gpio_is_valid(dsi->te_gpio)) { + free_irq(gpio_to_irq(dsi->te_gpio), dsi); + gpio_free(dsi->te_gpio); + dsi->te_gpio = -ENOENT; + } +} + +static void samsung_dsim_enable(struct samsung_dsim *dsi) +{ + struct drm_bridge *iter; + int ret; + + if (dsi->state & DSIM_STATE_ENABLED) + return; + + pm_runtime_get_sync(dsi->dev); + dsi->state |= DSIM_STATE_ENABLED; + + if (dsi->panel) { + ret = drm_panel_prepare(dsi->panel); + if (ret < 0) + goto err_put_sync; + } else { + list_for_each_entry_reverse(iter, &dsi->bridge_chain, + chain_node) { + if (iter->funcs->pre_enable) + iter->funcs->pre_enable(iter); + } + } + + samsung_dsim_set_display_mode(dsi); + samsung_dsim_set_display_enable(dsi, true); + + if (dsi->panel) { + ret = drm_panel_enable(dsi->panel); + if (ret < 0) + goto err_display_disable; + } else { + list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { + if (iter->funcs->enable) + iter->funcs->enable(iter); + } + } + + dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE; + return; + +err_display_disable: + samsung_dsim_set_display_enable(dsi, false); + drm_panel_unprepare(dsi->panel); + +err_put_sync: + dsi->state &= ~DSIM_STATE_ENABLED; + pm_runtime_put(dsi->dev); +} + +static void samsung_dsim_disable(struct samsung_dsim *dsi) +{ + struct drm_bridge *iter; + + if (!(dsi->state & DSIM_STATE_ENABLED)) + return; + + dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE; + + drm_panel_disable(dsi->panel); + + list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { + if (iter->funcs->disable) + iter->funcs->disable(iter); + } + + samsung_dsim_set_display_enable(dsi, false); + drm_panel_unprepare(dsi->panel); + + list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { + if (iter->funcs->post_disable) + iter->funcs->post_disable(iter); + } + + dsi->state &= ~DSIM_STATE_ENABLED; + pm_runtime_put_sync(dsi->dev); +} + +static enum drm_connector_status +samsung_dsim_detect(struct drm_connector *connector, bool force) +{ + return connector->status; +} + +static void samsung_dsim_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + connector->dev = NULL; +} + +static const struct drm_connector_funcs samsung_dsim_connector_funcs = { + .detect = samsung_dsim_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = samsung_dsim_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int samsung_dsim_get_modes(struct drm_connector *connector) +{ + struct samsung_dsim *dsi = connector_to_dsi(connector); + + if (dsi->panel) + return drm_panel_get_modes(dsi->panel, connector); + + return 0; +} + +static const struct drm_connector_helper_funcs samsung_dsim_connector_helper_funcs = { + .get_modes = samsung_dsim_get_modes, +}; + +static int samsung_dsim_create_connector(struct samsung_dsim *dsi) +{ + struct drm_connector *connector = &dsi->connector; + struct drm_device *drm = dsi->bridge.dev; + int ret; + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(drm, connector, &samsung_dsim_connector_funcs, + DRM_MODE_CONNECTOR_DSI); + if (ret) { + DRM_DEV_ERROR(dsi->dev, + "Failed to initialize connector with drm\n"); + return ret; + } + + connector->status = connector_status_disconnected; + drm_connector_helper_add(connector, &samsung_dsim_connector_helper_funcs); + drm_connector_attach_encoder(connector, dsi->bridge.encoder); + if (!drm->registered) + return 0; + + connector->funcs->reset(connector); + drm_connector_register(connector); + return 0; +} + +static int samsung_dsim_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct samsung_dsim *dsi = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + int ret; + + if (!dsi->out_bridge && !dsi->panel) + return -EPROBE_DEFER; + + if (dsi->out_bridge) { + ret = drm_bridge_attach(encoder, dsi->out_bridge, + bridge, flags); + if (ret) + return ret; + list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); + } else { + ret = samsung_dsim_create_connector(dsi); + if (ret) + return ret; + + if (dsi->panel) { + dsi->connector.status = connector_status_connected; + } + } + + return 0; +} + +static void samsung_dsim_bridge_detach(struct drm_bridge *bridge) +{ + struct samsung_dsim *dsi = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + struct drm_device *drm = encoder->dev; + + if (dsi->panel) { + mutex_lock(&drm->mode_config.mutex); + samsung_dsim_disable(dsi); + dsi->panel = NULL; + dsi->connector.status = connector_status_disconnected; + mutex_unlock(&drm->mode_config.mutex); + } else { + if (dsi->out_bridge->funcs->detach) + dsi->out_bridge->funcs->detach(dsi->out_bridge); + dsi->out_bridge = NULL; + INIT_LIST_HEAD(&dsi->bridge_chain); + } +} + +static void samsung_dsim_bridge_enable(struct drm_bridge *bridge) +{ + struct samsung_dsim *dsi = bridge->driver_private; + + samsung_dsim_enable(dsi); +} + +static void samsung_dsim_bridge_disable(struct drm_bridge *bridge) +{ + struct samsung_dsim *dsi = bridge->driver_private; + + samsung_dsim_disable(dsi); +} + +static void samsung_dsim_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct samsung_dsim *dsi = bridge->driver_private; + + /* The mode is set when actually enabling the device. */ + drm_mode_copy(&dsi->mode, adjusted_mode); +} + +static const struct drm_bridge_funcs samsung_dsim_bridge_funcs = { + .attach = samsung_dsim_bridge_attach, + .detach = samsung_dsim_bridge_detach, + .enable = samsung_dsim_bridge_enable, + .disable = samsung_dsim_bridge_disable, + .mode_set = samsung_dsim_bridge_mode_set, +}; + +static int samsung_dsim_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct samsung_dsim *dsi = host_to_dsi(host); + const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops; + struct drm_bridge *out_bridge; + + out_bridge = of_drm_find_bridge(device->dev.of_node); + if (out_bridge) { + dsi->out_bridge = out_bridge; + } else { + dsi->panel = of_drm_find_panel(device->dev.of_node); + if (IS_ERR(dsi->panel)) + dsi->panel = NULL; + else + dsi->connector.status = connector_status_connected; + } + + /* + * This is a temporary solution and should be made by more generic way. + * + * If attached panel device is for command mode one, dsi should register + * TE interrupt handler. + */ + if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) { + int ret = samsung_dsim_register_te_irq(dsi, &device->dev); + if (ret) + return ret; + } + + dsi->lanes = device->lanes; + dsi->format = device->format; + dsi->mode_flags = device->mode_flags; + + if (ops && ops->attach) + ops->attach(dsi->dsi_host.dev, device); + + return 0; +} + +static int samsung_dsim_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct samsung_dsim *dsi = host_to_dsi(host); + const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops; + + samsung_dsim_unregister_te_irq(dsi); + + if (ops && ops->detach) + ops->detach(dsi->dsi_host.dev, device); + + samsung_dsim_unregister_te_irq(dsi); + + return 0; +} + +static ssize_t samsung_dsim_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct samsung_dsim *dsi = host_to_dsi(host); + struct samsung_dsim_transfer xfer; + int ret; + + if (!(dsi->state & DSIM_STATE_ENABLED)) + return -EINVAL; + + if (!(dsi->state & DSIM_STATE_INITIALIZED)) { + ret = samsung_dsim_init(dsi); + if (ret) + return ret; + dsi->state |= DSIM_STATE_INITIALIZED; + } + + ret = mipi_dsi_create_packet(&xfer.packet, msg); + if (ret < 0) + return ret; + + xfer.rx_len = msg->rx_len; + xfer.rx_payload = msg->rx_buf; + xfer.flags = msg->flags; + + ret = samsung_dsim_transfer(dsi, &xfer); + return (ret < 0) ? ret : xfer.rx_done; +} + +static const struct mipi_dsi_host_ops samsung_dsim_ops = { + .attach = samsung_dsim_host_attach, + .detach = samsung_dsim_host_detach, + .transfer = samsung_dsim_host_transfer, +}; + +static int samsung_dsim_of_read_u32(const struct device_node *np, + const char *propname, u32 *out_value) +{ + int ret = of_property_read_u32(np, propname, out_value); + + if (ret < 0) + pr_err("%pOF: failed to get '%s' property\n", np, propname); + + return ret; +} + +static int samsung_dsim_parse_dt(struct samsung_dsim *dsi) +{ + struct device *dev = dsi->dev; + struct device_node *node = dev->of_node; + int ret; + + ret = samsung_dsim_of_read_u32(node, "samsung,pll-clock-frequency", + &dsi->pll_clk_rate); + if (ret < 0) + return ret; + + ret = samsung_dsim_of_read_u32(node, "samsung,burst-clock-frequency", + &dsi->burst_clk_rate); + if (ret < 0) + return ret; + + ret = samsung_dsim_of_read_u32(node, "samsung,esc-clock-frequency", + &dsi->esc_clk_rate); + if (ret < 0) + return ret; + + return 0; +} + +static struct samsung_dsim *__samsung_dsim_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct drm_bridge *bridge; + struct resource *res; + struct samsung_dsim *dsi; + int ret, i; + + dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return ERR_PTR(-ENOMEM); + + /* To be checked as invalid one */ + dsi->te_gpio = -ENOENT; + + init_completion(&dsi->completed); + spin_lock_init(&dsi->transfer_lock); + INIT_LIST_HEAD(&dsi->transfer_list); + INIT_LIST_HEAD(&dsi->bridge_chain); + + dsi->dsi_host.ops = &samsung_dsim_ops; + dsi->dsi_host.dev = dev; + + dsi->dev = dev; + dsi->driver_data = of_device_get_match_data(dev); + + dsi->supplies[0].supply = "vddcore"; + dsi->supplies[1].supply = "vddio"; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies), + dsi->supplies); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_info(dev, "failed to get regulators: %d\n", ret); + return ERR_PTR(ret); + } + + dsi->clks = devm_kcalloc(dev, + dsi->driver_data->num_clks, sizeof(*dsi->clks), + GFP_KERNEL); + if (!dsi->clks) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < dsi->driver_data->num_clks; i++) { + dsi->clks[i] = devm_clk_get(dev, clk_names[i]); + if (IS_ERR(dsi->clks[i])) { + if (strcmp(clk_names[i], "sclk_mipi") == 0) { + dsi->clks[i] = devm_clk_get(dev, + OLD_SCLK_MIPI_CLK_NAME); + if (!IS_ERR(dsi->clks[i])) + continue; + } + + dev_info(dev, "failed to get the clock: %s\n", + clk_names[i]); + return ERR_PTR(PTR_ERR(dsi->clks[i])); + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(dsi->reg_base)) { + dev_err(dev, "failed to remap io region\n"); + return dsi->reg_base; + } + + dsi->phy = devm_phy_get(dev, "dsim"); + if (IS_ERR(dsi->phy)) { + dev_info(dev, "failed to get dsim phy\n"); + return ERR_PTR(PTR_ERR(dsi->phy)); + } + + dsi->irq = platform_get_irq(pdev, 0); + if (dsi->irq < 0) + return ERR_PTR(dsi->irq); + + irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, dsi->irq, NULL, + samsung_dsim_irq, IRQF_ONESHOT, + dev_name(dev), dsi); + if (ret) { + dev_err(dev, "failed to request dsi irq\n"); + return ERR_PTR(ret); + } + + ret = samsung_dsim_parse_dt(dsi); + if (ret) + return ERR_PTR(ret); + + ret = mipi_dsi_host_register(&dsi->dsi_host); + if (ret) + return ERR_PTR(ret); + + bridge = &dsi->bridge; + bridge->driver_private = dsi; + bridge->funcs = &samsung_dsim_bridge_funcs; + bridge->of_node = dev->of_node; + drm_bridge_add(bridge); + + return dsi; +} + +static void __samsung_dsim_remove(struct samsung_dsim *dsi) +{ + drm_bridge_remove(&dsi->bridge); + + mipi_dsi_host_unregister(&dsi->dsi_host); +} + +/* + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev) +{ + return __samsung_dsim_probe(pdev); +} +EXPORT_SYMBOL_GPL(samsung_dsim_probe); + +void samsung_dsim_remove(struct samsung_dsim *dsi) +{ + return __samsung_dsim_remove(dsi); +} +EXPORT_SYMBOL_GPL(samsung_dsim_remove); + +/* + * Bind/unbind API, used from platforms based on the component framework. + */ +int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder) +{ + struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder); + + return drm_bridge_attach(encoder, &dsi->bridge, previous, 0); +} +EXPORT_SYMBOL_GPL(samsung_dsim_bind); + +void samsung_dsim_unbind(struct samsung_dsim *dsi) +{ + samsung_dsim_disable(dsi); +} +EXPORT_SYMBOL_GPL(samsung_dsim_unbind); + +int samsung_dsim_suspend(struct samsung_dsim *dsi) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + int ret, i; + + usleep_range(10000, 20000); + + if (dsi->state & DSIM_STATE_INITIALIZED) { + dsi->state &= ~DSIM_STATE_INITIALIZED; + + samsung_dsim_disable_clock(dsi); + + samsung_dsim_disable_irq(dsi); + } + + dsi->state &= ~DSIM_STATE_CMD_LPM; + + phy_power_off(dsi->phy); + + for (i = driver_data->num_clks - 1; i > -1; i--) + clk_disable_unprepare(dsi->clks[i]); + + ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) + dev_err(dsi->dev, "cannot disable regulators %d\n", ret); + + return 0; +} +EXPORT_SYMBOL_GPL(samsung_dsim_suspend); + +int samsung_dsim_resume(struct samsung_dsim *dsi) +{ + const struct samsung_dsim_driver_data *driver_data = dsi->driver_data; + int ret, i; + + ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + if (ret < 0) { + dev_err(dsi->dev, "cannot enable regulators %d\n", ret); + return ret; + } + + for (i = 0; i < driver_data->num_clks; i++) { + ret = clk_prepare_enable(dsi->clks[i]); + if (ret < 0) + goto err_clk; + } + + ret = phy_power_on(dsi->phy); + if (ret < 0) { + dev_err(dsi->dev, "cannot enable phy %d\n", ret); + goto err_clk; + } + + return 0; + +err_clk: + while (--i > -1) + clk_disable_unprepare(dsi->clks[i]); + regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); + + return ret; +} +EXPORT_SYMBOL_GPL(samsung_dsim_resume); + +MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); +MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 6417f374b923..3bc321ab5bc8 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -16,7 +16,6 @@ comment "CRTCs"
config DRM_EXYNOS_FIMD bool "FIMD" - depends on !FB_S3C select MFD_SYSCON help Choose this option if you want to use Exynos FIMD for DRM. @@ -28,7 +27,6 @@ config DRM_EXYNOS5433_DECON
config DRM_EXYNOS7_DECON bool "DECON on Exynos7" - depends on !FB_S3C help Choose this option if you want to use Exynos DECON for DRM.
@@ -55,8 +53,7 @@ config DRM_EXYNOS_DPI config DRM_EXYNOS_DSI bool "MIPI-DSI host" depends on DRM_EXYNOS_FIMD || DRM_EXYNOS5433_DECON || DRM_EXYNOS7_DECON - select DRM_MIPI_DSI - select DRM_PANEL + select DRM_SAMSUNG_DSIM default n help This enables support for Exynos MIPI-DSI device. diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index add70b336935..2fd2f3ee4fcf 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -11,7 +11,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o -exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynos_drm_dsi_pltfm.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index e8aea9d90c34..17f37fa74718 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -5,1774 +5,328 @@ * Copyright (c) 2014 Samsung Electronics Co., Ltd * * Contacts: Tomasz Figa t.figa@samsung.com -*/ + */
-#include <linux/clk.h> -#include <linux/delay.h> #include <linux/component.h> -#include <linux/gpio/consumer.h> -#include <linux/irq.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> -#include <linux/phy/phy.h> -#include <linux/regulator/consumer.h> - -#include <asm/unaligned.h> +#include <linux/pm_runtime.h>
-#include <video/mipi_display.h> -#include <video/videomode.h> - -#include <drm/drm_atomic_helper.h> +#include <drm/bridge/samsung-dsim.h> #include <drm/drm_bridge.h> -#include <drm/drm_fb_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> -#include <drm/drm_print.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_dsi.h" - -/* returns true iff both arguments logically differs */ -#define NEQV(a, b) (!(a) ^ !(b)) - -/* DSIM_STATUS */ -#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) -#define DSIM_STOP_STATE_CLK (1 << 8) -#define DSIM_TX_READY_HS_CLK (1 << 10) -#define DSIM_PLL_STABLE (1 << 31) - -/* DSIM_TIMEOUT */ -#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) -#define DSIM_BTA_TIMEOUT(x) ((x) << 16) - -/* DSIM_CLKCTRL */ -#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) -#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) -#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) -#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) -#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) -#define DSIM_BYTE_CLKEN (1 << 24) -#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) -#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) -#define DSIM_PLL_BYPASS (1 << 27) -#define DSIM_ESC_CLKEN (1 << 28) -#define DSIM_TX_REQUEST_HSCLK (1 << 31) - -/* DSIM_CONFIG */ -#define DSIM_LANE_EN_CLK (1 << 0) -#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) -#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) -#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) -#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) -#define DSIM_SUB_VC (((x) & 0x3) << 16) -#define DSIM_MAIN_VC (((x) & 0x3) << 18) -#define DSIM_HSA_MODE (1 << 20) -#define DSIM_HBP_MODE (1 << 21) -#define DSIM_HFP_MODE (1 << 22) -#define DSIM_HSE_MODE (1 << 23) -#define DSIM_AUTO_MODE (1 << 24) -#define DSIM_VIDEO_MODE (1 << 25) -#define DSIM_BURST_MODE (1 << 26) -#define DSIM_SYNC_INFORM (1 << 27) -#define DSIM_EOT_DISABLE (1 << 28) -#define DSIM_MFLUSH_VS (1 << 29) -/* This flag is valid only for exynos3250/3472/5260/5430 */ -#define DSIM_CLKLANE_STOP (1 << 30) - -/* DSIM_ESCMODE */ -#define DSIM_TX_TRIGGER_RST (1 << 4) -#define DSIM_TX_LPDT_LP (1 << 6) -#define DSIM_CMD_LPDT_LP (1 << 7) -#define DSIM_FORCE_BTA (1 << 16) -#define DSIM_FORCE_STOP_STATE (1 << 20) -#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) -#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21) - -/* DSIM_MDRESOL */ -#define DSIM_MAIN_STAND_BY (1 << 31) -#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) -#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0) - -/* DSIM_MVPORCH */ -#define DSIM_CMD_ALLOW(x) ((x) << 28) -#define DSIM_STABLE_VFP(x) ((x) << 16) -#define DSIM_MAIN_VBP(x) ((x) << 0) -#define DSIM_CMD_ALLOW_MASK (0xf << 28) -#define DSIM_STABLE_VFP_MASK (0x7ff << 16) -#define DSIM_MAIN_VBP_MASK (0x7ff << 0) - -/* DSIM_MHPORCH */ -#define DSIM_MAIN_HFP(x) ((x) << 16) -#define DSIM_MAIN_HBP(x) ((x) << 0) -#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) -#define DSIM_MAIN_HBP_MASK ((0xffff) << 0) - -/* DSIM_MSYNC */ -#define DSIM_MAIN_VSA(x) ((x) << 22) -#define DSIM_MAIN_HSA(x) ((x) << 0) -#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) -#define DSIM_MAIN_HSA_MASK ((0xffff) << 0) - -/* DSIM_SDRESOL */ -#define DSIM_SUB_STANDY(x) ((x) << 31) -#define DSIM_SUB_VRESOL(x) ((x) << 16) -#define DSIM_SUB_HRESOL(x) ((x) << 0) -#define DSIM_SUB_STANDY_MASK ((0x1) << 31) -#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) -#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0) - -/* DSIM_INTSRC */ -#define DSIM_INT_PLL_STABLE (1 << 31) -#define DSIM_INT_SW_RST_RELEASE (1 << 30) -#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) -#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) -#define DSIM_INT_BTA (1 << 25) -#define DSIM_INT_FRAME_DONE (1 << 24) -#define DSIM_INT_RX_TIMEOUT (1 << 21) -#define DSIM_INT_BTA_TIMEOUT (1 << 20) -#define DSIM_INT_RX_DONE (1 << 18) -#define DSIM_INT_RX_TE (1 << 17) -#define DSIM_INT_RX_ACK (1 << 16) -#define DSIM_INT_RX_ECC_ERR (1 << 15) -#define DSIM_INT_RX_CRC_ERR (1 << 14) +#include "exynos_drm_crtc.h" +#include "exynos_drm_drv.h"
-/* DSIM_FIFOCTRL */ -#define DSIM_RX_DATA_FULL (1 << 25) -#define DSIM_RX_DATA_EMPTY (1 << 24) -#define DSIM_SFR_HEADER_FULL (1 << 23) -#define DSIM_SFR_HEADER_EMPTY (1 << 22) -#define DSIM_SFR_PAYLOAD_FULL (1 << 21) -#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) -#define DSIM_I80_HEADER_FULL (1 << 19) -#define DSIM_I80_HEADER_EMPTY (1 << 18) -#define DSIM_I80_PAYLOAD_FULL (1 << 17) -#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) -#define DSIM_SD_HEADER_FULL (1 << 15) -#define DSIM_SD_HEADER_EMPTY (1 << 14) -#define DSIM_SD_PAYLOAD_FULL (1 << 13) -#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) -#define DSIM_MD_HEADER_FULL (1 << 11) -#define DSIM_MD_HEADER_EMPTY (1 << 10) -#define DSIM_MD_PAYLOAD_FULL (1 << 9) -#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) -#define DSIM_RX_FIFO (1 << 4) -#define DSIM_SFR_FIFO (1 << 3) -#define DSIM_I80_FIFO (1 << 2) -#define DSIM_SD_FIFO (1 << 1) -#define DSIM_MD_FIFO (1 << 0) - -/* DSIM_PHYACCHR */ -#define DSIM_AFC_EN (1 << 14) -#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5) - -/* DSIM_PLLCTRL */ -#define DSIM_FREQ_BAND(x) ((x) << 24) -#define DSIM_PLL_EN (1 << 23) -#define DSIM_PLL_P(x) ((x) << 13) -#define DSIM_PLL_M(x) ((x) << 4) -#define DSIM_PLL_S(x) ((x) << 1) - -/* DSIM_PHYCTRL */ -#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) -#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) -#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14) - -/* DSIM_PHYTIMING */ -#define DSIM_PHYTIMING_LPX(x) ((x) << 8) -#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0) - -/* DSIM_PHYTIMING1 */ -#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) -#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) -#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) -#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0) - -/* DSIM_PHYTIMING2 */ -#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) -#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) -#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0) - -#define DSI_MAX_BUS_WIDTH 4 -#define DSI_NUM_VIRTUAL_CHANNELS 4 -#define DSI_TX_FIFO_SIZE 2048 -#define DSI_RX_FIFO_SIZE 256 -#define DSI_XFER_TIMEOUT_MS 100 -#define DSI_RX_FIFO_EMPTY 0x30800002 - -#define OLD_SCLK_MIPI_CLK_NAME "pll_clk" - -static const char *const clk_names[5] = { "bus_clk", "sclk_mipi", - "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0", - "sclk_rgb_vclk_to_dsim0" }; - -enum exynos_dsi_transfer_type { - EXYNOS_DSI_TX, - EXYNOS_DSI_RX, +enum { + DSI_PORT_IN, + DSI_PORT_OUT };
-struct exynos_dsi_transfer { - struct list_head list; - struct completion completed; - int result; - struct mipi_dsi_packet packet; - u16 flags; - u16 tx_done; - - u8 *rx_payload; - u16 rx_len; - u16 rx_done; -}; - -#define DSIM_STATE_ENABLED BIT(0) -#define DSIM_STATE_INITIALIZED BIT(1) -#define DSIM_STATE_CMD_LPM BIT(2) -#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3) - struct exynos_dsi { - struct drm_bridge bridge; - struct mipi_dsi_host dsi_host; - struct drm_connector connector; - struct drm_panel *panel; - struct list_head bridge_chain; - struct drm_bridge *out_bridge; - struct device *dev; - - void __iomem *reg_base; - struct phy *phy; - struct clk **clks; - struct regulator_bulk_data supplies[2]; - int irq; - int te_gpio; - - u32 pll_clk_rate; - u32 burst_clk_rate; - u32 esc_clk_rate; - u32 lanes; - u32 mode_flags; - u32 format; - - struct drm_display_mode mode; - - int state; - struct drm_property *brightness; - struct completion completed; - - spinlock_t transfer_lock; /* protects transfer_list */ - struct list_head transfer_list; - - const struct exynos_dsi_driver_data *driver_data; + struct samsung_dsim *dsi; + struct drm_encoder encoder; };
-#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) -#define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector) - -enum reg_idx { - DSIM_STATUS_REG, /* Status register */ - DSIM_SWRST_REG, /* Software reset register */ - DSIM_CLKCTRL_REG, /* Clock control register */ - DSIM_TIMEOUT_REG, /* Time out register */ - DSIM_CONFIG_REG, /* Configuration register */ - DSIM_ESCMODE_REG, /* Escape mode register */ - DSIM_MDRESOL_REG, - DSIM_MVPORCH_REG, /* Main display Vporch register */ - DSIM_MHPORCH_REG, /* Main display Hporch register */ - DSIM_MSYNC_REG, /* Main display sync area register */ - DSIM_INTSRC_REG, /* Interrupt source register */ - DSIM_INTMSK_REG, /* Interrupt mask register */ - DSIM_PKTHDR_REG, /* Packet Header FIFO register */ - DSIM_PAYLOAD_REG, /* Payload FIFO register */ - DSIM_RXFIFO_REG, /* Read FIFO register */ - DSIM_FIFOCTRL_REG, /* FIFO status and control register */ - DSIM_PLLCTRL_REG, /* PLL control register */ - DSIM_PHYCTRL_REG, - DSIM_PHYTIMING_REG, - DSIM_PHYTIMING1_REG, - DSIM_PHYTIMING2_REG, - NUM_REGS +static const unsigned int reg_values[] = { + [RESET_TYPE] = DSIM_SWRST, + [PLL_TIMER] = 500, + [STOP_STATE_CNT] = 0xf, + [PHYCTRL_ULPS_EXIT] = 0x0af, + [PHYCTRL_VREG_LP] = 0, + [PHYCTRL_SLEW_UP] = 0, + [PHYTIMING_LPX] = 0x06, + [PHYTIMING_HS_EXIT] = 0x0b, + [PHYTIMING_CLK_PREPARE] = 0x07, + [PHYTIMING_CLK_ZERO] = 0x27, + [PHYTIMING_CLK_POST] = 0x0d, + [PHYTIMING_CLK_TRAIL] = 0x08, + [PHYTIMING_HS_PREPARE] = 0x09, + [PHYTIMING_HS_ZERO] = 0x0d, + [PHYTIMING_HS_TRAIL] = 0x0b, };
-static const unsigned int exynos_reg_ofs[] = { - [DSIM_STATUS_REG] = 0x00, - [DSIM_SWRST_REG] = 0x04, - [DSIM_CLKCTRL_REG] = 0x08, - [DSIM_TIMEOUT_REG] = 0x0c, - [DSIM_CONFIG_REG] = 0x10, - [DSIM_ESCMODE_REG] = 0x14, - [DSIM_MDRESOL_REG] = 0x18, - [DSIM_MVPORCH_REG] = 0x1c, - [DSIM_MHPORCH_REG] = 0x20, - [DSIM_MSYNC_REG] = 0x24, - [DSIM_INTSRC_REG] = 0x2c, - [DSIM_INTMSK_REG] = 0x30, - [DSIM_PKTHDR_REG] = 0x34, - [DSIM_PAYLOAD_REG] = 0x38, - [DSIM_RXFIFO_REG] = 0x3c, - [DSIM_FIFOCTRL_REG] = 0x44, - [DSIM_PLLCTRL_REG] = 0x4c, - [DSIM_PHYCTRL_REG] = 0x5c, - [DSIM_PHYTIMING_REG] = 0x64, - [DSIM_PHYTIMING1_REG] = 0x68, - [DSIM_PHYTIMING2_REG] = 0x6c, +static const unsigned int exynos5422_reg_values[] = { + [RESET_TYPE] = DSIM_SWRST, + [PLL_TIMER] = 500, + [STOP_STATE_CNT] = 0xf, + [PHYCTRL_ULPS_EXIT] = 0xaf, + [PHYCTRL_VREG_LP] = 0, + [PHYCTRL_SLEW_UP] = 0, + [PHYTIMING_LPX] = 0x08, + [PHYTIMING_HS_EXIT] = 0x0d, + [PHYTIMING_CLK_PREPARE] = 0x09, + [PHYTIMING_CLK_ZERO] = 0x30, + [PHYTIMING_CLK_POST] = 0x0e, + [PHYTIMING_CLK_TRAIL] = 0x0a, + [PHYTIMING_HS_PREPARE] = 0x0c, + [PHYTIMING_HS_ZERO] = 0x11, + [PHYTIMING_HS_TRAIL] = 0x0d, };
-static const unsigned int exynos5433_reg_ofs[] = { - [DSIM_STATUS_REG] = 0x04, - [DSIM_SWRST_REG] = 0x0C, - [DSIM_CLKCTRL_REG] = 0x10, - [DSIM_TIMEOUT_REG] = 0x14, - [DSIM_CONFIG_REG] = 0x18, - [DSIM_ESCMODE_REG] = 0x1C, - [DSIM_MDRESOL_REG] = 0x20, - [DSIM_MVPORCH_REG] = 0x24, - [DSIM_MHPORCH_REG] = 0x28, - [DSIM_MSYNC_REG] = 0x2C, - [DSIM_INTSRC_REG] = 0x34, - [DSIM_INTMSK_REG] = 0x38, - [DSIM_PKTHDR_REG] = 0x3C, - [DSIM_PAYLOAD_REG] = 0x40, - [DSIM_RXFIFO_REG] = 0x44, - [DSIM_FIFOCTRL_REG] = 0x4C, - [DSIM_PLLCTRL_REG] = 0x94, - [DSIM_PHYCTRL_REG] = 0xA4, - [DSIM_PHYTIMING_REG] = 0xB4, - [DSIM_PHYTIMING1_REG] = 0xB8, - [DSIM_PHYTIMING2_REG] = 0xBC, +static const unsigned int exynos5433_reg_values[] = { + [RESET_TYPE] = DSIM_FUNCRST, + [PLL_TIMER] = 22200, + [STOP_STATE_CNT] = 0xa, + [PHYCTRL_ULPS_EXIT] = 0x190, + [PHYCTRL_VREG_LP] = 1, + [PHYCTRL_SLEW_UP] = 1, + [PHYTIMING_LPX] = 0x07, + [PHYTIMING_HS_EXIT] = 0x0c, + [PHYTIMING_CLK_PREPARE] = 0x09, + [PHYTIMING_CLK_ZERO] = 0x2d, + [PHYTIMING_CLK_POST] = 0x0e, + [PHYTIMING_CLK_TRAIL] = 0x09, + [PHYTIMING_HS_PREPARE] = 0x0b, + [PHYTIMING_HS_ZERO] = 0x10, + [PHYTIMING_HS_TRAIL] = 0x0c, };
-static inline void exynos_dsi_write(struct exynos_dsi *dsi, enum reg_idx idx, - u32 val) -{ - const unsigned int *reg_ofs; - - if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS) - reg_ofs = exynos5433_reg_ofs; - else - reg_ofs = exynos_reg_ofs; - - writel(val, dsi->reg_base + reg_ofs[idx]); -} - -static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) -{ - const unsigned int *reg_ofs; - - if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS) - reg_ofs = exynos5433_reg_ofs; - else - reg_ofs = exynos_reg_ofs; - - return readl(dsi->reg_base + reg_ofs[idx]); -} - -static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) -{ - if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300))) - return; - - dev_err(dsi->dev, "timeout waiting for reset\n"); -} - -static void exynos_dsi_reset(struct exynos_dsi *dsi) -{ - u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE]; - - reinit_completion(&dsi->completed); - exynos_dsi_write(dsi, DSIM_SWRST_REG, reset_val); -} - -#ifndef MHZ -#define MHZ (1000*1000) -#endif - -static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi, - unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s) -{ - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - unsigned long best_freq = 0; - u32 min_delta = 0xffffffff; - u8 p_min, p_max; - u8 _p, best_p; - u16 _m, best_m; - u8 _s, best_s; - - p_min = DIV_ROUND_UP(fin, (12 * MHZ)); - p_max = fin / (6 * MHZ); - - for (_p = p_min; _p <= p_max; ++_p) { - for (_s = 0; _s <= 5; ++_s) { - u64 tmp; - u32 delta; - - tmp = (u64)fout * (_p << _s); - do_div(tmp, fin); - _m = tmp; - if (_m < 41 || _m > 125) - continue; - - tmp = (u64)_m * fin; - do_div(tmp, _p); - if (tmp < 500 * MHZ || - tmp > driver_data->max_freq * MHZ) - continue; - - tmp = (u64)_m * fin; - do_div(tmp, _p << _s); - - delta = abs(fout - tmp); - if (delta < min_delta) { - best_p = _p; - best_m = _m; - best_s = _s; - min_delta = delta; - best_freq = tmp; - } - } - } - - if (best_freq) { - *p = best_p; - *m = best_m; - *s = best_s; - } - - return best_freq; -} - -static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi, - unsigned long freq) -{ - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - unsigned long fin, fout; - int timeout; - u8 p, s; - u16 m; - u32 reg; - - fin = dsi->pll_clk_rate; - fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s); - if (!fout) { - dev_err(dsi->dev, - "failed to find PLL PMS for requested frequency\n"); - return 0; - } - dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s); - - writel(driver_data->reg_values[PLL_TIMER], - dsi->reg_base + driver_data->plltmr_reg); - - reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s); - - if (driver_data->has_freqband) { - static const unsigned long freq_bands[] = { - 100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ, - 270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ, - 510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ, - 770 * MHZ, 870 * MHZ, 950 * MHZ, - }; - int band; - - for (band = 0; band < ARRAY_SIZE(freq_bands); ++band) - if (fout < freq_bands[band]) - break; - - dev_dbg(dsi->dev, "band %d\n", band); - - reg |= DSIM_FREQ_BAND(band); - } - - exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg); - - timeout = 1000; - do { - if (timeout-- == 0) { - dev_err(dsi->dev, "PLL failed to stabilize\n"); - return 0; - } - reg = exynos_dsi_read(dsi, DSIM_STATUS_REG); - } while ((reg & DSIM_PLL_STABLE) == 0); - - return fout; -} - -static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) -{ - unsigned long hs_clk, byte_clk, esc_clk; - unsigned long esc_div; - u32 reg; - - hs_clk = exynos_dsi_set_pll(dsi, dsi->burst_clk_rate); - if (!hs_clk) { - dev_err(dsi->dev, "failed to configure DSI PLL\n"); - return -EFAULT; - } - - byte_clk = hs_clk / 8; - esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate); - esc_clk = byte_clk / esc_div; - - if (esc_clk > 20 * MHZ) { - ++esc_div; - esc_clk = byte_clk / esc_div; - } - - dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n", - hs_clk, byte_clk, esc_clk); - - reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG); - reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK - | DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS - | DSIM_BYTE_CLK_SRC_MASK); - reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN - | DSIM_ESC_PRESCALER(esc_div) - | DSIM_LANE_ESC_CLK_EN_CLK - | DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1) - | DSIM_BYTE_CLK_SRC(0) - | DSIM_TX_REQUEST_HSCLK; - exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg); - - return 0; -} - -static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) -{ - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - const unsigned int *reg_values = driver_data->reg_values; - u32 reg; - - if (driver_data->has_freqband) - return; - - /* B D-PHY: D-PHY Master & Slave Analog Block control */ - reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]); - if (reg_values[PHYCTRL_VREG_LP]) - reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP; - if (reg_values[PHYCTRL_SLEW_UP]) - reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP; - exynos_dsi_write(dsi, DSIM_PHYCTRL_REG, reg); - - /* - * T LPX: Transmitted length of any Low-Power state period - * T HS-EXIT: Time that the transmitter drives LP-11 following a HS - * burst - */ - reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) | - DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]); - exynos_dsi_write(dsi, DSIM_PHYTIMING_REG, reg); - - /* - * T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00 - * Line state immediately before the HS-0 Line state starting the - * HS transmission - * T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to - * transmitting the Clock. - * T CLK_POST: Time that the transmitter continues to send HS clock - * after the last associated Data Lane has transitioned to LP Mode - * Interval is defined as the period from the end of T HS-TRAIL to - * the beginning of T CLK-TRAIL - * T CLK-TRAIL: Time that the transmitter drives the HS-0 state after - * the last payload clock bit of a HS transmission burst - */ - reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) | - DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) | - DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) | - DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]); - exynos_dsi_write(dsi, DSIM_PHYTIMING1_REG, reg); - - /* - * T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00 - * Line state immediately before the HS-0 Line state starting the - * HS transmission - * T HS-ZERO: Time that the transmitter drives the HS-0 state prior to - * transmitting the Sync sequence. - * T HS-TRAIL: Time that the transmitter drives the flipped differential - * state after last payload data bit of a HS transmission burst - */ - reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) | - DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) | - DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]); - exynos_dsi_write(dsi, DSIM_PHYTIMING2_REG, reg); -} - -static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) -{ - u32 reg; - - reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG); - reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK - | DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN); - exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg); - - reg = exynos_dsi_read(dsi, DSIM_PLLCTRL_REG); - reg &= ~DSIM_PLL_EN; - exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg); -} - -static void exynos_dsi_enable_lane(struct exynos_dsi *dsi, u32 lane) -{ - u32 reg = exynos_dsi_read(dsi, DSIM_CONFIG_REG); - reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK | - DSIM_LANE_EN(lane)); - exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg); -} - -static int exynos_dsi_init_link(struct exynos_dsi *dsi) +static int exynos_dsi_host_attach(struct device *dev, + struct mipi_dsi_device *device) { - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - int timeout; - u32 reg; - u32 lanes_mask; - - /* Initialize FIFO pointers */ - reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG); - reg &= ~0x1f; - exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg); - - usleep_range(9000, 11000); - - reg |= 0x1f; - exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg); - usleep_range(9000, 11000); + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_device *drm = dsi->encoder.dev; + struct exynos_drm_crtc *crtc;
- /* DSI configuration */ - reg = 0; + mutex_lock(&drm->mode_config.mutex); + crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD); + crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO); + mutex_unlock(&drm->mode_config.mutex);
- /* - * The first bit of mode_flags specifies display configuration. - * If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video - * mode, otherwise it will support command mode. - */ - if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { - reg |= DSIM_VIDEO_MODE; - - /* - * The user manual describes that following bits are ignored in - * command mode. - */ - if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH)) - reg |= DSIM_MFLUSH_VS; - if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) - reg |= DSIM_SYNC_INFORM; - if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) - reg |= DSIM_BURST_MODE; - if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT) - reg |= DSIM_AUTO_MODE; - if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE) - reg |= DSIM_HSE_MODE; - if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP)) - reg |= DSIM_HFP_MODE; - if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP)) - reg |= DSIM_HBP_MODE; - if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA)) - reg |= DSIM_HSA_MODE; - } - - if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET)) - reg |= DSIM_EOT_DISABLE; - - switch (dsi->format) { - case MIPI_DSI_FMT_RGB888: - reg |= DSIM_MAIN_PIX_FORMAT_RGB888; - break; - case MIPI_DSI_FMT_RGB666: - reg |= DSIM_MAIN_PIX_FORMAT_RGB666; - break; - case MIPI_DSI_FMT_RGB666_PACKED: - reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P; - break; - case MIPI_DSI_FMT_RGB565: - reg |= DSIM_MAIN_PIX_FORMAT_RGB565; - break; - default: - dev_err(dsi->dev, "invalid pixel format\n"); - return -EINVAL; - } - - /* - * Use non-continuous clock mode if the periparal wants and - * host controller supports - * - * In non-continous clock mode, host controller will turn off - * the HS clock between high-speed transmissions to reduce - * power consumption. - */ - if (driver_data->has_clklane_stop && - dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) { - reg |= DSIM_CLKLANE_STOP; - } - exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg); - - lanes_mask = BIT(dsi->lanes) - 1; - exynos_dsi_enable_lane(dsi, lanes_mask); - - /* Check clock and data lane state are stop state */ - timeout = 100; - do { - if (timeout-- == 0) { - dev_err(dsi->dev, "waiting for bus lanes timed out\n"); - return -EFAULT; - } - - reg = exynos_dsi_read(dsi, DSIM_STATUS_REG); - if ((reg & DSIM_STOP_STATE_DAT(lanes_mask)) - != DSIM_STOP_STATE_DAT(lanes_mask)) - continue; - } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK))); - - reg = exynos_dsi_read(dsi, DSIM_ESCMODE_REG); - reg &= ~DSIM_STOP_STATE_CNT_MASK; - reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]); - exynos_dsi_write(dsi, DSIM_ESCMODE_REG, reg); - - reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff); - exynos_dsi_write(dsi, DSIM_TIMEOUT_REG, reg); + if (drm->mode_config.poll_enabled) + drm_kms_helper_hotplug_event(drm);
return 0; }
-static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) -{ - struct drm_display_mode *m = &dsi->mode; - unsigned int num_bits_resol = dsi->driver_data->num_bits_resol; - u32 reg; - - if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) { - reg = DSIM_CMD_ALLOW(0xf) - | DSIM_STABLE_VFP(m->vsync_start - m->vdisplay) - | DSIM_MAIN_VBP(m->vtotal - m->vsync_end); - exynos_dsi_write(dsi, DSIM_MVPORCH_REG, reg); - - reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay) - | DSIM_MAIN_HBP(m->htotal - m->hsync_end); - exynos_dsi_write(dsi, DSIM_MHPORCH_REG, reg); - - reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start) - | DSIM_MAIN_HSA(m->hsync_end - m->hsync_start); - exynos_dsi_write(dsi, DSIM_MSYNC_REG, reg); - } - reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) | - DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol); - - exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg); - - dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay); -} - -static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) -{ - u32 reg; - - reg = exynos_dsi_read(dsi, DSIM_MDRESOL_REG); - if (enable) - reg |= DSIM_MAIN_STAND_BY; - else - reg &= ~DSIM_MAIN_STAND_BY; - exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg); -} - -static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) -{ - int timeout = 2000; - - do { - u32 reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG); - - if (!(reg & DSIM_SFR_HEADER_FULL)) - return 0; - - if (!cond_resched()) - usleep_range(950, 1050); - } while (--timeout); - - return -ETIMEDOUT; -} - -static void exynos_dsi_set_cmd_lpm(struct exynos_dsi *dsi, bool lpm) -{ - u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG); - - if (lpm) - v |= DSIM_CMD_LPDT_LP; - else - v &= ~DSIM_CMD_LPDT_LP; - - exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v); -} - -static void exynos_dsi_force_bta(struct exynos_dsi *dsi) -{ - u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG); - v |= DSIM_FORCE_BTA; - exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v); -} - -static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi, - struct exynos_dsi_transfer *xfer) -{ - struct device *dev = dsi->dev; - struct mipi_dsi_packet *pkt = &xfer->packet; - const u8 *payload = pkt->payload + xfer->tx_done; - u16 length = pkt->payload_length - xfer->tx_done; - bool first = !xfer->tx_done; - u32 reg; - - dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n", - xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done); - - if (length > DSI_TX_FIFO_SIZE) - length = DSI_TX_FIFO_SIZE; - - xfer->tx_done += length; - - /* Send payload */ - while (length >= 4) { - reg = get_unaligned_le32(payload); - exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg); - payload += 4; - length -= 4; - } - - reg = 0; - switch (length) { - case 3: - reg |= payload[2] << 16; - fallthrough; - case 2: - reg |= payload[1] << 8; - fallthrough; - case 1: - reg |= payload[0]; - exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg); - break; - } - - /* Send packet header */ - if (!first) - return; - - reg = get_unaligned_le32(pkt->header); - if (exynos_dsi_wait_for_hdr_fifo(dsi)) { - dev_err(dev, "waiting for header FIFO timed out\n"); - return; - } - - if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM, - dsi->state & DSIM_STATE_CMD_LPM)) { - exynos_dsi_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM); - dsi->state ^= DSIM_STATE_CMD_LPM; - } - - exynos_dsi_write(dsi, DSIM_PKTHDR_REG, reg); - - if (xfer->flags & MIPI_DSI_MSG_REQ_ACK) - exynos_dsi_force_bta(dsi); -} - -static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi, - struct exynos_dsi_transfer *xfer) -{ - u8 *payload = xfer->rx_payload + xfer->rx_done; - bool first = !xfer->rx_done; - struct device *dev = dsi->dev; - u16 length; - u32 reg; - - if (first) { - reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG); - - switch (reg & 0x3f) { - case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE: - case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: - if (xfer->rx_len >= 2) { - payload[1] = reg >> 16; - ++xfer->rx_done; - } - fallthrough; - case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE: - case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: - payload[0] = reg >> 8; - ++xfer->rx_done; - xfer->rx_len = xfer->rx_done; - xfer->result = 0; - goto clear_fifo; - case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: - dev_err(dev, "DSI Error Report: 0x%04x\n", - (reg >> 8) & 0xffff); - xfer->result = 0; - goto clear_fifo; - } - - length = (reg >> 8) & 0xffff; - if (length > xfer->rx_len) { - dev_err(dev, - "response too long (%u > %u bytes), stripping\n", - xfer->rx_len, length); - length = xfer->rx_len; - } else if (length < xfer->rx_len) - xfer->rx_len = length; - } - - length = xfer->rx_len - xfer->rx_done; - xfer->rx_done += length; - - /* Receive payload */ - while (length >= 4) { - reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG); - payload[0] = (reg >> 0) & 0xff; - payload[1] = (reg >> 8) & 0xff; - payload[2] = (reg >> 16) & 0xff; - payload[3] = (reg >> 24) & 0xff; - payload += 4; - length -= 4; - } - - if (length) { - reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG); - switch (length) { - case 3: - payload[2] = (reg >> 16) & 0xff; - fallthrough; - case 2: - payload[1] = (reg >> 8) & 0xff; - fallthrough; - case 1: - payload[0] = reg & 0xff; - } - } - - if (xfer->rx_done == xfer->rx_len) - xfer->result = 0; - -clear_fifo: - length = DSI_RX_FIFO_SIZE / 4; - do { - reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG); - if (reg == DSI_RX_FIFO_EMPTY) - break; - } while (--length); -} - -static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) -{ - unsigned long flags; - struct exynos_dsi_transfer *xfer; - bool start = false; - -again: - spin_lock_irqsave(&dsi->transfer_lock, flags); - - if (list_empty(&dsi->transfer_list)) { - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - return; - } - - xfer = list_first_entry(&dsi->transfer_list, - struct exynos_dsi_transfer, list); - - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - - if (xfer->packet.payload_length && - xfer->tx_done == xfer->packet.payload_length) - /* waiting for RX */ - return; - - exynos_dsi_send_to_fifo(dsi, xfer); - - if (xfer->packet.payload_length || xfer->rx_len) - return; - - xfer->result = 0; - complete(&xfer->completed); - - spin_lock_irqsave(&dsi->transfer_lock, flags); - - list_del_init(&xfer->list); - start = !list_empty(&dsi->transfer_list); - - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - - if (start) - goto again; -} - -static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) -{ - struct exynos_dsi_transfer *xfer; - unsigned long flags; - bool start = true; - - spin_lock_irqsave(&dsi->transfer_lock, flags); - - if (list_empty(&dsi->transfer_list)) { - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - return false; - } - - xfer = list_first_entry(&dsi->transfer_list, - struct exynos_dsi_transfer, list); - - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - - dev_dbg(dsi->dev, - "> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n", - xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len, - xfer->rx_done); - - if (xfer->tx_done != xfer->packet.payload_length) - return true; - - if (xfer->rx_done != xfer->rx_len) - exynos_dsi_read_from_fifo(dsi, xfer); - - if (xfer->rx_done != xfer->rx_len) - return true; - - spin_lock_irqsave(&dsi->transfer_lock, flags); - - list_del_init(&xfer->list); - start = !list_empty(&dsi->transfer_list); - - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - - if (!xfer->rx_len) - xfer->result = 0; - complete(&xfer->completed); - - return start; -} - -static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi, - struct exynos_dsi_transfer *xfer) -{ - unsigned long flags; - bool start; - - spin_lock_irqsave(&dsi->transfer_lock, flags); - - if (!list_empty(&dsi->transfer_list) && - xfer == list_first_entry(&dsi->transfer_list, - struct exynos_dsi_transfer, list)) { - list_del_init(&xfer->list); - start = !list_empty(&dsi->transfer_list); - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - if (start) - exynos_dsi_transfer_start(dsi); - return; - } - - list_del_init(&xfer->list); - - spin_unlock_irqrestore(&dsi->transfer_lock, flags); -} - -static int exynos_dsi_transfer(struct exynos_dsi *dsi, - struct exynos_dsi_transfer *xfer) -{ - unsigned long flags; - bool stopped; - - xfer->tx_done = 0; - xfer->rx_done = 0; - xfer->result = -ETIMEDOUT; - init_completion(&xfer->completed); - - spin_lock_irqsave(&dsi->transfer_lock, flags); - - stopped = list_empty(&dsi->transfer_list); - list_add_tail(&xfer->list, &dsi->transfer_list); - - spin_unlock_irqrestore(&dsi->transfer_lock, flags); - - if (stopped) - exynos_dsi_transfer_start(dsi); - - wait_for_completion_timeout(&xfer->completed, - msecs_to_jiffies(DSI_XFER_TIMEOUT_MS)); - if (xfer->result == -ETIMEDOUT) { - struct mipi_dsi_packet *pkt = &xfer->packet; - exynos_dsi_remove_transfer(dsi, xfer); - dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header, - (int)pkt->payload_length, pkt->payload); - return -ETIMEDOUT; - } - - /* Also covers hardware timeout condition */ - return xfer->result; -} - -static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) -{ - struct exynos_dsi *dsi = dev_id; - u32 status; - - status = exynos_dsi_read(dsi, DSIM_INTSRC_REG); - if (!status) { - static unsigned long int j; - if (printk_timed_ratelimit(&j, 500)) - dev_warn(dsi->dev, "spurious interrupt\n"); - return IRQ_HANDLED; - } - exynos_dsi_write(dsi, DSIM_INTSRC_REG, status); - - if (status & DSIM_INT_SW_RST_RELEASE) { - u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY | - DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR | - DSIM_INT_SW_RST_RELEASE); - exynos_dsi_write(dsi, DSIM_INTMSK_REG, mask); - complete(&dsi->completed); - return IRQ_HANDLED; - } - - if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY | - DSIM_INT_PLL_STABLE))) - return IRQ_HANDLED; - - if (exynos_dsi_transfer_finish(dsi)) - exynos_dsi_transfer_start(dsi); - - return IRQ_HANDLED; -} - -static irqreturn_t exynos_dsi_te_irq_handler(int irq, void *dev_id) -{ - struct exynos_dsi *dsi = dev_id; - const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; - - if (ops && ops->te_handler && - (dsi->state & DSIM_STATE_VIDOUT_AVAILABLE)) - ops->te_handler(dsi->dsi_host.dev); - - return IRQ_HANDLED; -} - -static void exynos_dsi_enable_irq(struct exynos_dsi *dsi) -{ - enable_irq(dsi->irq); - - if (gpio_is_valid(dsi->te_gpio)) - enable_irq(gpio_to_irq(dsi->te_gpio)); -} - -static void exynos_dsi_disable_irq(struct exynos_dsi *dsi) -{ - if (gpio_is_valid(dsi->te_gpio)) - disable_irq(gpio_to_irq(dsi->te_gpio)); - - disable_irq(dsi->irq); -} - -static int exynos_dsi_init(struct exynos_dsi *dsi) +static int exynos_dsi_host_detach(struct device *dev, + struct mipi_dsi_device *device) { - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - - exynos_dsi_reset(dsi); - exynos_dsi_enable_irq(dsi); + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_device *drm = dsi->encoder.dev;
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST) - exynos_dsi_enable_lane(dsi, BIT(dsi->lanes) - 1); - - exynos_dsi_enable_clock(dsi); - if (driver_data->wait_for_reset) - exynos_dsi_wait_for_reset(dsi); - exynos_dsi_set_phy_ctrl(dsi); - exynos_dsi_init_link(dsi); + if (drm->mode_config.poll_enabled) + drm_kms_helper_hotplug_event(drm);
return 0; }
-static int exynos_dsi_register_te_irq(struct exynos_dsi *dsi, - struct device *panel) +static void exynos_dsi_te_handler(struct device *dev) { - int ret; - int te_gpio_irq; + struct exynos_dsi *dsi = dev_get_drvdata(dev);
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0); - if (dsi->te_gpio == -ENOENT) - return 0; - - if (!gpio_is_valid(dsi->te_gpio)) { - ret = dsi->te_gpio; - dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret); - goto out; - } - - ret = gpio_request(dsi->te_gpio, "te_gpio"); - if (ret) { - dev_err(dsi->dev, "gpio request failed with %d\n", ret); - goto out; - } - - te_gpio_irq = gpio_to_irq(dsi->te_gpio); - irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN); - - ret = request_threaded_irq(te_gpio_irq, exynos_dsi_te_irq_handler, NULL, - IRQF_TRIGGER_RISING, "TE", dsi); - if (ret) { - dev_err(dsi->dev, "request interrupt failed with %d\n", ret); - gpio_free(dsi->te_gpio); - goto out; - } - -out: - return ret; + exynos_drm_crtc_te_handler(dsi->encoder.crtc); }
-static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) -{ - if (gpio_is_valid(dsi->te_gpio)) { - free_irq(gpio_to_irq(dsi->te_gpio), dsi); - gpio_free(dsi->te_gpio); - dsi->te_gpio = -ENOENT; - } -} - -static void exynos_dsi_enable(struct exynos_dsi *dsi) -{ - struct drm_bridge *iter; - int ret; - - if (dsi->state & DSIM_STATE_ENABLED) - return; - - pm_runtime_get_sync(dsi->dev); - dsi->state |= DSIM_STATE_ENABLED; - - if (dsi->panel) { - ret = drm_panel_prepare(dsi->panel); - if (ret < 0) - goto err_put_sync; - } else { - list_for_each_entry_reverse(iter, &dsi->bridge_chain, - chain_node) { - if (iter->funcs->pre_enable) - iter->funcs->pre_enable(iter); - } - } - - exynos_dsi_set_display_mode(dsi); - exynos_dsi_set_display_enable(dsi, true); - - if (dsi->panel) { - ret = drm_panel_enable(dsi->panel); - if (ret < 0) - goto err_display_disable; - } else { - list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { - if (iter->funcs->enable) - iter->funcs->enable(iter); - } - } - - dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE; - return; - -err_display_disable: - exynos_dsi_set_display_enable(dsi, false); - drm_panel_unprepare(dsi->panel); - -err_put_sync: - dsi->state &= ~DSIM_STATE_ENABLED; - pm_runtime_put(dsi->dev); -} - -static void exynos_dsi_disable(struct exynos_dsi *dsi) -{ - struct drm_bridge *iter; - - if (!(dsi->state & DSIM_STATE_ENABLED)) - return; - - dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE; - - drm_panel_disable(dsi->panel); - - list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) { - if (iter->funcs->disable) - iter->funcs->disable(iter); - } - - exynos_dsi_set_display_enable(dsi, false); - drm_panel_unprepare(dsi->panel); - - list_for_each_entry(iter, &dsi->bridge_chain, chain_node) { - if (iter->funcs->post_disable) - iter->funcs->post_disable(iter); - } - - dsi->state &= ~DSIM_STATE_ENABLED; - pm_runtime_put_sync(dsi->dev); -} - -static enum drm_connector_status -exynos_dsi_detect(struct drm_connector *connector, bool force) -{ - return connector->status; -} +static const struct samsung_dsim_host_ops exynos_dsi_host_ops = { + .attach = exynos_dsi_host_attach, + .detach = exynos_dsi_host_detach, + .te_handler = exynos_dsi_te_handler, +};
-static void exynos_dsi_connector_destroy(struct drm_connector *connector) -{ - drm_connector_unregister(connector); - drm_connector_cleanup(connector); - connector->dev = NULL; -} +static const struct samsung_dsim_driver_data exynos3_dsi_driver_data = { + .reg_ofs = EXYNOS_REG_OFS, + .plltmr_reg = 0x50, + .has_freqband = 1, + .has_clklane_stop = 1, + .num_clks = 2, + .max_freq = 1000, + .wait_for_reset = 1, + .num_bits_resol = 11, + .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, +};
-static const struct drm_connector_funcs exynos_dsi_connector_funcs = { - .detect = exynos_dsi_detect, - .fill_modes = drm_helper_probe_single_connector_modes, - .destroy = exynos_dsi_connector_destroy, - .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 samsung_dsim_driver_data exynos4_dsi_driver_data = { + .reg_ofs = EXYNOS_REG_OFS, + .plltmr_reg = 0x50, + .has_freqband = 1, + .has_clklane_stop = 1, + .num_clks = 2, + .max_freq = 1000, + .wait_for_reset = 1, + .num_bits_resol = 11, + .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, };
-static int exynos_dsi_get_modes(struct drm_connector *connector) -{ - struct exynos_dsi *dsi = connector_to_dsi(connector); +static const struct samsung_dsim_driver_data exynos5_dsi_driver_data = { + .reg_ofs = EXYNOS_REG_OFS, + .plltmr_reg = 0x58, + .num_clks = 2, + .max_freq = 1000, + .wait_for_reset = 1, + .num_bits_resol = 11, + .reg_values = reg_values, + .host_ops = &exynos_dsi_host_ops, +};
- if (dsi->panel) - return drm_panel_get_modes(dsi->panel, connector); +static const struct samsung_dsim_driver_data exynos5433_dsi_driver_data = { + .reg_ofs = EXYNOS5433_REG_OFS, + .plltmr_reg = 0xa0, + .has_clklane_stop = 1, + .num_clks = 5, + .max_freq = 1500, + .wait_for_reset = 0, + .num_bits_resol = 12, + .reg_values = exynos5433_reg_values, + .host_ops = &exynos_dsi_host_ops, +};
- return 0; -} +static const struct samsung_dsim_driver_data exynos5422_dsi_driver_data = { + .reg_ofs = EXYNOS5433_REG_OFS, + .plltmr_reg = 0xa0, + .has_clklane_stop = 1, + .num_clks = 2, + .max_freq = 1500, + .wait_for_reset = 1, + .num_bits_resol = 12, + .reg_values = exynos5422_reg_values, + .host_ops = &exynos_dsi_host_ops, +};
-static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs = { - .get_modes = exynos_dsi_get_modes, +static const struct of_device_id exynos_dsi_of_match[] = { + { .compatible = "samsung,exynos3250-mipi-dsi", + .data = &exynos3_dsi_driver_data }, + { .compatible = "samsung,exynos4210-mipi-dsi", + .data = &exynos4_dsi_driver_data }, + { .compatible = "samsung,exynos5410-mipi-dsi", + .data = &exynos5_dsi_driver_data }, + { .compatible = "samsung,exynos5422-mipi-dsi", + .data = &exynos5422_dsi_driver_data }, + { .compatible = "samsung,exynos5433-mipi-dsi", + .data = &exynos5433_dsi_driver_data }, + { } };
-static int exynos_dsi_create_connector(struct exynos_dsi *dsi) +static int exynos_dsi_bind(struct device *dev, + struct device *master, void *data) { - struct drm_connector *connector = &dsi->connector; - struct drm_device *drm = dsi->bridge.dev; + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder; + struct drm_device *drm_dev = data; + struct device_node *in_bridge_node; + struct drm_bridge *in_bridge; int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD; + drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = drm_connector_init(drm, connector, &exynos_dsi_connector_funcs, - DRM_MODE_CONNECTOR_DSI); - if (ret) { - DRM_DEV_ERROR(dsi->dev, - "Failed to initialize connector with drm\n"); + ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); + if (ret < 0) return ret; - } - - connector->status = connector_status_disconnected; - drm_connector_helper_add(connector, &exynos_dsi_connector_helper_funcs); - drm_connector_attach_encoder(connector, dsi->bridge.encoder); - if (!drm->registered) - return 0; - - connector->funcs->reset(connector); - drm_connector_register(connector); - return 0; -} - -static int exynos_dsi_bridge_attach(struct drm_bridge *bridge, - enum drm_bridge_attach_flags flags) -{ - struct exynos_dsi *dsi = bridge->driver_private; - struct drm_encoder *encoder = bridge->encoder; - int ret; - - if (!dsi->out_bridge && !dsi->panel) - return -EPROBE_DEFER; - - if (dsi->out_bridge) { - ret = drm_bridge_attach(encoder, dsi->out_bridge, - bridge, flags); - if (ret) - return ret; - list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain); - } else { - ret = exynos_dsi_create_connector(dsi); - if (ret) - return ret; - - if (dsi->panel) { - dsi->connector.status = connector_status_connected; - } - }
- return 0; -} - -static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) -{ - struct exynos_dsi *dsi = bridge->driver_private; - struct drm_encoder *encoder = bridge->encoder; - struct drm_device *drm = encoder->dev; - - if (dsi->panel) { - mutex_lock(&drm->mode_config.mutex); - exynos_dsi_disable(dsi); - dsi->panel = NULL; - dsi->connector.status = connector_status_disconnected; - mutex_unlock(&drm->mode_config.mutex); - } else { - if (dsi->out_bridge->funcs->detach) - dsi->out_bridge->funcs->detach(dsi->out_bridge); - dsi->out_bridge = NULL; - INIT_LIST_HEAD(&dsi->bridge_chain); + in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); + if (in_bridge_node) { + in_bridge = of_drm_find_bridge(in_bridge_node); + if (in_bridge) + drm_bridge_attach(encoder, in_bridge, NULL, 0); + of_node_put(in_bridge_node); } -} - -static void exynos_dsi_bridge_enable(struct drm_bridge *bridge) -{ - struct exynos_dsi *dsi = bridge->driver_private; - - exynos_dsi_enable(dsi); -} - -static void exynos_dsi_bridge_disable(struct drm_bridge *bridge) -{ - struct exynos_dsi *dsi = bridge->driver_private; - - exynos_dsi_disable(dsi); -}
-static void exynos_dsi_bridge_mode_set(struct drm_bridge *bridge, - const struct drm_display_mode *mode, - const struct drm_display_mode *adjusted_mode) -{ - struct exynos_dsi *dsi = bridge->driver_private; - - /* The mode is set when actually enabling the device. */ - drm_mode_copy(&dsi->mode, adjusted_mode); -} - -static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = { - .attach = exynos_dsi_bridge_attach, - .detach = exynos_dsi_bridge_detach, - .enable = exynos_dsi_bridge_enable, - .disable = exynos_dsi_bridge_disable, - .mode_set = exynos_dsi_bridge_mode_set, -}; - -static int exynos_dsi_host_attach(struct mipi_dsi_host *host, - struct mipi_dsi_device *device) -{ - struct exynos_dsi *dsi = host_to_dsi(host); - const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops; - struct drm_bridge *out_bridge; - - out_bridge = of_drm_find_bridge(device->dev.of_node); - if (out_bridge) { - dsi->out_bridge = out_bridge; - } else { - dsi->panel = of_drm_find_panel(device->dev.of_node); - if (IS_ERR(dsi->panel)) - dsi->panel = NULL; - else - dsi->connector.status = connector_status_connected; - } - - /* - * This is a temporary solution and should be made by more generic way. - * - * If attached panel device is for command mode one, dsi should register - * TE interrupt handler. - */ - if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) { - int ret = exynos_dsi_register_te_irq(dsi, &device->dev); - if (ret) - return ret; - } - - dsi->lanes = device->lanes; - dsi->format = device->format; - dsi->mode_flags = device->mode_flags; - - if (ops && ops->attach) - ops->attach(dsi->dsi_host.dev, device); + ret = samsung_dsim_bind(dsi->dsi, encoder); + if (ret) + goto err;
return 0; -} - -static int exynos_dsi_host_detach(struct mipi_dsi_host *host, - struct mipi_dsi_device *device) -{ - struct exynos_dsi *dsi = host_to_dsi(host); - const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->detach) - ops->detach(dsi->dsi_host.dev, device); - - exynos_dsi_unregister_te_irq(dsi); - - return 0; +err: + drm_encoder_cleanup(encoder); + return ret; }
-static ssize_t exynos_dsi_host_transfer(struct mipi_dsi_host *host, - const struct mipi_dsi_msg *msg) +static void exynos_dsi_unbind(struct device *dev, + struct device *master, void *data) { - struct exynos_dsi *dsi = host_to_dsi(host); - struct exynos_dsi_transfer xfer; - int ret; + struct exynos_dsi *dsi = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dsi->encoder;
- if (!(dsi->state & DSIM_STATE_ENABLED)) - return -EINVAL; - - if (!(dsi->state & DSIM_STATE_INITIALIZED)) { - ret = exynos_dsi_init(dsi); - if (ret) - return ret; - dsi->state |= DSIM_STATE_INITIALIZED; - } - - ret = mipi_dsi_create_packet(&xfer.packet, msg); - if (ret < 0) - return ret; + samsung_dsim_unbind(dsi->dsi);
- xfer.rx_len = msg->rx_len; - xfer.rx_payload = msg->rx_buf; - xfer.flags = msg->flags; - - ret = exynos_dsi_transfer(dsi, &xfer); - return (ret < 0) ? ret : xfer.rx_done; + drm_encoder_cleanup(encoder); }
-static const struct mipi_dsi_host_ops exynos_dsi_ops = { - .attach = exynos_dsi_host_attach, - .detach = exynos_dsi_host_detach, - .transfer = exynos_dsi_host_transfer, +static const struct component_ops exynos_dsi_component_ops = { + .bind = exynos_dsi_bind, + .unbind = exynos_dsi_unbind, };
-static int exynos_dsi_of_read_u32(const struct device_node *np, - const char *propname, u32 *out_value) -{ - int ret = of_property_read_u32(np, propname, out_value); - - if (ret < 0) - pr_err("%pOF: failed to get '%s' property\n", np, propname); - - return ret; -} - -static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) -{ - struct device *dev = dsi->dev; - struct device_node *node = dev->of_node; - int ret; - - ret = exynos_dsi_of_read_u32(node, "samsung,pll-clock-frequency", - &dsi->pll_clk_rate); - if (ret < 0) - return ret; - - ret = exynos_dsi_of_read_u32(node, "samsung,burst-clock-frequency", - &dsi->burst_clk_rate); - if (ret < 0) - return ret; - - ret = exynos_dsi_of_read_u32(node, "samsung,esc-clock-frequency", - &dsi->esc_clk_rate); - if (ret < 0) - return ret; - - return 0; -} - -static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) +static int exynos_dsi_probe(struct platform_device *pdev) { - struct device *dev = &pdev->dev; - struct drm_bridge *bridge; - struct resource *res; struct exynos_dsi *dsi; - int ret, i; + struct device *dev = &pdev->dev; + int ret;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); if (!dsi) - return ERR_PTR(-ENOMEM); + return -ENOMEM; + platform_set_drvdata(pdev, dsi);
- /* To be checked as invalid one */ - dsi->te_gpio = -ENOENT; + dsi->dsi = samsung_dsim_probe(pdev); + if (IS_ERR(dsi->dsi)) + return PTR_ERR(dsi->dsi);
- init_completion(&dsi->completed); - spin_lock_init(&dsi->transfer_lock); - INIT_LIST_HEAD(&dsi->transfer_list); - INIT_LIST_HEAD(&dsi->bridge_chain); + pm_runtime_enable(dev);
- dsi->dsi_host.ops = &exynos_dsi_ops; - dsi->dsi_host.dev = dev; - - dsi->dev = dev; - dsi->driver_data = of_device_get_match_data(dev); - - dsi->supplies[0].supply = "vddcore"; - dsi->supplies[1].supply = "vddio"; - ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies), - dsi->supplies); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_info(dev, "failed to get regulators: %d\n", ret); - return ERR_PTR(ret); - } - - dsi->clks = devm_kcalloc(dev, - dsi->driver_data->num_clks, sizeof(*dsi->clks), - GFP_KERNEL); - if (!dsi->clks) - return ERR_PTR(-ENOMEM); - - for (i = 0; i < dsi->driver_data->num_clks; i++) { - dsi->clks[i] = devm_clk_get(dev, clk_names[i]); - if (IS_ERR(dsi->clks[i])) { - if (strcmp(clk_names[i], "sclk_mipi") == 0) { - dsi->clks[i] = devm_clk_get(dev, - OLD_SCLK_MIPI_CLK_NAME); - if (!IS_ERR(dsi->clks[i])) - continue; - } - - dev_info(dev, "failed to get the clock: %s\n", - clk_names[i]); - return ERR_PTR(PTR_ERR(dsi->clks[i])); - } - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - dsi->reg_base = devm_ioremap_resource(dev, res); - if (IS_ERR(dsi->reg_base)) { - dev_err(dev, "failed to remap io region\n"); - return dsi->reg_base; - } - - dsi->phy = devm_phy_get(dev, "dsim"); - if (IS_ERR(dsi->phy)) { - dev_info(dev, "failed to get dsim phy\n"); - return ERR_PTR(PTR_ERR(dsi->phy)); - } - - dsi->irq = platform_get_irq(pdev, 0); - if (dsi->irq < 0) - return ERR_PTR(dsi->irq); - - irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN); - ret = devm_request_threaded_irq(dev, dsi->irq, NULL, - exynos_dsi_irq, IRQF_ONESHOT, - dev_name(dev), dsi); - if (ret) { - dev_err(dev, "failed to request dsi irq\n"); - return ERR_PTR(ret); - } - - ret = exynos_dsi_parse_dt(dsi); + ret = component_add(dev, &exynos_dsi_component_ops); if (ret) - return ERR_PTR(ret); + goto err_disable_runtime;
- ret = mipi_dsi_host_register(&dsi->dsi_host); - if (ret) - return ERR_PTR(ret); + return 0;
- bridge = &dsi->bridge; - bridge->driver_private = dsi; - bridge->funcs = &exynos_dsi_bridge_funcs; - bridge->of_node = dev->of_node; - drm_bridge_add(bridge); +err_disable_runtime: + pm_runtime_disable(dev);
- return dsi; + return ret; }
-static void __exynos_dsi_remove(struct exynos_dsi *dsi) +static int exynos_dsi_remove(struct platform_device *pdev) { - drm_bridge_remove(&dsi->bridge); + struct exynos_dsi *dsi = platform_get_drvdata(pdev);
- mipi_dsi_host_unregister(&dsi->dsi_host); -} - -/* - * Probe/remove API, used from platforms based on the DRM bridge API. - */ -struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) -{ - return __exynos_dsi_probe(pdev); -} + pm_runtime_disable(&pdev->dev);
-void exynos_dsi_remove(struct exynos_dsi *dsi) -{ - return __exynos_dsi_remove(dsi); -} + samsung_dsim_remove(dsi->dsi);
-/* - * Bind/unbind API, used from platforms based on the component framework. - */ -int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) -{ - struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder); + component_del(&pdev->dev, &exynos_dsi_component_ops);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0); -} - -void exynos_dsi_unbind(struct exynos_dsi *dsi) -{ - exynos_dsi_disable(dsi); + return 0; }
-int exynos_dsi_suspend(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_suspend(struct device *dev) { - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - int ret, i; - - usleep_range(10000, 20000); - - if (dsi->state & DSIM_STATE_INITIALIZED) { - dsi->state &= ~DSIM_STATE_INITIALIZED; - - exynos_dsi_disable_clock(dsi); - - exynos_dsi_disable_irq(dsi); - } - - dsi->state &= ~DSIM_STATE_CMD_LPM; - - phy_power_off(dsi->phy); - - for (i = driver_data->num_clks - 1; i > -1; i--) - clk_disable_unprepare(dsi->clks[i]); - - ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); - if (ret < 0) - dev_err(dsi->dev, "cannot disable regulators %d\n", ret); + struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0; + return samsung_dsim_suspend(dsi->dsi); }
-int exynos_dsi_resume(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_resume(struct device *dev) { - const struct exynos_dsi_driver_data *driver_data = dsi->driver_data; - int ret, i; - - ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies); - if (ret < 0) { - dev_err(dsi->dev, "cannot enable regulators %d\n", ret); - return ret; - } - - for (i = 0; i < driver_data->num_clks; i++) { - ret = clk_prepare_enable(dsi->clks[i]); - if (ret < 0) - goto err_clk; - } - - ret = phy_power_on(dsi->phy); - if (ret < 0) { - dev_err(dsi->dev, "cannot enable phy %d\n", ret); - goto err_clk; - } + struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0; + return samsung_dsim_resume(dsi->dsi); +}
-err_clk: - while (--i > -1) - clk_disable_unprepare(dsi->clks[i]); - regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies); +static const struct dev_pm_ops exynos_dsi_pm_ops = { + SET_RUNTIME_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +};
- return ret; -} +struct platform_driver dsi_driver = { + .probe = exynos_dsi_probe, + .remove = exynos_dsi_remove, + .driver = { + .name = "exynos-dsi", + .owner = THIS_MODULE, + .pm = &exynos_dsi_pm_ops, + .of_match_table = exynos_dsi_of_match, + }, +};
MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c b/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c deleted file mode 100644 index 79d9ec6ade45..000000000000 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c +++ /dev/null @@ -1,333 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Samsung SoC MIPI DSI Master driver. - * - * Copyright (c) 2014 Samsung Electronics Co., Ltd - * - * Contacts: Tomasz Figa t.figa@samsung.com - */ - -#include <linux/component.h> -#include <linux/of_device.h> -#include <linux/of_graph.h> -#include <linux/pm_runtime.h> - -#include <drm/drm_bridge.h> -#include <drm/drm_encoder.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_probe_helper.h> -#include <drm/drm_simple_kms_helper.h> - -#include "exynos_drm_crtc.h" -#include "exynos_drm_drv.h" -#include "exynos_drm_dsi.h" - -enum { - DSI_PORT_IN, - DSI_PORT_OUT -}; - -struct exynos_dsi_pltfm { - struct exynos_dsi *dsi; - struct drm_encoder encoder; -}; - -static const unsigned int reg_values[] = { - [RESET_TYPE] = DSIM_SWRST, - [PLL_TIMER] = 500, - [STOP_STATE_CNT] = 0xf, - [PHYCTRL_ULPS_EXIT] = 0x0af, - [PHYCTRL_VREG_LP] = 0, - [PHYCTRL_SLEW_UP] = 0, - [PHYTIMING_LPX] = 0x06, - [PHYTIMING_HS_EXIT] = 0x0b, - [PHYTIMING_CLK_PREPARE] = 0x07, - [PHYTIMING_CLK_ZERO] = 0x27, - [PHYTIMING_CLK_POST] = 0x0d, - [PHYTIMING_CLK_TRAIL] = 0x08, - [PHYTIMING_HS_PREPARE] = 0x09, - [PHYTIMING_HS_ZERO] = 0x0d, - [PHYTIMING_HS_TRAIL] = 0x0b, -}; - -static const unsigned int exynos5422_reg_values[] = { - [RESET_TYPE] = DSIM_SWRST, - [PLL_TIMER] = 500, - [STOP_STATE_CNT] = 0xf, - [PHYCTRL_ULPS_EXIT] = 0xaf, - [PHYCTRL_VREG_LP] = 0, - [PHYCTRL_SLEW_UP] = 0, - [PHYTIMING_LPX] = 0x08, - [PHYTIMING_HS_EXIT] = 0x0d, - [PHYTIMING_CLK_PREPARE] = 0x09, - [PHYTIMING_CLK_ZERO] = 0x30, - [PHYTIMING_CLK_POST] = 0x0e, - [PHYTIMING_CLK_TRAIL] = 0x0a, - [PHYTIMING_HS_PREPARE] = 0x0c, - [PHYTIMING_HS_ZERO] = 0x11, - [PHYTIMING_HS_TRAIL] = 0x0d, -}; - -static const unsigned int exynos5433_reg_values[] = { - [RESET_TYPE] = DSIM_FUNCRST, - [PLL_TIMER] = 22200, - [STOP_STATE_CNT] = 0xa, - [PHYCTRL_ULPS_EXIT] = 0x190, - [PHYCTRL_VREG_LP] = 1, - [PHYCTRL_SLEW_UP] = 1, - [PHYTIMING_LPX] = 0x07, - [PHYTIMING_HS_EXIT] = 0x0c, - [PHYTIMING_CLK_PREPARE] = 0x09, - [PHYTIMING_CLK_ZERO] = 0x2d, - [PHYTIMING_CLK_POST] = 0x0e, - [PHYTIMING_CLK_TRAIL] = 0x09, - [PHYTIMING_HS_PREPARE] = 0x0b, - [PHYTIMING_HS_ZERO] = 0x10, - [PHYTIMING_HS_TRAIL] = 0x0c, -}; - -static int __exynos_dsi_host_attach(struct device *dev, - struct mipi_dsi_device *device) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_device *drm = dsi->encoder.dev; - struct exynos_drm_crtc *crtc; - - mutex_lock(&drm->mode_config.mutex); - crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD); - crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO); - mutex_unlock(&drm->mode_config.mutex); - - if (drm->mode_config.poll_enabled) - drm_kms_helper_hotplug_event(drm); - - return 0; -} - -static int __exynos_dsi_host_detach(struct device *dev, - struct mipi_dsi_device *device) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_device *drm = dsi->encoder.dev; - - if (drm->mode_config.poll_enabled) - drm_kms_helper_hotplug_event(drm); - - return 0; -} - -static void __exynos_dsi_te_handler(struct device *dev) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - - exynos_drm_crtc_te_handler(dsi->encoder.crtc); -} - -static const struct exynos_dsi_host_ops exynos_dsi_host_ops = { - .attach = __exynos_dsi_host_attach, - .detach = __exynos_dsi_host_detach, - .te_handler = __exynos_dsi_te_handler, -}; - -static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = { - .reg_ofs = EXYNOS_REG_OFS, - .plltmr_reg = 0x50, - .has_freqband = 1, - .has_clklane_stop = 1, - .num_clks = 2, - .max_freq = 1000, - .wait_for_reset = 1, - .num_bits_resol = 11, - .reg_values = reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = { - .reg_ofs = EXYNOS_REG_OFS, - .plltmr_reg = 0x50, - .has_freqband = 1, - .has_clklane_stop = 1, - .num_clks = 2, - .max_freq = 1000, - .wait_for_reset = 1, - .num_bits_resol = 11, - .reg_values = reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = { - .reg_ofs = EXYNOS_REG_OFS, - .plltmr_reg = 0x58, - .num_clks = 2, - .max_freq = 1000, - .wait_for_reset = 1, - .num_bits_resol = 11, - .reg_values = reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = { - .reg_ofs = EXYNOS5433_REG_OFS, - .plltmr_reg = 0xa0, - .has_clklane_stop = 1, - .num_clks = 5, - .max_freq = 1500, - .wait_for_reset = 0, - .num_bits_resol = 12, - .reg_values = exynos5433_reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = { - .reg_ofs = EXYNOS5433_REG_OFS, - .plltmr_reg = 0xa0, - .has_clklane_stop = 1, - .num_clks = 2, - .max_freq = 1500, - .wait_for_reset = 1, - .num_bits_resol = 12, - .reg_values = exynos5422_reg_values, - .host_ops = &exynos_dsi_host_ops, -}; - -static const struct of_device_id exynos_dsi_of_match[] = { - { .compatible = "samsung,exynos3250-mipi-dsi", - .data = &exynos3_dsi_driver_data }, - { .compatible = "samsung,exynos4210-mipi-dsi", - .data = &exynos4_dsi_driver_data }, - { .compatible = "samsung,exynos5410-mipi-dsi", - .data = &exynos5_dsi_driver_data }, - { .compatible = "samsung,exynos5422-mipi-dsi", - .data = &exynos5422_dsi_driver_data }, - { .compatible = "samsung,exynos5433-mipi-dsi", - .data = &exynos5433_dsi_driver_data }, - { } -}; - -static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dsi->encoder; - struct drm_device *drm_dev = data; - struct device_node *in_bridge_node; - struct drm_bridge *in_bridge; - int ret; - - drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); - - ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD); - if (ret < 0) - return ret; - - in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0); - if (in_bridge_node) { - in_bridge = of_drm_find_bridge(in_bridge_node); - if (in_bridge) - drm_bridge_attach(encoder, in_bridge, NULL, 0); - of_node_put(in_bridge_node); - } - - ret = exynos_dsi_bind(dsi->dsi, encoder); - if (ret) - goto err; - - return 0; - -err: - drm_encoder_cleanup(encoder); - return ret; -} - -static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master, - void *data) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dsi->encoder; - - exynos_dsi_unbind(dsi->dsi); - - drm_encoder_cleanup(encoder); -} - -static const struct component_ops exynos_dsi_pltfm_component_ops = { - .bind = exynos_dsi_pltfm_bind, - .unbind = exynos_dsi_pltfm_unbind, -}; - -static int exynos_dsi_pltfm_probe(struct platform_device *pdev) -{ - struct exynos_dsi_pltfm *dsi; - struct device *dev = &pdev->dev; - int ret; - - dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); - if (!dsi) - return -ENOMEM; - platform_set_drvdata(pdev, dsi); - - dsi->dsi = exynos_dsi_probe(pdev); - if (IS_ERR(dsi->dsi)) - return PTR_ERR(dsi->dsi); - - pm_runtime_enable(dev); - - ret = component_add(dev, &exynos_dsi_pltfm_component_ops); - if (ret) - goto err_disable_runtime; - - return 0; - -err_disable_runtime: - pm_runtime_disable(dev); - - return ret; -} - -static int exynos_dsi_pltfm_remove(struct platform_device *pdev) -{ - struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev); - - pm_runtime_disable(&pdev->dev); - - exynos_dsi_remove(dsi->dsi); - - component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops); - - return 0; -} - -static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - - return exynos_dsi_suspend(dsi->dsi); -} - -static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) -{ - struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev); - - return exynos_dsi_resume(dsi->dsi); -} - -static const struct dev_pm_ops exynos_dsi_pm_ops = { - SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL) - SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) -}; - -struct platform_driver dsi_driver = { - .probe = exynos_dsi_pltfm_probe, - .remove = exynos_dsi_pltfm_remove, - .driver = { - .name = "exynos-dsi", - .owner = THIS_MODULE, - .pm = &exynos_dsi_pm_ops, - .of_match_table = exynos_dsi_of_match, - }, -}; - -MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); -MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); -MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.h b/include/drm/bridge/samsung-dsim.h similarity index 69% rename from drivers/gpu/drm/exynos/exynos_drm_dsi.h rename to include/drm/bridge/samsung-dsim.h index 8fa3276889de..be8b4913aa9c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.h +++ b/include/drm/bridge/samsung-dsim.h @@ -3,7 +3,7 @@ #define __EXYNOS_DRM_DSI__
struct drm_encoder; -struct exynos_dsi; +struct samsung_dsim; struct platform_device; struct mipi_dsi_device;
@@ -12,13 +12,13 @@ enum exynos_reg_offset { EXYNOS5433_REG_OFS };
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev); -void exynos_dsi_remove(struct exynos_dsi *dsi); -int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); -void exynos_dsi_unbind(struct exynos_dsi *dsi); +struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev); +void samsung_dsim_remove(struct samsung_dsim *dsi); +int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder); +void samsung_dsim_unbind(struct samsung_dsim *dsi);
-int exynos_dsi_suspend(struct exynos_dsi *dsi); -int exynos_dsi_resume(struct exynos_dsi *dsi); +int samsung_dsim_suspend(struct samsung_dsim *dsi); +int samsung_dsim_resume(struct samsung_dsim *dsi);
enum reg_value_idx { RESET_TYPE, @@ -42,13 +42,13 @@ enum reg_value_idx { #define DSIM_FUNCRST (1 << 16) #define DSIM_SWRST (1 << 0)
-struct exynos_dsi_host_ops { +struct samsung_dsim_host_ops { int (*attach)(struct device *dev, struct mipi_dsi_device *device); int (*detach)(struct device *dev, struct mipi_dsi_device *device); void (*te_handler)(struct device *dev); };
-struct exynos_dsi_driver_data { +struct samsung_dsim_driver_data { enum exynos_reg_offset reg_ofs; unsigned int plltmr_reg; unsigned int has_freqband:1; @@ -58,7 +58,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values; - const struct exynos_dsi_host_ops *host_ops; + const struct samsung_dsim_host_ops *host_ops; };
#endif /* __EXYNOS_DRM_DSI__ */
On Fri, Sep 11, 2020 at 03:54:13PM +0200, Michael Tretter wrote:
As the driver is not platform dependent anymore, move it to the drm bridge driver directory.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
So new drm_bridge drivers that still use the component stuff is a bit uncool. We're trying to get away from that everywhere, bridges should be abstracted enough that just going through the of lookup functions to get at your bridge should work.
Is there anything here that prevents this, or could this be included? -Daniel
v2:
- select DRM_SAMSUNG_DSIM from DRM_EXYNOS_DSI
- add removal of depends on !FB_S3C
drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1790 ++++++++++++++++ drivers/gpu/drm/exynos/Kconfig | 5 +- drivers/gpu/drm/exynos/Makefile | 2 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 1896 ++--------------- drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c | 333 --- .../drm/bridge/samsung-dsim.h | 20 +- 8 files changed, 2037 insertions(+), 2019 deletions(-) create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c delete mode 100644 drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c rename drivers/gpu/drm/exynos/exynos_drm_dsi.h => include/drm/bridge/samsung-dsim.h (69%)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3e11af4e9f63..55ab5030c6cf 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -125,6 +125,15 @@ config DRM_PARADE_PS8640 The PS8640 is a high-performance and low-power MIPI DSI to eDP converter
+config DRM_SAMSUNG_DSIM
- tristate "Samsung MIPI DSI bridge"
- depends on OF
- select DRM_KMS_HELPER
- select DRM_MIPI_DSI
- select DRM_PANEL
- help
Samsung MIPI DSI bridge driver.
config DRM_SIL_SII8620 tristate "Silicon Image SII8620 HDMI/MHL bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c589a6a7cbe1..5ac7a5c413dc 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o +obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII9234) += sii9234.o diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c new file mode 100644 index 000000000000..6d2d8dc027de --- /dev/null +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -0,0 +1,1790 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Samsung SoC MIPI DSI Master driver.
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
+*/
+#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/component.h> +#include <linux/gpio/consumer.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h>
+#include <asm/unaligned.h>
+#include <video/mipi_display.h> +#include <video/videomode.h>
+#include <drm/bridge/samsung-dsim.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h>
+/* returns true iff both arguments logically differs */ +#define NEQV(a, b) (!(a) ^ !(b))
+/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31)
+/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) +#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
+/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31)
+/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) +/* This flag is valid only for exynos3250/3472/5260/5430 */ +#define DSIM_CLKLANE_STOP (1 << 30)
+/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
+/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) +#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
+/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
+/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
+/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
+/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
+/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14)
+/* DSIM_FIFOCTRL */ +#define DSIM_RX_DATA_FULL (1 << 25) +#define DSIM_RX_DATA_EMPTY (1 << 24) +#define DSIM_SFR_HEADER_FULL (1 << 23) +#define DSIM_SFR_HEADER_EMPTY (1 << 22) +#define DSIM_SFR_PAYLOAD_FULL (1 << 21) +#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) +#define DSIM_I80_HEADER_FULL (1 << 19) +#define DSIM_I80_HEADER_EMPTY (1 << 18) +#define DSIM_I80_PAYLOAD_FULL (1 << 17) +#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) +#define DSIM_SD_HEADER_FULL (1 << 15) +#define DSIM_SD_HEADER_EMPTY (1 << 14) +#define DSIM_SD_PAYLOAD_FULL (1 << 13) +#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) +#define DSIM_MD_HEADER_FULL (1 << 11) +#define DSIM_MD_HEADER_EMPTY (1 << 10) +#define DSIM_MD_PAYLOAD_FULL (1 << 9) +#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) +#define DSIM_RX_FIFO (1 << 4) +#define DSIM_SFR_FIFO (1 << 3) +#define DSIM_I80_FIFO (1 << 2) +#define DSIM_SD_FIFO (1 << 1) +#define DSIM_MD_FIFO (1 << 0)
+/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
+/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1)
+/* DSIM_PHYCTRL */ +#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) +#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) +#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14)
+/* DSIM_PHYTIMING */ +#define DSIM_PHYTIMING_LPX(x) ((x) << 8) +#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
+/* DSIM_PHYTIMING1 */ +#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) +#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) +#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) +#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
+/* DSIM_PHYTIMING2 */ +#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) +#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) +#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
+#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002
+#define OLD_SCLK_MIPI_CLK_NAME "pll_clk"
+static const char *const clk_names[5] = { "bus_clk", "sclk_mipi",
- "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0",
- "sclk_rgb_vclk_to_dsim0" };
+enum samsung_dsim_transfer_type {
- EXYNOS_DSI_TX,
- EXYNOS_DSI_RX,
+};
+struct samsung_dsim_transfer {
- struct list_head list;
- struct completion completed;
- int result;
- struct mipi_dsi_packet packet;
- u16 flags;
- u16 tx_done;
- u8 *rx_payload;
- u16 rx_len;
- u16 rx_done;
+};
+#define DSIM_STATE_ENABLED BIT(0) +#define DSIM_STATE_INITIALIZED BIT(1) +#define DSIM_STATE_CMD_LPM BIT(2) +#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct samsung_dsim {
- struct drm_bridge bridge;
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_panel *panel;
- struct list_head bridge_chain;
- struct drm_bridge *out_bridge;
- struct device *dev;
- void __iomem *reg_base;
- struct phy *phy;
- struct clk **clks;
- struct regulator_bulk_data supplies[2];
- int irq;
- int te_gpio;
- u32 pll_clk_rate;
- u32 burst_clk_rate;
- u32 esc_clk_rate;
- u32 lanes;
- u32 mode_flags;
- u32 format;
- struct drm_display_mode mode;
- int state;
- struct drm_property *brightness;
- struct completion completed;
- spinlock_t transfer_lock; /* protects transfer_list */
- struct list_head transfer_list;
- const struct samsung_dsim_driver_data *driver_data;
+};
+#define host_to_dsi(host) container_of(host, struct samsung_dsim, dsi_host) +#define connector_to_dsi(c) container_of(c, struct samsung_dsim, connector)
+enum reg_idx {
- DSIM_STATUS_REG, /* Status register */
- DSIM_SWRST_REG, /* Software reset register */
- DSIM_CLKCTRL_REG, /* Clock control register */
- DSIM_TIMEOUT_REG, /* Time out register */
- DSIM_CONFIG_REG, /* Configuration register */
- DSIM_ESCMODE_REG, /* Escape mode register */
- DSIM_MDRESOL_REG,
- DSIM_MVPORCH_REG, /* Main display Vporch register */
- DSIM_MHPORCH_REG, /* Main display Hporch register */
- DSIM_MSYNC_REG, /* Main display sync area register */
- DSIM_INTSRC_REG, /* Interrupt source register */
- DSIM_INTMSK_REG, /* Interrupt mask register */
- DSIM_PKTHDR_REG, /* Packet Header FIFO register */
- DSIM_PAYLOAD_REG, /* Payload FIFO register */
- DSIM_RXFIFO_REG, /* Read FIFO register */
- DSIM_FIFOCTRL_REG, /* FIFO status and control register */
- DSIM_PLLCTRL_REG, /* PLL control register */
- DSIM_PHYCTRL_REG,
- DSIM_PHYTIMING_REG,
- DSIM_PHYTIMING1_REG,
- DSIM_PHYTIMING2_REG,
- NUM_REGS
+};
+static const unsigned int exynos_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x00,
- [DSIM_SWRST_REG] = 0x04,
- [DSIM_CLKCTRL_REG] = 0x08,
- [DSIM_TIMEOUT_REG] = 0x0c,
- [DSIM_CONFIG_REG] = 0x10,
- [DSIM_ESCMODE_REG] = 0x14,
- [DSIM_MDRESOL_REG] = 0x18,
- [DSIM_MVPORCH_REG] = 0x1c,
- [DSIM_MHPORCH_REG] = 0x20,
- [DSIM_MSYNC_REG] = 0x24,
- [DSIM_INTSRC_REG] = 0x2c,
- [DSIM_INTMSK_REG] = 0x30,
- [DSIM_PKTHDR_REG] = 0x34,
- [DSIM_PAYLOAD_REG] = 0x38,
- [DSIM_RXFIFO_REG] = 0x3c,
- [DSIM_FIFOCTRL_REG] = 0x44,
- [DSIM_PLLCTRL_REG] = 0x4c,
- [DSIM_PHYCTRL_REG] = 0x5c,
- [DSIM_PHYTIMING_REG] = 0x64,
- [DSIM_PHYTIMING1_REG] = 0x68,
- [DSIM_PHYTIMING2_REG] = 0x6c,
+};
+static const unsigned int exynos5433_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x04,
- [DSIM_SWRST_REG] = 0x0C,
- [DSIM_CLKCTRL_REG] = 0x10,
- [DSIM_TIMEOUT_REG] = 0x14,
- [DSIM_CONFIG_REG] = 0x18,
- [DSIM_ESCMODE_REG] = 0x1C,
- [DSIM_MDRESOL_REG] = 0x20,
- [DSIM_MVPORCH_REG] = 0x24,
- [DSIM_MHPORCH_REG] = 0x28,
- [DSIM_MSYNC_REG] = 0x2C,
- [DSIM_INTSRC_REG] = 0x34,
- [DSIM_INTMSK_REG] = 0x38,
- [DSIM_PKTHDR_REG] = 0x3C,
- [DSIM_PAYLOAD_REG] = 0x40,
- [DSIM_RXFIFO_REG] = 0x44,
- [DSIM_FIFOCTRL_REG] = 0x4C,
- [DSIM_PLLCTRL_REG] = 0x94,
- [DSIM_PHYCTRL_REG] = 0xA4,
- [DSIM_PHYTIMING_REG] = 0xB4,
- [DSIM_PHYTIMING1_REG] = 0xB8,
- [DSIM_PHYTIMING2_REG] = 0xBC,
+};
+static inline void samsung_dsim_write(struct samsung_dsim *dsi,
enum reg_idx idx, u32 val)
+{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- writel(val, dsi->reg_base + reg_ofs[idx]);
+}
+static inline u32 samsung_dsim_read(struct samsung_dsim *dsi, enum reg_idx idx) +{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- return readl(dsi->reg_base + reg_ofs[idx]);
+}
+static void samsung_dsim_wait_for_reset(struct samsung_dsim *dsi) +{
- if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300)))
return;
- dev_err(dsi->dev, "timeout waiting for reset\n");
+}
+static void samsung_dsim_reset(struct samsung_dsim *dsi) +{
- u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE];
- reinit_completion(&dsi->completed);
- samsung_dsim_write(dsi, DSIM_SWRST_REG, reset_val);
+}
+#ifndef MHZ +#define MHZ (1000*1000) +#endif
+static unsigned long samsung_dsim_pll_find_pms(struct samsung_dsim *dsi,
unsigned long fin,
unsigned long fout,
u8 *p, u16 *m, u8 *s)
+{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- unsigned long best_freq = 0;
- u32 min_delta = 0xffffffff;
- u8 p_min, p_max;
- u8 _p, best_p;
- u16 _m, best_m;
- u8 _s, best_s;
- p_min = DIV_ROUND_UP(fin, (12 * MHZ));
- p_max = fin / (6 * MHZ);
- for (_p = p_min; _p <= p_max; ++_p) {
for (_s = 0; _s <= 5; ++_s) {
u64 tmp;
u32 delta;
tmp = (u64)fout * (_p << _s);
do_div(tmp, fin);
_m = tmp;
if (_m < 41 || _m > 125)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p);
if (tmp < 500 * MHZ ||
tmp > driver_data->max_freq * MHZ)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p << _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_m = _m;
best_s = _s;
min_delta = delta;
best_freq = tmp;
}
}
- }
- if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
- }
- return best_freq;
+}
+static unsigned long samsung_dsim_set_pll(struct samsung_dsim *dsi,
unsigned long freq)
+{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- unsigned long fin, fout;
- int timeout;
- u8 p, s;
- u16 m;
- u32 reg;
- fin = dsi->pll_clk_rate;
- fout = samsung_dsim_pll_find_pms(dsi, fin, freq, &p, &m, &s);
- if (!fout) {
dev_err(dsi->dev,
"failed to find PLL PMS for requested frequency\n");
return 0;
- }
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- writel(driver_data->reg_values[PLL_TIMER],
dsi->reg_base + driver_data->plltmr_reg);
- reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
- if (driver_data->has_freqband) {
static const unsigned long freq_bands[] = {
100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
770 * MHZ, 870 * MHZ, 950 * MHZ,
};
int band;
for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
if (fout < freq_bands[band])
break;
dev_dbg(dsi->dev, "band %d\n", band);
reg |= DSIM_FREQ_BAND(band);
- }
- samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg);
- timeout = 1000;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "PLL failed to stabilize\n");
return 0;
}
reg = samsung_dsim_read(dsi, DSIM_STATUS_REG);
- } while ((reg & DSIM_PLL_STABLE) == 0);
- return fout;
+}
+static int samsung_dsim_enable_clock(struct samsung_dsim *dsi) +{
- unsigned long hs_clk, byte_clk, esc_clk;
- unsigned long esc_div;
- u32 reg;
- hs_clk = samsung_dsim_set_pll(dsi, dsi->burst_clk_rate);
- if (!hs_clk) {
dev_err(dsi->dev, "failed to configure DSI PLL\n");
return -EFAULT;
- }
- byte_clk = hs_clk / 8;
- esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate);
- esc_clk = byte_clk / esc_div;
- if (esc_clk > 20 * MHZ) {
++esc_div;
esc_clk = byte_clk / esc_div;
- }
- dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
hs_clk, byte_clk, esc_clk);
- reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
| DSIM_BYTE_CLK_SRC_MASK);
- reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
| DSIM_ESC_PRESCALER(esc_div)
| DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1)
| DSIM_BYTE_CLK_SRC(0)
| DSIM_TX_REQUEST_HSCLK;
- samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg);
- return 0;
+}
+static void samsung_dsim_set_phy_ctrl(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- const unsigned int *reg_values = driver_data->reg_values;
- u32 reg;
- if (driver_data->has_freqband)
return;
- /* B D-PHY: D-PHY Master & Slave Analog Block control */
- reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]);
- if (reg_values[PHYCTRL_VREG_LP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP;
- if (reg_values[PHYCTRL_SLEW_UP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP;
- samsung_dsim_write(dsi, DSIM_PHYCTRL_REG, reg);
- /*
* T LPX: Transmitted length of any Low-Power state period
* T HS-EXIT: Time that the transmitter drives LP-11 following a HS
* burst
*/
- reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) |
DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING_REG, reg);
- /*
* T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Clock.
* T CLK_POST: Time that the transmitter continues to send HS clock
* after the last associated Data Lane has transitioned to LP Mode
* Interval is defined as the period from the end of T HS-TRAIL to
* the beginning of T CLK-TRAIL
* T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
* the last payload clock bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) |
DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) |
DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) |
DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING1_REG, reg);
- /*
* T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Sync sequence.
* T HS-TRAIL: Time that the transmitter drives the flipped differential
* state after last payload data bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) |
DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) |
DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING2_REG, reg);
+}
+static void samsung_dsim_disable_clock(struct samsung_dsim *dsi) +{
- u32 reg;
- reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK
| DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
- samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg);
- reg = samsung_dsim_read(dsi, DSIM_PLLCTRL_REG);
- reg &= ~DSIM_PLL_EN;
- samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg);
+}
+static void samsung_dsim_enable_lane(struct samsung_dsim *dsi, u32 lane) +{
- u32 reg = samsung_dsim_read(dsi, DSIM_CONFIG_REG);
- reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK |
DSIM_LANE_EN(lane));
- samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg);
+}
+static int samsung_dsim_init_link(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int timeout;
- u32 reg;
- u32 lanes_mask;
- /* Initialize FIFO pointers */
- reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG);
- reg &= ~0x1f;
- samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- reg |= 0x1f;
- samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- /* DSI configuration */
- reg = 0;
- /*
* The first bit of mode_flags specifies display configuration.
* If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video
* mode, otherwise it will support command mode.
*/
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg |= DSIM_VIDEO_MODE;
/*
* The user manual describes that following bits are ignored in
* command mode.
*/
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH))
reg |= DSIM_MFLUSH_VS;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
reg |= DSIM_SYNC_INFORM;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
reg |= DSIM_BURST_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
reg |= DSIM_AUTO_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
reg |= DSIM_HSE_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP))
reg |= DSIM_HFP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP))
reg |= DSIM_HBP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA))
reg |= DSIM_HSA_MODE;
- }
- if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
reg |= DSIM_EOT_DISABLE;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
break;
- case MIPI_DSI_FMT_RGB666:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
break;
- case MIPI_DSI_FMT_RGB565:
reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
break;
- default:
dev_err(dsi->dev, "invalid pixel format\n");
return -EINVAL;
- }
- /*
* Use non-continuous clock mode if the periparal wants and
* host controller supports
*
* In non-continous clock mode, host controller will turn off
* the HS clock between high-speed transmissions to reduce
* power consumption.
*/
- if (driver_data->has_clklane_stop &&
dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
reg |= DSIM_CLKLANE_STOP;
- }
- samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg);
- lanes_mask = BIT(dsi->lanes) - 1;
- samsung_dsim_enable_lane(dsi, lanes_mask);
- /* Check clock and data lane state are stop state */
- timeout = 100;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "waiting for bus lanes timed out\n");
return -EFAULT;
}
reg = samsung_dsim_read(dsi, DSIM_STATUS_REG);
if ((reg & DSIM_STOP_STATE_DAT(lanes_mask))
!= DSIM_STOP_STATE_DAT(lanes_mask))
continue;
- } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
- reg = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- reg &= ~DSIM_STOP_STATE_CNT_MASK;
- reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]);
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, reg);
- reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
- samsung_dsim_write(dsi, DSIM_TIMEOUT_REG, reg);
- return 0;
+}
+static void samsung_dsim_set_display_mode(struct samsung_dsim *dsi) +{
- struct drm_display_mode *m = &dsi->mode;
- unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
- u32 reg;
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg = DSIM_CMD_ALLOW(0xf)
| DSIM_STABLE_VFP(m->vsync_start - m->vdisplay)
| DSIM_MAIN_VBP(m->vtotal - m->vsync_end);
samsung_dsim_write(dsi, DSIM_MVPORCH_REG, reg);
reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay)
| DSIM_MAIN_HBP(m->htotal - m->hsync_end);
samsung_dsim_write(dsi, DSIM_MHPORCH_REG, reg);
reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start)
| DSIM_MAIN_HSA(m->hsync_end - m->hsync_start);
samsung_dsim_write(dsi, DSIM_MSYNC_REG, reg);
- }
- reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) |
DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol);
- samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg);
- dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay);
+}
+static void samsung_dsim_set_display_enable(struct samsung_dsim *dsi,
bool enable)
+{
- u32 reg;
- reg = samsung_dsim_read(dsi, DSIM_MDRESOL_REG);
- if (enable)
reg |= DSIM_MAIN_STAND_BY;
- else
reg &= ~DSIM_MAIN_STAND_BY;
- samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg);
+}
+static int samsung_dsim_wait_for_hdr_fifo(struct samsung_dsim *dsi) +{
- int timeout = 2000;
- do {
u32 reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG);
if (!(reg & DSIM_SFR_HEADER_FULL))
return 0;
if (!cond_resched())
usleep_range(950, 1050);
- } while (--timeout);
- return -ETIMEDOUT;
+}
+static void samsung_dsim_set_cmd_lpm(struct samsung_dsim *dsi, bool lpm) +{
- u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- if (lpm)
v |= DSIM_CMD_LPDT_LP;
- else
v &= ~DSIM_CMD_LPDT_LP;
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v);
+}
+static void samsung_dsim_force_bta(struct samsung_dsim *dsi) +{
- u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- v |= DSIM_FORCE_BTA;
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v);
+}
+static void samsung_dsim_send_to_fifo(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- struct device *dev = dsi->dev;
- struct mipi_dsi_packet *pkt = &xfer->packet;
- const u8 *payload = pkt->payload + xfer->tx_done;
- u16 length = pkt->payload_length - xfer->tx_done;
- bool first = !xfer->tx_done;
- u32 reg;
- dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n",
xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done);
- if (length > DSI_TX_FIFO_SIZE)
length = DSI_TX_FIFO_SIZE;
- xfer->tx_done += length;
- /* Send payload */
- while (length >= 4) {
reg = get_unaligned_le32(payload);
samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg);
payload += 4;
length -= 4;
- }
- reg = 0;
- switch (length) {
- case 3:
reg |= payload[2] << 16;
fallthrough;
- case 2:
reg |= payload[1] << 8;
fallthrough;
- case 1:
reg |= payload[0];
samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg);
break;
- }
- /* Send packet header */
- if (!first)
return;
- reg = get_unaligned_le32(pkt->header);
- if (samsung_dsim_wait_for_hdr_fifo(dsi)) {
dev_err(dev, "waiting for header FIFO timed out\n");
return;
- }
- if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM,
dsi->state & DSIM_STATE_CMD_LPM)) {
samsung_dsim_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM);
dsi->state ^= DSIM_STATE_CMD_LPM;
- }
- samsung_dsim_write(dsi, DSIM_PKTHDR_REG, reg);
- if (xfer->flags & MIPI_DSI_MSG_REQ_ACK)
samsung_dsim_force_bta(dsi);
+}
+static void samsung_dsim_read_from_fifo(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- u8 *payload = xfer->rx_payload + xfer->rx_done;
- bool first = !xfer->rx_done;
- struct device *dev = dsi->dev;
- u16 length;
- u32 reg;
- if (first) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
switch (reg & 0x3f) {
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
if (xfer->rx_len >= 2) {
payload[1] = reg >> 16;
++xfer->rx_done;
}
fallthrough;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
payload[0] = reg >> 8;
++xfer->rx_done;
xfer->rx_len = xfer->rx_done;
xfer->result = 0;
goto clear_fifo;
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
dev_err(dev, "DSI Error Report: 0x%04x\n",
(reg >> 8) & 0xffff);
xfer->result = 0;
goto clear_fifo;
}
length = (reg >> 8) & 0xffff;
if (length > xfer->rx_len) {
dev_err(dev,
"response too long (%u > %u bytes), stripping\n",
xfer->rx_len, length);
length = xfer->rx_len;
} else if (length < xfer->rx_len)
xfer->rx_len = length;
- }
- length = xfer->rx_len - xfer->rx_done;
- xfer->rx_done += length;
- /* Receive payload */
- while (length >= 4) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
payload[0] = (reg >> 0) & 0xff;
payload[1] = (reg >> 8) & 0xff;
payload[2] = (reg >> 16) & 0xff;
payload[3] = (reg >> 24) & 0xff;
payload += 4;
length -= 4;
- }
- if (length) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
switch (length) {
case 3:
payload[2] = (reg >> 16) & 0xff;
fallthrough;
case 2:
payload[1] = (reg >> 8) & 0xff;
fallthrough;
case 1:
payload[0] = reg & 0xff;
}
- }
- if (xfer->rx_done == xfer->rx_len)
xfer->result = 0;
+clear_fifo:
- length = DSI_RX_FIFO_SIZE / 4;
- do {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
if (reg == DSI_RX_FIFO_EMPTY)
break;
- } while (--length);
+}
+static void samsung_dsim_transfer_start(struct samsung_dsim *dsi) +{
- unsigned long flags;
- struct samsung_dsim_transfer *xfer;
- bool start = false;
+again:
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (xfer->packet.payload_length &&
xfer->tx_done == xfer->packet.payload_length)
/* waiting for RX */
return;
- samsung_dsim_send_to_fifo(dsi, xfer);
- if (xfer->packet.payload_length || xfer->rx_len)
return;
- xfer->result = 0;
- complete(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (start)
goto again;
+}
+static bool samsung_dsim_transfer_finish(struct samsung_dsim *dsi) +{
- struct samsung_dsim_transfer *xfer;
- unsigned long flags;
- bool start = true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return false;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- dev_dbg(dsi->dev,
"> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n",
xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len,
xfer->rx_done);
- if (xfer->tx_done != xfer->packet.payload_length)
return true;
- if (xfer->rx_done != xfer->rx_len)
samsung_dsim_read_from_fifo(dsi, xfer);
- if (xfer->rx_done != xfer->rx_len)
return true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (!xfer->rx_len)
xfer->result = 0;
- complete(&xfer->completed);
- return start;
+}
+static void samsung_dsim_remove_transfer(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- unsigned long flags;
- bool start;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (!list_empty(&dsi->transfer_list) &&
xfer == list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list)) {
list_del_init(&xfer->list);
start = !list_empty(&dsi->transfer_list);
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
if (start)
samsung_dsim_transfer_start(dsi);
return;
- }
- list_del_init(&xfer->list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+}
+static int samsung_dsim_transfer(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- unsigned long flags;
- bool stopped;
- xfer->tx_done = 0;
- xfer->rx_done = 0;
- xfer->result = -ETIMEDOUT;
- init_completion(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- stopped = list_empty(&dsi->transfer_list);
- list_add_tail(&xfer->list, &dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (stopped)
samsung_dsim_transfer_start(dsi);
- wait_for_completion_timeout(&xfer->completed,
msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
- if (xfer->result == -ETIMEDOUT) {
struct mipi_dsi_packet *pkt = &xfer->packet;
samsung_dsim_remove_transfer(dsi, xfer);
dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header,
(int)pkt->payload_length, pkt->payload);
return -ETIMEDOUT;
- }
- /* Also covers hardware timeout condition */
- return xfer->result;
+}
+static irqreturn_t samsung_dsim_irq(int irq, void *dev_id) +{
- struct samsung_dsim *dsi = dev_id;
- u32 status;
- status = samsung_dsim_read(dsi, DSIM_INTSRC_REG);
- if (!status) {
static unsigned long int j;
if (printk_timed_ratelimit(&j, 500))
dev_warn(dsi->dev, "spurious interrupt\n");
return IRQ_HANDLED;
- }
- samsung_dsim_write(dsi, DSIM_INTSRC_REG, status);
- if (status & DSIM_INT_SW_RST_RELEASE) {
u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR |
DSIM_INT_SW_RST_RELEASE);
samsung_dsim_write(dsi, DSIM_INTMSK_REG, mask);
complete(&dsi->completed);
return IRQ_HANDLED;
- }
- if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_PLL_STABLE)))
return IRQ_HANDLED;
- if (samsung_dsim_transfer_finish(dsi))
samsung_dsim_transfer_start(dsi);
- return IRQ_HANDLED;
+}
+static irqreturn_t samsung_dsim_te_irq_handler(int irq, void *dev_id) +{
- struct samsung_dsim *dsi = dev_id;
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->te_handler &&
(dsi->state & DSIM_STATE_VIDOUT_AVAILABLE))
ops->te_handler(dsi->dsi_host.dev);
- return IRQ_HANDLED;
+}
+static void samsung_dsim_enable_irq(struct samsung_dsim *dsi) +{
- enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
enable_irq(gpio_to_irq(dsi->te_gpio));
+}
+static void samsung_dsim_disable_irq(struct samsung_dsim *dsi) +{
- if (gpio_is_valid(dsi->te_gpio))
disable_irq(gpio_to_irq(dsi->te_gpio));
- disable_irq(dsi->irq);
+}
+static int samsung_dsim_init(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- samsung_dsim_reset(dsi);
- samsung_dsim_enable_irq(dsi);
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST)
samsung_dsim_enable_lane(dsi, BIT(dsi->lanes) - 1);
- samsung_dsim_enable_clock(dsi);
- if (driver_data->wait_for_reset)
samsung_dsim_wait_for_reset(dsi);
- samsung_dsim_set_phy_ctrl(dsi);
- samsung_dsim_init_link(dsi);
- return 0;
+}
+static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi,
struct device *panel)
+{
- int ret;
- int te_gpio_irq;
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
return 0;
- if (!gpio_is_valid(dsi->te_gpio)) {
ret = dsi->te_gpio;
dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
goto out;
- }
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
dev_err(dsi->dev, "gpio request failed with %d\n", ret);
goto out;
- }
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
- irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN);
- ret = request_threaded_irq(te_gpio_irq, samsung_dsim_te_irq_handler,
NULL, IRQF_TRIGGER_RISING, "TE", dsi);
- if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
gpio_free(dsi->te_gpio);
goto out;
- }
+out:
- return ret;
+}
+static void samsung_dsim_unregister_te_irq(struct samsung_dsim *dsi) +{
- if (gpio_is_valid(dsi->te_gpio)) {
free_irq(gpio_to_irq(dsi->te_gpio), dsi);
gpio_free(dsi->te_gpio);
dsi->te_gpio = -ENOENT;
- }
+}
+static void samsung_dsim_enable(struct samsung_dsim *dsi) +{
- struct drm_bridge *iter;
- int ret;
- if (dsi->state & DSIM_STATE_ENABLED)
return;
- pm_runtime_get_sync(dsi->dev);
- dsi->state |= DSIM_STATE_ENABLED;
- if (dsi->panel) {
ret = drm_panel_prepare(dsi->panel);
if (ret < 0)
goto err_put_sync;
- } else {
list_for_each_entry_reverse(iter, &dsi->bridge_chain,
chain_node) {
if (iter->funcs->pre_enable)
iter->funcs->pre_enable(iter);
}
- }
- samsung_dsim_set_display_mode(dsi);
- samsung_dsim_set_display_enable(dsi, true);
- if (dsi->panel) {
ret = drm_panel_enable(dsi->panel);
if (ret < 0)
goto err_display_disable;
- } else {
list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->enable)
iter->funcs->enable(iter);
}
- }
- dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE;
- return;
+err_display_disable:
- samsung_dsim_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
+err_put_sync:
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put(dsi->dev);
+}
+static void samsung_dsim_disable(struct samsung_dsim *dsi) +{
- struct drm_bridge *iter;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return;
- dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE;
- drm_panel_disable(dsi->panel);
- list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->disable)
iter->funcs->disable(iter);
- }
- samsung_dsim_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
- list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->post_disable)
iter->funcs->post_disable(iter);
- }
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put_sync(dsi->dev);
+}
+static enum drm_connector_status +samsung_dsim_detect(struct drm_connector *connector, bool force) +{
- return connector->status;
+}
+static void samsung_dsim_connector_destroy(struct drm_connector *connector) +{
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- connector->dev = NULL;
+}
+static const struct drm_connector_funcs samsung_dsim_connector_funcs = {
- .detect = samsung_dsim_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = samsung_dsim_connector_destroy,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+static int samsung_dsim_get_modes(struct drm_connector *connector) +{
- struct samsung_dsim *dsi = connector_to_dsi(connector);
- if (dsi->panel)
return drm_panel_get_modes(dsi->panel, connector);
- return 0;
+}
+static const struct drm_connector_helper_funcs samsung_dsim_connector_helper_funcs = {
- .get_modes = samsung_dsim_get_modes,
+};
+static int samsung_dsim_create_connector(struct samsung_dsim *dsi) +{
- struct drm_connector *connector = &dsi->connector;
- struct drm_device *drm = dsi->bridge.dev;
- int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD;
- ret = drm_connector_init(drm, connector, &samsung_dsim_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
- if (ret) {
DRM_DEV_ERROR(dsi->dev,
"Failed to initialize connector with drm\n");
return ret;
- }
- connector->status = connector_status_disconnected;
- drm_connector_helper_add(connector, &samsung_dsim_connector_helper_funcs);
- drm_connector_attach_encoder(connector, dsi->bridge.encoder);
- if (!drm->registered)
return 0;
- connector->funcs->reset(connector);
- drm_connector_register(connector);
- return 0;
+}
+static int samsung_dsim_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
+{
- struct samsung_dsim *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- int ret;
- if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
- if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
- } else {
ret = samsung_dsim_create_connector(dsi);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
- }
- return 0;
+}
+static void samsung_dsim_bridge_detach(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
samsung_dsim_disable(dsi);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- }
+}
+static void samsung_dsim_bridge_enable(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- samsung_dsim_enable(dsi);
+}
+static void samsung_dsim_bridge_disable(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- samsung_dsim_disable(dsi);
+}
+static void samsung_dsim_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
+{
- struct samsung_dsim *dsi = bridge->driver_private;
- /* The mode is set when actually enabling the device. */
- drm_mode_copy(&dsi->mode, adjusted_mode);
+}
+static const struct drm_bridge_funcs samsung_dsim_bridge_funcs = {
- .attach = samsung_dsim_bridge_attach,
- .detach = samsung_dsim_bridge_detach,
- .enable = samsung_dsim_bridge_enable,
- .disable = samsung_dsim_bridge_disable,
- .mode_set = samsung_dsim_bridge_mode_set,
+};
+static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node);
- if (out_bridge) {
dsi->out_bridge = out_bridge;
- } else {
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
else
dsi->connector.status = connector_status_connected;
- }
- /*
* This is a temporary solution and should be made by more generic way.
*
* If attached panel device is for command mode one, dsi should register
* TE interrupt handler.
*/
- if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
int ret = samsung_dsim_register_te_irq(dsi, &device->dev);
if (ret)
return ret;
- }
- dsi->lanes = device->lanes;
- dsi->format = device->format;
- dsi->mode_flags = device->mode_flags;
- if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
- return 0;
+}
+static int samsung_dsim_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- samsung_dsim_unregister_te_irq(dsi);
- if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
- samsung_dsim_unregister_te_irq(dsi);
- return 0;
+}
+static ssize_t samsung_dsim_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- struct samsung_dsim_transfer xfer;
- int ret;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return -EINVAL;
- if (!(dsi->state & DSIM_STATE_INITIALIZED)) {
ret = samsung_dsim_init(dsi);
if (ret)
return ret;
dsi->state |= DSIM_STATE_INITIALIZED;
- }
- ret = mipi_dsi_create_packet(&xfer.packet, msg);
- if (ret < 0)
return ret;
- xfer.rx_len = msg->rx_len;
- xfer.rx_payload = msg->rx_buf;
- xfer.flags = msg->flags;
- ret = samsung_dsim_transfer(dsi, &xfer);
- return (ret < 0) ? ret : xfer.rx_done;
+}
+static const struct mipi_dsi_host_ops samsung_dsim_ops = {
- .attach = samsung_dsim_host_attach,
- .detach = samsung_dsim_host_detach,
- .transfer = samsung_dsim_host_transfer,
+};
+static int samsung_dsim_of_read_u32(const struct device_node *np,
const char *propname, u32 *out_value)
+{
- int ret = of_property_read_u32(np, propname, out_value);
- if (ret < 0)
pr_err("%pOF: failed to get '%s' property\n", np, propname);
- return ret;
+}
+static int samsung_dsim_parse_dt(struct samsung_dsim *dsi) +{
- struct device *dev = dsi->dev;
- struct device_node *node = dev->of_node;
- int ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,pll-clock-frequency",
&dsi->pll_clk_rate);
- if (ret < 0)
return ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,burst-clock-frequency",
&dsi->burst_clk_rate);
- if (ret < 0)
return ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,esc-clock-frequency",
&dsi->esc_clk_rate);
- if (ret < 0)
return ret;
- return 0;
+}
+static struct samsung_dsim *__samsung_dsim_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- struct resource *res;
- struct samsung_dsim *dsi;
- int ret, i;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
- if (!dsi)
return ERR_PTR(-ENOMEM);
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
- init_completion(&dsi->completed);
- spin_lock_init(&dsi->transfer_lock);
- INIT_LIST_HEAD(&dsi->transfer_list);
- INIT_LIST_HEAD(&dsi->bridge_chain);
- dsi->dsi_host.ops = &samsung_dsim_ops;
- dsi->dsi_host.dev = dev;
- dsi->dev = dev;
- dsi->driver_data = of_device_get_match_data(dev);
- dsi->supplies[0].supply = "vddcore";
- dsi->supplies[1].supply = "vddio";
- ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies),
dsi->supplies);
- if (ret) {
if (ret != -EPROBE_DEFER)
dev_info(dev, "failed to get regulators: %d\n", ret);
return ERR_PTR(ret);
- }
- dsi->clks = devm_kcalloc(dev,
dsi->driver_data->num_clks, sizeof(*dsi->clks),
GFP_KERNEL);
- if (!dsi->clks)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < dsi->driver_data->num_clks; i++) {
dsi->clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(dsi->clks[i])) {
if (strcmp(clk_names[i], "sclk_mipi") == 0) {
dsi->clks[i] = devm_clk_get(dev,
OLD_SCLK_MIPI_CLK_NAME);
if (!IS_ERR(dsi->clks[i]))
continue;
}
dev_info(dev, "failed to get the clock: %s\n",
clk_names[i]);
return ERR_PTR(PTR_ERR(dsi->clks[i]));
}
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->reg_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(dsi->reg_base)) {
dev_err(dev, "failed to remap io region\n");
return dsi->reg_base;
- }
- dsi->phy = devm_phy_get(dev, "dsim");
- if (IS_ERR(dsi->phy)) {
dev_info(dev, "failed to get dsim phy\n");
return ERR_PTR(PTR_ERR(dsi->phy));
- }
- dsi->irq = platform_get_irq(pdev, 0);
- if (dsi->irq < 0)
return ERR_PTR(dsi->irq);
- irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN);
- ret = devm_request_threaded_irq(dev, dsi->irq, NULL,
samsung_dsim_irq, IRQF_ONESHOT,
dev_name(dev), dsi);
- if (ret) {
dev_err(dev, "failed to request dsi irq\n");
return ERR_PTR(ret);
- }
- ret = samsung_dsim_parse_dt(dsi);
- if (ret)
return ERR_PTR(ret);
- ret = mipi_dsi_host_register(&dsi->dsi_host);
- if (ret)
return ERR_PTR(ret);
- bridge = &dsi->bridge;
- bridge->driver_private = dsi;
- bridge->funcs = &samsung_dsim_bridge_funcs;
- bridge->of_node = dev->of_node;
- drm_bridge_add(bridge);
- return dsi;
+}
+static void __samsung_dsim_remove(struct samsung_dsim *dsi) +{
- drm_bridge_remove(&dsi->bridge);
- mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+/*
- Probe/remove API, used from platforms based on the DRM bridge API.
- */
+struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev) +{
- return __samsung_dsim_probe(pdev);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_probe);
+void samsung_dsim_remove(struct samsung_dsim *dsi) +{
- return __samsung_dsim_remove(dsi);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_remove);
+/*
- Bind/unbind API, used from platforms based on the component framework.
- */
+int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder) +{
- struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_bind);
+void samsung_dsim_unbind(struct samsung_dsim *dsi) +{
- samsung_dsim_disable(dsi);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_unbind);
+int samsung_dsim_suspend(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- usleep_range(10000, 20000);
- if (dsi->state & DSIM_STATE_INITIALIZED) {
dsi->state &= ~DSIM_STATE_INITIALIZED;
samsung_dsim_disable_clock(dsi);
samsung_dsim_disable_irq(dsi);
- }
- dsi->state &= ~DSIM_STATE_CMD_LPM;
- phy_power_off(dsi->phy);
- for (i = driver_data->num_clks - 1; i > -1; i--)
clk_disable_unprepare(dsi->clks[i]);
- ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0)
dev_err(dsi->dev, "cannot disable regulators %d\n", ret);
- return 0;
+} +EXPORT_SYMBOL_GPL(samsung_dsim_suspend);
+int samsung_dsim_resume(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable regulators %d\n", ret);
return ret;
- }
- for (i = 0; i < driver_data->num_clks; i++) {
ret = clk_prepare_enable(dsi->clks[i]);
if (ret < 0)
goto err_clk;
- }
- ret = phy_power_on(dsi->phy);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable phy %d\n", ret);
goto err_clk;
- }
- return 0;
+err_clk:
- while (--i > -1)
clk_disable_unprepare(dsi->clks[i]);
- regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- return ret;
+} +EXPORT_SYMBOL_GPL(samsung_dsim_resume);
+MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); +MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 6417f374b923..3bc321ab5bc8 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -16,7 +16,6 @@ comment "CRTCs"
config DRM_EXYNOS_FIMD bool "FIMD"
- depends on !FB_S3C select MFD_SYSCON help Choose this option if you want to use Exynos FIMD for DRM.
@@ -28,7 +27,6 @@ config DRM_EXYNOS5433_DECON
config DRM_EXYNOS7_DECON bool "DECON on Exynos7"
- depends on !FB_S3C help Choose this option if you want to use Exynos DECON for DRM.
@@ -55,8 +53,7 @@ config DRM_EXYNOS_DPI config DRM_EXYNOS_DSI bool "MIPI-DSI host" depends on DRM_EXYNOS_FIMD || DRM_EXYNOS5433_DECON || DRM_EXYNOS7_DECON
- select DRM_MIPI_DSI
- select DRM_PANEL
- select DRM_SAMSUNG_DSIM default n help This enables support for Exynos MIPI-DSI device.
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index add70b336935..2fd2f3ee4fcf 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -11,7 +11,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o -exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynos_drm_dsi_pltfm.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index e8aea9d90c34..17f37fa74718 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -5,1774 +5,328 @@
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
-*/
- */
-#include <linux/clk.h> -#include <linux/delay.h> #include <linux/component.h> -#include <linux/gpio/consumer.h> -#include <linux/irq.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> -#include <linux/phy/phy.h> -#include <linux/regulator/consumer.h>
-#include <asm/unaligned.h> +#include <linux/pm_runtime.h>
-#include <video/mipi_display.h> -#include <video/videomode.h>
-#include <drm/drm_atomic_helper.h> +#include <drm/bridge/samsung-dsim.h> #include <drm/drm_bridge.h> -#include <drm/drm_fb_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> -#include <drm/drm_print.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_dsi.h"
-/* returns true iff both arguments logically differs */ -#define NEQV(a, b) (!(a) ^ !(b))
-/* DSIM_STATUS */ -#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) -#define DSIM_STOP_STATE_CLK (1 << 8) -#define DSIM_TX_READY_HS_CLK (1 << 10) -#define DSIM_PLL_STABLE (1 << 31)
-/* DSIM_TIMEOUT */ -#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) -#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
-/* DSIM_CLKCTRL */ -#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) -#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) -#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) -#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) -#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) -#define DSIM_BYTE_CLKEN (1 << 24) -#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) -#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) -#define DSIM_PLL_BYPASS (1 << 27) -#define DSIM_ESC_CLKEN (1 << 28) -#define DSIM_TX_REQUEST_HSCLK (1 << 31)
-/* DSIM_CONFIG */ -#define DSIM_LANE_EN_CLK (1 << 0) -#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) -#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) -#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) -#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) -#define DSIM_SUB_VC (((x) & 0x3) << 16) -#define DSIM_MAIN_VC (((x) & 0x3) << 18) -#define DSIM_HSA_MODE (1 << 20) -#define DSIM_HBP_MODE (1 << 21) -#define DSIM_HFP_MODE (1 << 22) -#define DSIM_HSE_MODE (1 << 23) -#define DSIM_AUTO_MODE (1 << 24) -#define DSIM_VIDEO_MODE (1 << 25) -#define DSIM_BURST_MODE (1 << 26) -#define DSIM_SYNC_INFORM (1 << 27) -#define DSIM_EOT_DISABLE (1 << 28) -#define DSIM_MFLUSH_VS (1 << 29) -/* This flag is valid only for exynos3250/3472/5260/5430 */ -#define DSIM_CLKLANE_STOP (1 << 30)
-/* DSIM_ESCMODE */ -#define DSIM_TX_TRIGGER_RST (1 << 4) -#define DSIM_TX_LPDT_LP (1 << 6) -#define DSIM_CMD_LPDT_LP (1 << 7) -#define DSIM_FORCE_BTA (1 << 16) -#define DSIM_FORCE_STOP_STATE (1 << 20) -#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) -#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
-/* DSIM_MDRESOL */ -#define DSIM_MAIN_STAND_BY (1 << 31) -#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) -#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
-/* DSIM_MVPORCH */ -#define DSIM_CMD_ALLOW(x) ((x) << 28) -#define DSIM_STABLE_VFP(x) ((x) << 16) -#define DSIM_MAIN_VBP(x) ((x) << 0) -#define DSIM_CMD_ALLOW_MASK (0xf << 28) -#define DSIM_STABLE_VFP_MASK (0x7ff << 16) -#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
-/* DSIM_MHPORCH */ -#define DSIM_MAIN_HFP(x) ((x) << 16) -#define DSIM_MAIN_HBP(x) ((x) << 0) -#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) -#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
-/* DSIM_MSYNC */ -#define DSIM_MAIN_VSA(x) ((x) << 22) -#define DSIM_MAIN_HSA(x) ((x) << 0) -#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) -#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
-/* DSIM_SDRESOL */ -#define DSIM_SUB_STANDY(x) ((x) << 31) -#define DSIM_SUB_VRESOL(x) ((x) << 16) -#define DSIM_SUB_HRESOL(x) ((x) << 0) -#define DSIM_SUB_STANDY_MASK ((0x1) << 31) -#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) -#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
-/* DSIM_INTSRC */ -#define DSIM_INT_PLL_STABLE (1 << 31) -#define DSIM_INT_SW_RST_RELEASE (1 << 30) -#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) -#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) -#define DSIM_INT_BTA (1 << 25) -#define DSIM_INT_FRAME_DONE (1 << 24) -#define DSIM_INT_RX_TIMEOUT (1 << 21) -#define DSIM_INT_BTA_TIMEOUT (1 << 20) -#define DSIM_INT_RX_DONE (1 << 18) -#define DSIM_INT_RX_TE (1 << 17) -#define DSIM_INT_RX_ACK (1 << 16) -#define DSIM_INT_RX_ECC_ERR (1 << 15) -#define DSIM_INT_RX_CRC_ERR (1 << 14) +#include "exynos_drm_crtc.h" +#include "exynos_drm_drv.h"
-/* DSIM_FIFOCTRL */ -#define DSIM_RX_DATA_FULL (1 << 25) -#define DSIM_RX_DATA_EMPTY (1 << 24) -#define DSIM_SFR_HEADER_FULL (1 << 23) -#define DSIM_SFR_HEADER_EMPTY (1 << 22) -#define DSIM_SFR_PAYLOAD_FULL (1 << 21) -#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) -#define DSIM_I80_HEADER_FULL (1 << 19) -#define DSIM_I80_HEADER_EMPTY (1 << 18) -#define DSIM_I80_PAYLOAD_FULL (1 << 17) -#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) -#define DSIM_SD_HEADER_FULL (1 << 15) -#define DSIM_SD_HEADER_EMPTY (1 << 14) -#define DSIM_SD_PAYLOAD_FULL (1 << 13) -#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) -#define DSIM_MD_HEADER_FULL (1 << 11) -#define DSIM_MD_HEADER_EMPTY (1 << 10) -#define DSIM_MD_PAYLOAD_FULL (1 << 9) -#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) -#define DSIM_RX_FIFO (1 << 4) -#define DSIM_SFR_FIFO (1 << 3) -#define DSIM_I80_FIFO (1 << 2) -#define DSIM_SD_FIFO (1 << 1) -#define DSIM_MD_FIFO (1 << 0)
-/* DSIM_PHYACCHR */ -#define DSIM_AFC_EN (1 << 14) -#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
-/* DSIM_PLLCTRL */ -#define DSIM_FREQ_BAND(x) ((x) << 24) -#define DSIM_PLL_EN (1 << 23) -#define DSIM_PLL_P(x) ((x) << 13) -#define DSIM_PLL_M(x) ((x) << 4) -#define DSIM_PLL_S(x) ((x) << 1)
-/* DSIM_PHYCTRL */ -#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) -#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) -#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14)
-/* DSIM_PHYTIMING */ -#define DSIM_PHYTIMING_LPX(x) ((x) << 8) -#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
-/* DSIM_PHYTIMING1 */ -#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) -#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) -#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) -#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
-/* DSIM_PHYTIMING2 */ -#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) -#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) -#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
-#define DSI_MAX_BUS_WIDTH 4 -#define DSI_NUM_VIRTUAL_CHANNELS 4 -#define DSI_TX_FIFO_SIZE 2048 -#define DSI_RX_FIFO_SIZE 256 -#define DSI_XFER_TIMEOUT_MS 100 -#define DSI_RX_FIFO_EMPTY 0x30800002
-#define OLD_SCLK_MIPI_CLK_NAME "pll_clk"
-static const char *const clk_names[5] = { "bus_clk", "sclk_mipi",
- "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0",
- "sclk_rgb_vclk_to_dsim0" };
-enum exynos_dsi_transfer_type {
- EXYNOS_DSI_TX,
- EXYNOS_DSI_RX,
+enum {
- DSI_PORT_IN,
- DSI_PORT_OUT
};
-struct exynos_dsi_transfer {
- struct list_head list;
- struct completion completed;
- int result;
- struct mipi_dsi_packet packet;
- u16 flags;
- u16 tx_done;
- u8 *rx_payload;
- u16 rx_len;
- u16 rx_done;
-};
-#define DSIM_STATE_ENABLED BIT(0) -#define DSIM_STATE_INITIALIZED BIT(1) -#define DSIM_STATE_CMD_LPM BIT(2) -#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
struct exynos_dsi {
- struct drm_bridge bridge;
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_panel *panel;
- struct list_head bridge_chain;
- struct drm_bridge *out_bridge;
- struct device *dev;
- void __iomem *reg_base;
- struct phy *phy;
- struct clk **clks;
- struct regulator_bulk_data supplies[2];
- int irq;
- int te_gpio;
- u32 pll_clk_rate;
- u32 burst_clk_rate;
- u32 esc_clk_rate;
- u32 lanes;
- u32 mode_flags;
- u32 format;
- struct drm_display_mode mode;
- int state;
- struct drm_property *brightness;
- struct completion completed;
- spinlock_t transfer_lock; /* protects transfer_list */
- struct list_head transfer_list;
- const struct exynos_dsi_driver_data *driver_data;
- struct samsung_dsim *dsi;
- struct drm_encoder encoder;
};
-#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) -#define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector)
-enum reg_idx {
- DSIM_STATUS_REG, /* Status register */
- DSIM_SWRST_REG, /* Software reset register */
- DSIM_CLKCTRL_REG, /* Clock control register */
- DSIM_TIMEOUT_REG, /* Time out register */
- DSIM_CONFIG_REG, /* Configuration register */
- DSIM_ESCMODE_REG, /* Escape mode register */
- DSIM_MDRESOL_REG,
- DSIM_MVPORCH_REG, /* Main display Vporch register */
- DSIM_MHPORCH_REG, /* Main display Hporch register */
- DSIM_MSYNC_REG, /* Main display sync area register */
- DSIM_INTSRC_REG, /* Interrupt source register */
- DSIM_INTMSK_REG, /* Interrupt mask register */
- DSIM_PKTHDR_REG, /* Packet Header FIFO register */
- DSIM_PAYLOAD_REG, /* Payload FIFO register */
- DSIM_RXFIFO_REG, /* Read FIFO register */
- DSIM_FIFOCTRL_REG, /* FIFO status and control register */
- DSIM_PLLCTRL_REG, /* PLL control register */
- DSIM_PHYCTRL_REG,
- DSIM_PHYTIMING_REG,
- DSIM_PHYTIMING1_REG,
- DSIM_PHYTIMING2_REG,
- NUM_REGS
+static const unsigned int reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0x0af,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x06,
- [PHYTIMING_HS_EXIT] = 0x0b,
- [PHYTIMING_CLK_PREPARE] = 0x07,
- [PHYTIMING_CLK_ZERO] = 0x27,
- [PHYTIMING_CLK_POST] = 0x0d,
- [PHYTIMING_CLK_TRAIL] = 0x08,
- [PHYTIMING_HS_PREPARE] = 0x09,
- [PHYTIMING_HS_ZERO] = 0x0d,
- [PHYTIMING_HS_TRAIL] = 0x0b,
};
-static const unsigned int exynos_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x00,
- [DSIM_SWRST_REG] = 0x04,
- [DSIM_CLKCTRL_REG] = 0x08,
- [DSIM_TIMEOUT_REG] = 0x0c,
- [DSIM_CONFIG_REG] = 0x10,
- [DSIM_ESCMODE_REG] = 0x14,
- [DSIM_MDRESOL_REG] = 0x18,
- [DSIM_MVPORCH_REG] = 0x1c,
- [DSIM_MHPORCH_REG] = 0x20,
- [DSIM_MSYNC_REG] = 0x24,
- [DSIM_INTSRC_REG] = 0x2c,
- [DSIM_INTMSK_REG] = 0x30,
- [DSIM_PKTHDR_REG] = 0x34,
- [DSIM_PAYLOAD_REG] = 0x38,
- [DSIM_RXFIFO_REG] = 0x3c,
- [DSIM_FIFOCTRL_REG] = 0x44,
- [DSIM_PLLCTRL_REG] = 0x4c,
- [DSIM_PHYCTRL_REG] = 0x5c,
- [DSIM_PHYTIMING_REG] = 0x64,
- [DSIM_PHYTIMING1_REG] = 0x68,
- [DSIM_PHYTIMING2_REG] = 0x6c,
+static const unsigned int exynos5422_reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0xaf,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x08,
- [PHYTIMING_HS_EXIT] = 0x0d,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x30,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x0a,
- [PHYTIMING_HS_PREPARE] = 0x0c,
- [PHYTIMING_HS_ZERO] = 0x11,
- [PHYTIMING_HS_TRAIL] = 0x0d,
};
-static const unsigned int exynos5433_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x04,
- [DSIM_SWRST_REG] = 0x0C,
- [DSIM_CLKCTRL_REG] = 0x10,
- [DSIM_TIMEOUT_REG] = 0x14,
- [DSIM_CONFIG_REG] = 0x18,
- [DSIM_ESCMODE_REG] = 0x1C,
- [DSIM_MDRESOL_REG] = 0x20,
- [DSIM_MVPORCH_REG] = 0x24,
- [DSIM_MHPORCH_REG] = 0x28,
- [DSIM_MSYNC_REG] = 0x2C,
- [DSIM_INTSRC_REG] = 0x34,
- [DSIM_INTMSK_REG] = 0x38,
- [DSIM_PKTHDR_REG] = 0x3C,
- [DSIM_PAYLOAD_REG] = 0x40,
- [DSIM_RXFIFO_REG] = 0x44,
- [DSIM_FIFOCTRL_REG] = 0x4C,
- [DSIM_PLLCTRL_REG] = 0x94,
- [DSIM_PHYCTRL_REG] = 0xA4,
- [DSIM_PHYTIMING_REG] = 0xB4,
- [DSIM_PHYTIMING1_REG] = 0xB8,
- [DSIM_PHYTIMING2_REG] = 0xBC,
+static const unsigned int exynos5433_reg_values[] = {
- [RESET_TYPE] = DSIM_FUNCRST,
- [PLL_TIMER] = 22200,
- [STOP_STATE_CNT] = 0xa,
- [PHYCTRL_ULPS_EXIT] = 0x190,
- [PHYCTRL_VREG_LP] = 1,
- [PHYCTRL_SLEW_UP] = 1,
- [PHYTIMING_LPX] = 0x07,
- [PHYTIMING_HS_EXIT] = 0x0c,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x2d,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x09,
- [PHYTIMING_HS_PREPARE] = 0x0b,
- [PHYTIMING_HS_ZERO] = 0x10,
- [PHYTIMING_HS_TRAIL] = 0x0c,
};
-static inline void exynos_dsi_write(struct exynos_dsi *dsi, enum reg_idx idx,
u32 val)
-{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- writel(val, dsi->reg_base + reg_ofs[idx]);
-}
-static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) -{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- return readl(dsi->reg_base + reg_ofs[idx]);
-}
-static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) -{
- if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300)))
return;
- dev_err(dsi->dev, "timeout waiting for reset\n");
-}
-static void exynos_dsi_reset(struct exynos_dsi *dsi) -{
- u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE];
- reinit_completion(&dsi->completed);
- exynos_dsi_write(dsi, DSIM_SWRST_REG, reset_val);
-}
-#ifndef MHZ -#define MHZ (1000*1000) -#endif
-static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi,
unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s)
-{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- unsigned long best_freq = 0;
- u32 min_delta = 0xffffffff;
- u8 p_min, p_max;
- u8 _p, best_p;
- u16 _m, best_m;
- u8 _s, best_s;
- p_min = DIV_ROUND_UP(fin, (12 * MHZ));
- p_max = fin / (6 * MHZ);
- for (_p = p_min; _p <= p_max; ++_p) {
for (_s = 0; _s <= 5; ++_s) {
u64 tmp;
u32 delta;
tmp = (u64)fout * (_p << _s);
do_div(tmp, fin);
_m = tmp;
if (_m < 41 || _m > 125)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p);
if (tmp < 500 * MHZ ||
tmp > driver_data->max_freq * MHZ)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p << _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_m = _m;
best_s = _s;
min_delta = delta;
best_freq = tmp;
}
}
- }
- if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
- }
- return best_freq;
-}
-static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi,
unsigned long freq)
-{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- unsigned long fin, fout;
- int timeout;
- u8 p, s;
- u16 m;
- u32 reg;
- fin = dsi->pll_clk_rate;
- fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s);
- if (!fout) {
dev_err(dsi->dev,
"failed to find PLL PMS for requested frequency\n");
return 0;
- }
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- writel(driver_data->reg_values[PLL_TIMER],
dsi->reg_base + driver_data->plltmr_reg);
- reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
- if (driver_data->has_freqband) {
static const unsigned long freq_bands[] = {
100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
770 * MHZ, 870 * MHZ, 950 * MHZ,
};
int band;
for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
if (fout < freq_bands[band])
break;
dev_dbg(dsi->dev, "band %d\n", band);
reg |= DSIM_FREQ_BAND(band);
- }
- exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg);
- timeout = 1000;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "PLL failed to stabilize\n");
return 0;
}
reg = exynos_dsi_read(dsi, DSIM_STATUS_REG);
- } while ((reg & DSIM_PLL_STABLE) == 0);
- return fout;
-}
-static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) -{
- unsigned long hs_clk, byte_clk, esc_clk;
- unsigned long esc_div;
- u32 reg;
- hs_clk = exynos_dsi_set_pll(dsi, dsi->burst_clk_rate);
- if (!hs_clk) {
dev_err(dsi->dev, "failed to configure DSI PLL\n");
return -EFAULT;
- }
- byte_clk = hs_clk / 8;
- esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate);
- esc_clk = byte_clk / esc_div;
- if (esc_clk > 20 * MHZ) {
++esc_div;
esc_clk = byte_clk / esc_div;
- }
- dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
hs_clk, byte_clk, esc_clk);
- reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
| DSIM_BYTE_CLK_SRC_MASK);
- reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
| DSIM_ESC_PRESCALER(esc_div)
| DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1)
| DSIM_BYTE_CLK_SRC(0)
| DSIM_TX_REQUEST_HSCLK;
- exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg);
- return 0;
-}
-static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) -{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- const unsigned int *reg_values = driver_data->reg_values;
- u32 reg;
- if (driver_data->has_freqband)
return;
- /* B D-PHY: D-PHY Master & Slave Analog Block control */
- reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]);
- if (reg_values[PHYCTRL_VREG_LP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP;
- if (reg_values[PHYCTRL_SLEW_UP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP;
- exynos_dsi_write(dsi, DSIM_PHYCTRL_REG, reg);
- /*
* T LPX: Transmitted length of any Low-Power state period
* T HS-EXIT: Time that the transmitter drives LP-11 following a HS
* burst
*/
- reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) |
DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING_REG, reg);
- /*
* T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Clock.
* T CLK_POST: Time that the transmitter continues to send HS clock
* after the last associated Data Lane has transitioned to LP Mode
* Interval is defined as the period from the end of T HS-TRAIL to
* the beginning of T CLK-TRAIL
* T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
* the last payload clock bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) |
DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) |
DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) |
DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING1_REG, reg);
- /*
* T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Sync sequence.
* T HS-TRAIL: Time that the transmitter drives the flipped differential
* state after last payload data bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) |
DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) |
DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING2_REG, reg);
-}
-static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) -{
- u32 reg;
- reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK
| DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
- exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg);
- reg = exynos_dsi_read(dsi, DSIM_PLLCTRL_REG);
- reg &= ~DSIM_PLL_EN;
- exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg);
-}
-static void exynos_dsi_enable_lane(struct exynos_dsi *dsi, u32 lane) -{
- u32 reg = exynos_dsi_read(dsi, DSIM_CONFIG_REG);
- reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK |
DSIM_LANE_EN(lane));
- exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg);
-}
-static int exynos_dsi_init_link(struct exynos_dsi *dsi) +static int exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int timeout;
- u32 reg;
- u32 lanes_mask;
- /* Initialize FIFO pointers */
- reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG);
- reg &= ~0x1f;
- exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- reg |= 0x1f;
- exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- struct exynos_drm_crtc *crtc;
- /* DSI configuration */
- reg = 0;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- /*
* The first bit of mode_flags specifies display configuration.
* If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video
* mode, otherwise it will support command mode.
*/
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg |= DSIM_VIDEO_MODE;
/*
* The user manual describes that following bits are ignored in
* command mode.
*/
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH))
reg |= DSIM_MFLUSH_VS;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
reg |= DSIM_SYNC_INFORM;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
reg |= DSIM_BURST_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
reg |= DSIM_AUTO_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
reg |= DSIM_HSE_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP))
reg |= DSIM_HFP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP))
reg |= DSIM_HBP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA))
reg |= DSIM_HSA_MODE;
- }
- if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
reg |= DSIM_EOT_DISABLE;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
break;
- case MIPI_DSI_FMT_RGB666:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
break;
- case MIPI_DSI_FMT_RGB565:
reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
break;
- default:
dev_err(dsi->dev, "invalid pixel format\n");
return -EINVAL;
- }
- /*
* Use non-continuous clock mode if the periparal wants and
* host controller supports
*
* In non-continous clock mode, host controller will turn off
* the HS clock between high-speed transmissions to reduce
* power consumption.
*/
- if (driver_data->has_clklane_stop &&
dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
reg |= DSIM_CLKLANE_STOP;
- }
- exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg);
- lanes_mask = BIT(dsi->lanes) - 1;
- exynos_dsi_enable_lane(dsi, lanes_mask);
- /* Check clock and data lane state are stop state */
- timeout = 100;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "waiting for bus lanes timed out\n");
return -EFAULT;
}
reg = exynos_dsi_read(dsi, DSIM_STATUS_REG);
if ((reg & DSIM_STOP_STATE_DAT(lanes_mask))
!= DSIM_STOP_STATE_DAT(lanes_mask))
continue;
- } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
- reg = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- reg &= ~DSIM_STOP_STATE_CNT_MASK;
- reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]);
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, reg);
- reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
- exynos_dsi_write(dsi, DSIM_TIMEOUT_REG, reg);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
return 0;
}
-static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) -{
- struct drm_display_mode *m = &dsi->mode;
- unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
- u32 reg;
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg = DSIM_CMD_ALLOW(0xf)
| DSIM_STABLE_VFP(m->vsync_start - m->vdisplay)
| DSIM_MAIN_VBP(m->vtotal - m->vsync_end);
exynos_dsi_write(dsi, DSIM_MVPORCH_REG, reg);
reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay)
| DSIM_MAIN_HBP(m->htotal - m->hsync_end);
exynos_dsi_write(dsi, DSIM_MHPORCH_REG, reg);
reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start)
| DSIM_MAIN_HSA(m->hsync_end - m->hsync_start);
exynos_dsi_write(dsi, DSIM_MSYNC_REG, reg);
- }
- reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) |
DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol);
- exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg);
- dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay);
-}
-static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) -{
- u32 reg;
- reg = exynos_dsi_read(dsi, DSIM_MDRESOL_REG);
- if (enable)
reg |= DSIM_MAIN_STAND_BY;
- else
reg &= ~DSIM_MAIN_STAND_BY;
- exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg);
-}
-static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) -{
- int timeout = 2000;
- do {
u32 reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG);
if (!(reg & DSIM_SFR_HEADER_FULL))
return 0;
if (!cond_resched())
usleep_range(950, 1050);
- } while (--timeout);
- return -ETIMEDOUT;
-}
-static void exynos_dsi_set_cmd_lpm(struct exynos_dsi *dsi, bool lpm) -{
- u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- if (lpm)
v |= DSIM_CMD_LPDT_LP;
- else
v &= ~DSIM_CMD_LPDT_LP;
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v);
-}
-static void exynos_dsi_force_bta(struct exynos_dsi *dsi) -{
- u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- v |= DSIM_FORCE_BTA;
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v);
-}
-static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- struct device *dev = dsi->dev;
- struct mipi_dsi_packet *pkt = &xfer->packet;
- const u8 *payload = pkt->payload + xfer->tx_done;
- u16 length = pkt->payload_length - xfer->tx_done;
- bool first = !xfer->tx_done;
- u32 reg;
- dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n",
xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done);
- if (length > DSI_TX_FIFO_SIZE)
length = DSI_TX_FIFO_SIZE;
- xfer->tx_done += length;
- /* Send payload */
- while (length >= 4) {
reg = get_unaligned_le32(payload);
exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg);
payload += 4;
length -= 4;
- }
- reg = 0;
- switch (length) {
- case 3:
reg |= payload[2] << 16;
fallthrough;
- case 2:
reg |= payload[1] << 8;
fallthrough;
- case 1:
reg |= payload[0];
exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg);
break;
- }
- /* Send packet header */
- if (!first)
return;
- reg = get_unaligned_le32(pkt->header);
- if (exynos_dsi_wait_for_hdr_fifo(dsi)) {
dev_err(dev, "waiting for header FIFO timed out\n");
return;
- }
- if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM,
dsi->state & DSIM_STATE_CMD_LPM)) {
exynos_dsi_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM);
dsi->state ^= DSIM_STATE_CMD_LPM;
- }
- exynos_dsi_write(dsi, DSIM_PKTHDR_REG, reg);
- if (xfer->flags & MIPI_DSI_MSG_REQ_ACK)
exynos_dsi_force_bta(dsi);
-}
-static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- u8 *payload = xfer->rx_payload + xfer->rx_done;
- bool first = !xfer->rx_done;
- struct device *dev = dsi->dev;
- u16 length;
- u32 reg;
- if (first) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
switch (reg & 0x3f) {
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
if (xfer->rx_len >= 2) {
payload[1] = reg >> 16;
++xfer->rx_done;
}
fallthrough;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
payload[0] = reg >> 8;
++xfer->rx_done;
xfer->rx_len = xfer->rx_done;
xfer->result = 0;
goto clear_fifo;
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
dev_err(dev, "DSI Error Report: 0x%04x\n",
(reg >> 8) & 0xffff);
xfer->result = 0;
goto clear_fifo;
}
length = (reg >> 8) & 0xffff;
if (length > xfer->rx_len) {
dev_err(dev,
"response too long (%u > %u bytes), stripping\n",
xfer->rx_len, length);
length = xfer->rx_len;
} else if (length < xfer->rx_len)
xfer->rx_len = length;
- }
- length = xfer->rx_len - xfer->rx_done;
- xfer->rx_done += length;
- /* Receive payload */
- while (length >= 4) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
payload[0] = (reg >> 0) & 0xff;
payload[1] = (reg >> 8) & 0xff;
payload[2] = (reg >> 16) & 0xff;
payload[3] = (reg >> 24) & 0xff;
payload += 4;
length -= 4;
- }
- if (length) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
switch (length) {
case 3:
payload[2] = (reg >> 16) & 0xff;
fallthrough;
case 2:
payload[1] = (reg >> 8) & 0xff;
fallthrough;
case 1:
payload[0] = reg & 0xff;
}
- }
- if (xfer->rx_done == xfer->rx_len)
xfer->result = 0;
-clear_fifo:
- length = DSI_RX_FIFO_SIZE / 4;
- do {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
if (reg == DSI_RX_FIFO_EMPTY)
break;
- } while (--length);
-}
-static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) -{
- unsigned long flags;
- struct exynos_dsi_transfer *xfer;
- bool start = false;
-again:
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (xfer->packet.payload_length &&
xfer->tx_done == xfer->packet.payload_length)
/* waiting for RX */
return;
- exynos_dsi_send_to_fifo(dsi, xfer);
- if (xfer->packet.payload_length || xfer->rx_len)
return;
- xfer->result = 0;
- complete(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (start)
goto again;
-}
-static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) -{
- struct exynos_dsi_transfer *xfer;
- unsigned long flags;
- bool start = true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return false;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- dev_dbg(dsi->dev,
"> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n",
xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len,
xfer->rx_done);
- if (xfer->tx_done != xfer->packet.payload_length)
return true;
- if (xfer->rx_done != xfer->rx_len)
exynos_dsi_read_from_fifo(dsi, xfer);
- if (xfer->rx_done != xfer->rx_len)
return true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (!xfer->rx_len)
xfer->result = 0;
- complete(&xfer->completed);
- return start;
-}
-static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- unsigned long flags;
- bool start;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (!list_empty(&dsi->transfer_list) &&
xfer == list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list)) {
list_del_init(&xfer->list);
start = !list_empty(&dsi->transfer_list);
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
if (start)
exynos_dsi_transfer_start(dsi);
return;
- }
- list_del_init(&xfer->list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
-}
-static int exynos_dsi_transfer(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- unsigned long flags;
- bool stopped;
- xfer->tx_done = 0;
- xfer->rx_done = 0;
- xfer->result = -ETIMEDOUT;
- init_completion(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- stopped = list_empty(&dsi->transfer_list);
- list_add_tail(&xfer->list, &dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (stopped)
exynos_dsi_transfer_start(dsi);
- wait_for_completion_timeout(&xfer->completed,
msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
- if (xfer->result == -ETIMEDOUT) {
struct mipi_dsi_packet *pkt = &xfer->packet;
exynos_dsi_remove_transfer(dsi, xfer);
dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header,
(int)pkt->payload_length, pkt->payload);
return -ETIMEDOUT;
- }
- /* Also covers hardware timeout condition */
- return xfer->result;
-}
-static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) -{
- struct exynos_dsi *dsi = dev_id;
- u32 status;
- status = exynos_dsi_read(dsi, DSIM_INTSRC_REG);
- if (!status) {
static unsigned long int j;
if (printk_timed_ratelimit(&j, 500))
dev_warn(dsi->dev, "spurious interrupt\n");
return IRQ_HANDLED;
- }
- exynos_dsi_write(dsi, DSIM_INTSRC_REG, status);
- if (status & DSIM_INT_SW_RST_RELEASE) {
u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR |
DSIM_INT_SW_RST_RELEASE);
exynos_dsi_write(dsi, DSIM_INTMSK_REG, mask);
complete(&dsi->completed);
return IRQ_HANDLED;
- }
- if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_PLL_STABLE)))
return IRQ_HANDLED;
- if (exynos_dsi_transfer_finish(dsi))
exynos_dsi_transfer_start(dsi);
- return IRQ_HANDLED;
-}
-static irqreturn_t exynos_dsi_te_irq_handler(int irq, void *dev_id) -{
- struct exynos_dsi *dsi = dev_id;
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->te_handler &&
(dsi->state & DSIM_STATE_VIDOUT_AVAILABLE))
ops->te_handler(dsi->dsi_host.dev);
- return IRQ_HANDLED;
-}
-static void exynos_dsi_enable_irq(struct exynos_dsi *dsi) -{
- enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
enable_irq(gpio_to_irq(dsi->te_gpio));
-}
-static void exynos_dsi_disable_irq(struct exynos_dsi *dsi) -{
- if (gpio_is_valid(dsi->te_gpio))
disable_irq(gpio_to_irq(dsi->te_gpio));
- disable_irq(dsi->irq);
-}
-static int exynos_dsi_init(struct exynos_dsi *dsi) +static int exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- exynos_dsi_reset(dsi);
- exynos_dsi_enable_irq(dsi);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST)
exynos_dsi_enable_lane(dsi, BIT(dsi->lanes) - 1);
- exynos_dsi_enable_clock(dsi);
- if (driver_data->wait_for_reset)
exynos_dsi_wait_for_reset(dsi);
- exynos_dsi_set_phy_ctrl(dsi);
- exynos_dsi_init_link(dsi);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
return 0;
}
-static int exynos_dsi_register_te_irq(struct exynos_dsi *dsi,
struct device *panel)
+static void exynos_dsi_te_handler(struct device *dev) {
- int ret;
- int te_gpio_irq;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
return 0;
- if (!gpio_is_valid(dsi->te_gpio)) {
ret = dsi->te_gpio;
dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
goto out;
- }
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
dev_err(dsi->dev, "gpio request failed with %d\n", ret);
goto out;
- }
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
- irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN);
- ret = request_threaded_irq(te_gpio_irq, exynos_dsi_te_irq_handler, NULL,
IRQF_TRIGGER_RISING, "TE", dsi);
- if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
gpio_free(dsi->te_gpio);
goto out;
- }
-out:
- return ret;
- exynos_drm_crtc_te_handler(dsi->encoder.crtc);
}
-static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) -{
- if (gpio_is_valid(dsi->te_gpio)) {
free_irq(gpio_to_irq(dsi->te_gpio), dsi);
gpio_free(dsi->te_gpio);
dsi->te_gpio = -ENOENT;
- }
-}
-static void exynos_dsi_enable(struct exynos_dsi *dsi) -{
- struct drm_bridge *iter;
- int ret;
- if (dsi->state & DSIM_STATE_ENABLED)
return;
- pm_runtime_get_sync(dsi->dev);
- dsi->state |= DSIM_STATE_ENABLED;
- if (dsi->panel) {
ret = drm_panel_prepare(dsi->panel);
if (ret < 0)
goto err_put_sync;
- } else {
list_for_each_entry_reverse(iter, &dsi->bridge_chain,
chain_node) {
if (iter->funcs->pre_enable)
iter->funcs->pre_enable(iter);
}
- }
- exynos_dsi_set_display_mode(dsi);
- exynos_dsi_set_display_enable(dsi, true);
- if (dsi->panel) {
ret = drm_panel_enable(dsi->panel);
if (ret < 0)
goto err_display_disable;
- } else {
list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->enable)
iter->funcs->enable(iter);
}
- }
- dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE;
- return;
-err_display_disable:
- exynos_dsi_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
-err_put_sync:
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put(dsi->dev);
-}
-static void exynos_dsi_disable(struct exynos_dsi *dsi) -{
- struct drm_bridge *iter;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return;
- dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE;
- drm_panel_disable(dsi->panel);
- list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->disable)
iter->funcs->disable(iter);
- }
- exynos_dsi_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
- list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->post_disable)
iter->funcs->post_disable(iter);
- }
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put_sync(dsi->dev);
-}
-static enum drm_connector_status -exynos_dsi_detect(struct drm_connector *connector, bool force) -{
- return connector->status;
-} +static const struct samsung_dsim_host_ops exynos_dsi_host_ops = {
- .attach = exynos_dsi_host_attach,
- .detach = exynos_dsi_host_detach,
- .te_handler = exynos_dsi_te_handler,
+};
-static void exynos_dsi_connector_destroy(struct drm_connector *connector) -{
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- connector->dev = NULL;
-} +static const struct samsung_dsim_driver_data exynos3_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
-static const struct drm_connector_funcs exynos_dsi_connector_funcs = {
- .detect = exynos_dsi_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = exynos_dsi_connector_destroy,
- .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 samsung_dsim_driver_data exynos4_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
};
-static int exynos_dsi_get_modes(struct drm_connector *connector) -{
- struct exynos_dsi *dsi = connector_to_dsi(connector);
+static const struct samsung_dsim_driver_data exynos5_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x58,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
- if (dsi->panel)
return drm_panel_get_modes(dsi->panel, connector);
+static const struct samsung_dsim_driver_data exynos5433_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 5,
- .max_freq = 1500,
- .wait_for_reset = 0,
- .num_bits_resol = 12,
- .reg_values = exynos5433_reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
- return 0;
-} +static const struct samsung_dsim_driver_data exynos5422_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1500,
- .wait_for_reset = 1,
- .num_bits_resol = 12,
- .reg_values = exynos5422_reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
-static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs = {
- .get_modes = exynos_dsi_get_modes,
+static const struct of_device_id exynos_dsi_of_match[] = {
- { .compatible = "samsung,exynos3250-mipi-dsi",
.data = &exynos3_dsi_driver_data },
- { .compatible = "samsung,exynos4210-mipi-dsi",
.data = &exynos4_dsi_driver_data },
- { .compatible = "samsung,exynos5410-mipi-dsi",
.data = &exynos5_dsi_driver_data },
- { .compatible = "samsung,exynos5422-mipi-dsi",
.data = &exynos5422_dsi_driver_data },
- { .compatible = "samsung,exynos5433-mipi-dsi",
.data = &exynos5433_dsi_driver_data },
- { }
};
-static int exynos_dsi_create_connector(struct exynos_dsi *dsi) +static int exynos_dsi_bind(struct device *dev,
struct device *master, void *data)
{
- struct drm_connector *connector = &dsi->connector;
- struct drm_device *drm = dsi->bridge.dev;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_device *drm_dev = data;
- struct device_node *in_bridge_node;
- struct drm_bridge *in_bridge; int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = drm_connector_init(drm, connector, &exynos_dsi_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
- if (ret) {
DRM_DEV_ERROR(dsi->dev,
"Failed to initialize connector with drm\n");
- ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
- if (ret < 0) return ret;
- }
- connector->status = connector_status_disconnected;
- drm_connector_helper_add(connector, &exynos_dsi_connector_helper_funcs);
- drm_connector_attach_encoder(connector, dsi->bridge.encoder);
- if (!drm->registered)
return 0;
- connector->funcs->reset(connector);
- drm_connector_register(connector);
- return 0;
-}
-static int exynos_dsi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
-{
struct exynos_dsi *dsi = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder;
int ret;
if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
} else {
ret = exynos_dsi_create_connector(dsi);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
}
return 0;
-}
-static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
exynos_dsi_disable(dsi);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0);
- if (in_bridge_node) {
in_bridge = of_drm_find_bridge(in_bridge_node);
if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL, 0);
}of_node_put(in_bridge_node);
-}
-static void exynos_dsi_bridge_enable(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- exynos_dsi_enable(dsi);
-}
-static void exynos_dsi_bridge_disable(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- exynos_dsi_disable(dsi);
-}
-static void exynos_dsi_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
-{
- struct exynos_dsi *dsi = bridge->driver_private;
- /* The mode is set when actually enabling the device. */
- drm_mode_copy(&dsi->mode, adjusted_mode);
-}
-static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = {
- .attach = exynos_dsi_bridge_attach,
- .detach = exynos_dsi_bridge_detach,
- .enable = exynos_dsi_bridge_enable,
- .disable = exynos_dsi_bridge_disable,
- .mode_set = exynos_dsi_bridge_mode_set,
-};
-static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi *dsi = host_to_dsi(host);
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node);
- if (out_bridge) {
dsi->out_bridge = out_bridge;
- } else {
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
else
dsi->connector.status = connector_status_connected;
- }
- /*
* This is a temporary solution and should be made by more generic way.
*
* If attached panel device is for command mode one, dsi should register
* TE interrupt handler.
*/
- if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
int ret = exynos_dsi_register_te_irq(dsi, &device->dev);
if (ret)
return ret;
- }
- dsi->lanes = device->lanes;
- dsi->format = device->format;
- dsi->mode_flags = device->mode_flags;
- if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
ret = samsung_dsim_bind(dsi->dsi, encoder);
if (ret)
goto err;
return 0;
-}
-static int exynos_dsi_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
-{
struct exynos_dsi *dsi = host_to_dsi(host);
const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
return 0;
+err:
- drm_encoder_cleanup(encoder);
- return ret;
}
-static ssize_t exynos_dsi_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
+static void exynos_dsi_unbind(struct device *dev,
struct device *master, void *data)
{
- struct exynos_dsi *dsi = host_to_dsi(host);
- struct exynos_dsi_transfer xfer;
- int ret;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return -EINVAL;
- if (!(dsi->state & DSIM_STATE_INITIALIZED)) {
ret = exynos_dsi_init(dsi);
if (ret)
return ret;
dsi->state |= DSIM_STATE_INITIALIZED;
- }
- ret = mipi_dsi_create_packet(&xfer.packet, msg);
- if (ret < 0)
return ret;
- samsung_dsim_unbind(dsi->dsi);
- xfer.rx_len = msg->rx_len;
- xfer.rx_payload = msg->rx_buf;
- xfer.flags = msg->flags;
- ret = exynos_dsi_transfer(dsi, &xfer);
- return (ret < 0) ? ret : xfer.rx_done;
- drm_encoder_cleanup(encoder);
}
-static const struct mipi_dsi_host_ops exynos_dsi_ops = {
- .attach = exynos_dsi_host_attach,
- .detach = exynos_dsi_host_detach,
- .transfer = exynos_dsi_host_transfer,
+static const struct component_ops exynos_dsi_component_ops = {
- .bind = exynos_dsi_bind,
- .unbind = exynos_dsi_unbind,
};
-static int exynos_dsi_of_read_u32(const struct device_node *np,
const char *propname, u32 *out_value)
-{
- int ret = of_property_read_u32(np, propname, out_value);
- if (ret < 0)
pr_err("%pOF: failed to get '%s' property\n", np, propname);
- return ret;
-}
-static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) -{
- struct device *dev = dsi->dev;
- struct device_node *node = dev->of_node;
- int ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,pll-clock-frequency",
&dsi->pll_clk_rate);
- if (ret < 0)
return ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,burst-clock-frequency",
&dsi->burst_clk_rate);
- if (ret < 0)
return ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,esc-clock-frequency",
&dsi->esc_clk_rate);
- if (ret < 0)
return ret;
- return 0;
-}
-static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) +static int exynos_dsi_probe(struct platform_device *pdev) {
- struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- struct resource *res; struct exynos_dsi *dsi;
- int ret, i;
struct device *dev = &pdev->dev;
int ret;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); if (!dsi)
return ERR_PTR(-ENOMEM);
return -ENOMEM;
- platform_set_drvdata(pdev, dsi);
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
- dsi->dsi = samsung_dsim_probe(pdev);
- if (IS_ERR(dsi->dsi))
return PTR_ERR(dsi->dsi);
- init_completion(&dsi->completed);
- spin_lock_init(&dsi->transfer_lock);
- INIT_LIST_HEAD(&dsi->transfer_list);
- INIT_LIST_HEAD(&dsi->bridge_chain);
- pm_runtime_enable(dev);
- dsi->dsi_host.ops = &exynos_dsi_ops;
- dsi->dsi_host.dev = dev;
- dsi->dev = dev;
- dsi->driver_data = of_device_get_match_data(dev);
- dsi->supplies[0].supply = "vddcore";
- dsi->supplies[1].supply = "vddio";
- ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies),
dsi->supplies);
- if (ret) {
if (ret != -EPROBE_DEFER)
dev_info(dev, "failed to get regulators: %d\n", ret);
return ERR_PTR(ret);
- }
- dsi->clks = devm_kcalloc(dev,
dsi->driver_data->num_clks, sizeof(*dsi->clks),
GFP_KERNEL);
- if (!dsi->clks)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < dsi->driver_data->num_clks; i++) {
dsi->clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(dsi->clks[i])) {
if (strcmp(clk_names[i], "sclk_mipi") == 0) {
dsi->clks[i] = devm_clk_get(dev,
OLD_SCLK_MIPI_CLK_NAME);
if (!IS_ERR(dsi->clks[i]))
continue;
}
dev_info(dev, "failed to get the clock: %s\n",
clk_names[i]);
return ERR_PTR(PTR_ERR(dsi->clks[i]));
}
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->reg_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(dsi->reg_base)) {
dev_err(dev, "failed to remap io region\n");
return dsi->reg_base;
- }
- dsi->phy = devm_phy_get(dev, "dsim");
- if (IS_ERR(dsi->phy)) {
dev_info(dev, "failed to get dsim phy\n");
return ERR_PTR(PTR_ERR(dsi->phy));
- }
- dsi->irq = platform_get_irq(pdev, 0);
- if (dsi->irq < 0)
return ERR_PTR(dsi->irq);
- irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN);
- ret = devm_request_threaded_irq(dev, dsi->irq, NULL,
exynos_dsi_irq, IRQF_ONESHOT,
dev_name(dev), dsi);
- if (ret) {
dev_err(dev, "failed to request dsi irq\n");
return ERR_PTR(ret);
- }
- ret = exynos_dsi_parse_dt(dsi);
- ret = component_add(dev, &exynos_dsi_component_ops); if (ret)
return ERR_PTR(ret);
goto err_disable_runtime;
- ret = mipi_dsi_host_register(&dsi->dsi_host);
- if (ret)
return ERR_PTR(ret);
- return 0;
- bridge = &dsi->bridge;
- bridge->driver_private = dsi;
- bridge->funcs = &exynos_dsi_bridge_funcs;
- bridge->of_node = dev->of_node;
- drm_bridge_add(bridge);
+err_disable_runtime:
- pm_runtime_disable(dev);
- return dsi;
- return ret;
}
-static void __exynos_dsi_remove(struct exynos_dsi *dsi) +static int exynos_dsi_remove(struct platform_device *pdev) {
- drm_bridge_remove(&dsi->bridge);
- struct exynos_dsi *dsi = platform_get_drvdata(pdev);
- mipi_dsi_host_unregister(&dsi->dsi_host);
-}
-/*
- Probe/remove API, used from platforms based on the DRM bridge API.
- */
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) -{
- return __exynos_dsi_probe(pdev);
-}
- pm_runtime_disable(&pdev->dev);
-void exynos_dsi_remove(struct exynos_dsi *dsi) -{
- return __exynos_dsi_remove(dsi);
-}
- samsung_dsim_remove(dsi->dsi);
-/*
- Bind/unbind API, used from platforms based on the component framework.
- */
-int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) -{
- struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
- component_del(&pdev->dev, &exynos_dsi_component_ops);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0);
-}
-void exynos_dsi_unbind(struct exynos_dsi *dsi) -{
- exynos_dsi_disable(dsi);
- return 0;
}
-int exynos_dsi_suspend(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_suspend(struct device *dev) {
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- usleep_range(10000, 20000);
- if (dsi->state & DSIM_STATE_INITIALIZED) {
dsi->state &= ~DSIM_STATE_INITIALIZED;
exynos_dsi_disable_clock(dsi);
exynos_dsi_disable_irq(dsi);
- }
- dsi->state &= ~DSIM_STATE_CMD_LPM;
- phy_power_off(dsi->phy);
- for (i = driver_data->num_clks - 1; i > -1; i--)
clk_disable_unprepare(dsi->clks[i]);
- ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0)
dev_err(dsi->dev, "cannot disable regulators %d\n", ret);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0;
- return samsung_dsim_suspend(dsi->dsi);
}
-int exynos_dsi_resume(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_resume(struct device *dev) {
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable regulators %d\n", ret);
return ret;
- }
- for (i = 0; i < driver_data->num_clks; i++) {
ret = clk_prepare_enable(dsi->clks[i]);
if (ret < 0)
goto err_clk;
- }
- ret = phy_power_on(dsi->phy);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable phy %d\n", ret);
goto err_clk;
- }
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0;
- return samsung_dsim_resume(dsi->dsi);
+}
-err_clk:
- while (--i > -1)
clk_disable_unprepare(dsi->clks[i]);
- regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
+static const struct dev_pm_ops exynos_dsi_pm_ops = {
- SET_RUNTIME_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
+};
- return ret;
-} +struct platform_driver dsi_driver = {
- .probe = exynos_dsi_probe,
- .remove = exynos_dsi_remove,
- .driver = {
.name = "exynos-dsi",
.owner = THIS_MODULE,
.pm = &exynos_dsi_pm_ops,
.of_match_table = exynos_dsi_of_match,
- },
+};
MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c b/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c deleted file mode 100644 index 79d9ec6ade45..000000000000 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c +++ /dev/null @@ -1,333 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/*
- Samsung SoC MIPI DSI Master driver.
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
- */
-#include <linux/component.h> -#include <linux/of_device.h> -#include <linux/of_graph.h> -#include <linux/pm_runtime.h>
-#include <drm/drm_bridge.h> -#include <drm/drm_encoder.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_probe_helper.h> -#include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_crtc.h" -#include "exynos_drm_drv.h" -#include "exynos_drm_dsi.h"
-enum {
- DSI_PORT_IN,
- DSI_PORT_OUT
-};
-struct exynos_dsi_pltfm {
- struct exynos_dsi *dsi;
- struct drm_encoder encoder;
-};
-static const unsigned int reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0x0af,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x06,
- [PHYTIMING_HS_EXIT] = 0x0b,
- [PHYTIMING_CLK_PREPARE] = 0x07,
- [PHYTIMING_CLK_ZERO] = 0x27,
- [PHYTIMING_CLK_POST] = 0x0d,
- [PHYTIMING_CLK_TRAIL] = 0x08,
- [PHYTIMING_HS_PREPARE] = 0x09,
- [PHYTIMING_HS_ZERO] = 0x0d,
- [PHYTIMING_HS_TRAIL] = 0x0b,
-};
-static const unsigned int exynos5422_reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0xaf,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x08,
- [PHYTIMING_HS_EXIT] = 0x0d,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x30,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x0a,
- [PHYTIMING_HS_PREPARE] = 0x0c,
- [PHYTIMING_HS_ZERO] = 0x11,
- [PHYTIMING_HS_TRAIL] = 0x0d,
-};
-static const unsigned int exynos5433_reg_values[] = {
- [RESET_TYPE] = DSIM_FUNCRST,
- [PLL_TIMER] = 22200,
- [STOP_STATE_CNT] = 0xa,
- [PHYCTRL_ULPS_EXIT] = 0x190,
- [PHYCTRL_VREG_LP] = 1,
- [PHYCTRL_SLEW_UP] = 1,
- [PHYTIMING_LPX] = 0x07,
- [PHYTIMING_HS_EXIT] = 0x0c,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x2d,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x09,
- [PHYTIMING_HS_PREPARE] = 0x0b,
- [PHYTIMING_HS_ZERO] = 0x10,
- [PHYTIMING_HS_TRAIL] = 0x0c,
-};
-static int __exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- struct exynos_drm_crtc *crtc;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
-}
-static int __exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
-}
-static void __exynos_dsi_te_handler(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- exynos_drm_crtc_te_handler(dsi->encoder.crtc);
-}
-static const struct exynos_dsi_host_ops exynos_dsi_host_ops = {
- .attach = __exynos_dsi_host_attach,
- .detach = __exynos_dsi_host_detach,
- .te_handler = __exynos_dsi_te_handler,
-};
-static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x58,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 5,
- .max_freq = 1500,
- .wait_for_reset = 0,
- .num_bits_resol = 12,
- .reg_values = exynos5433_reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1500,
- .wait_for_reset = 1,
- .num_bits_resol = 12,
- .reg_values = exynos5422_reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct of_device_id exynos_dsi_of_match[] = {
- { .compatible = "samsung,exynos3250-mipi-dsi",
.data = &exynos3_dsi_driver_data },
- { .compatible = "samsung,exynos4210-mipi-dsi",
.data = &exynos4_dsi_driver_data },
- { .compatible = "samsung,exynos5410-mipi-dsi",
.data = &exynos5_dsi_driver_data },
- { .compatible = "samsung,exynos5422-mipi-dsi",
.data = &exynos5422_dsi_driver_data },
- { .compatible = "samsung,exynos5433-mipi-dsi",
.data = &exynos5433_dsi_driver_data },
- { }
-};
-static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_device *drm_dev = data;
- struct device_node *in_bridge_node;
- struct drm_bridge *in_bridge;
- int ret;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
- if (ret < 0)
return ret;
- in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0);
- if (in_bridge_node) {
in_bridge = of_drm_find_bridge(in_bridge_node);
if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL, 0);
of_node_put(in_bridge_node);
- }
- ret = exynos_dsi_bind(dsi->dsi, encoder);
- if (ret)
goto err;
- return 0;
-err:
- drm_encoder_cleanup(encoder);
- return ret;
-}
-static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master,
void *data)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- exynos_dsi_unbind(dsi->dsi);
- drm_encoder_cleanup(encoder);
-}
-static const struct component_ops exynos_dsi_pltfm_component_ops = {
- .bind = exynos_dsi_pltfm_bind,
- .unbind = exynos_dsi_pltfm_unbind,
-};
-static int exynos_dsi_pltfm_probe(struct platform_device *pdev) -{
- struct exynos_dsi_pltfm *dsi;
- struct device *dev = &pdev->dev;
- int ret;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
- if (!dsi)
return -ENOMEM;
- platform_set_drvdata(pdev, dsi);
- dsi->dsi = exynos_dsi_probe(pdev);
- if (IS_ERR(dsi->dsi))
return PTR_ERR(dsi->dsi);
- pm_runtime_enable(dev);
- ret = component_add(dev, &exynos_dsi_pltfm_component_ops);
- if (ret)
goto err_disable_runtime;
- return 0;
-err_disable_runtime:
- pm_runtime_disable(dev);
- return ret;
-}
-static int exynos_dsi_pltfm_remove(struct platform_device *pdev) -{
- struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev);
- pm_runtime_disable(&pdev->dev);
- exynos_dsi_remove(dsi->dsi);
- component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops);
- return 0;
-}
-static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- return exynos_dsi_suspend(dsi->dsi);
-}
-static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- return exynos_dsi_resume(dsi->dsi);
-}
-static const struct dev_pm_ops exynos_dsi_pm_ops = {
- SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
-};
-struct platform_driver dsi_driver = {
- .probe = exynos_dsi_pltfm_probe,
- .remove = exynos_dsi_pltfm_remove,
- .driver = {
.name = "exynos-dsi",
.owner = THIS_MODULE,
.pm = &exynos_dsi_pm_ops,
.of_match_table = exynos_dsi_of_match,
- },
-};
-MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); -MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); -MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.h b/include/drm/bridge/samsung-dsim.h similarity index 69% rename from drivers/gpu/drm/exynos/exynos_drm_dsi.h rename to include/drm/bridge/samsung-dsim.h index 8fa3276889de..be8b4913aa9c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.h +++ b/include/drm/bridge/samsung-dsim.h @@ -3,7 +3,7 @@ #define __EXYNOS_DRM_DSI__
struct drm_encoder; -struct exynos_dsi; +struct samsung_dsim; struct platform_device; struct mipi_dsi_device;
@@ -12,13 +12,13 @@ enum exynos_reg_offset { EXYNOS5433_REG_OFS };
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev); -void exynos_dsi_remove(struct exynos_dsi *dsi); -int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); -void exynos_dsi_unbind(struct exynos_dsi *dsi); +struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev); +void samsung_dsim_remove(struct samsung_dsim *dsi); +int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder); +void samsung_dsim_unbind(struct samsung_dsim *dsi);
-int exynos_dsi_suspend(struct exynos_dsi *dsi); -int exynos_dsi_resume(struct exynos_dsi *dsi); +int samsung_dsim_suspend(struct samsung_dsim *dsi); +int samsung_dsim_resume(struct samsung_dsim *dsi);
enum reg_value_idx { RESET_TYPE, @@ -42,13 +42,13 @@ enum reg_value_idx { #define DSIM_FUNCRST (1 << 16) #define DSIM_SWRST (1 << 0)
-struct exynos_dsi_host_ops { +struct samsung_dsim_host_ops { int (*attach)(struct device *dev, struct mipi_dsi_device *device); int (*detach)(struct device *dev, struct mipi_dsi_device *device); void (*te_handler)(struct device *dev); };
-struct exynos_dsi_driver_data { +struct samsung_dsim_driver_data { enum exynos_reg_offset reg_ofs; unsigned int plltmr_reg; unsigned int has_freqband:1; @@ -58,7 +58,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values;
- const struct exynos_dsi_host_ops *host_ops;
- const struct samsung_dsim_host_ops *host_ops;
};
#endif /* __EXYNOS_DRM_DSI__ */
2.20.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
On Wed, 16 Sep 2020 09:58:39 +0200, Daniel Vetter wrote:
On Fri, Sep 11, 2020 at 03:54:13PM +0200, Michael Tretter wrote:
As the driver is not platform dependent anymore, move it to the drm bridge driver directory.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
So new drm_bridge drivers that still use the component stuff is a bit uncool. We're trying to get away from that everywhere, bridges should be abstracted enough that just going through the of lookup functions to get at your bridge should work.
Is there anything here that prevents this, or could this be included?
The Exynos drm driver uses the component framework. Maybe I can avoid exposing the bind/unbind API in the drm_bridge driver by implementing the bind/unbind in the platform specific part. However, completely getting rid of the component framework for this drm_bridge is not possible without changing the entire Exynos drm driver.
Michael
-Daniel
v2:
- select DRM_SAMSUNG_DSIM from DRM_EXYNOS_DSI
- add removal of depends on !FB_S3C
drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1790 ++++++++++++++++ drivers/gpu/drm/exynos/Kconfig | 5 +- drivers/gpu/drm/exynos/Makefile | 2 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 1896 ++--------------- drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c | 333 --- .../drm/bridge/samsung-dsim.h | 20 +- 8 files changed, 2037 insertions(+), 2019 deletions(-) create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c delete mode 100644 drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c rename drivers/gpu/drm/exynos/exynos_drm_dsi.h => include/drm/bridge/samsung-dsim.h (69%)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3e11af4e9f63..55ab5030c6cf 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -125,6 +125,15 @@ config DRM_PARADE_PS8640 The PS8640 is a high-performance and low-power MIPI DSI to eDP converter
+config DRM_SAMSUNG_DSIM
- tristate "Samsung MIPI DSI bridge"
- depends on OF
- select DRM_KMS_HELPER
- select DRM_MIPI_DSI
- select DRM_PANEL
- help
Samsung MIPI DSI bridge driver.
config DRM_SIL_SII8620 tristate "Silicon Image SII8620 HDMI/MHL bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c589a6a7cbe1..5ac7a5c413dc 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o +obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII9234) += sii9234.o diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c new file mode 100644 index 000000000000..6d2d8dc027de --- /dev/null +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -0,0 +1,1790 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Samsung SoC MIPI DSI Master driver.
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
+*/
+#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/component.h> +#include <linux/gpio/consumer.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h>
+#include <asm/unaligned.h>
+#include <video/mipi_display.h> +#include <video/videomode.h>
+#include <drm/bridge/samsung-dsim.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h>
+/* returns true iff both arguments logically differs */ +#define NEQV(a, b) (!(a) ^ !(b))
+/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31)
+/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) +#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
+/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31)
+/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) +/* This flag is valid only for exynos3250/3472/5260/5430 */ +#define DSIM_CLKLANE_STOP (1 << 30)
+/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
+/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) +#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
+/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
+/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
+/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
+/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
+/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14)
+/* DSIM_FIFOCTRL */ +#define DSIM_RX_DATA_FULL (1 << 25) +#define DSIM_RX_DATA_EMPTY (1 << 24) +#define DSIM_SFR_HEADER_FULL (1 << 23) +#define DSIM_SFR_HEADER_EMPTY (1 << 22) +#define DSIM_SFR_PAYLOAD_FULL (1 << 21) +#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) +#define DSIM_I80_HEADER_FULL (1 << 19) +#define DSIM_I80_HEADER_EMPTY (1 << 18) +#define DSIM_I80_PAYLOAD_FULL (1 << 17) +#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) +#define DSIM_SD_HEADER_FULL (1 << 15) +#define DSIM_SD_HEADER_EMPTY (1 << 14) +#define DSIM_SD_PAYLOAD_FULL (1 << 13) +#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) +#define DSIM_MD_HEADER_FULL (1 << 11) +#define DSIM_MD_HEADER_EMPTY (1 << 10) +#define DSIM_MD_PAYLOAD_FULL (1 << 9) +#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) +#define DSIM_RX_FIFO (1 << 4) +#define DSIM_SFR_FIFO (1 << 3) +#define DSIM_I80_FIFO (1 << 2) +#define DSIM_SD_FIFO (1 << 1) +#define DSIM_MD_FIFO (1 << 0)
+/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
+/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1)
+/* DSIM_PHYCTRL */ +#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) +#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) +#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14)
+/* DSIM_PHYTIMING */ +#define DSIM_PHYTIMING_LPX(x) ((x) << 8) +#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
+/* DSIM_PHYTIMING1 */ +#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) +#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) +#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) +#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
+/* DSIM_PHYTIMING2 */ +#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) +#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) +#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
+#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002
+#define OLD_SCLK_MIPI_CLK_NAME "pll_clk"
+static const char *const clk_names[5] = { "bus_clk", "sclk_mipi",
- "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0",
- "sclk_rgb_vclk_to_dsim0" };
+enum samsung_dsim_transfer_type {
- EXYNOS_DSI_TX,
- EXYNOS_DSI_RX,
+};
+struct samsung_dsim_transfer {
- struct list_head list;
- struct completion completed;
- int result;
- struct mipi_dsi_packet packet;
- u16 flags;
- u16 tx_done;
- u8 *rx_payload;
- u16 rx_len;
- u16 rx_done;
+};
+#define DSIM_STATE_ENABLED BIT(0) +#define DSIM_STATE_INITIALIZED BIT(1) +#define DSIM_STATE_CMD_LPM BIT(2) +#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct samsung_dsim {
- struct drm_bridge bridge;
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_panel *panel;
- struct list_head bridge_chain;
- struct drm_bridge *out_bridge;
- struct device *dev;
- void __iomem *reg_base;
- struct phy *phy;
- struct clk **clks;
- struct regulator_bulk_data supplies[2];
- int irq;
- int te_gpio;
- u32 pll_clk_rate;
- u32 burst_clk_rate;
- u32 esc_clk_rate;
- u32 lanes;
- u32 mode_flags;
- u32 format;
- struct drm_display_mode mode;
- int state;
- struct drm_property *brightness;
- struct completion completed;
- spinlock_t transfer_lock; /* protects transfer_list */
- struct list_head transfer_list;
- const struct samsung_dsim_driver_data *driver_data;
+};
+#define host_to_dsi(host) container_of(host, struct samsung_dsim, dsi_host) +#define connector_to_dsi(c) container_of(c, struct samsung_dsim, connector)
+enum reg_idx {
- DSIM_STATUS_REG, /* Status register */
- DSIM_SWRST_REG, /* Software reset register */
- DSIM_CLKCTRL_REG, /* Clock control register */
- DSIM_TIMEOUT_REG, /* Time out register */
- DSIM_CONFIG_REG, /* Configuration register */
- DSIM_ESCMODE_REG, /* Escape mode register */
- DSIM_MDRESOL_REG,
- DSIM_MVPORCH_REG, /* Main display Vporch register */
- DSIM_MHPORCH_REG, /* Main display Hporch register */
- DSIM_MSYNC_REG, /* Main display sync area register */
- DSIM_INTSRC_REG, /* Interrupt source register */
- DSIM_INTMSK_REG, /* Interrupt mask register */
- DSIM_PKTHDR_REG, /* Packet Header FIFO register */
- DSIM_PAYLOAD_REG, /* Payload FIFO register */
- DSIM_RXFIFO_REG, /* Read FIFO register */
- DSIM_FIFOCTRL_REG, /* FIFO status and control register */
- DSIM_PLLCTRL_REG, /* PLL control register */
- DSIM_PHYCTRL_REG,
- DSIM_PHYTIMING_REG,
- DSIM_PHYTIMING1_REG,
- DSIM_PHYTIMING2_REG,
- NUM_REGS
+};
+static const unsigned int exynos_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x00,
- [DSIM_SWRST_REG] = 0x04,
- [DSIM_CLKCTRL_REG] = 0x08,
- [DSIM_TIMEOUT_REG] = 0x0c,
- [DSIM_CONFIG_REG] = 0x10,
- [DSIM_ESCMODE_REG] = 0x14,
- [DSIM_MDRESOL_REG] = 0x18,
- [DSIM_MVPORCH_REG] = 0x1c,
- [DSIM_MHPORCH_REG] = 0x20,
- [DSIM_MSYNC_REG] = 0x24,
- [DSIM_INTSRC_REG] = 0x2c,
- [DSIM_INTMSK_REG] = 0x30,
- [DSIM_PKTHDR_REG] = 0x34,
- [DSIM_PAYLOAD_REG] = 0x38,
- [DSIM_RXFIFO_REG] = 0x3c,
- [DSIM_FIFOCTRL_REG] = 0x44,
- [DSIM_PLLCTRL_REG] = 0x4c,
- [DSIM_PHYCTRL_REG] = 0x5c,
- [DSIM_PHYTIMING_REG] = 0x64,
- [DSIM_PHYTIMING1_REG] = 0x68,
- [DSIM_PHYTIMING2_REG] = 0x6c,
+};
+static const unsigned int exynos5433_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x04,
- [DSIM_SWRST_REG] = 0x0C,
- [DSIM_CLKCTRL_REG] = 0x10,
- [DSIM_TIMEOUT_REG] = 0x14,
- [DSIM_CONFIG_REG] = 0x18,
- [DSIM_ESCMODE_REG] = 0x1C,
- [DSIM_MDRESOL_REG] = 0x20,
- [DSIM_MVPORCH_REG] = 0x24,
- [DSIM_MHPORCH_REG] = 0x28,
- [DSIM_MSYNC_REG] = 0x2C,
- [DSIM_INTSRC_REG] = 0x34,
- [DSIM_INTMSK_REG] = 0x38,
- [DSIM_PKTHDR_REG] = 0x3C,
- [DSIM_PAYLOAD_REG] = 0x40,
- [DSIM_RXFIFO_REG] = 0x44,
- [DSIM_FIFOCTRL_REG] = 0x4C,
- [DSIM_PLLCTRL_REG] = 0x94,
- [DSIM_PHYCTRL_REG] = 0xA4,
- [DSIM_PHYTIMING_REG] = 0xB4,
- [DSIM_PHYTIMING1_REG] = 0xB8,
- [DSIM_PHYTIMING2_REG] = 0xBC,
+};
+static inline void samsung_dsim_write(struct samsung_dsim *dsi,
enum reg_idx idx, u32 val)
+{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- writel(val, dsi->reg_base + reg_ofs[idx]);
+}
+static inline u32 samsung_dsim_read(struct samsung_dsim *dsi, enum reg_idx idx) +{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- return readl(dsi->reg_base + reg_ofs[idx]);
+}
+static void samsung_dsim_wait_for_reset(struct samsung_dsim *dsi) +{
- if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300)))
return;
- dev_err(dsi->dev, "timeout waiting for reset\n");
+}
+static void samsung_dsim_reset(struct samsung_dsim *dsi) +{
- u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE];
- reinit_completion(&dsi->completed);
- samsung_dsim_write(dsi, DSIM_SWRST_REG, reset_val);
+}
+#ifndef MHZ +#define MHZ (1000*1000) +#endif
+static unsigned long samsung_dsim_pll_find_pms(struct samsung_dsim *dsi,
unsigned long fin,
unsigned long fout,
u8 *p, u16 *m, u8 *s)
+{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- unsigned long best_freq = 0;
- u32 min_delta = 0xffffffff;
- u8 p_min, p_max;
- u8 _p, best_p;
- u16 _m, best_m;
- u8 _s, best_s;
- p_min = DIV_ROUND_UP(fin, (12 * MHZ));
- p_max = fin / (6 * MHZ);
- for (_p = p_min; _p <= p_max; ++_p) {
for (_s = 0; _s <= 5; ++_s) {
u64 tmp;
u32 delta;
tmp = (u64)fout * (_p << _s);
do_div(tmp, fin);
_m = tmp;
if (_m < 41 || _m > 125)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p);
if (tmp < 500 * MHZ ||
tmp > driver_data->max_freq * MHZ)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p << _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_m = _m;
best_s = _s;
min_delta = delta;
best_freq = tmp;
}
}
- }
- if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
- }
- return best_freq;
+}
+static unsigned long samsung_dsim_set_pll(struct samsung_dsim *dsi,
unsigned long freq)
+{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- unsigned long fin, fout;
- int timeout;
- u8 p, s;
- u16 m;
- u32 reg;
- fin = dsi->pll_clk_rate;
- fout = samsung_dsim_pll_find_pms(dsi, fin, freq, &p, &m, &s);
- if (!fout) {
dev_err(dsi->dev,
"failed to find PLL PMS for requested frequency\n");
return 0;
- }
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- writel(driver_data->reg_values[PLL_TIMER],
dsi->reg_base + driver_data->plltmr_reg);
- reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
- if (driver_data->has_freqband) {
static const unsigned long freq_bands[] = {
100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
770 * MHZ, 870 * MHZ, 950 * MHZ,
};
int band;
for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
if (fout < freq_bands[band])
break;
dev_dbg(dsi->dev, "band %d\n", band);
reg |= DSIM_FREQ_BAND(band);
- }
- samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg);
- timeout = 1000;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "PLL failed to stabilize\n");
return 0;
}
reg = samsung_dsim_read(dsi, DSIM_STATUS_REG);
- } while ((reg & DSIM_PLL_STABLE) == 0);
- return fout;
+}
+static int samsung_dsim_enable_clock(struct samsung_dsim *dsi) +{
- unsigned long hs_clk, byte_clk, esc_clk;
- unsigned long esc_div;
- u32 reg;
- hs_clk = samsung_dsim_set_pll(dsi, dsi->burst_clk_rate);
- if (!hs_clk) {
dev_err(dsi->dev, "failed to configure DSI PLL\n");
return -EFAULT;
- }
- byte_clk = hs_clk / 8;
- esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate);
- esc_clk = byte_clk / esc_div;
- if (esc_clk > 20 * MHZ) {
++esc_div;
esc_clk = byte_clk / esc_div;
- }
- dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
hs_clk, byte_clk, esc_clk);
- reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
| DSIM_BYTE_CLK_SRC_MASK);
- reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
| DSIM_ESC_PRESCALER(esc_div)
| DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1)
| DSIM_BYTE_CLK_SRC(0)
| DSIM_TX_REQUEST_HSCLK;
- samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg);
- return 0;
+}
+static void samsung_dsim_set_phy_ctrl(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- const unsigned int *reg_values = driver_data->reg_values;
- u32 reg;
- if (driver_data->has_freqband)
return;
- /* B D-PHY: D-PHY Master & Slave Analog Block control */
- reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]);
- if (reg_values[PHYCTRL_VREG_LP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP;
- if (reg_values[PHYCTRL_SLEW_UP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP;
- samsung_dsim_write(dsi, DSIM_PHYCTRL_REG, reg);
- /*
* T LPX: Transmitted length of any Low-Power state period
* T HS-EXIT: Time that the transmitter drives LP-11 following a HS
* burst
*/
- reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) |
DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING_REG, reg);
- /*
* T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Clock.
* T CLK_POST: Time that the transmitter continues to send HS clock
* after the last associated Data Lane has transitioned to LP Mode
* Interval is defined as the period from the end of T HS-TRAIL to
* the beginning of T CLK-TRAIL
* T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
* the last payload clock bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) |
DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) |
DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) |
DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING1_REG, reg);
- /*
* T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Sync sequence.
* T HS-TRAIL: Time that the transmitter drives the flipped differential
* state after last payload data bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) |
DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) |
DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING2_REG, reg);
+}
+static void samsung_dsim_disable_clock(struct samsung_dsim *dsi) +{
- u32 reg;
- reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK
| DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
- samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg);
- reg = samsung_dsim_read(dsi, DSIM_PLLCTRL_REG);
- reg &= ~DSIM_PLL_EN;
- samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg);
+}
+static void samsung_dsim_enable_lane(struct samsung_dsim *dsi, u32 lane) +{
- u32 reg = samsung_dsim_read(dsi, DSIM_CONFIG_REG);
- reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK |
DSIM_LANE_EN(lane));
- samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg);
+}
+static int samsung_dsim_init_link(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int timeout;
- u32 reg;
- u32 lanes_mask;
- /* Initialize FIFO pointers */
- reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG);
- reg &= ~0x1f;
- samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- reg |= 0x1f;
- samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- /* DSI configuration */
- reg = 0;
- /*
* The first bit of mode_flags specifies display configuration.
* If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video
* mode, otherwise it will support command mode.
*/
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg |= DSIM_VIDEO_MODE;
/*
* The user manual describes that following bits are ignored in
* command mode.
*/
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH))
reg |= DSIM_MFLUSH_VS;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
reg |= DSIM_SYNC_INFORM;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
reg |= DSIM_BURST_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
reg |= DSIM_AUTO_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
reg |= DSIM_HSE_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP))
reg |= DSIM_HFP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP))
reg |= DSIM_HBP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA))
reg |= DSIM_HSA_MODE;
- }
- if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
reg |= DSIM_EOT_DISABLE;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
break;
- case MIPI_DSI_FMT_RGB666:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
break;
- case MIPI_DSI_FMT_RGB565:
reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
break;
- default:
dev_err(dsi->dev, "invalid pixel format\n");
return -EINVAL;
- }
- /*
* Use non-continuous clock mode if the periparal wants and
* host controller supports
*
* In non-continous clock mode, host controller will turn off
* the HS clock between high-speed transmissions to reduce
* power consumption.
*/
- if (driver_data->has_clklane_stop &&
dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
reg |= DSIM_CLKLANE_STOP;
- }
- samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg);
- lanes_mask = BIT(dsi->lanes) - 1;
- samsung_dsim_enable_lane(dsi, lanes_mask);
- /* Check clock and data lane state are stop state */
- timeout = 100;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "waiting for bus lanes timed out\n");
return -EFAULT;
}
reg = samsung_dsim_read(dsi, DSIM_STATUS_REG);
if ((reg & DSIM_STOP_STATE_DAT(lanes_mask))
!= DSIM_STOP_STATE_DAT(lanes_mask))
continue;
- } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
- reg = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- reg &= ~DSIM_STOP_STATE_CNT_MASK;
- reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]);
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, reg);
- reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
- samsung_dsim_write(dsi, DSIM_TIMEOUT_REG, reg);
- return 0;
+}
+static void samsung_dsim_set_display_mode(struct samsung_dsim *dsi) +{
- struct drm_display_mode *m = &dsi->mode;
- unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
- u32 reg;
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg = DSIM_CMD_ALLOW(0xf)
| DSIM_STABLE_VFP(m->vsync_start - m->vdisplay)
| DSIM_MAIN_VBP(m->vtotal - m->vsync_end);
samsung_dsim_write(dsi, DSIM_MVPORCH_REG, reg);
reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay)
| DSIM_MAIN_HBP(m->htotal - m->hsync_end);
samsung_dsim_write(dsi, DSIM_MHPORCH_REG, reg);
reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start)
| DSIM_MAIN_HSA(m->hsync_end - m->hsync_start);
samsung_dsim_write(dsi, DSIM_MSYNC_REG, reg);
- }
- reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) |
DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol);
- samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg);
- dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay);
+}
+static void samsung_dsim_set_display_enable(struct samsung_dsim *dsi,
bool enable)
+{
- u32 reg;
- reg = samsung_dsim_read(dsi, DSIM_MDRESOL_REG);
- if (enable)
reg |= DSIM_MAIN_STAND_BY;
- else
reg &= ~DSIM_MAIN_STAND_BY;
- samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg);
+}
+static int samsung_dsim_wait_for_hdr_fifo(struct samsung_dsim *dsi) +{
- int timeout = 2000;
- do {
u32 reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG);
if (!(reg & DSIM_SFR_HEADER_FULL))
return 0;
if (!cond_resched())
usleep_range(950, 1050);
- } while (--timeout);
- return -ETIMEDOUT;
+}
+static void samsung_dsim_set_cmd_lpm(struct samsung_dsim *dsi, bool lpm) +{
- u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- if (lpm)
v |= DSIM_CMD_LPDT_LP;
- else
v &= ~DSIM_CMD_LPDT_LP;
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v);
+}
+static void samsung_dsim_force_bta(struct samsung_dsim *dsi) +{
- u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- v |= DSIM_FORCE_BTA;
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v);
+}
+static void samsung_dsim_send_to_fifo(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- struct device *dev = dsi->dev;
- struct mipi_dsi_packet *pkt = &xfer->packet;
- const u8 *payload = pkt->payload + xfer->tx_done;
- u16 length = pkt->payload_length - xfer->tx_done;
- bool first = !xfer->tx_done;
- u32 reg;
- dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n",
xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done);
- if (length > DSI_TX_FIFO_SIZE)
length = DSI_TX_FIFO_SIZE;
- xfer->tx_done += length;
- /* Send payload */
- while (length >= 4) {
reg = get_unaligned_le32(payload);
samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg);
payload += 4;
length -= 4;
- }
- reg = 0;
- switch (length) {
- case 3:
reg |= payload[2] << 16;
fallthrough;
- case 2:
reg |= payload[1] << 8;
fallthrough;
- case 1:
reg |= payload[0];
samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg);
break;
- }
- /* Send packet header */
- if (!first)
return;
- reg = get_unaligned_le32(pkt->header);
- if (samsung_dsim_wait_for_hdr_fifo(dsi)) {
dev_err(dev, "waiting for header FIFO timed out\n");
return;
- }
- if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM,
dsi->state & DSIM_STATE_CMD_LPM)) {
samsung_dsim_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM);
dsi->state ^= DSIM_STATE_CMD_LPM;
- }
- samsung_dsim_write(dsi, DSIM_PKTHDR_REG, reg);
- if (xfer->flags & MIPI_DSI_MSG_REQ_ACK)
samsung_dsim_force_bta(dsi);
+}
+static void samsung_dsim_read_from_fifo(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- u8 *payload = xfer->rx_payload + xfer->rx_done;
- bool first = !xfer->rx_done;
- struct device *dev = dsi->dev;
- u16 length;
- u32 reg;
- if (first) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
switch (reg & 0x3f) {
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
if (xfer->rx_len >= 2) {
payload[1] = reg >> 16;
++xfer->rx_done;
}
fallthrough;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
payload[0] = reg >> 8;
++xfer->rx_done;
xfer->rx_len = xfer->rx_done;
xfer->result = 0;
goto clear_fifo;
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
dev_err(dev, "DSI Error Report: 0x%04x\n",
(reg >> 8) & 0xffff);
xfer->result = 0;
goto clear_fifo;
}
length = (reg >> 8) & 0xffff;
if (length > xfer->rx_len) {
dev_err(dev,
"response too long (%u > %u bytes), stripping\n",
xfer->rx_len, length);
length = xfer->rx_len;
} else if (length < xfer->rx_len)
xfer->rx_len = length;
- }
- length = xfer->rx_len - xfer->rx_done;
- xfer->rx_done += length;
- /* Receive payload */
- while (length >= 4) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
payload[0] = (reg >> 0) & 0xff;
payload[1] = (reg >> 8) & 0xff;
payload[2] = (reg >> 16) & 0xff;
payload[3] = (reg >> 24) & 0xff;
payload += 4;
length -= 4;
- }
- if (length) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
switch (length) {
case 3:
payload[2] = (reg >> 16) & 0xff;
fallthrough;
case 2:
payload[1] = (reg >> 8) & 0xff;
fallthrough;
case 1:
payload[0] = reg & 0xff;
}
- }
- if (xfer->rx_done == xfer->rx_len)
xfer->result = 0;
+clear_fifo:
- length = DSI_RX_FIFO_SIZE / 4;
- do {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
if (reg == DSI_RX_FIFO_EMPTY)
break;
- } while (--length);
+}
+static void samsung_dsim_transfer_start(struct samsung_dsim *dsi) +{
- unsigned long flags;
- struct samsung_dsim_transfer *xfer;
- bool start = false;
+again:
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (xfer->packet.payload_length &&
xfer->tx_done == xfer->packet.payload_length)
/* waiting for RX */
return;
- samsung_dsim_send_to_fifo(dsi, xfer);
- if (xfer->packet.payload_length || xfer->rx_len)
return;
- xfer->result = 0;
- complete(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (start)
goto again;
+}
+static bool samsung_dsim_transfer_finish(struct samsung_dsim *dsi) +{
- struct samsung_dsim_transfer *xfer;
- unsigned long flags;
- bool start = true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return false;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- dev_dbg(dsi->dev,
"> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n",
xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len,
xfer->rx_done);
- if (xfer->tx_done != xfer->packet.payload_length)
return true;
- if (xfer->rx_done != xfer->rx_len)
samsung_dsim_read_from_fifo(dsi, xfer);
- if (xfer->rx_done != xfer->rx_len)
return true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (!xfer->rx_len)
xfer->result = 0;
- complete(&xfer->completed);
- return start;
+}
+static void samsung_dsim_remove_transfer(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- unsigned long flags;
- bool start;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (!list_empty(&dsi->transfer_list) &&
xfer == list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list)) {
list_del_init(&xfer->list);
start = !list_empty(&dsi->transfer_list);
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
if (start)
samsung_dsim_transfer_start(dsi);
return;
- }
- list_del_init(&xfer->list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+}
+static int samsung_dsim_transfer(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- unsigned long flags;
- bool stopped;
- xfer->tx_done = 0;
- xfer->rx_done = 0;
- xfer->result = -ETIMEDOUT;
- init_completion(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- stopped = list_empty(&dsi->transfer_list);
- list_add_tail(&xfer->list, &dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (stopped)
samsung_dsim_transfer_start(dsi);
- wait_for_completion_timeout(&xfer->completed,
msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
- if (xfer->result == -ETIMEDOUT) {
struct mipi_dsi_packet *pkt = &xfer->packet;
samsung_dsim_remove_transfer(dsi, xfer);
dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header,
(int)pkt->payload_length, pkt->payload);
return -ETIMEDOUT;
- }
- /* Also covers hardware timeout condition */
- return xfer->result;
+}
+static irqreturn_t samsung_dsim_irq(int irq, void *dev_id) +{
- struct samsung_dsim *dsi = dev_id;
- u32 status;
- status = samsung_dsim_read(dsi, DSIM_INTSRC_REG);
- if (!status) {
static unsigned long int j;
if (printk_timed_ratelimit(&j, 500))
dev_warn(dsi->dev, "spurious interrupt\n");
return IRQ_HANDLED;
- }
- samsung_dsim_write(dsi, DSIM_INTSRC_REG, status);
- if (status & DSIM_INT_SW_RST_RELEASE) {
u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR |
DSIM_INT_SW_RST_RELEASE);
samsung_dsim_write(dsi, DSIM_INTMSK_REG, mask);
complete(&dsi->completed);
return IRQ_HANDLED;
- }
- if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_PLL_STABLE)))
return IRQ_HANDLED;
- if (samsung_dsim_transfer_finish(dsi))
samsung_dsim_transfer_start(dsi);
- return IRQ_HANDLED;
+}
+static irqreturn_t samsung_dsim_te_irq_handler(int irq, void *dev_id) +{
- struct samsung_dsim *dsi = dev_id;
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->te_handler &&
(dsi->state & DSIM_STATE_VIDOUT_AVAILABLE))
ops->te_handler(dsi->dsi_host.dev);
- return IRQ_HANDLED;
+}
+static void samsung_dsim_enable_irq(struct samsung_dsim *dsi) +{
- enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
enable_irq(gpio_to_irq(dsi->te_gpio));
+}
+static void samsung_dsim_disable_irq(struct samsung_dsim *dsi) +{
- if (gpio_is_valid(dsi->te_gpio))
disable_irq(gpio_to_irq(dsi->te_gpio));
- disable_irq(dsi->irq);
+}
+static int samsung_dsim_init(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- samsung_dsim_reset(dsi);
- samsung_dsim_enable_irq(dsi);
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST)
samsung_dsim_enable_lane(dsi, BIT(dsi->lanes) - 1);
- samsung_dsim_enable_clock(dsi);
- if (driver_data->wait_for_reset)
samsung_dsim_wait_for_reset(dsi);
- samsung_dsim_set_phy_ctrl(dsi);
- samsung_dsim_init_link(dsi);
- return 0;
+}
+static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi,
struct device *panel)
+{
- int ret;
- int te_gpio_irq;
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
return 0;
- if (!gpio_is_valid(dsi->te_gpio)) {
ret = dsi->te_gpio;
dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
goto out;
- }
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
dev_err(dsi->dev, "gpio request failed with %d\n", ret);
goto out;
- }
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
- irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN);
- ret = request_threaded_irq(te_gpio_irq, samsung_dsim_te_irq_handler,
NULL, IRQF_TRIGGER_RISING, "TE", dsi);
- if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
gpio_free(dsi->te_gpio);
goto out;
- }
+out:
- return ret;
+}
+static void samsung_dsim_unregister_te_irq(struct samsung_dsim *dsi) +{
- if (gpio_is_valid(dsi->te_gpio)) {
free_irq(gpio_to_irq(dsi->te_gpio), dsi);
gpio_free(dsi->te_gpio);
dsi->te_gpio = -ENOENT;
- }
+}
+static void samsung_dsim_enable(struct samsung_dsim *dsi) +{
- struct drm_bridge *iter;
- int ret;
- if (dsi->state & DSIM_STATE_ENABLED)
return;
- pm_runtime_get_sync(dsi->dev);
- dsi->state |= DSIM_STATE_ENABLED;
- if (dsi->panel) {
ret = drm_panel_prepare(dsi->panel);
if (ret < 0)
goto err_put_sync;
- } else {
list_for_each_entry_reverse(iter, &dsi->bridge_chain,
chain_node) {
if (iter->funcs->pre_enable)
iter->funcs->pre_enable(iter);
}
- }
- samsung_dsim_set_display_mode(dsi);
- samsung_dsim_set_display_enable(dsi, true);
- if (dsi->panel) {
ret = drm_panel_enable(dsi->panel);
if (ret < 0)
goto err_display_disable;
- } else {
list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->enable)
iter->funcs->enable(iter);
}
- }
- dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE;
- return;
+err_display_disable:
- samsung_dsim_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
+err_put_sync:
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put(dsi->dev);
+}
+static void samsung_dsim_disable(struct samsung_dsim *dsi) +{
- struct drm_bridge *iter;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return;
- dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE;
- drm_panel_disable(dsi->panel);
- list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->disable)
iter->funcs->disable(iter);
- }
- samsung_dsim_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
- list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->post_disable)
iter->funcs->post_disable(iter);
- }
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put_sync(dsi->dev);
+}
+static enum drm_connector_status +samsung_dsim_detect(struct drm_connector *connector, bool force) +{
- return connector->status;
+}
+static void samsung_dsim_connector_destroy(struct drm_connector *connector) +{
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- connector->dev = NULL;
+}
+static const struct drm_connector_funcs samsung_dsim_connector_funcs = {
- .detect = samsung_dsim_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = samsung_dsim_connector_destroy,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+static int samsung_dsim_get_modes(struct drm_connector *connector) +{
- struct samsung_dsim *dsi = connector_to_dsi(connector);
- if (dsi->panel)
return drm_panel_get_modes(dsi->panel, connector);
- return 0;
+}
+static const struct drm_connector_helper_funcs samsung_dsim_connector_helper_funcs = {
- .get_modes = samsung_dsim_get_modes,
+};
+static int samsung_dsim_create_connector(struct samsung_dsim *dsi) +{
- struct drm_connector *connector = &dsi->connector;
- struct drm_device *drm = dsi->bridge.dev;
- int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD;
- ret = drm_connector_init(drm, connector, &samsung_dsim_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
- if (ret) {
DRM_DEV_ERROR(dsi->dev,
"Failed to initialize connector with drm\n");
return ret;
- }
- connector->status = connector_status_disconnected;
- drm_connector_helper_add(connector, &samsung_dsim_connector_helper_funcs);
- drm_connector_attach_encoder(connector, dsi->bridge.encoder);
- if (!drm->registered)
return 0;
- connector->funcs->reset(connector);
- drm_connector_register(connector);
- return 0;
+}
+static int samsung_dsim_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
+{
- struct samsung_dsim *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- int ret;
- if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
- if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
- } else {
ret = samsung_dsim_create_connector(dsi);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
- }
- return 0;
+}
+static void samsung_dsim_bridge_detach(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
samsung_dsim_disable(dsi);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- }
+}
+static void samsung_dsim_bridge_enable(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- samsung_dsim_enable(dsi);
+}
+static void samsung_dsim_bridge_disable(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- samsung_dsim_disable(dsi);
+}
+static void samsung_dsim_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
+{
- struct samsung_dsim *dsi = bridge->driver_private;
- /* The mode is set when actually enabling the device. */
- drm_mode_copy(&dsi->mode, adjusted_mode);
+}
+static const struct drm_bridge_funcs samsung_dsim_bridge_funcs = {
- .attach = samsung_dsim_bridge_attach,
- .detach = samsung_dsim_bridge_detach,
- .enable = samsung_dsim_bridge_enable,
- .disable = samsung_dsim_bridge_disable,
- .mode_set = samsung_dsim_bridge_mode_set,
+};
+static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node);
- if (out_bridge) {
dsi->out_bridge = out_bridge;
- } else {
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
else
dsi->connector.status = connector_status_connected;
- }
- /*
* This is a temporary solution and should be made by more generic way.
*
* If attached panel device is for command mode one, dsi should register
* TE interrupt handler.
*/
- if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
int ret = samsung_dsim_register_te_irq(dsi, &device->dev);
if (ret)
return ret;
- }
- dsi->lanes = device->lanes;
- dsi->format = device->format;
- dsi->mode_flags = device->mode_flags;
- if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
- return 0;
+}
+static int samsung_dsim_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- samsung_dsim_unregister_te_irq(dsi);
- if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
- samsung_dsim_unregister_te_irq(dsi);
- return 0;
+}
+static ssize_t samsung_dsim_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- struct samsung_dsim_transfer xfer;
- int ret;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return -EINVAL;
- if (!(dsi->state & DSIM_STATE_INITIALIZED)) {
ret = samsung_dsim_init(dsi);
if (ret)
return ret;
dsi->state |= DSIM_STATE_INITIALIZED;
- }
- ret = mipi_dsi_create_packet(&xfer.packet, msg);
- if (ret < 0)
return ret;
- xfer.rx_len = msg->rx_len;
- xfer.rx_payload = msg->rx_buf;
- xfer.flags = msg->flags;
- ret = samsung_dsim_transfer(dsi, &xfer);
- return (ret < 0) ? ret : xfer.rx_done;
+}
+static const struct mipi_dsi_host_ops samsung_dsim_ops = {
- .attach = samsung_dsim_host_attach,
- .detach = samsung_dsim_host_detach,
- .transfer = samsung_dsim_host_transfer,
+};
+static int samsung_dsim_of_read_u32(const struct device_node *np,
const char *propname, u32 *out_value)
+{
- int ret = of_property_read_u32(np, propname, out_value);
- if (ret < 0)
pr_err("%pOF: failed to get '%s' property\n", np, propname);
- return ret;
+}
+static int samsung_dsim_parse_dt(struct samsung_dsim *dsi) +{
- struct device *dev = dsi->dev;
- struct device_node *node = dev->of_node;
- int ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,pll-clock-frequency",
&dsi->pll_clk_rate);
- if (ret < 0)
return ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,burst-clock-frequency",
&dsi->burst_clk_rate);
- if (ret < 0)
return ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,esc-clock-frequency",
&dsi->esc_clk_rate);
- if (ret < 0)
return ret;
- return 0;
+}
+static struct samsung_dsim *__samsung_dsim_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- struct resource *res;
- struct samsung_dsim *dsi;
- int ret, i;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
- if (!dsi)
return ERR_PTR(-ENOMEM);
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
- init_completion(&dsi->completed);
- spin_lock_init(&dsi->transfer_lock);
- INIT_LIST_HEAD(&dsi->transfer_list);
- INIT_LIST_HEAD(&dsi->bridge_chain);
- dsi->dsi_host.ops = &samsung_dsim_ops;
- dsi->dsi_host.dev = dev;
- dsi->dev = dev;
- dsi->driver_data = of_device_get_match_data(dev);
- dsi->supplies[0].supply = "vddcore";
- dsi->supplies[1].supply = "vddio";
- ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies),
dsi->supplies);
- if (ret) {
if (ret != -EPROBE_DEFER)
dev_info(dev, "failed to get regulators: %d\n", ret);
return ERR_PTR(ret);
- }
- dsi->clks = devm_kcalloc(dev,
dsi->driver_data->num_clks, sizeof(*dsi->clks),
GFP_KERNEL);
- if (!dsi->clks)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < dsi->driver_data->num_clks; i++) {
dsi->clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(dsi->clks[i])) {
if (strcmp(clk_names[i], "sclk_mipi") == 0) {
dsi->clks[i] = devm_clk_get(dev,
OLD_SCLK_MIPI_CLK_NAME);
if (!IS_ERR(dsi->clks[i]))
continue;
}
dev_info(dev, "failed to get the clock: %s\n",
clk_names[i]);
return ERR_PTR(PTR_ERR(dsi->clks[i]));
}
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->reg_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(dsi->reg_base)) {
dev_err(dev, "failed to remap io region\n");
return dsi->reg_base;
- }
- dsi->phy = devm_phy_get(dev, "dsim");
- if (IS_ERR(dsi->phy)) {
dev_info(dev, "failed to get dsim phy\n");
return ERR_PTR(PTR_ERR(dsi->phy));
- }
- dsi->irq = platform_get_irq(pdev, 0);
- if (dsi->irq < 0)
return ERR_PTR(dsi->irq);
- irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN);
- ret = devm_request_threaded_irq(dev, dsi->irq, NULL,
samsung_dsim_irq, IRQF_ONESHOT,
dev_name(dev), dsi);
- if (ret) {
dev_err(dev, "failed to request dsi irq\n");
return ERR_PTR(ret);
- }
- ret = samsung_dsim_parse_dt(dsi);
- if (ret)
return ERR_PTR(ret);
- ret = mipi_dsi_host_register(&dsi->dsi_host);
- if (ret)
return ERR_PTR(ret);
- bridge = &dsi->bridge;
- bridge->driver_private = dsi;
- bridge->funcs = &samsung_dsim_bridge_funcs;
- bridge->of_node = dev->of_node;
- drm_bridge_add(bridge);
- return dsi;
+}
+static void __samsung_dsim_remove(struct samsung_dsim *dsi) +{
- drm_bridge_remove(&dsi->bridge);
- mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+/*
- Probe/remove API, used from platforms based on the DRM bridge API.
- */
+struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev) +{
- return __samsung_dsim_probe(pdev);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_probe);
+void samsung_dsim_remove(struct samsung_dsim *dsi) +{
- return __samsung_dsim_remove(dsi);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_remove);
+/*
- Bind/unbind API, used from platforms based on the component framework.
- */
+int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder) +{
- struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_bind);
+void samsung_dsim_unbind(struct samsung_dsim *dsi) +{
- samsung_dsim_disable(dsi);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_unbind);
+int samsung_dsim_suspend(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- usleep_range(10000, 20000);
- if (dsi->state & DSIM_STATE_INITIALIZED) {
dsi->state &= ~DSIM_STATE_INITIALIZED;
samsung_dsim_disable_clock(dsi);
samsung_dsim_disable_irq(dsi);
- }
- dsi->state &= ~DSIM_STATE_CMD_LPM;
- phy_power_off(dsi->phy);
- for (i = driver_data->num_clks - 1; i > -1; i--)
clk_disable_unprepare(dsi->clks[i]);
- ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0)
dev_err(dsi->dev, "cannot disable regulators %d\n", ret);
- return 0;
+} +EXPORT_SYMBOL_GPL(samsung_dsim_suspend);
+int samsung_dsim_resume(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable regulators %d\n", ret);
return ret;
- }
- for (i = 0; i < driver_data->num_clks; i++) {
ret = clk_prepare_enable(dsi->clks[i]);
if (ret < 0)
goto err_clk;
- }
- ret = phy_power_on(dsi->phy);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable phy %d\n", ret);
goto err_clk;
- }
- return 0;
+err_clk:
- while (--i > -1)
clk_disable_unprepare(dsi->clks[i]);
- regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- return ret;
+} +EXPORT_SYMBOL_GPL(samsung_dsim_resume);
+MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); +MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 6417f374b923..3bc321ab5bc8 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -16,7 +16,6 @@ comment "CRTCs"
config DRM_EXYNOS_FIMD bool "FIMD"
- depends on !FB_S3C select MFD_SYSCON help Choose this option if you want to use Exynos FIMD for DRM.
@@ -28,7 +27,6 @@ config DRM_EXYNOS5433_DECON
config DRM_EXYNOS7_DECON bool "DECON on Exynos7"
- depends on !FB_S3C help Choose this option if you want to use Exynos DECON for DRM.
@@ -55,8 +53,7 @@ config DRM_EXYNOS_DPI config DRM_EXYNOS_DSI bool "MIPI-DSI host" depends on DRM_EXYNOS_FIMD || DRM_EXYNOS5433_DECON || DRM_EXYNOS7_DECON
- select DRM_MIPI_DSI
- select DRM_PANEL
- select DRM_SAMSUNG_DSIM default n help This enables support for Exynos MIPI-DSI device.
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index add70b336935..2fd2f3ee4fcf 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -11,7 +11,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o -exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynos_drm_dsi_pltfm.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index e8aea9d90c34..17f37fa74718 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -5,1774 +5,328 @@
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
-*/
- */
-#include <linux/clk.h> -#include <linux/delay.h> #include <linux/component.h> -#include <linux/gpio/consumer.h> -#include <linux/irq.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> -#include <linux/phy/phy.h> -#include <linux/regulator/consumer.h>
-#include <asm/unaligned.h> +#include <linux/pm_runtime.h>
-#include <video/mipi_display.h> -#include <video/videomode.h>
-#include <drm/drm_atomic_helper.h> +#include <drm/bridge/samsung-dsim.h> #include <drm/drm_bridge.h> -#include <drm/drm_fb_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> -#include <drm/drm_print.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_dsi.h"
-/* returns true iff both arguments logically differs */ -#define NEQV(a, b) (!(a) ^ !(b))
-/* DSIM_STATUS */ -#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) -#define DSIM_STOP_STATE_CLK (1 << 8) -#define DSIM_TX_READY_HS_CLK (1 << 10) -#define DSIM_PLL_STABLE (1 << 31)
-/* DSIM_TIMEOUT */ -#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) -#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
-/* DSIM_CLKCTRL */ -#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) -#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) -#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) -#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) -#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) -#define DSIM_BYTE_CLKEN (1 << 24) -#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) -#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) -#define DSIM_PLL_BYPASS (1 << 27) -#define DSIM_ESC_CLKEN (1 << 28) -#define DSIM_TX_REQUEST_HSCLK (1 << 31)
-/* DSIM_CONFIG */ -#define DSIM_LANE_EN_CLK (1 << 0) -#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) -#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) -#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) -#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) -#define DSIM_SUB_VC (((x) & 0x3) << 16) -#define DSIM_MAIN_VC (((x) & 0x3) << 18) -#define DSIM_HSA_MODE (1 << 20) -#define DSIM_HBP_MODE (1 << 21) -#define DSIM_HFP_MODE (1 << 22) -#define DSIM_HSE_MODE (1 << 23) -#define DSIM_AUTO_MODE (1 << 24) -#define DSIM_VIDEO_MODE (1 << 25) -#define DSIM_BURST_MODE (1 << 26) -#define DSIM_SYNC_INFORM (1 << 27) -#define DSIM_EOT_DISABLE (1 << 28) -#define DSIM_MFLUSH_VS (1 << 29) -/* This flag is valid only for exynos3250/3472/5260/5430 */ -#define DSIM_CLKLANE_STOP (1 << 30)
-/* DSIM_ESCMODE */ -#define DSIM_TX_TRIGGER_RST (1 << 4) -#define DSIM_TX_LPDT_LP (1 << 6) -#define DSIM_CMD_LPDT_LP (1 << 7) -#define DSIM_FORCE_BTA (1 << 16) -#define DSIM_FORCE_STOP_STATE (1 << 20) -#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) -#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
-/* DSIM_MDRESOL */ -#define DSIM_MAIN_STAND_BY (1 << 31) -#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) -#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
-/* DSIM_MVPORCH */ -#define DSIM_CMD_ALLOW(x) ((x) << 28) -#define DSIM_STABLE_VFP(x) ((x) << 16) -#define DSIM_MAIN_VBP(x) ((x) << 0) -#define DSIM_CMD_ALLOW_MASK (0xf << 28) -#define DSIM_STABLE_VFP_MASK (0x7ff << 16) -#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
-/* DSIM_MHPORCH */ -#define DSIM_MAIN_HFP(x) ((x) << 16) -#define DSIM_MAIN_HBP(x) ((x) << 0) -#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) -#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
-/* DSIM_MSYNC */ -#define DSIM_MAIN_VSA(x) ((x) << 22) -#define DSIM_MAIN_HSA(x) ((x) << 0) -#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) -#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
-/* DSIM_SDRESOL */ -#define DSIM_SUB_STANDY(x) ((x) << 31) -#define DSIM_SUB_VRESOL(x) ((x) << 16) -#define DSIM_SUB_HRESOL(x) ((x) << 0) -#define DSIM_SUB_STANDY_MASK ((0x1) << 31) -#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) -#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
-/* DSIM_INTSRC */ -#define DSIM_INT_PLL_STABLE (1 << 31) -#define DSIM_INT_SW_RST_RELEASE (1 << 30) -#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) -#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) -#define DSIM_INT_BTA (1 << 25) -#define DSIM_INT_FRAME_DONE (1 << 24) -#define DSIM_INT_RX_TIMEOUT (1 << 21) -#define DSIM_INT_BTA_TIMEOUT (1 << 20) -#define DSIM_INT_RX_DONE (1 << 18) -#define DSIM_INT_RX_TE (1 << 17) -#define DSIM_INT_RX_ACK (1 << 16) -#define DSIM_INT_RX_ECC_ERR (1 << 15) -#define DSIM_INT_RX_CRC_ERR (1 << 14) +#include "exynos_drm_crtc.h" +#include "exynos_drm_drv.h"
-/* DSIM_FIFOCTRL */ -#define DSIM_RX_DATA_FULL (1 << 25) -#define DSIM_RX_DATA_EMPTY (1 << 24) -#define DSIM_SFR_HEADER_FULL (1 << 23) -#define DSIM_SFR_HEADER_EMPTY (1 << 22) -#define DSIM_SFR_PAYLOAD_FULL (1 << 21) -#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) -#define DSIM_I80_HEADER_FULL (1 << 19) -#define DSIM_I80_HEADER_EMPTY (1 << 18) -#define DSIM_I80_PAYLOAD_FULL (1 << 17) -#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) -#define DSIM_SD_HEADER_FULL (1 << 15) -#define DSIM_SD_HEADER_EMPTY (1 << 14) -#define DSIM_SD_PAYLOAD_FULL (1 << 13) -#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) -#define DSIM_MD_HEADER_FULL (1 << 11) -#define DSIM_MD_HEADER_EMPTY (1 << 10) -#define DSIM_MD_PAYLOAD_FULL (1 << 9) -#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) -#define DSIM_RX_FIFO (1 << 4) -#define DSIM_SFR_FIFO (1 << 3) -#define DSIM_I80_FIFO (1 << 2) -#define DSIM_SD_FIFO (1 << 1) -#define DSIM_MD_FIFO (1 << 0)
-/* DSIM_PHYACCHR */ -#define DSIM_AFC_EN (1 << 14) -#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
-/* DSIM_PLLCTRL */ -#define DSIM_FREQ_BAND(x) ((x) << 24) -#define DSIM_PLL_EN (1 << 23) -#define DSIM_PLL_P(x) ((x) << 13) -#define DSIM_PLL_M(x) ((x) << 4) -#define DSIM_PLL_S(x) ((x) << 1)
-/* DSIM_PHYCTRL */ -#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) -#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) -#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14)
-/* DSIM_PHYTIMING */ -#define DSIM_PHYTIMING_LPX(x) ((x) << 8) -#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
-/* DSIM_PHYTIMING1 */ -#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) -#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) -#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) -#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
-/* DSIM_PHYTIMING2 */ -#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) -#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) -#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
-#define DSI_MAX_BUS_WIDTH 4 -#define DSI_NUM_VIRTUAL_CHANNELS 4 -#define DSI_TX_FIFO_SIZE 2048 -#define DSI_RX_FIFO_SIZE 256 -#define DSI_XFER_TIMEOUT_MS 100 -#define DSI_RX_FIFO_EMPTY 0x30800002
-#define OLD_SCLK_MIPI_CLK_NAME "pll_clk"
-static const char *const clk_names[5] = { "bus_clk", "sclk_mipi",
- "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0",
- "sclk_rgb_vclk_to_dsim0" };
-enum exynos_dsi_transfer_type {
- EXYNOS_DSI_TX,
- EXYNOS_DSI_RX,
+enum {
- DSI_PORT_IN,
- DSI_PORT_OUT
};
-struct exynos_dsi_transfer {
- struct list_head list;
- struct completion completed;
- int result;
- struct mipi_dsi_packet packet;
- u16 flags;
- u16 tx_done;
- u8 *rx_payload;
- u16 rx_len;
- u16 rx_done;
-};
-#define DSIM_STATE_ENABLED BIT(0) -#define DSIM_STATE_INITIALIZED BIT(1) -#define DSIM_STATE_CMD_LPM BIT(2) -#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
struct exynos_dsi {
- struct drm_bridge bridge;
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_panel *panel;
- struct list_head bridge_chain;
- struct drm_bridge *out_bridge;
- struct device *dev;
- void __iomem *reg_base;
- struct phy *phy;
- struct clk **clks;
- struct regulator_bulk_data supplies[2];
- int irq;
- int te_gpio;
- u32 pll_clk_rate;
- u32 burst_clk_rate;
- u32 esc_clk_rate;
- u32 lanes;
- u32 mode_flags;
- u32 format;
- struct drm_display_mode mode;
- int state;
- struct drm_property *brightness;
- struct completion completed;
- spinlock_t transfer_lock; /* protects transfer_list */
- struct list_head transfer_list;
- const struct exynos_dsi_driver_data *driver_data;
- struct samsung_dsim *dsi;
- struct drm_encoder encoder;
};
-#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) -#define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector)
-enum reg_idx {
- DSIM_STATUS_REG, /* Status register */
- DSIM_SWRST_REG, /* Software reset register */
- DSIM_CLKCTRL_REG, /* Clock control register */
- DSIM_TIMEOUT_REG, /* Time out register */
- DSIM_CONFIG_REG, /* Configuration register */
- DSIM_ESCMODE_REG, /* Escape mode register */
- DSIM_MDRESOL_REG,
- DSIM_MVPORCH_REG, /* Main display Vporch register */
- DSIM_MHPORCH_REG, /* Main display Hporch register */
- DSIM_MSYNC_REG, /* Main display sync area register */
- DSIM_INTSRC_REG, /* Interrupt source register */
- DSIM_INTMSK_REG, /* Interrupt mask register */
- DSIM_PKTHDR_REG, /* Packet Header FIFO register */
- DSIM_PAYLOAD_REG, /* Payload FIFO register */
- DSIM_RXFIFO_REG, /* Read FIFO register */
- DSIM_FIFOCTRL_REG, /* FIFO status and control register */
- DSIM_PLLCTRL_REG, /* PLL control register */
- DSIM_PHYCTRL_REG,
- DSIM_PHYTIMING_REG,
- DSIM_PHYTIMING1_REG,
- DSIM_PHYTIMING2_REG,
- NUM_REGS
+static const unsigned int reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0x0af,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x06,
- [PHYTIMING_HS_EXIT] = 0x0b,
- [PHYTIMING_CLK_PREPARE] = 0x07,
- [PHYTIMING_CLK_ZERO] = 0x27,
- [PHYTIMING_CLK_POST] = 0x0d,
- [PHYTIMING_CLK_TRAIL] = 0x08,
- [PHYTIMING_HS_PREPARE] = 0x09,
- [PHYTIMING_HS_ZERO] = 0x0d,
- [PHYTIMING_HS_TRAIL] = 0x0b,
};
-static const unsigned int exynos_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x00,
- [DSIM_SWRST_REG] = 0x04,
- [DSIM_CLKCTRL_REG] = 0x08,
- [DSIM_TIMEOUT_REG] = 0x0c,
- [DSIM_CONFIG_REG] = 0x10,
- [DSIM_ESCMODE_REG] = 0x14,
- [DSIM_MDRESOL_REG] = 0x18,
- [DSIM_MVPORCH_REG] = 0x1c,
- [DSIM_MHPORCH_REG] = 0x20,
- [DSIM_MSYNC_REG] = 0x24,
- [DSIM_INTSRC_REG] = 0x2c,
- [DSIM_INTMSK_REG] = 0x30,
- [DSIM_PKTHDR_REG] = 0x34,
- [DSIM_PAYLOAD_REG] = 0x38,
- [DSIM_RXFIFO_REG] = 0x3c,
- [DSIM_FIFOCTRL_REG] = 0x44,
- [DSIM_PLLCTRL_REG] = 0x4c,
- [DSIM_PHYCTRL_REG] = 0x5c,
- [DSIM_PHYTIMING_REG] = 0x64,
- [DSIM_PHYTIMING1_REG] = 0x68,
- [DSIM_PHYTIMING2_REG] = 0x6c,
+static const unsigned int exynos5422_reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0xaf,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x08,
- [PHYTIMING_HS_EXIT] = 0x0d,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x30,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x0a,
- [PHYTIMING_HS_PREPARE] = 0x0c,
- [PHYTIMING_HS_ZERO] = 0x11,
- [PHYTIMING_HS_TRAIL] = 0x0d,
};
-static const unsigned int exynos5433_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x04,
- [DSIM_SWRST_REG] = 0x0C,
- [DSIM_CLKCTRL_REG] = 0x10,
- [DSIM_TIMEOUT_REG] = 0x14,
- [DSIM_CONFIG_REG] = 0x18,
- [DSIM_ESCMODE_REG] = 0x1C,
- [DSIM_MDRESOL_REG] = 0x20,
- [DSIM_MVPORCH_REG] = 0x24,
- [DSIM_MHPORCH_REG] = 0x28,
- [DSIM_MSYNC_REG] = 0x2C,
- [DSIM_INTSRC_REG] = 0x34,
- [DSIM_INTMSK_REG] = 0x38,
- [DSIM_PKTHDR_REG] = 0x3C,
- [DSIM_PAYLOAD_REG] = 0x40,
- [DSIM_RXFIFO_REG] = 0x44,
- [DSIM_FIFOCTRL_REG] = 0x4C,
- [DSIM_PLLCTRL_REG] = 0x94,
- [DSIM_PHYCTRL_REG] = 0xA4,
- [DSIM_PHYTIMING_REG] = 0xB4,
- [DSIM_PHYTIMING1_REG] = 0xB8,
- [DSIM_PHYTIMING2_REG] = 0xBC,
+static const unsigned int exynos5433_reg_values[] = {
- [RESET_TYPE] = DSIM_FUNCRST,
- [PLL_TIMER] = 22200,
- [STOP_STATE_CNT] = 0xa,
- [PHYCTRL_ULPS_EXIT] = 0x190,
- [PHYCTRL_VREG_LP] = 1,
- [PHYCTRL_SLEW_UP] = 1,
- [PHYTIMING_LPX] = 0x07,
- [PHYTIMING_HS_EXIT] = 0x0c,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x2d,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x09,
- [PHYTIMING_HS_PREPARE] = 0x0b,
- [PHYTIMING_HS_ZERO] = 0x10,
- [PHYTIMING_HS_TRAIL] = 0x0c,
};
-static inline void exynos_dsi_write(struct exynos_dsi *dsi, enum reg_idx idx,
u32 val)
-{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- writel(val, dsi->reg_base + reg_ofs[idx]);
-}
-static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) -{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- return readl(dsi->reg_base + reg_ofs[idx]);
-}
-static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) -{
- if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300)))
return;
- dev_err(dsi->dev, "timeout waiting for reset\n");
-}
-static void exynos_dsi_reset(struct exynos_dsi *dsi) -{
- u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE];
- reinit_completion(&dsi->completed);
- exynos_dsi_write(dsi, DSIM_SWRST_REG, reset_val);
-}
-#ifndef MHZ -#define MHZ (1000*1000) -#endif
-static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi,
unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s)
-{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- unsigned long best_freq = 0;
- u32 min_delta = 0xffffffff;
- u8 p_min, p_max;
- u8 _p, best_p;
- u16 _m, best_m;
- u8 _s, best_s;
- p_min = DIV_ROUND_UP(fin, (12 * MHZ));
- p_max = fin / (6 * MHZ);
- for (_p = p_min; _p <= p_max; ++_p) {
for (_s = 0; _s <= 5; ++_s) {
u64 tmp;
u32 delta;
tmp = (u64)fout * (_p << _s);
do_div(tmp, fin);
_m = tmp;
if (_m < 41 || _m > 125)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p);
if (tmp < 500 * MHZ ||
tmp > driver_data->max_freq * MHZ)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p << _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_m = _m;
best_s = _s;
min_delta = delta;
best_freq = tmp;
}
}
- }
- if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
- }
- return best_freq;
-}
-static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi,
unsigned long freq)
-{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- unsigned long fin, fout;
- int timeout;
- u8 p, s;
- u16 m;
- u32 reg;
- fin = dsi->pll_clk_rate;
- fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s);
- if (!fout) {
dev_err(dsi->dev,
"failed to find PLL PMS for requested frequency\n");
return 0;
- }
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- writel(driver_data->reg_values[PLL_TIMER],
dsi->reg_base + driver_data->plltmr_reg);
- reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
- if (driver_data->has_freqband) {
static const unsigned long freq_bands[] = {
100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
770 * MHZ, 870 * MHZ, 950 * MHZ,
};
int band;
for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
if (fout < freq_bands[band])
break;
dev_dbg(dsi->dev, "band %d\n", band);
reg |= DSIM_FREQ_BAND(band);
- }
- exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg);
- timeout = 1000;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "PLL failed to stabilize\n");
return 0;
}
reg = exynos_dsi_read(dsi, DSIM_STATUS_REG);
- } while ((reg & DSIM_PLL_STABLE) == 0);
- return fout;
-}
-static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) -{
- unsigned long hs_clk, byte_clk, esc_clk;
- unsigned long esc_div;
- u32 reg;
- hs_clk = exynos_dsi_set_pll(dsi, dsi->burst_clk_rate);
- if (!hs_clk) {
dev_err(dsi->dev, "failed to configure DSI PLL\n");
return -EFAULT;
- }
- byte_clk = hs_clk / 8;
- esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate);
- esc_clk = byte_clk / esc_div;
- if (esc_clk > 20 * MHZ) {
++esc_div;
esc_clk = byte_clk / esc_div;
- }
- dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
hs_clk, byte_clk, esc_clk);
- reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
| DSIM_BYTE_CLK_SRC_MASK);
- reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
| DSIM_ESC_PRESCALER(esc_div)
| DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1)
| DSIM_BYTE_CLK_SRC(0)
| DSIM_TX_REQUEST_HSCLK;
- exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg);
- return 0;
-}
-static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) -{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- const unsigned int *reg_values = driver_data->reg_values;
- u32 reg;
- if (driver_data->has_freqband)
return;
- /* B D-PHY: D-PHY Master & Slave Analog Block control */
- reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]);
- if (reg_values[PHYCTRL_VREG_LP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP;
- if (reg_values[PHYCTRL_SLEW_UP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP;
- exynos_dsi_write(dsi, DSIM_PHYCTRL_REG, reg);
- /*
* T LPX: Transmitted length of any Low-Power state period
* T HS-EXIT: Time that the transmitter drives LP-11 following a HS
* burst
*/
- reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) |
DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING_REG, reg);
- /*
* T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Clock.
* T CLK_POST: Time that the transmitter continues to send HS clock
* after the last associated Data Lane has transitioned to LP Mode
* Interval is defined as the period from the end of T HS-TRAIL to
* the beginning of T CLK-TRAIL
* T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
* the last payload clock bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) |
DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) |
DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) |
DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING1_REG, reg);
- /*
* T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Sync sequence.
* T HS-TRAIL: Time that the transmitter drives the flipped differential
* state after last payload data bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) |
DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) |
DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING2_REG, reg);
-}
-static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) -{
- u32 reg;
- reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK
| DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
- exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg);
- reg = exynos_dsi_read(dsi, DSIM_PLLCTRL_REG);
- reg &= ~DSIM_PLL_EN;
- exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg);
-}
-static void exynos_dsi_enable_lane(struct exynos_dsi *dsi, u32 lane) -{
- u32 reg = exynos_dsi_read(dsi, DSIM_CONFIG_REG);
- reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK |
DSIM_LANE_EN(lane));
- exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg);
-}
-static int exynos_dsi_init_link(struct exynos_dsi *dsi) +static int exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int timeout;
- u32 reg;
- u32 lanes_mask;
- /* Initialize FIFO pointers */
- reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG);
- reg &= ~0x1f;
- exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- reg |= 0x1f;
- exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- struct exynos_drm_crtc *crtc;
- /* DSI configuration */
- reg = 0;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- /*
* The first bit of mode_flags specifies display configuration.
* If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video
* mode, otherwise it will support command mode.
*/
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg |= DSIM_VIDEO_MODE;
/*
* The user manual describes that following bits are ignored in
* command mode.
*/
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH))
reg |= DSIM_MFLUSH_VS;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
reg |= DSIM_SYNC_INFORM;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
reg |= DSIM_BURST_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
reg |= DSIM_AUTO_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
reg |= DSIM_HSE_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP))
reg |= DSIM_HFP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP))
reg |= DSIM_HBP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA))
reg |= DSIM_HSA_MODE;
- }
- if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
reg |= DSIM_EOT_DISABLE;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
break;
- case MIPI_DSI_FMT_RGB666:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
break;
- case MIPI_DSI_FMT_RGB565:
reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
break;
- default:
dev_err(dsi->dev, "invalid pixel format\n");
return -EINVAL;
- }
- /*
* Use non-continuous clock mode if the periparal wants and
* host controller supports
*
* In non-continous clock mode, host controller will turn off
* the HS clock between high-speed transmissions to reduce
* power consumption.
*/
- if (driver_data->has_clklane_stop &&
dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
reg |= DSIM_CLKLANE_STOP;
- }
- exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg);
- lanes_mask = BIT(dsi->lanes) - 1;
- exynos_dsi_enable_lane(dsi, lanes_mask);
- /* Check clock and data lane state are stop state */
- timeout = 100;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "waiting for bus lanes timed out\n");
return -EFAULT;
}
reg = exynos_dsi_read(dsi, DSIM_STATUS_REG);
if ((reg & DSIM_STOP_STATE_DAT(lanes_mask))
!= DSIM_STOP_STATE_DAT(lanes_mask))
continue;
- } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
- reg = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- reg &= ~DSIM_STOP_STATE_CNT_MASK;
- reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]);
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, reg);
- reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
- exynos_dsi_write(dsi, DSIM_TIMEOUT_REG, reg);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
return 0;
}
-static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) -{
- struct drm_display_mode *m = &dsi->mode;
- unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
- u32 reg;
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg = DSIM_CMD_ALLOW(0xf)
| DSIM_STABLE_VFP(m->vsync_start - m->vdisplay)
| DSIM_MAIN_VBP(m->vtotal - m->vsync_end);
exynos_dsi_write(dsi, DSIM_MVPORCH_REG, reg);
reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay)
| DSIM_MAIN_HBP(m->htotal - m->hsync_end);
exynos_dsi_write(dsi, DSIM_MHPORCH_REG, reg);
reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start)
| DSIM_MAIN_HSA(m->hsync_end - m->hsync_start);
exynos_dsi_write(dsi, DSIM_MSYNC_REG, reg);
- }
- reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) |
DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol);
- exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg);
- dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay);
-}
-static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) -{
- u32 reg;
- reg = exynos_dsi_read(dsi, DSIM_MDRESOL_REG);
- if (enable)
reg |= DSIM_MAIN_STAND_BY;
- else
reg &= ~DSIM_MAIN_STAND_BY;
- exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg);
-}
-static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) -{
- int timeout = 2000;
- do {
u32 reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG);
if (!(reg & DSIM_SFR_HEADER_FULL))
return 0;
if (!cond_resched())
usleep_range(950, 1050);
- } while (--timeout);
- return -ETIMEDOUT;
-}
-static void exynos_dsi_set_cmd_lpm(struct exynos_dsi *dsi, bool lpm) -{
- u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- if (lpm)
v |= DSIM_CMD_LPDT_LP;
- else
v &= ~DSIM_CMD_LPDT_LP;
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v);
-}
-static void exynos_dsi_force_bta(struct exynos_dsi *dsi) -{
- u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- v |= DSIM_FORCE_BTA;
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v);
-}
-static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- struct device *dev = dsi->dev;
- struct mipi_dsi_packet *pkt = &xfer->packet;
- const u8 *payload = pkt->payload + xfer->tx_done;
- u16 length = pkt->payload_length - xfer->tx_done;
- bool first = !xfer->tx_done;
- u32 reg;
- dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n",
xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done);
- if (length > DSI_TX_FIFO_SIZE)
length = DSI_TX_FIFO_SIZE;
- xfer->tx_done += length;
- /* Send payload */
- while (length >= 4) {
reg = get_unaligned_le32(payload);
exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg);
payload += 4;
length -= 4;
- }
- reg = 0;
- switch (length) {
- case 3:
reg |= payload[2] << 16;
fallthrough;
- case 2:
reg |= payload[1] << 8;
fallthrough;
- case 1:
reg |= payload[0];
exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg);
break;
- }
- /* Send packet header */
- if (!first)
return;
- reg = get_unaligned_le32(pkt->header);
- if (exynos_dsi_wait_for_hdr_fifo(dsi)) {
dev_err(dev, "waiting for header FIFO timed out\n");
return;
- }
- if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM,
dsi->state & DSIM_STATE_CMD_LPM)) {
exynos_dsi_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM);
dsi->state ^= DSIM_STATE_CMD_LPM;
- }
- exynos_dsi_write(dsi, DSIM_PKTHDR_REG, reg);
- if (xfer->flags & MIPI_DSI_MSG_REQ_ACK)
exynos_dsi_force_bta(dsi);
-}
-static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- u8 *payload = xfer->rx_payload + xfer->rx_done;
- bool first = !xfer->rx_done;
- struct device *dev = dsi->dev;
- u16 length;
- u32 reg;
- if (first) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
switch (reg & 0x3f) {
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
if (xfer->rx_len >= 2) {
payload[1] = reg >> 16;
++xfer->rx_done;
}
fallthrough;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
payload[0] = reg >> 8;
++xfer->rx_done;
xfer->rx_len = xfer->rx_done;
xfer->result = 0;
goto clear_fifo;
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
dev_err(dev, "DSI Error Report: 0x%04x\n",
(reg >> 8) & 0xffff);
xfer->result = 0;
goto clear_fifo;
}
length = (reg >> 8) & 0xffff;
if (length > xfer->rx_len) {
dev_err(dev,
"response too long (%u > %u bytes), stripping\n",
xfer->rx_len, length);
length = xfer->rx_len;
} else if (length < xfer->rx_len)
xfer->rx_len = length;
- }
- length = xfer->rx_len - xfer->rx_done;
- xfer->rx_done += length;
- /* Receive payload */
- while (length >= 4) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
payload[0] = (reg >> 0) & 0xff;
payload[1] = (reg >> 8) & 0xff;
payload[2] = (reg >> 16) & 0xff;
payload[3] = (reg >> 24) & 0xff;
payload += 4;
length -= 4;
- }
- if (length) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
switch (length) {
case 3:
payload[2] = (reg >> 16) & 0xff;
fallthrough;
case 2:
payload[1] = (reg >> 8) & 0xff;
fallthrough;
case 1:
payload[0] = reg & 0xff;
}
- }
- if (xfer->rx_done == xfer->rx_len)
xfer->result = 0;
-clear_fifo:
- length = DSI_RX_FIFO_SIZE / 4;
- do {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
if (reg == DSI_RX_FIFO_EMPTY)
break;
- } while (--length);
-}
-static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) -{
- unsigned long flags;
- struct exynos_dsi_transfer *xfer;
- bool start = false;
-again:
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (xfer->packet.payload_length &&
xfer->tx_done == xfer->packet.payload_length)
/* waiting for RX */
return;
- exynos_dsi_send_to_fifo(dsi, xfer);
- if (xfer->packet.payload_length || xfer->rx_len)
return;
- xfer->result = 0;
- complete(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (start)
goto again;
-}
-static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) -{
- struct exynos_dsi_transfer *xfer;
- unsigned long flags;
- bool start = true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return false;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- dev_dbg(dsi->dev,
"> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n",
xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len,
xfer->rx_done);
- if (xfer->tx_done != xfer->packet.payload_length)
return true;
- if (xfer->rx_done != xfer->rx_len)
exynos_dsi_read_from_fifo(dsi, xfer);
- if (xfer->rx_done != xfer->rx_len)
return true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (!xfer->rx_len)
xfer->result = 0;
- complete(&xfer->completed);
- return start;
-}
-static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- unsigned long flags;
- bool start;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (!list_empty(&dsi->transfer_list) &&
xfer == list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list)) {
list_del_init(&xfer->list);
start = !list_empty(&dsi->transfer_list);
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
if (start)
exynos_dsi_transfer_start(dsi);
return;
- }
- list_del_init(&xfer->list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
-}
-static int exynos_dsi_transfer(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- unsigned long flags;
- bool stopped;
- xfer->tx_done = 0;
- xfer->rx_done = 0;
- xfer->result = -ETIMEDOUT;
- init_completion(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- stopped = list_empty(&dsi->transfer_list);
- list_add_tail(&xfer->list, &dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (stopped)
exynos_dsi_transfer_start(dsi);
- wait_for_completion_timeout(&xfer->completed,
msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
- if (xfer->result == -ETIMEDOUT) {
struct mipi_dsi_packet *pkt = &xfer->packet;
exynos_dsi_remove_transfer(dsi, xfer);
dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header,
(int)pkt->payload_length, pkt->payload);
return -ETIMEDOUT;
- }
- /* Also covers hardware timeout condition */
- return xfer->result;
-}
-static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) -{
- struct exynos_dsi *dsi = dev_id;
- u32 status;
- status = exynos_dsi_read(dsi, DSIM_INTSRC_REG);
- if (!status) {
static unsigned long int j;
if (printk_timed_ratelimit(&j, 500))
dev_warn(dsi->dev, "spurious interrupt\n");
return IRQ_HANDLED;
- }
- exynos_dsi_write(dsi, DSIM_INTSRC_REG, status);
- if (status & DSIM_INT_SW_RST_RELEASE) {
u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR |
DSIM_INT_SW_RST_RELEASE);
exynos_dsi_write(dsi, DSIM_INTMSK_REG, mask);
complete(&dsi->completed);
return IRQ_HANDLED;
- }
- if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_PLL_STABLE)))
return IRQ_HANDLED;
- if (exynos_dsi_transfer_finish(dsi))
exynos_dsi_transfer_start(dsi);
- return IRQ_HANDLED;
-}
-static irqreturn_t exynos_dsi_te_irq_handler(int irq, void *dev_id) -{
- struct exynos_dsi *dsi = dev_id;
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->te_handler &&
(dsi->state & DSIM_STATE_VIDOUT_AVAILABLE))
ops->te_handler(dsi->dsi_host.dev);
- return IRQ_HANDLED;
-}
-static void exynos_dsi_enable_irq(struct exynos_dsi *dsi) -{
- enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
enable_irq(gpio_to_irq(dsi->te_gpio));
-}
-static void exynos_dsi_disable_irq(struct exynos_dsi *dsi) -{
- if (gpio_is_valid(dsi->te_gpio))
disable_irq(gpio_to_irq(dsi->te_gpio));
- disable_irq(dsi->irq);
-}
-static int exynos_dsi_init(struct exynos_dsi *dsi) +static int exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- exynos_dsi_reset(dsi);
- exynos_dsi_enable_irq(dsi);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST)
exynos_dsi_enable_lane(dsi, BIT(dsi->lanes) - 1);
- exynos_dsi_enable_clock(dsi);
- if (driver_data->wait_for_reset)
exynos_dsi_wait_for_reset(dsi);
- exynos_dsi_set_phy_ctrl(dsi);
- exynos_dsi_init_link(dsi);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
return 0;
}
-static int exynos_dsi_register_te_irq(struct exynos_dsi *dsi,
struct device *panel)
+static void exynos_dsi_te_handler(struct device *dev) {
- int ret;
- int te_gpio_irq;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
return 0;
- if (!gpio_is_valid(dsi->te_gpio)) {
ret = dsi->te_gpio;
dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
goto out;
- }
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
dev_err(dsi->dev, "gpio request failed with %d\n", ret);
goto out;
- }
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
- irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN);
- ret = request_threaded_irq(te_gpio_irq, exynos_dsi_te_irq_handler, NULL,
IRQF_TRIGGER_RISING, "TE", dsi);
- if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
gpio_free(dsi->te_gpio);
goto out;
- }
-out:
- return ret;
- exynos_drm_crtc_te_handler(dsi->encoder.crtc);
}
-static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) -{
- if (gpio_is_valid(dsi->te_gpio)) {
free_irq(gpio_to_irq(dsi->te_gpio), dsi);
gpio_free(dsi->te_gpio);
dsi->te_gpio = -ENOENT;
- }
-}
-static void exynos_dsi_enable(struct exynos_dsi *dsi) -{
- struct drm_bridge *iter;
- int ret;
- if (dsi->state & DSIM_STATE_ENABLED)
return;
- pm_runtime_get_sync(dsi->dev);
- dsi->state |= DSIM_STATE_ENABLED;
- if (dsi->panel) {
ret = drm_panel_prepare(dsi->panel);
if (ret < 0)
goto err_put_sync;
- } else {
list_for_each_entry_reverse(iter, &dsi->bridge_chain,
chain_node) {
if (iter->funcs->pre_enable)
iter->funcs->pre_enable(iter);
}
- }
- exynos_dsi_set_display_mode(dsi);
- exynos_dsi_set_display_enable(dsi, true);
- if (dsi->panel) {
ret = drm_panel_enable(dsi->panel);
if (ret < 0)
goto err_display_disable;
- } else {
list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->enable)
iter->funcs->enable(iter);
}
- }
- dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE;
- return;
-err_display_disable:
- exynos_dsi_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
-err_put_sync:
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put(dsi->dev);
-}
-static void exynos_dsi_disable(struct exynos_dsi *dsi) -{
- struct drm_bridge *iter;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return;
- dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE;
- drm_panel_disable(dsi->panel);
- list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->disable)
iter->funcs->disable(iter);
- }
- exynos_dsi_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
- list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->post_disable)
iter->funcs->post_disable(iter);
- }
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put_sync(dsi->dev);
-}
-static enum drm_connector_status -exynos_dsi_detect(struct drm_connector *connector, bool force) -{
- return connector->status;
-} +static const struct samsung_dsim_host_ops exynos_dsi_host_ops = {
- .attach = exynos_dsi_host_attach,
- .detach = exynos_dsi_host_detach,
- .te_handler = exynos_dsi_te_handler,
+};
-static void exynos_dsi_connector_destroy(struct drm_connector *connector) -{
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- connector->dev = NULL;
-} +static const struct samsung_dsim_driver_data exynos3_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
-static const struct drm_connector_funcs exynos_dsi_connector_funcs = {
- .detect = exynos_dsi_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = exynos_dsi_connector_destroy,
- .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 samsung_dsim_driver_data exynos4_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
};
-static int exynos_dsi_get_modes(struct drm_connector *connector) -{
- struct exynos_dsi *dsi = connector_to_dsi(connector);
+static const struct samsung_dsim_driver_data exynos5_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x58,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
- if (dsi->panel)
return drm_panel_get_modes(dsi->panel, connector);
+static const struct samsung_dsim_driver_data exynos5433_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 5,
- .max_freq = 1500,
- .wait_for_reset = 0,
- .num_bits_resol = 12,
- .reg_values = exynos5433_reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
- return 0;
-} +static const struct samsung_dsim_driver_data exynos5422_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1500,
- .wait_for_reset = 1,
- .num_bits_resol = 12,
- .reg_values = exynos5422_reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
-static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs = {
- .get_modes = exynos_dsi_get_modes,
+static const struct of_device_id exynos_dsi_of_match[] = {
- { .compatible = "samsung,exynos3250-mipi-dsi",
.data = &exynos3_dsi_driver_data },
- { .compatible = "samsung,exynos4210-mipi-dsi",
.data = &exynos4_dsi_driver_data },
- { .compatible = "samsung,exynos5410-mipi-dsi",
.data = &exynos5_dsi_driver_data },
- { .compatible = "samsung,exynos5422-mipi-dsi",
.data = &exynos5422_dsi_driver_data },
- { .compatible = "samsung,exynos5433-mipi-dsi",
.data = &exynos5433_dsi_driver_data },
- { }
};
-static int exynos_dsi_create_connector(struct exynos_dsi *dsi) +static int exynos_dsi_bind(struct device *dev,
struct device *master, void *data)
{
- struct drm_connector *connector = &dsi->connector;
- struct drm_device *drm = dsi->bridge.dev;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_device *drm_dev = data;
- struct device_node *in_bridge_node;
- struct drm_bridge *in_bridge; int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = drm_connector_init(drm, connector, &exynos_dsi_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
- if (ret) {
DRM_DEV_ERROR(dsi->dev,
"Failed to initialize connector with drm\n");
- ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
- if (ret < 0) return ret;
- }
- connector->status = connector_status_disconnected;
- drm_connector_helper_add(connector, &exynos_dsi_connector_helper_funcs);
- drm_connector_attach_encoder(connector, dsi->bridge.encoder);
- if (!drm->registered)
return 0;
- connector->funcs->reset(connector);
- drm_connector_register(connector);
- return 0;
-}
-static int exynos_dsi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
-{
struct exynos_dsi *dsi = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder;
int ret;
if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
} else {
ret = exynos_dsi_create_connector(dsi);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
}
return 0;
-}
-static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
exynos_dsi_disable(dsi);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0);
- if (in_bridge_node) {
in_bridge = of_drm_find_bridge(in_bridge_node);
if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL, 0);
}of_node_put(in_bridge_node);
-}
-static void exynos_dsi_bridge_enable(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- exynos_dsi_enable(dsi);
-}
-static void exynos_dsi_bridge_disable(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- exynos_dsi_disable(dsi);
-}
-static void exynos_dsi_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
-{
- struct exynos_dsi *dsi = bridge->driver_private;
- /* The mode is set when actually enabling the device. */
- drm_mode_copy(&dsi->mode, adjusted_mode);
-}
-static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = {
- .attach = exynos_dsi_bridge_attach,
- .detach = exynos_dsi_bridge_detach,
- .enable = exynos_dsi_bridge_enable,
- .disable = exynos_dsi_bridge_disable,
- .mode_set = exynos_dsi_bridge_mode_set,
-};
-static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi *dsi = host_to_dsi(host);
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node);
- if (out_bridge) {
dsi->out_bridge = out_bridge;
- } else {
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
else
dsi->connector.status = connector_status_connected;
- }
- /*
* This is a temporary solution and should be made by more generic way.
*
* If attached panel device is for command mode one, dsi should register
* TE interrupt handler.
*/
- if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
int ret = exynos_dsi_register_te_irq(dsi, &device->dev);
if (ret)
return ret;
- }
- dsi->lanes = device->lanes;
- dsi->format = device->format;
- dsi->mode_flags = device->mode_flags;
- if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
ret = samsung_dsim_bind(dsi->dsi, encoder);
if (ret)
goto err;
return 0;
-}
-static int exynos_dsi_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
-{
struct exynos_dsi *dsi = host_to_dsi(host);
const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
return 0;
+err:
- drm_encoder_cleanup(encoder);
- return ret;
}
-static ssize_t exynos_dsi_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
+static void exynos_dsi_unbind(struct device *dev,
struct device *master, void *data)
{
- struct exynos_dsi *dsi = host_to_dsi(host);
- struct exynos_dsi_transfer xfer;
- int ret;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return -EINVAL;
- if (!(dsi->state & DSIM_STATE_INITIALIZED)) {
ret = exynos_dsi_init(dsi);
if (ret)
return ret;
dsi->state |= DSIM_STATE_INITIALIZED;
- }
- ret = mipi_dsi_create_packet(&xfer.packet, msg);
- if (ret < 0)
return ret;
- samsung_dsim_unbind(dsi->dsi);
- xfer.rx_len = msg->rx_len;
- xfer.rx_payload = msg->rx_buf;
- xfer.flags = msg->flags;
- ret = exynos_dsi_transfer(dsi, &xfer);
- return (ret < 0) ? ret : xfer.rx_done;
- drm_encoder_cleanup(encoder);
}
-static const struct mipi_dsi_host_ops exynos_dsi_ops = {
- .attach = exynos_dsi_host_attach,
- .detach = exynos_dsi_host_detach,
- .transfer = exynos_dsi_host_transfer,
+static const struct component_ops exynos_dsi_component_ops = {
- .bind = exynos_dsi_bind,
- .unbind = exynos_dsi_unbind,
};
-static int exynos_dsi_of_read_u32(const struct device_node *np,
const char *propname, u32 *out_value)
-{
- int ret = of_property_read_u32(np, propname, out_value);
- if (ret < 0)
pr_err("%pOF: failed to get '%s' property\n", np, propname);
- return ret;
-}
-static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) -{
- struct device *dev = dsi->dev;
- struct device_node *node = dev->of_node;
- int ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,pll-clock-frequency",
&dsi->pll_clk_rate);
- if (ret < 0)
return ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,burst-clock-frequency",
&dsi->burst_clk_rate);
- if (ret < 0)
return ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,esc-clock-frequency",
&dsi->esc_clk_rate);
- if (ret < 0)
return ret;
- return 0;
-}
-static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) +static int exynos_dsi_probe(struct platform_device *pdev) {
- struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- struct resource *res; struct exynos_dsi *dsi;
- int ret, i;
struct device *dev = &pdev->dev;
int ret;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); if (!dsi)
return ERR_PTR(-ENOMEM);
return -ENOMEM;
- platform_set_drvdata(pdev, dsi);
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
- dsi->dsi = samsung_dsim_probe(pdev);
- if (IS_ERR(dsi->dsi))
return PTR_ERR(dsi->dsi);
- init_completion(&dsi->completed);
- spin_lock_init(&dsi->transfer_lock);
- INIT_LIST_HEAD(&dsi->transfer_list);
- INIT_LIST_HEAD(&dsi->bridge_chain);
- pm_runtime_enable(dev);
- dsi->dsi_host.ops = &exynos_dsi_ops;
- dsi->dsi_host.dev = dev;
- dsi->dev = dev;
- dsi->driver_data = of_device_get_match_data(dev);
- dsi->supplies[0].supply = "vddcore";
- dsi->supplies[1].supply = "vddio";
- ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies),
dsi->supplies);
- if (ret) {
if (ret != -EPROBE_DEFER)
dev_info(dev, "failed to get regulators: %d\n", ret);
return ERR_PTR(ret);
- }
- dsi->clks = devm_kcalloc(dev,
dsi->driver_data->num_clks, sizeof(*dsi->clks),
GFP_KERNEL);
- if (!dsi->clks)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < dsi->driver_data->num_clks; i++) {
dsi->clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(dsi->clks[i])) {
if (strcmp(clk_names[i], "sclk_mipi") == 0) {
dsi->clks[i] = devm_clk_get(dev,
OLD_SCLK_MIPI_CLK_NAME);
if (!IS_ERR(dsi->clks[i]))
continue;
}
dev_info(dev, "failed to get the clock: %s\n",
clk_names[i]);
return ERR_PTR(PTR_ERR(dsi->clks[i]));
}
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->reg_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(dsi->reg_base)) {
dev_err(dev, "failed to remap io region\n");
return dsi->reg_base;
- }
- dsi->phy = devm_phy_get(dev, "dsim");
- if (IS_ERR(dsi->phy)) {
dev_info(dev, "failed to get dsim phy\n");
return ERR_PTR(PTR_ERR(dsi->phy));
- }
- dsi->irq = platform_get_irq(pdev, 0);
- if (dsi->irq < 0)
return ERR_PTR(dsi->irq);
- irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN);
- ret = devm_request_threaded_irq(dev, dsi->irq, NULL,
exynos_dsi_irq, IRQF_ONESHOT,
dev_name(dev), dsi);
- if (ret) {
dev_err(dev, "failed to request dsi irq\n");
return ERR_PTR(ret);
- }
- ret = exynos_dsi_parse_dt(dsi);
- ret = component_add(dev, &exynos_dsi_component_ops); if (ret)
return ERR_PTR(ret);
goto err_disable_runtime;
- ret = mipi_dsi_host_register(&dsi->dsi_host);
- if (ret)
return ERR_PTR(ret);
- return 0;
- bridge = &dsi->bridge;
- bridge->driver_private = dsi;
- bridge->funcs = &exynos_dsi_bridge_funcs;
- bridge->of_node = dev->of_node;
- drm_bridge_add(bridge);
+err_disable_runtime:
- pm_runtime_disable(dev);
- return dsi;
- return ret;
}
-static void __exynos_dsi_remove(struct exynos_dsi *dsi) +static int exynos_dsi_remove(struct platform_device *pdev) {
- drm_bridge_remove(&dsi->bridge);
- struct exynos_dsi *dsi = platform_get_drvdata(pdev);
- mipi_dsi_host_unregister(&dsi->dsi_host);
-}
-/*
- Probe/remove API, used from platforms based on the DRM bridge API.
- */
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) -{
- return __exynos_dsi_probe(pdev);
-}
- pm_runtime_disable(&pdev->dev);
-void exynos_dsi_remove(struct exynos_dsi *dsi) -{
- return __exynos_dsi_remove(dsi);
-}
- samsung_dsim_remove(dsi->dsi);
-/*
- Bind/unbind API, used from platforms based on the component framework.
- */
-int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) -{
- struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
- component_del(&pdev->dev, &exynos_dsi_component_ops);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0);
-}
-void exynos_dsi_unbind(struct exynos_dsi *dsi) -{
- exynos_dsi_disable(dsi);
- return 0;
}
-int exynos_dsi_suspend(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_suspend(struct device *dev) {
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- usleep_range(10000, 20000);
- if (dsi->state & DSIM_STATE_INITIALIZED) {
dsi->state &= ~DSIM_STATE_INITIALIZED;
exynos_dsi_disable_clock(dsi);
exynos_dsi_disable_irq(dsi);
- }
- dsi->state &= ~DSIM_STATE_CMD_LPM;
- phy_power_off(dsi->phy);
- for (i = driver_data->num_clks - 1; i > -1; i--)
clk_disable_unprepare(dsi->clks[i]);
- ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0)
dev_err(dsi->dev, "cannot disable regulators %d\n", ret);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0;
- return samsung_dsim_suspend(dsi->dsi);
}
-int exynos_dsi_resume(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_resume(struct device *dev) {
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable regulators %d\n", ret);
return ret;
- }
- for (i = 0; i < driver_data->num_clks; i++) {
ret = clk_prepare_enable(dsi->clks[i]);
if (ret < 0)
goto err_clk;
- }
- ret = phy_power_on(dsi->phy);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable phy %d\n", ret);
goto err_clk;
- }
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0;
- return samsung_dsim_resume(dsi->dsi);
+}
-err_clk:
- while (--i > -1)
clk_disable_unprepare(dsi->clks[i]);
- regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
+static const struct dev_pm_ops exynos_dsi_pm_ops = {
- SET_RUNTIME_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
+};
- return ret;
-} +struct platform_driver dsi_driver = {
- .probe = exynos_dsi_probe,
- .remove = exynos_dsi_remove,
- .driver = {
.name = "exynos-dsi",
.owner = THIS_MODULE,
.pm = &exynos_dsi_pm_ops,
.of_match_table = exynos_dsi_of_match,
- },
+};
MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c b/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c deleted file mode 100644 index 79d9ec6ade45..000000000000 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c +++ /dev/null @@ -1,333 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/*
- Samsung SoC MIPI DSI Master driver.
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
- */
-#include <linux/component.h> -#include <linux/of_device.h> -#include <linux/of_graph.h> -#include <linux/pm_runtime.h>
-#include <drm/drm_bridge.h> -#include <drm/drm_encoder.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_probe_helper.h> -#include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_crtc.h" -#include "exynos_drm_drv.h" -#include "exynos_drm_dsi.h"
-enum {
- DSI_PORT_IN,
- DSI_PORT_OUT
-};
-struct exynos_dsi_pltfm {
- struct exynos_dsi *dsi;
- struct drm_encoder encoder;
-};
-static const unsigned int reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0x0af,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x06,
- [PHYTIMING_HS_EXIT] = 0x0b,
- [PHYTIMING_CLK_PREPARE] = 0x07,
- [PHYTIMING_CLK_ZERO] = 0x27,
- [PHYTIMING_CLK_POST] = 0x0d,
- [PHYTIMING_CLK_TRAIL] = 0x08,
- [PHYTIMING_HS_PREPARE] = 0x09,
- [PHYTIMING_HS_ZERO] = 0x0d,
- [PHYTIMING_HS_TRAIL] = 0x0b,
-};
-static const unsigned int exynos5422_reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0xaf,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x08,
- [PHYTIMING_HS_EXIT] = 0x0d,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x30,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x0a,
- [PHYTIMING_HS_PREPARE] = 0x0c,
- [PHYTIMING_HS_ZERO] = 0x11,
- [PHYTIMING_HS_TRAIL] = 0x0d,
-};
-static const unsigned int exynos5433_reg_values[] = {
- [RESET_TYPE] = DSIM_FUNCRST,
- [PLL_TIMER] = 22200,
- [STOP_STATE_CNT] = 0xa,
- [PHYCTRL_ULPS_EXIT] = 0x190,
- [PHYCTRL_VREG_LP] = 1,
- [PHYCTRL_SLEW_UP] = 1,
- [PHYTIMING_LPX] = 0x07,
- [PHYTIMING_HS_EXIT] = 0x0c,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x2d,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x09,
- [PHYTIMING_HS_PREPARE] = 0x0b,
- [PHYTIMING_HS_ZERO] = 0x10,
- [PHYTIMING_HS_TRAIL] = 0x0c,
-};
-static int __exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- struct exynos_drm_crtc *crtc;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
-}
-static int __exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
-}
-static void __exynos_dsi_te_handler(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- exynos_drm_crtc_te_handler(dsi->encoder.crtc);
-}
-static const struct exynos_dsi_host_ops exynos_dsi_host_ops = {
- .attach = __exynos_dsi_host_attach,
- .detach = __exynos_dsi_host_detach,
- .te_handler = __exynos_dsi_te_handler,
-};
-static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x58,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 5,
- .max_freq = 1500,
- .wait_for_reset = 0,
- .num_bits_resol = 12,
- .reg_values = exynos5433_reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1500,
- .wait_for_reset = 1,
- .num_bits_resol = 12,
- .reg_values = exynos5422_reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct of_device_id exynos_dsi_of_match[] = {
- { .compatible = "samsung,exynos3250-mipi-dsi",
.data = &exynos3_dsi_driver_data },
- { .compatible = "samsung,exynos4210-mipi-dsi",
.data = &exynos4_dsi_driver_data },
- { .compatible = "samsung,exynos5410-mipi-dsi",
.data = &exynos5_dsi_driver_data },
- { .compatible = "samsung,exynos5422-mipi-dsi",
.data = &exynos5422_dsi_driver_data },
- { .compatible = "samsung,exynos5433-mipi-dsi",
.data = &exynos5433_dsi_driver_data },
- { }
-};
-static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_device *drm_dev = data;
- struct device_node *in_bridge_node;
- struct drm_bridge *in_bridge;
- int ret;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
- if (ret < 0)
return ret;
- in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0);
- if (in_bridge_node) {
in_bridge = of_drm_find_bridge(in_bridge_node);
if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL, 0);
of_node_put(in_bridge_node);
- }
- ret = exynos_dsi_bind(dsi->dsi, encoder);
- if (ret)
goto err;
- return 0;
-err:
- drm_encoder_cleanup(encoder);
- return ret;
-}
-static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master,
void *data)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- exynos_dsi_unbind(dsi->dsi);
- drm_encoder_cleanup(encoder);
-}
-static const struct component_ops exynos_dsi_pltfm_component_ops = {
- .bind = exynos_dsi_pltfm_bind,
- .unbind = exynos_dsi_pltfm_unbind,
-};
-static int exynos_dsi_pltfm_probe(struct platform_device *pdev) -{
- struct exynos_dsi_pltfm *dsi;
- struct device *dev = &pdev->dev;
- int ret;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
- if (!dsi)
return -ENOMEM;
- platform_set_drvdata(pdev, dsi);
- dsi->dsi = exynos_dsi_probe(pdev);
- if (IS_ERR(dsi->dsi))
return PTR_ERR(dsi->dsi);
- pm_runtime_enable(dev);
- ret = component_add(dev, &exynos_dsi_pltfm_component_ops);
- if (ret)
goto err_disable_runtime;
- return 0;
-err_disable_runtime:
- pm_runtime_disable(dev);
- return ret;
-}
-static int exynos_dsi_pltfm_remove(struct platform_device *pdev) -{
- struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev);
- pm_runtime_disable(&pdev->dev);
- exynos_dsi_remove(dsi->dsi);
- component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops);
- return 0;
-}
-static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- return exynos_dsi_suspend(dsi->dsi);
-}
-static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- return exynos_dsi_resume(dsi->dsi);
-}
-static const struct dev_pm_ops exynos_dsi_pm_ops = {
- SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
-};
-struct platform_driver dsi_driver = {
- .probe = exynos_dsi_pltfm_probe,
- .remove = exynos_dsi_pltfm_remove,
- .driver = {
.name = "exynos-dsi",
.owner = THIS_MODULE,
.pm = &exynos_dsi_pm_ops,
.of_match_table = exynos_dsi_of_match,
- },
-};
-MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); -MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); -MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.h b/include/drm/bridge/samsung-dsim.h similarity index 69% rename from drivers/gpu/drm/exynos/exynos_drm_dsi.h rename to include/drm/bridge/samsung-dsim.h index 8fa3276889de..be8b4913aa9c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.h +++ b/include/drm/bridge/samsung-dsim.h @@ -3,7 +3,7 @@ #define __EXYNOS_DRM_DSI__
struct drm_encoder; -struct exynos_dsi; +struct samsung_dsim; struct platform_device; struct mipi_dsi_device;
@@ -12,13 +12,13 @@ enum exynos_reg_offset { EXYNOS5433_REG_OFS };
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev); -void exynos_dsi_remove(struct exynos_dsi *dsi); -int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); -void exynos_dsi_unbind(struct exynos_dsi *dsi); +struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev); +void samsung_dsim_remove(struct samsung_dsim *dsi); +int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder); +void samsung_dsim_unbind(struct samsung_dsim *dsi);
-int exynos_dsi_suspend(struct exynos_dsi *dsi); -int exynos_dsi_resume(struct exynos_dsi *dsi); +int samsung_dsim_suspend(struct samsung_dsim *dsi); +int samsung_dsim_resume(struct samsung_dsim *dsi);
enum reg_value_idx { RESET_TYPE, @@ -42,13 +42,13 @@ enum reg_value_idx { #define DSIM_FUNCRST (1 << 16) #define DSIM_SWRST (1 << 0)
-struct exynos_dsi_host_ops { +struct samsung_dsim_host_ops { int (*attach)(struct device *dev, struct mipi_dsi_device *device); int (*detach)(struct device *dev, struct mipi_dsi_device *device); void (*te_handler)(struct device *dev); };
-struct exynos_dsi_driver_data { +struct samsung_dsim_driver_data { enum exynos_reg_offset reg_ofs; unsigned int plltmr_reg; unsigned int has_freqband:1; @@ -58,7 +58,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values;
- const struct exynos_dsi_host_ops *host_ops;
- const struct samsung_dsim_host_ops *host_ops;
};
#endif /* __EXYNOS_DRM_DSI__ */
2.20.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
On Wed, Sep 16, 2020 at 10:58:33AM +0200, Michael Tretter wrote:
On Wed, 16 Sep 2020 09:58:39 +0200, Daniel Vetter wrote:
On Fri, Sep 11, 2020 at 03:54:13PM +0200, Michael Tretter wrote:
As the driver is not platform dependent anymore, move it to the drm bridge driver directory.
Signed-off-by: Michael Tretter m.tretter@pengutronix.de
So new drm_bridge drivers that still use the component stuff is a bit uncool. We're trying to get away from that everywhere, bridges should be abstracted enough that just going through the of lookup functions to get at your bridge should work.
Is there anything here that prevents this, or could this be included?
The Exynos drm driver uses the component framework. Maybe I can avoid exposing the bind/unbind API in the drm_bridge driver by implementing the bind/unbind in the platform specific part. However, completely getting rid of the component framework for this drm_bridge is not possible without changing the entire Exynos drm driver.
You can combine the component framework for internal stuff with of_drm_find_bridge() for bridges. That's at least supposed to work.
I guess the of_drm_find_bridge would need to be stuffed in one of the existing bind hooks (maybe the master one), since having a component for the bridge and using the of_ function doesn't really make sense I think.
I guess another option would be that instead of adding the bridge component when you create your match, call of_drm_find_bridge, but at that point you don't yet have the drm_device nor drm_encoder. -Daniel
Michael
-Daniel
v2:
- select DRM_SAMSUNG_DSIM from DRM_EXYNOS_DSI
- add removal of depends on !FB_S3C
drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1790 ++++++++++++++++ drivers/gpu/drm/exynos/Kconfig | 5 +- drivers/gpu/drm/exynos/Makefile | 2 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 1896 ++--------------- drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c | 333 --- .../drm/bridge/samsung-dsim.h | 20 +- 8 files changed, 2037 insertions(+), 2019 deletions(-) create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c delete mode 100644 drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c rename drivers/gpu/drm/exynos/exynos_drm_dsi.h => include/drm/bridge/samsung-dsim.h (69%)
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 3e11af4e9f63..55ab5030c6cf 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -125,6 +125,15 @@ config DRM_PARADE_PS8640 The PS8640 is a high-performance and low-power MIPI DSI to eDP converter
+config DRM_SAMSUNG_DSIM
- tristate "Samsung MIPI DSI bridge"
- depends on OF
- select DRM_KMS_HELPER
- select DRM_MIPI_DSI
- select DRM_PANEL
- help
Samsung MIPI DSI bridge driver.
config DRM_SIL_SII8620 tristate "Silicon Image SII8620 HDMI/MHL bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index c589a6a7cbe1..5ac7a5c413dc 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o +obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII9234) += sii9234.o diff --git a/drivers/gpu/drm/bridge/samsung-dsim.c b/drivers/gpu/drm/bridge/samsung-dsim.c new file mode 100644 index 000000000000..6d2d8dc027de --- /dev/null +++ b/drivers/gpu/drm/bridge/samsung-dsim.c @@ -0,0 +1,1790 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Samsung SoC MIPI DSI Master driver.
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
+*/
+#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/component.h> +#include <linux/gpio/consumer.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_graph.h> +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h>
+#include <asm/unaligned.h>
+#include <video/mipi_display.h> +#include <video/videomode.h>
+#include <drm/bridge/samsung-dsim.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h>
+/* returns true iff both arguments logically differs */ +#define NEQV(a, b) (!(a) ^ !(b))
+/* DSIM_STATUS */ +#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) +#define DSIM_STOP_STATE_CLK (1 << 8) +#define DSIM_TX_READY_HS_CLK (1 << 10) +#define DSIM_PLL_STABLE (1 << 31)
+/* DSIM_TIMEOUT */ +#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) +#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
+/* DSIM_CLKCTRL */ +#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) +#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) +#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) +#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) +#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) +#define DSIM_BYTE_CLKEN (1 << 24) +#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) +#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) +#define DSIM_PLL_BYPASS (1 << 27) +#define DSIM_ESC_CLKEN (1 << 28) +#define DSIM_TX_REQUEST_HSCLK (1 << 31)
+/* DSIM_CONFIG */ +#define DSIM_LANE_EN_CLK (1 << 0) +#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) +#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) +#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) +#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) +#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) +#define DSIM_SUB_VC (((x) & 0x3) << 16) +#define DSIM_MAIN_VC (((x) & 0x3) << 18) +#define DSIM_HSA_MODE (1 << 20) +#define DSIM_HBP_MODE (1 << 21) +#define DSIM_HFP_MODE (1 << 22) +#define DSIM_HSE_MODE (1 << 23) +#define DSIM_AUTO_MODE (1 << 24) +#define DSIM_VIDEO_MODE (1 << 25) +#define DSIM_BURST_MODE (1 << 26) +#define DSIM_SYNC_INFORM (1 << 27) +#define DSIM_EOT_DISABLE (1 << 28) +#define DSIM_MFLUSH_VS (1 << 29) +/* This flag is valid only for exynos3250/3472/5260/5430 */ +#define DSIM_CLKLANE_STOP (1 << 30)
+/* DSIM_ESCMODE */ +#define DSIM_TX_TRIGGER_RST (1 << 4) +#define DSIM_TX_LPDT_LP (1 << 6) +#define DSIM_CMD_LPDT_LP (1 << 7) +#define DSIM_FORCE_BTA (1 << 16) +#define DSIM_FORCE_STOP_STATE (1 << 20) +#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) +#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
+/* DSIM_MDRESOL */ +#define DSIM_MAIN_STAND_BY (1 << 31) +#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) +#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
+/* DSIM_MVPORCH */ +#define DSIM_CMD_ALLOW(x) ((x) << 28) +#define DSIM_STABLE_VFP(x) ((x) << 16) +#define DSIM_MAIN_VBP(x) ((x) << 0) +#define DSIM_CMD_ALLOW_MASK (0xf << 28) +#define DSIM_STABLE_VFP_MASK (0x7ff << 16) +#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
+/* DSIM_MHPORCH */ +#define DSIM_MAIN_HFP(x) ((x) << 16) +#define DSIM_MAIN_HBP(x) ((x) << 0) +#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) +#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
+/* DSIM_MSYNC */ +#define DSIM_MAIN_VSA(x) ((x) << 22) +#define DSIM_MAIN_HSA(x) ((x) << 0) +#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) +#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
+/* DSIM_SDRESOL */ +#define DSIM_SUB_STANDY(x) ((x) << 31) +#define DSIM_SUB_VRESOL(x) ((x) << 16) +#define DSIM_SUB_HRESOL(x) ((x) << 0) +#define DSIM_SUB_STANDY_MASK ((0x1) << 31) +#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) +#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
+/* DSIM_INTSRC */ +#define DSIM_INT_PLL_STABLE (1 << 31) +#define DSIM_INT_SW_RST_RELEASE (1 << 30) +#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) +#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) +#define DSIM_INT_BTA (1 << 25) +#define DSIM_INT_FRAME_DONE (1 << 24) +#define DSIM_INT_RX_TIMEOUT (1 << 21) +#define DSIM_INT_BTA_TIMEOUT (1 << 20) +#define DSIM_INT_RX_DONE (1 << 18) +#define DSIM_INT_RX_TE (1 << 17) +#define DSIM_INT_RX_ACK (1 << 16) +#define DSIM_INT_RX_ECC_ERR (1 << 15) +#define DSIM_INT_RX_CRC_ERR (1 << 14)
+/* DSIM_FIFOCTRL */ +#define DSIM_RX_DATA_FULL (1 << 25) +#define DSIM_RX_DATA_EMPTY (1 << 24) +#define DSIM_SFR_HEADER_FULL (1 << 23) +#define DSIM_SFR_HEADER_EMPTY (1 << 22) +#define DSIM_SFR_PAYLOAD_FULL (1 << 21) +#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) +#define DSIM_I80_HEADER_FULL (1 << 19) +#define DSIM_I80_HEADER_EMPTY (1 << 18) +#define DSIM_I80_PAYLOAD_FULL (1 << 17) +#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) +#define DSIM_SD_HEADER_FULL (1 << 15) +#define DSIM_SD_HEADER_EMPTY (1 << 14) +#define DSIM_SD_PAYLOAD_FULL (1 << 13) +#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) +#define DSIM_MD_HEADER_FULL (1 << 11) +#define DSIM_MD_HEADER_EMPTY (1 << 10) +#define DSIM_MD_PAYLOAD_FULL (1 << 9) +#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) +#define DSIM_RX_FIFO (1 << 4) +#define DSIM_SFR_FIFO (1 << 3) +#define DSIM_I80_FIFO (1 << 2) +#define DSIM_SD_FIFO (1 << 1) +#define DSIM_MD_FIFO (1 << 0)
+/* DSIM_PHYACCHR */ +#define DSIM_AFC_EN (1 << 14) +#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
+/* DSIM_PLLCTRL */ +#define DSIM_FREQ_BAND(x) ((x) << 24) +#define DSIM_PLL_EN (1 << 23) +#define DSIM_PLL_P(x) ((x) << 13) +#define DSIM_PLL_M(x) ((x) << 4) +#define DSIM_PLL_S(x) ((x) << 1)
+/* DSIM_PHYCTRL */ +#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) +#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) +#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14)
+/* DSIM_PHYTIMING */ +#define DSIM_PHYTIMING_LPX(x) ((x) << 8) +#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
+/* DSIM_PHYTIMING1 */ +#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) +#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) +#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) +#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
+/* DSIM_PHYTIMING2 */ +#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) +#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) +#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
+#define DSI_MAX_BUS_WIDTH 4 +#define DSI_NUM_VIRTUAL_CHANNELS 4 +#define DSI_TX_FIFO_SIZE 2048 +#define DSI_RX_FIFO_SIZE 256 +#define DSI_XFER_TIMEOUT_MS 100 +#define DSI_RX_FIFO_EMPTY 0x30800002
+#define OLD_SCLK_MIPI_CLK_NAME "pll_clk"
+static const char *const clk_names[5] = { "bus_clk", "sclk_mipi",
- "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0",
- "sclk_rgb_vclk_to_dsim0" };
+enum samsung_dsim_transfer_type {
- EXYNOS_DSI_TX,
- EXYNOS_DSI_RX,
+};
+struct samsung_dsim_transfer {
- struct list_head list;
- struct completion completed;
- int result;
- struct mipi_dsi_packet packet;
- u16 flags;
- u16 tx_done;
- u8 *rx_payload;
- u16 rx_len;
- u16 rx_done;
+};
+#define DSIM_STATE_ENABLED BIT(0) +#define DSIM_STATE_INITIALIZED BIT(1) +#define DSIM_STATE_CMD_LPM BIT(2) +#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
+struct samsung_dsim {
- struct drm_bridge bridge;
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_panel *panel;
- struct list_head bridge_chain;
- struct drm_bridge *out_bridge;
- struct device *dev;
- void __iomem *reg_base;
- struct phy *phy;
- struct clk **clks;
- struct regulator_bulk_data supplies[2];
- int irq;
- int te_gpio;
- u32 pll_clk_rate;
- u32 burst_clk_rate;
- u32 esc_clk_rate;
- u32 lanes;
- u32 mode_flags;
- u32 format;
- struct drm_display_mode mode;
- int state;
- struct drm_property *brightness;
- struct completion completed;
- spinlock_t transfer_lock; /* protects transfer_list */
- struct list_head transfer_list;
- const struct samsung_dsim_driver_data *driver_data;
+};
+#define host_to_dsi(host) container_of(host, struct samsung_dsim, dsi_host) +#define connector_to_dsi(c) container_of(c, struct samsung_dsim, connector)
+enum reg_idx {
- DSIM_STATUS_REG, /* Status register */
- DSIM_SWRST_REG, /* Software reset register */
- DSIM_CLKCTRL_REG, /* Clock control register */
- DSIM_TIMEOUT_REG, /* Time out register */
- DSIM_CONFIG_REG, /* Configuration register */
- DSIM_ESCMODE_REG, /* Escape mode register */
- DSIM_MDRESOL_REG,
- DSIM_MVPORCH_REG, /* Main display Vporch register */
- DSIM_MHPORCH_REG, /* Main display Hporch register */
- DSIM_MSYNC_REG, /* Main display sync area register */
- DSIM_INTSRC_REG, /* Interrupt source register */
- DSIM_INTMSK_REG, /* Interrupt mask register */
- DSIM_PKTHDR_REG, /* Packet Header FIFO register */
- DSIM_PAYLOAD_REG, /* Payload FIFO register */
- DSIM_RXFIFO_REG, /* Read FIFO register */
- DSIM_FIFOCTRL_REG, /* FIFO status and control register */
- DSIM_PLLCTRL_REG, /* PLL control register */
- DSIM_PHYCTRL_REG,
- DSIM_PHYTIMING_REG,
- DSIM_PHYTIMING1_REG,
- DSIM_PHYTIMING2_REG,
- NUM_REGS
+};
+static const unsigned int exynos_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x00,
- [DSIM_SWRST_REG] = 0x04,
- [DSIM_CLKCTRL_REG] = 0x08,
- [DSIM_TIMEOUT_REG] = 0x0c,
- [DSIM_CONFIG_REG] = 0x10,
- [DSIM_ESCMODE_REG] = 0x14,
- [DSIM_MDRESOL_REG] = 0x18,
- [DSIM_MVPORCH_REG] = 0x1c,
- [DSIM_MHPORCH_REG] = 0x20,
- [DSIM_MSYNC_REG] = 0x24,
- [DSIM_INTSRC_REG] = 0x2c,
- [DSIM_INTMSK_REG] = 0x30,
- [DSIM_PKTHDR_REG] = 0x34,
- [DSIM_PAYLOAD_REG] = 0x38,
- [DSIM_RXFIFO_REG] = 0x3c,
- [DSIM_FIFOCTRL_REG] = 0x44,
- [DSIM_PLLCTRL_REG] = 0x4c,
- [DSIM_PHYCTRL_REG] = 0x5c,
- [DSIM_PHYTIMING_REG] = 0x64,
- [DSIM_PHYTIMING1_REG] = 0x68,
- [DSIM_PHYTIMING2_REG] = 0x6c,
+};
+static const unsigned int exynos5433_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x04,
- [DSIM_SWRST_REG] = 0x0C,
- [DSIM_CLKCTRL_REG] = 0x10,
- [DSIM_TIMEOUT_REG] = 0x14,
- [DSIM_CONFIG_REG] = 0x18,
- [DSIM_ESCMODE_REG] = 0x1C,
- [DSIM_MDRESOL_REG] = 0x20,
- [DSIM_MVPORCH_REG] = 0x24,
- [DSIM_MHPORCH_REG] = 0x28,
- [DSIM_MSYNC_REG] = 0x2C,
- [DSIM_INTSRC_REG] = 0x34,
- [DSIM_INTMSK_REG] = 0x38,
- [DSIM_PKTHDR_REG] = 0x3C,
- [DSIM_PAYLOAD_REG] = 0x40,
- [DSIM_RXFIFO_REG] = 0x44,
- [DSIM_FIFOCTRL_REG] = 0x4C,
- [DSIM_PLLCTRL_REG] = 0x94,
- [DSIM_PHYCTRL_REG] = 0xA4,
- [DSIM_PHYTIMING_REG] = 0xB4,
- [DSIM_PHYTIMING1_REG] = 0xB8,
- [DSIM_PHYTIMING2_REG] = 0xBC,
+};
+static inline void samsung_dsim_write(struct samsung_dsim *dsi,
enum reg_idx idx, u32 val)
+{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- writel(val, dsi->reg_base + reg_ofs[idx]);
+}
+static inline u32 samsung_dsim_read(struct samsung_dsim *dsi, enum reg_idx idx) +{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- return readl(dsi->reg_base + reg_ofs[idx]);
+}
+static void samsung_dsim_wait_for_reset(struct samsung_dsim *dsi) +{
- if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300)))
return;
- dev_err(dsi->dev, "timeout waiting for reset\n");
+}
+static void samsung_dsim_reset(struct samsung_dsim *dsi) +{
- u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE];
- reinit_completion(&dsi->completed);
- samsung_dsim_write(dsi, DSIM_SWRST_REG, reset_val);
+}
+#ifndef MHZ +#define MHZ (1000*1000) +#endif
+static unsigned long samsung_dsim_pll_find_pms(struct samsung_dsim *dsi,
unsigned long fin,
unsigned long fout,
u8 *p, u16 *m, u8 *s)
+{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- unsigned long best_freq = 0;
- u32 min_delta = 0xffffffff;
- u8 p_min, p_max;
- u8 _p, best_p;
- u16 _m, best_m;
- u8 _s, best_s;
- p_min = DIV_ROUND_UP(fin, (12 * MHZ));
- p_max = fin / (6 * MHZ);
- for (_p = p_min; _p <= p_max; ++_p) {
for (_s = 0; _s <= 5; ++_s) {
u64 tmp;
u32 delta;
tmp = (u64)fout * (_p << _s);
do_div(tmp, fin);
_m = tmp;
if (_m < 41 || _m > 125)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p);
if (tmp < 500 * MHZ ||
tmp > driver_data->max_freq * MHZ)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p << _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_m = _m;
best_s = _s;
min_delta = delta;
best_freq = tmp;
}
}
- }
- if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
- }
- return best_freq;
+}
+static unsigned long samsung_dsim_set_pll(struct samsung_dsim *dsi,
unsigned long freq)
+{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- unsigned long fin, fout;
- int timeout;
- u8 p, s;
- u16 m;
- u32 reg;
- fin = dsi->pll_clk_rate;
- fout = samsung_dsim_pll_find_pms(dsi, fin, freq, &p, &m, &s);
- if (!fout) {
dev_err(dsi->dev,
"failed to find PLL PMS for requested frequency\n");
return 0;
- }
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- writel(driver_data->reg_values[PLL_TIMER],
dsi->reg_base + driver_data->plltmr_reg);
- reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
- if (driver_data->has_freqband) {
static const unsigned long freq_bands[] = {
100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
770 * MHZ, 870 * MHZ, 950 * MHZ,
};
int band;
for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
if (fout < freq_bands[band])
break;
dev_dbg(dsi->dev, "band %d\n", band);
reg |= DSIM_FREQ_BAND(band);
- }
- samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg);
- timeout = 1000;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "PLL failed to stabilize\n");
return 0;
}
reg = samsung_dsim_read(dsi, DSIM_STATUS_REG);
- } while ((reg & DSIM_PLL_STABLE) == 0);
- return fout;
+}
+static int samsung_dsim_enable_clock(struct samsung_dsim *dsi) +{
- unsigned long hs_clk, byte_clk, esc_clk;
- unsigned long esc_div;
- u32 reg;
- hs_clk = samsung_dsim_set_pll(dsi, dsi->burst_clk_rate);
- if (!hs_clk) {
dev_err(dsi->dev, "failed to configure DSI PLL\n");
return -EFAULT;
- }
- byte_clk = hs_clk / 8;
- esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate);
- esc_clk = byte_clk / esc_div;
- if (esc_clk > 20 * MHZ) {
++esc_div;
esc_clk = byte_clk / esc_div;
- }
- dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
hs_clk, byte_clk, esc_clk);
- reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
| DSIM_BYTE_CLK_SRC_MASK);
- reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
| DSIM_ESC_PRESCALER(esc_div)
| DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1)
| DSIM_BYTE_CLK_SRC(0)
| DSIM_TX_REQUEST_HSCLK;
- samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg);
- return 0;
+}
+static void samsung_dsim_set_phy_ctrl(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- const unsigned int *reg_values = driver_data->reg_values;
- u32 reg;
- if (driver_data->has_freqband)
return;
- /* B D-PHY: D-PHY Master & Slave Analog Block control */
- reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]);
- if (reg_values[PHYCTRL_VREG_LP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP;
- if (reg_values[PHYCTRL_SLEW_UP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP;
- samsung_dsim_write(dsi, DSIM_PHYCTRL_REG, reg);
- /*
* T LPX: Transmitted length of any Low-Power state period
* T HS-EXIT: Time that the transmitter drives LP-11 following a HS
* burst
*/
- reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) |
DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING_REG, reg);
- /*
* T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Clock.
* T CLK_POST: Time that the transmitter continues to send HS clock
* after the last associated Data Lane has transitioned to LP Mode
* Interval is defined as the period from the end of T HS-TRAIL to
* the beginning of T CLK-TRAIL
* T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
* the last payload clock bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) |
DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) |
DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) |
DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING1_REG, reg);
- /*
* T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Sync sequence.
* T HS-TRAIL: Time that the transmitter drives the flipped differential
* state after last payload data bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) |
DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) |
DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]);
- samsung_dsim_write(dsi, DSIM_PHYTIMING2_REG, reg);
+}
+static void samsung_dsim_disable_clock(struct samsung_dsim *dsi) +{
- u32 reg;
- reg = samsung_dsim_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK
| DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
- samsung_dsim_write(dsi, DSIM_CLKCTRL_REG, reg);
- reg = samsung_dsim_read(dsi, DSIM_PLLCTRL_REG);
- reg &= ~DSIM_PLL_EN;
- samsung_dsim_write(dsi, DSIM_PLLCTRL_REG, reg);
+}
+static void samsung_dsim_enable_lane(struct samsung_dsim *dsi, u32 lane) +{
- u32 reg = samsung_dsim_read(dsi, DSIM_CONFIG_REG);
- reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK |
DSIM_LANE_EN(lane));
- samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg);
+}
+static int samsung_dsim_init_link(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int timeout;
- u32 reg;
- u32 lanes_mask;
- /* Initialize FIFO pointers */
- reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG);
- reg &= ~0x1f;
- samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- reg |= 0x1f;
- samsung_dsim_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- /* DSI configuration */
- reg = 0;
- /*
* The first bit of mode_flags specifies display configuration.
* If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video
* mode, otherwise it will support command mode.
*/
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg |= DSIM_VIDEO_MODE;
/*
* The user manual describes that following bits are ignored in
* command mode.
*/
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH))
reg |= DSIM_MFLUSH_VS;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
reg |= DSIM_SYNC_INFORM;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
reg |= DSIM_BURST_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
reg |= DSIM_AUTO_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
reg |= DSIM_HSE_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP))
reg |= DSIM_HFP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP))
reg |= DSIM_HBP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA))
reg |= DSIM_HSA_MODE;
- }
- if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
reg |= DSIM_EOT_DISABLE;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
break;
- case MIPI_DSI_FMT_RGB666:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
break;
- case MIPI_DSI_FMT_RGB565:
reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
break;
- default:
dev_err(dsi->dev, "invalid pixel format\n");
return -EINVAL;
- }
- /*
* Use non-continuous clock mode if the periparal wants and
* host controller supports
*
* In non-continous clock mode, host controller will turn off
* the HS clock between high-speed transmissions to reduce
* power consumption.
*/
- if (driver_data->has_clklane_stop &&
dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
reg |= DSIM_CLKLANE_STOP;
- }
- samsung_dsim_write(dsi, DSIM_CONFIG_REG, reg);
- lanes_mask = BIT(dsi->lanes) - 1;
- samsung_dsim_enable_lane(dsi, lanes_mask);
- /* Check clock and data lane state are stop state */
- timeout = 100;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "waiting for bus lanes timed out\n");
return -EFAULT;
}
reg = samsung_dsim_read(dsi, DSIM_STATUS_REG);
if ((reg & DSIM_STOP_STATE_DAT(lanes_mask))
!= DSIM_STOP_STATE_DAT(lanes_mask))
continue;
- } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
- reg = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- reg &= ~DSIM_STOP_STATE_CNT_MASK;
- reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]);
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, reg);
- reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
- samsung_dsim_write(dsi, DSIM_TIMEOUT_REG, reg);
- return 0;
+}
+static void samsung_dsim_set_display_mode(struct samsung_dsim *dsi) +{
- struct drm_display_mode *m = &dsi->mode;
- unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
- u32 reg;
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg = DSIM_CMD_ALLOW(0xf)
| DSIM_STABLE_VFP(m->vsync_start - m->vdisplay)
| DSIM_MAIN_VBP(m->vtotal - m->vsync_end);
samsung_dsim_write(dsi, DSIM_MVPORCH_REG, reg);
reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay)
| DSIM_MAIN_HBP(m->htotal - m->hsync_end);
samsung_dsim_write(dsi, DSIM_MHPORCH_REG, reg);
reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start)
| DSIM_MAIN_HSA(m->hsync_end - m->hsync_start);
samsung_dsim_write(dsi, DSIM_MSYNC_REG, reg);
- }
- reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) |
DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol);
- samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg);
- dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay);
+}
+static void samsung_dsim_set_display_enable(struct samsung_dsim *dsi,
bool enable)
+{
- u32 reg;
- reg = samsung_dsim_read(dsi, DSIM_MDRESOL_REG);
- if (enable)
reg |= DSIM_MAIN_STAND_BY;
- else
reg &= ~DSIM_MAIN_STAND_BY;
- samsung_dsim_write(dsi, DSIM_MDRESOL_REG, reg);
+}
+static int samsung_dsim_wait_for_hdr_fifo(struct samsung_dsim *dsi) +{
- int timeout = 2000;
- do {
u32 reg = samsung_dsim_read(dsi, DSIM_FIFOCTRL_REG);
if (!(reg & DSIM_SFR_HEADER_FULL))
return 0;
if (!cond_resched())
usleep_range(950, 1050);
- } while (--timeout);
- return -ETIMEDOUT;
+}
+static void samsung_dsim_set_cmd_lpm(struct samsung_dsim *dsi, bool lpm) +{
- u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- if (lpm)
v |= DSIM_CMD_LPDT_LP;
- else
v &= ~DSIM_CMD_LPDT_LP;
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v);
+}
+static void samsung_dsim_force_bta(struct samsung_dsim *dsi) +{
- u32 v = samsung_dsim_read(dsi, DSIM_ESCMODE_REG);
- v |= DSIM_FORCE_BTA;
- samsung_dsim_write(dsi, DSIM_ESCMODE_REG, v);
+}
+static void samsung_dsim_send_to_fifo(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- struct device *dev = dsi->dev;
- struct mipi_dsi_packet *pkt = &xfer->packet;
- const u8 *payload = pkt->payload + xfer->tx_done;
- u16 length = pkt->payload_length - xfer->tx_done;
- bool first = !xfer->tx_done;
- u32 reg;
- dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n",
xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done);
- if (length > DSI_TX_FIFO_SIZE)
length = DSI_TX_FIFO_SIZE;
- xfer->tx_done += length;
- /* Send payload */
- while (length >= 4) {
reg = get_unaligned_le32(payload);
samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg);
payload += 4;
length -= 4;
- }
- reg = 0;
- switch (length) {
- case 3:
reg |= payload[2] << 16;
fallthrough;
- case 2:
reg |= payload[1] << 8;
fallthrough;
- case 1:
reg |= payload[0];
samsung_dsim_write(dsi, DSIM_PAYLOAD_REG, reg);
break;
- }
- /* Send packet header */
- if (!first)
return;
- reg = get_unaligned_le32(pkt->header);
- if (samsung_dsim_wait_for_hdr_fifo(dsi)) {
dev_err(dev, "waiting for header FIFO timed out\n");
return;
- }
- if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM,
dsi->state & DSIM_STATE_CMD_LPM)) {
samsung_dsim_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM);
dsi->state ^= DSIM_STATE_CMD_LPM;
- }
- samsung_dsim_write(dsi, DSIM_PKTHDR_REG, reg);
- if (xfer->flags & MIPI_DSI_MSG_REQ_ACK)
samsung_dsim_force_bta(dsi);
+}
+static void samsung_dsim_read_from_fifo(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- u8 *payload = xfer->rx_payload + xfer->rx_done;
- bool first = !xfer->rx_done;
- struct device *dev = dsi->dev;
- u16 length;
- u32 reg;
- if (first) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
switch (reg & 0x3f) {
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
if (xfer->rx_len >= 2) {
payload[1] = reg >> 16;
++xfer->rx_done;
}
fallthrough;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
payload[0] = reg >> 8;
++xfer->rx_done;
xfer->rx_len = xfer->rx_done;
xfer->result = 0;
goto clear_fifo;
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
dev_err(dev, "DSI Error Report: 0x%04x\n",
(reg >> 8) & 0xffff);
xfer->result = 0;
goto clear_fifo;
}
length = (reg >> 8) & 0xffff;
if (length > xfer->rx_len) {
dev_err(dev,
"response too long (%u > %u bytes), stripping\n",
xfer->rx_len, length);
length = xfer->rx_len;
} else if (length < xfer->rx_len)
xfer->rx_len = length;
- }
- length = xfer->rx_len - xfer->rx_done;
- xfer->rx_done += length;
- /* Receive payload */
- while (length >= 4) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
payload[0] = (reg >> 0) & 0xff;
payload[1] = (reg >> 8) & 0xff;
payload[2] = (reg >> 16) & 0xff;
payload[3] = (reg >> 24) & 0xff;
payload += 4;
length -= 4;
- }
- if (length) {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
switch (length) {
case 3:
payload[2] = (reg >> 16) & 0xff;
fallthrough;
case 2:
payload[1] = (reg >> 8) & 0xff;
fallthrough;
case 1:
payload[0] = reg & 0xff;
}
- }
- if (xfer->rx_done == xfer->rx_len)
xfer->result = 0;
+clear_fifo:
- length = DSI_RX_FIFO_SIZE / 4;
- do {
reg = samsung_dsim_read(dsi, DSIM_RXFIFO_REG);
if (reg == DSI_RX_FIFO_EMPTY)
break;
- } while (--length);
+}
+static void samsung_dsim_transfer_start(struct samsung_dsim *dsi) +{
- unsigned long flags;
- struct samsung_dsim_transfer *xfer;
- bool start = false;
+again:
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (xfer->packet.payload_length &&
xfer->tx_done == xfer->packet.payload_length)
/* waiting for RX */
return;
- samsung_dsim_send_to_fifo(dsi, xfer);
- if (xfer->packet.payload_length || xfer->rx_len)
return;
- xfer->result = 0;
- complete(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (start)
goto again;
+}
+static bool samsung_dsim_transfer_finish(struct samsung_dsim *dsi) +{
- struct samsung_dsim_transfer *xfer;
- unsigned long flags;
- bool start = true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return false;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- dev_dbg(dsi->dev,
"> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n",
xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len,
xfer->rx_done);
- if (xfer->tx_done != xfer->packet.payload_length)
return true;
- if (xfer->rx_done != xfer->rx_len)
samsung_dsim_read_from_fifo(dsi, xfer);
- if (xfer->rx_done != xfer->rx_len)
return true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (!xfer->rx_len)
xfer->result = 0;
- complete(&xfer->completed);
- return start;
+}
+static void samsung_dsim_remove_transfer(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- unsigned long flags;
- bool start;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (!list_empty(&dsi->transfer_list) &&
xfer == list_first_entry(&dsi->transfer_list,
struct samsung_dsim_transfer, list)) {
list_del_init(&xfer->list);
start = !list_empty(&dsi->transfer_list);
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
if (start)
samsung_dsim_transfer_start(dsi);
return;
- }
- list_del_init(&xfer->list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
+}
+static int samsung_dsim_transfer(struct samsung_dsim *dsi,
struct samsung_dsim_transfer *xfer)
+{
- unsigned long flags;
- bool stopped;
- xfer->tx_done = 0;
- xfer->rx_done = 0;
- xfer->result = -ETIMEDOUT;
- init_completion(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- stopped = list_empty(&dsi->transfer_list);
- list_add_tail(&xfer->list, &dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (stopped)
samsung_dsim_transfer_start(dsi);
- wait_for_completion_timeout(&xfer->completed,
msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
- if (xfer->result == -ETIMEDOUT) {
struct mipi_dsi_packet *pkt = &xfer->packet;
samsung_dsim_remove_transfer(dsi, xfer);
dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header,
(int)pkt->payload_length, pkt->payload);
return -ETIMEDOUT;
- }
- /* Also covers hardware timeout condition */
- return xfer->result;
+}
+static irqreturn_t samsung_dsim_irq(int irq, void *dev_id) +{
- struct samsung_dsim *dsi = dev_id;
- u32 status;
- status = samsung_dsim_read(dsi, DSIM_INTSRC_REG);
- if (!status) {
static unsigned long int j;
if (printk_timed_ratelimit(&j, 500))
dev_warn(dsi->dev, "spurious interrupt\n");
return IRQ_HANDLED;
- }
- samsung_dsim_write(dsi, DSIM_INTSRC_REG, status);
- if (status & DSIM_INT_SW_RST_RELEASE) {
u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR |
DSIM_INT_SW_RST_RELEASE);
samsung_dsim_write(dsi, DSIM_INTMSK_REG, mask);
complete(&dsi->completed);
return IRQ_HANDLED;
- }
- if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_PLL_STABLE)))
return IRQ_HANDLED;
- if (samsung_dsim_transfer_finish(dsi))
samsung_dsim_transfer_start(dsi);
- return IRQ_HANDLED;
+}
+static irqreturn_t samsung_dsim_te_irq_handler(int irq, void *dev_id) +{
- struct samsung_dsim *dsi = dev_id;
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->te_handler &&
(dsi->state & DSIM_STATE_VIDOUT_AVAILABLE))
ops->te_handler(dsi->dsi_host.dev);
- return IRQ_HANDLED;
+}
+static void samsung_dsim_enable_irq(struct samsung_dsim *dsi) +{
- enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
enable_irq(gpio_to_irq(dsi->te_gpio));
+}
+static void samsung_dsim_disable_irq(struct samsung_dsim *dsi) +{
- if (gpio_is_valid(dsi->te_gpio))
disable_irq(gpio_to_irq(dsi->te_gpio));
- disable_irq(dsi->irq);
+}
+static int samsung_dsim_init(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- samsung_dsim_reset(dsi);
- samsung_dsim_enable_irq(dsi);
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST)
samsung_dsim_enable_lane(dsi, BIT(dsi->lanes) - 1);
- samsung_dsim_enable_clock(dsi);
- if (driver_data->wait_for_reset)
samsung_dsim_wait_for_reset(dsi);
- samsung_dsim_set_phy_ctrl(dsi);
- samsung_dsim_init_link(dsi);
- return 0;
+}
+static int samsung_dsim_register_te_irq(struct samsung_dsim *dsi,
struct device *panel)
+{
- int ret;
- int te_gpio_irq;
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
return 0;
- if (!gpio_is_valid(dsi->te_gpio)) {
ret = dsi->te_gpio;
dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
goto out;
- }
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
dev_err(dsi->dev, "gpio request failed with %d\n", ret);
goto out;
- }
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
- irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN);
- ret = request_threaded_irq(te_gpio_irq, samsung_dsim_te_irq_handler,
NULL, IRQF_TRIGGER_RISING, "TE", dsi);
- if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
gpio_free(dsi->te_gpio);
goto out;
- }
+out:
- return ret;
+}
+static void samsung_dsim_unregister_te_irq(struct samsung_dsim *dsi) +{
- if (gpio_is_valid(dsi->te_gpio)) {
free_irq(gpio_to_irq(dsi->te_gpio), dsi);
gpio_free(dsi->te_gpio);
dsi->te_gpio = -ENOENT;
- }
+}
+static void samsung_dsim_enable(struct samsung_dsim *dsi) +{
- struct drm_bridge *iter;
- int ret;
- if (dsi->state & DSIM_STATE_ENABLED)
return;
- pm_runtime_get_sync(dsi->dev);
- dsi->state |= DSIM_STATE_ENABLED;
- if (dsi->panel) {
ret = drm_panel_prepare(dsi->panel);
if (ret < 0)
goto err_put_sync;
- } else {
list_for_each_entry_reverse(iter, &dsi->bridge_chain,
chain_node) {
if (iter->funcs->pre_enable)
iter->funcs->pre_enable(iter);
}
- }
- samsung_dsim_set_display_mode(dsi);
- samsung_dsim_set_display_enable(dsi, true);
- if (dsi->panel) {
ret = drm_panel_enable(dsi->panel);
if (ret < 0)
goto err_display_disable;
- } else {
list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->enable)
iter->funcs->enable(iter);
}
- }
- dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE;
- return;
+err_display_disable:
- samsung_dsim_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
+err_put_sync:
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put(dsi->dev);
+}
+static void samsung_dsim_disable(struct samsung_dsim *dsi) +{
- struct drm_bridge *iter;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return;
- dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE;
- drm_panel_disable(dsi->panel);
- list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->disable)
iter->funcs->disable(iter);
- }
- samsung_dsim_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
- list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->post_disable)
iter->funcs->post_disable(iter);
- }
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put_sync(dsi->dev);
+}
+static enum drm_connector_status +samsung_dsim_detect(struct drm_connector *connector, bool force) +{
- return connector->status;
+}
+static void samsung_dsim_connector_destroy(struct drm_connector *connector) +{
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- connector->dev = NULL;
+}
+static const struct drm_connector_funcs samsung_dsim_connector_funcs = {
- .detect = samsung_dsim_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = samsung_dsim_connector_destroy,
- .reset = drm_atomic_helper_connector_reset,
- .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+static int samsung_dsim_get_modes(struct drm_connector *connector) +{
- struct samsung_dsim *dsi = connector_to_dsi(connector);
- if (dsi->panel)
return drm_panel_get_modes(dsi->panel, connector);
- return 0;
+}
+static const struct drm_connector_helper_funcs samsung_dsim_connector_helper_funcs = {
- .get_modes = samsung_dsim_get_modes,
+};
+static int samsung_dsim_create_connector(struct samsung_dsim *dsi) +{
- struct drm_connector *connector = &dsi->connector;
- struct drm_device *drm = dsi->bridge.dev;
- int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD;
- ret = drm_connector_init(drm, connector, &samsung_dsim_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
- if (ret) {
DRM_DEV_ERROR(dsi->dev,
"Failed to initialize connector with drm\n");
return ret;
- }
- connector->status = connector_status_disconnected;
- drm_connector_helper_add(connector, &samsung_dsim_connector_helper_funcs);
- drm_connector_attach_encoder(connector, dsi->bridge.encoder);
- if (!drm->registered)
return 0;
- connector->funcs->reset(connector);
- drm_connector_register(connector);
- return 0;
+}
+static int samsung_dsim_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
+{
- struct samsung_dsim *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- int ret;
- if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
- if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
- } else {
ret = samsung_dsim_create_connector(dsi);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
- }
- return 0;
+}
+static void samsung_dsim_bridge_detach(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
samsung_dsim_disable(dsi);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- }
+}
+static void samsung_dsim_bridge_enable(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- samsung_dsim_enable(dsi);
+}
+static void samsung_dsim_bridge_disable(struct drm_bridge *bridge) +{
- struct samsung_dsim *dsi = bridge->driver_private;
- samsung_dsim_disable(dsi);
+}
+static void samsung_dsim_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
+{
- struct samsung_dsim *dsi = bridge->driver_private;
- /* The mode is set when actually enabling the device. */
- drm_mode_copy(&dsi->mode, adjusted_mode);
+}
+static const struct drm_bridge_funcs samsung_dsim_bridge_funcs = {
- .attach = samsung_dsim_bridge_attach,
- .detach = samsung_dsim_bridge_detach,
- .enable = samsung_dsim_bridge_enable,
- .disable = samsung_dsim_bridge_disable,
- .mode_set = samsung_dsim_bridge_mode_set,
+};
+static int samsung_dsim_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node);
- if (out_bridge) {
dsi->out_bridge = out_bridge;
- } else {
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
else
dsi->connector.status = connector_status_connected;
- }
- /*
* This is a temporary solution and should be made by more generic way.
*
* If attached panel device is for command mode one, dsi should register
* TE interrupt handler.
*/
- if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
int ret = samsung_dsim_register_te_irq(dsi, &device->dev);
if (ret)
return ret;
- }
- dsi->lanes = device->lanes;
- dsi->format = device->format;
- dsi->mode_flags = device->mode_flags;
- if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
- return 0;
+}
+static int samsung_dsim_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- const struct samsung_dsim_host_ops *ops = dsi->driver_data->host_ops;
- samsung_dsim_unregister_te_irq(dsi);
- if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
- samsung_dsim_unregister_te_irq(dsi);
- return 0;
+}
+static ssize_t samsung_dsim_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
+{
- struct samsung_dsim *dsi = host_to_dsi(host);
- struct samsung_dsim_transfer xfer;
- int ret;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return -EINVAL;
- if (!(dsi->state & DSIM_STATE_INITIALIZED)) {
ret = samsung_dsim_init(dsi);
if (ret)
return ret;
dsi->state |= DSIM_STATE_INITIALIZED;
- }
- ret = mipi_dsi_create_packet(&xfer.packet, msg);
- if (ret < 0)
return ret;
- xfer.rx_len = msg->rx_len;
- xfer.rx_payload = msg->rx_buf;
- xfer.flags = msg->flags;
- ret = samsung_dsim_transfer(dsi, &xfer);
- return (ret < 0) ? ret : xfer.rx_done;
+}
+static const struct mipi_dsi_host_ops samsung_dsim_ops = {
- .attach = samsung_dsim_host_attach,
- .detach = samsung_dsim_host_detach,
- .transfer = samsung_dsim_host_transfer,
+};
+static int samsung_dsim_of_read_u32(const struct device_node *np,
const char *propname, u32 *out_value)
+{
- int ret = of_property_read_u32(np, propname, out_value);
- if (ret < 0)
pr_err("%pOF: failed to get '%s' property\n", np, propname);
- return ret;
+}
+static int samsung_dsim_parse_dt(struct samsung_dsim *dsi) +{
- struct device *dev = dsi->dev;
- struct device_node *node = dev->of_node;
- int ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,pll-clock-frequency",
&dsi->pll_clk_rate);
- if (ret < 0)
return ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,burst-clock-frequency",
&dsi->burst_clk_rate);
- if (ret < 0)
return ret;
- ret = samsung_dsim_of_read_u32(node, "samsung,esc-clock-frequency",
&dsi->esc_clk_rate);
- if (ret < 0)
return ret;
- return 0;
+}
+static struct samsung_dsim *__samsung_dsim_probe(struct platform_device *pdev) +{
- struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- struct resource *res;
- struct samsung_dsim *dsi;
- int ret, i;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
- if (!dsi)
return ERR_PTR(-ENOMEM);
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
- init_completion(&dsi->completed);
- spin_lock_init(&dsi->transfer_lock);
- INIT_LIST_HEAD(&dsi->transfer_list);
- INIT_LIST_HEAD(&dsi->bridge_chain);
- dsi->dsi_host.ops = &samsung_dsim_ops;
- dsi->dsi_host.dev = dev;
- dsi->dev = dev;
- dsi->driver_data = of_device_get_match_data(dev);
- dsi->supplies[0].supply = "vddcore";
- dsi->supplies[1].supply = "vddio";
- ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies),
dsi->supplies);
- if (ret) {
if (ret != -EPROBE_DEFER)
dev_info(dev, "failed to get regulators: %d\n", ret);
return ERR_PTR(ret);
- }
- dsi->clks = devm_kcalloc(dev,
dsi->driver_data->num_clks, sizeof(*dsi->clks),
GFP_KERNEL);
- if (!dsi->clks)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < dsi->driver_data->num_clks; i++) {
dsi->clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(dsi->clks[i])) {
if (strcmp(clk_names[i], "sclk_mipi") == 0) {
dsi->clks[i] = devm_clk_get(dev,
OLD_SCLK_MIPI_CLK_NAME);
if (!IS_ERR(dsi->clks[i]))
continue;
}
dev_info(dev, "failed to get the clock: %s\n",
clk_names[i]);
return ERR_PTR(PTR_ERR(dsi->clks[i]));
}
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->reg_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(dsi->reg_base)) {
dev_err(dev, "failed to remap io region\n");
return dsi->reg_base;
- }
- dsi->phy = devm_phy_get(dev, "dsim");
- if (IS_ERR(dsi->phy)) {
dev_info(dev, "failed to get dsim phy\n");
return ERR_PTR(PTR_ERR(dsi->phy));
- }
- dsi->irq = platform_get_irq(pdev, 0);
- if (dsi->irq < 0)
return ERR_PTR(dsi->irq);
- irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN);
- ret = devm_request_threaded_irq(dev, dsi->irq, NULL,
samsung_dsim_irq, IRQF_ONESHOT,
dev_name(dev), dsi);
- if (ret) {
dev_err(dev, "failed to request dsi irq\n");
return ERR_PTR(ret);
- }
- ret = samsung_dsim_parse_dt(dsi);
- if (ret)
return ERR_PTR(ret);
- ret = mipi_dsi_host_register(&dsi->dsi_host);
- if (ret)
return ERR_PTR(ret);
- bridge = &dsi->bridge;
- bridge->driver_private = dsi;
- bridge->funcs = &samsung_dsim_bridge_funcs;
- bridge->of_node = dev->of_node;
- drm_bridge_add(bridge);
- return dsi;
+}
+static void __samsung_dsim_remove(struct samsung_dsim *dsi) +{
- drm_bridge_remove(&dsi->bridge);
- mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+/*
- Probe/remove API, used from platforms based on the DRM bridge API.
- */
+struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev) +{
- return __samsung_dsim_probe(pdev);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_probe);
+void samsung_dsim_remove(struct samsung_dsim *dsi) +{
- return __samsung_dsim_remove(dsi);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_remove);
+/*
- Bind/unbind API, used from platforms based on the component framework.
- */
+int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder) +{
- struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_bind);
+void samsung_dsim_unbind(struct samsung_dsim *dsi) +{
- samsung_dsim_disable(dsi);
+} +EXPORT_SYMBOL_GPL(samsung_dsim_unbind);
+int samsung_dsim_suspend(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- usleep_range(10000, 20000);
- if (dsi->state & DSIM_STATE_INITIALIZED) {
dsi->state &= ~DSIM_STATE_INITIALIZED;
samsung_dsim_disable_clock(dsi);
samsung_dsim_disable_irq(dsi);
- }
- dsi->state &= ~DSIM_STATE_CMD_LPM;
- phy_power_off(dsi->phy);
- for (i = driver_data->num_clks - 1; i > -1; i--)
clk_disable_unprepare(dsi->clks[i]);
- ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0)
dev_err(dsi->dev, "cannot disable regulators %d\n", ret);
- return 0;
+} +EXPORT_SYMBOL_GPL(samsung_dsim_suspend);
+int samsung_dsim_resume(struct samsung_dsim *dsi) +{
- const struct samsung_dsim_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable regulators %d\n", ret);
return ret;
- }
- for (i = 0; i < driver_data->num_clks; i++) {
ret = clk_prepare_enable(dsi->clks[i]);
if (ret < 0)
goto err_clk;
- }
- ret = phy_power_on(dsi->phy);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable phy %d\n", ret);
goto err_clk;
- }
- return 0;
+err_clk:
- while (--i > -1)
clk_disable_unprepare(dsi->clks[i]);
- regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- return ret;
+} +EXPORT_SYMBOL_GPL(samsung_dsim_resume);
+MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); +MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); +MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 6417f374b923..3bc321ab5bc8 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -16,7 +16,6 @@ comment "CRTCs"
config DRM_EXYNOS_FIMD bool "FIMD"
- depends on !FB_S3C select MFD_SYSCON help Choose this option if you want to use Exynos FIMD for DRM.
@@ -28,7 +27,6 @@ config DRM_EXYNOS5433_DECON
config DRM_EXYNOS7_DECON bool "DECON on Exynos7"
- depends on !FB_S3C help Choose this option if you want to use Exynos DECON for DRM.
@@ -55,8 +53,7 @@ config DRM_EXYNOS_DPI config DRM_EXYNOS_DSI bool "MIPI-DSI host" depends on DRM_EXYNOS_FIMD || DRM_EXYNOS5433_DECON || DRM_EXYNOS7_DECON
- select DRM_MIPI_DSI
- select DRM_PANEL
- select DRM_SAMSUNG_DSIM default n help This enables support for Exynos MIPI-DSI device.
diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index add70b336935..2fd2f3ee4fcf 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -11,7 +11,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o -exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynos_drm_dsi_pltfm.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index e8aea9d90c34..17f37fa74718 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -5,1774 +5,328 @@
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
-*/
- */
-#include <linux/clk.h> -#include <linux/delay.h> #include <linux/component.h> -#include <linux/gpio/consumer.h> -#include <linux/irq.h> #include <linux/of_device.h> -#include <linux/of_gpio.h> #include <linux/of_graph.h> -#include <linux/phy/phy.h> -#include <linux/regulator/consumer.h>
-#include <asm/unaligned.h> +#include <linux/pm_runtime.h>
-#include <video/mipi_display.h> -#include <video/videomode.h>
-#include <drm/drm_atomic_helper.h> +#include <drm/bridge/samsung-dsim.h> #include <drm/drm_bridge.h> -#include <drm/drm_fb_helper.h> +#include <drm/drm_encoder.h> #include <drm/drm_mipi_dsi.h> -#include <drm/drm_panel.h> -#include <drm/drm_print.h> #include <drm/drm_probe_helper.h> #include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_dsi.h"
-/* returns true iff both arguments logically differs */ -#define NEQV(a, b) (!(a) ^ !(b))
-/* DSIM_STATUS */ -#define DSIM_STOP_STATE_DAT(x) (((x) & 0xf) << 0) -#define DSIM_STOP_STATE_CLK (1 << 8) -#define DSIM_TX_READY_HS_CLK (1 << 10) -#define DSIM_PLL_STABLE (1 << 31)
-/* DSIM_TIMEOUT */ -#define DSIM_LPDR_TIMEOUT(x) ((x) << 0) -#define DSIM_BTA_TIMEOUT(x) ((x) << 16)
-/* DSIM_CLKCTRL */ -#define DSIM_ESC_PRESCALER(x) (((x) & 0xffff) << 0) -#define DSIM_ESC_PRESCALER_MASK (0xffff << 0) -#define DSIM_LANE_ESC_CLK_EN_CLK (1 << 19) -#define DSIM_LANE_ESC_CLK_EN_DATA(x) (((x) & 0xf) << 20) -#define DSIM_LANE_ESC_CLK_EN_DATA_MASK (0xf << 20) -#define DSIM_BYTE_CLKEN (1 << 24) -#define DSIM_BYTE_CLK_SRC(x) (((x) & 0x3) << 25) -#define DSIM_BYTE_CLK_SRC_MASK (0x3 << 25) -#define DSIM_PLL_BYPASS (1 << 27) -#define DSIM_ESC_CLKEN (1 << 28) -#define DSIM_TX_REQUEST_HSCLK (1 << 31)
-/* DSIM_CONFIG */ -#define DSIM_LANE_EN_CLK (1 << 0) -#define DSIM_LANE_EN(x) (((x) & 0xf) << 1) -#define DSIM_NUM_OF_DATA_LANE(x) (((x) & 0x3) << 5) -#define DSIM_SUB_PIX_FORMAT(x) (((x) & 0x7) << 8) -#define DSIM_MAIN_PIX_FORMAT_MASK (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB888 (0x7 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666 (0x6 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB666_P (0x5 << 12) -#define DSIM_MAIN_PIX_FORMAT_RGB565 (0x4 << 12) -#define DSIM_SUB_VC (((x) & 0x3) << 16) -#define DSIM_MAIN_VC (((x) & 0x3) << 18) -#define DSIM_HSA_MODE (1 << 20) -#define DSIM_HBP_MODE (1 << 21) -#define DSIM_HFP_MODE (1 << 22) -#define DSIM_HSE_MODE (1 << 23) -#define DSIM_AUTO_MODE (1 << 24) -#define DSIM_VIDEO_MODE (1 << 25) -#define DSIM_BURST_MODE (1 << 26) -#define DSIM_SYNC_INFORM (1 << 27) -#define DSIM_EOT_DISABLE (1 << 28) -#define DSIM_MFLUSH_VS (1 << 29) -/* This flag is valid only for exynos3250/3472/5260/5430 */ -#define DSIM_CLKLANE_STOP (1 << 30)
-/* DSIM_ESCMODE */ -#define DSIM_TX_TRIGGER_RST (1 << 4) -#define DSIM_TX_LPDT_LP (1 << 6) -#define DSIM_CMD_LPDT_LP (1 << 7) -#define DSIM_FORCE_BTA (1 << 16) -#define DSIM_FORCE_STOP_STATE (1 << 20) -#define DSIM_STOP_STATE_CNT(x) (((x) & 0x7ff) << 21) -#define DSIM_STOP_STATE_CNT_MASK (0x7ff << 21)
-/* DSIM_MDRESOL */ -#define DSIM_MAIN_STAND_BY (1 << 31) -#define DSIM_MAIN_VRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 16) -#define DSIM_MAIN_HRESOL(x, num_bits) (((x) & ((1 << (num_bits)) - 1)) << 0)
-/* DSIM_MVPORCH */ -#define DSIM_CMD_ALLOW(x) ((x) << 28) -#define DSIM_STABLE_VFP(x) ((x) << 16) -#define DSIM_MAIN_VBP(x) ((x) << 0) -#define DSIM_CMD_ALLOW_MASK (0xf << 28) -#define DSIM_STABLE_VFP_MASK (0x7ff << 16) -#define DSIM_MAIN_VBP_MASK (0x7ff << 0)
-/* DSIM_MHPORCH */ -#define DSIM_MAIN_HFP(x) ((x) << 16) -#define DSIM_MAIN_HBP(x) ((x) << 0) -#define DSIM_MAIN_HFP_MASK ((0xffff) << 16) -#define DSIM_MAIN_HBP_MASK ((0xffff) << 0)
-/* DSIM_MSYNC */ -#define DSIM_MAIN_VSA(x) ((x) << 22) -#define DSIM_MAIN_HSA(x) ((x) << 0) -#define DSIM_MAIN_VSA_MASK ((0x3ff) << 22) -#define DSIM_MAIN_HSA_MASK ((0xffff) << 0)
-/* DSIM_SDRESOL */ -#define DSIM_SUB_STANDY(x) ((x) << 31) -#define DSIM_SUB_VRESOL(x) ((x) << 16) -#define DSIM_SUB_HRESOL(x) ((x) << 0) -#define DSIM_SUB_STANDY_MASK ((0x1) << 31) -#define DSIM_SUB_VRESOL_MASK ((0x7ff) << 16) -#define DSIM_SUB_HRESOL_MASK ((0x7ff) << 0)
-/* DSIM_INTSRC */ -#define DSIM_INT_PLL_STABLE (1 << 31) -#define DSIM_INT_SW_RST_RELEASE (1 << 30) -#define DSIM_INT_SFR_FIFO_EMPTY (1 << 29) -#define DSIM_INT_SFR_HDR_FIFO_EMPTY (1 << 28) -#define DSIM_INT_BTA (1 << 25) -#define DSIM_INT_FRAME_DONE (1 << 24) -#define DSIM_INT_RX_TIMEOUT (1 << 21) -#define DSIM_INT_BTA_TIMEOUT (1 << 20) -#define DSIM_INT_RX_DONE (1 << 18) -#define DSIM_INT_RX_TE (1 << 17) -#define DSIM_INT_RX_ACK (1 << 16) -#define DSIM_INT_RX_ECC_ERR (1 << 15) -#define DSIM_INT_RX_CRC_ERR (1 << 14) +#include "exynos_drm_crtc.h" +#include "exynos_drm_drv.h"
-/* DSIM_FIFOCTRL */ -#define DSIM_RX_DATA_FULL (1 << 25) -#define DSIM_RX_DATA_EMPTY (1 << 24) -#define DSIM_SFR_HEADER_FULL (1 << 23) -#define DSIM_SFR_HEADER_EMPTY (1 << 22) -#define DSIM_SFR_PAYLOAD_FULL (1 << 21) -#define DSIM_SFR_PAYLOAD_EMPTY (1 << 20) -#define DSIM_I80_HEADER_FULL (1 << 19) -#define DSIM_I80_HEADER_EMPTY (1 << 18) -#define DSIM_I80_PAYLOAD_FULL (1 << 17) -#define DSIM_I80_PAYLOAD_EMPTY (1 << 16) -#define DSIM_SD_HEADER_FULL (1 << 15) -#define DSIM_SD_HEADER_EMPTY (1 << 14) -#define DSIM_SD_PAYLOAD_FULL (1 << 13) -#define DSIM_SD_PAYLOAD_EMPTY (1 << 12) -#define DSIM_MD_HEADER_FULL (1 << 11) -#define DSIM_MD_HEADER_EMPTY (1 << 10) -#define DSIM_MD_PAYLOAD_FULL (1 << 9) -#define DSIM_MD_PAYLOAD_EMPTY (1 << 8) -#define DSIM_RX_FIFO (1 << 4) -#define DSIM_SFR_FIFO (1 << 3) -#define DSIM_I80_FIFO (1 << 2) -#define DSIM_SD_FIFO (1 << 1) -#define DSIM_MD_FIFO (1 << 0)
-/* DSIM_PHYACCHR */ -#define DSIM_AFC_EN (1 << 14) -#define DSIM_AFC_CTL(x) (((x) & 0x7) << 5)
-/* DSIM_PLLCTRL */ -#define DSIM_FREQ_BAND(x) ((x) << 24) -#define DSIM_PLL_EN (1 << 23) -#define DSIM_PLL_P(x) ((x) << 13) -#define DSIM_PLL_M(x) ((x) << 4) -#define DSIM_PLL_S(x) ((x) << 1)
-/* DSIM_PHYCTRL */ -#define DSIM_PHYCTRL_ULPS_EXIT(x) (((x) & 0x1ff) << 0) -#define DSIM_PHYCTRL_B_DPHYCTL_VREG_LP (1 << 30) -#define DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP (1 << 14)
-/* DSIM_PHYTIMING */ -#define DSIM_PHYTIMING_LPX(x) ((x) << 8) -#define DSIM_PHYTIMING_HS_EXIT(x) ((x) << 0)
-/* DSIM_PHYTIMING1 */ -#define DSIM_PHYTIMING1_CLK_PREPARE(x) ((x) << 24) -#define DSIM_PHYTIMING1_CLK_ZERO(x) ((x) << 16) -#define DSIM_PHYTIMING1_CLK_POST(x) ((x) << 8) -#define DSIM_PHYTIMING1_CLK_TRAIL(x) ((x) << 0)
-/* DSIM_PHYTIMING2 */ -#define DSIM_PHYTIMING2_HS_PREPARE(x) ((x) << 16) -#define DSIM_PHYTIMING2_HS_ZERO(x) ((x) << 8) -#define DSIM_PHYTIMING2_HS_TRAIL(x) ((x) << 0)
-#define DSI_MAX_BUS_WIDTH 4 -#define DSI_NUM_VIRTUAL_CHANNELS 4 -#define DSI_TX_FIFO_SIZE 2048 -#define DSI_RX_FIFO_SIZE 256 -#define DSI_XFER_TIMEOUT_MS 100 -#define DSI_RX_FIFO_EMPTY 0x30800002
-#define OLD_SCLK_MIPI_CLK_NAME "pll_clk"
-static const char *const clk_names[5] = { "bus_clk", "sclk_mipi",
- "phyclk_mipidphy0_bitclkdiv8", "phyclk_mipidphy0_rxclkesc0",
- "sclk_rgb_vclk_to_dsim0" };
-enum exynos_dsi_transfer_type {
- EXYNOS_DSI_TX,
- EXYNOS_DSI_RX,
+enum {
- DSI_PORT_IN,
- DSI_PORT_OUT
};
-struct exynos_dsi_transfer {
- struct list_head list;
- struct completion completed;
- int result;
- struct mipi_dsi_packet packet;
- u16 flags;
- u16 tx_done;
- u8 *rx_payload;
- u16 rx_len;
- u16 rx_done;
-};
-#define DSIM_STATE_ENABLED BIT(0) -#define DSIM_STATE_INITIALIZED BIT(1) -#define DSIM_STATE_CMD_LPM BIT(2) -#define DSIM_STATE_VIDOUT_AVAILABLE BIT(3)
struct exynos_dsi {
- struct drm_bridge bridge;
- struct mipi_dsi_host dsi_host;
- struct drm_connector connector;
- struct drm_panel *panel;
- struct list_head bridge_chain;
- struct drm_bridge *out_bridge;
- struct device *dev;
- void __iomem *reg_base;
- struct phy *phy;
- struct clk **clks;
- struct regulator_bulk_data supplies[2];
- int irq;
- int te_gpio;
- u32 pll_clk_rate;
- u32 burst_clk_rate;
- u32 esc_clk_rate;
- u32 lanes;
- u32 mode_flags;
- u32 format;
- struct drm_display_mode mode;
- int state;
- struct drm_property *brightness;
- struct completion completed;
- spinlock_t transfer_lock; /* protects transfer_list */
- struct list_head transfer_list;
- const struct exynos_dsi_driver_data *driver_data;
- struct samsung_dsim *dsi;
- struct drm_encoder encoder;
};
-#define host_to_dsi(host) container_of(host, struct exynos_dsi, dsi_host) -#define connector_to_dsi(c) container_of(c, struct exynos_dsi, connector)
-enum reg_idx {
- DSIM_STATUS_REG, /* Status register */
- DSIM_SWRST_REG, /* Software reset register */
- DSIM_CLKCTRL_REG, /* Clock control register */
- DSIM_TIMEOUT_REG, /* Time out register */
- DSIM_CONFIG_REG, /* Configuration register */
- DSIM_ESCMODE_REG, /* Escape mode register */
- DSIM_MDRESOL_REG,
- DSIM_MVPORCH_REG, /* Main display Vporch register */
- DSIM_MHPORCH_REG, /* Main display Hporch register */
- DSIM_MSYNC_REG, /* Main display sync area register */
- DSIM_INTSRC_REG, /* Interrupt source register */
- DSIM_INTMSK_REG, /* Interrupt mask register */
- DSIM_PKTHDR_REG, /* Packet Header FIFO register */
- DSIM_PAYLOAD_REG, /* Payload FIFO register */
- DSIM_RXFIFO_REG, /* Read FIFO register */
- DSIM_FIFOCTRL_REG, /* FIFO status and control register */
- DSIM_PLLCTRL_REG, /* PLL control register */
- DSIM_PHYCTRL_REG,
- DSIM_PHYTIMING_REG,
- DSIM_PHYTIMING1_REG,
- DSIM_PHYTIMING2_REG,
- NUM_REGS
+static const unsigned int reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0x0af,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x06,
- [PHYTIMING_HS_EXIT] = 0x0b,
- [PHYTIMING_CLK_PREPARE] = 0x07,
- [PHYTIMING_CLK_ZERO] = 0x27,
- [PHYTIMING_CLK_POST] = 0x0d,
- [PHYTIMING_CLK_TRAIL] = 0x08,
- [PHYTIMING_HS_PREPARE] = 0x09,
- [PHYTIMING_HS_ZERO] = 0x0d,
- [PHYTIMING_HS_TRAIL] = 0x0b,
};
-static const unsigned int exynos_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x00,
- [DSIM_SWRST_REG] = 0x04,
- [DSIM_CLKCTRL_REG] = 0x08,
- [DSIM_TIMEOUT_REG] = 0x0c,
- [DSIM_CONFIG_REG] = 0x10,
- [DSIM_ESCMODE_REG] = 0x14,
- [DSIM_MDRESOL_REG] = 0x18,
- [DSIM_MVPORCH_REG] = 0x1c,
- [DSIM_MHPORCH_REG] = 0x20,
- [DSIM_MSYNC_REG] = 0x24,
- [DSIM_INTSRC_REG] = 0x2c,
- [DSIM_INTMSK_REG] = 0x30,
- [DSIM_PKTHDR_REG] = 0x34,
- [DSIM_PAYLOAD_REG] = 0x38,
- [DSIM_RXFIFO_REG] = 0x3c,
- [DSIM_FIFOCTRL_REG] = 0x44,
- [DSIM_PLLCTRL_REG] = 0x4c,
- [DSIM_PHYCTRL_REG] = 0x5c,
- [DSIM_PHYTIMING_REG] = 0x64,
- [DSIM_PHYTIMING1_REG] = 0x68,
- [DSIM_PHYTIMING2_REG] = 0x6c,
+static const unsigned int exynos5422_reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0xaf,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x08,
- [PHYTIMING_HS_EXIT] = 0x0d,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x30,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x0a,
- [PHYTIMING_HS_PREPARE] = 0x0c,
- [PHYTIMING_HS_ZERO] = 0x11,
- [PHYTIMING_HS_TRAIL] = 0x0d,
};
-static const unsigned int exynos5433_reg_ofs[] = {
- [DSIM_STATUS_REG] = 0x04,
- [DSIM_SWRST_REG] = 0x0C,
- [DSIM_CLKCTRL_REG] = 0x10,
- [DSIM_TIMEOUT_REG] = 0x14,
- [DSIM_CONFIG_REG] = 0x18,
- [DSIM_ESCMODE_REG] = 0x1C,
- [DSIM_MDRESOL_REG] = 0x20,
- [DSIM_MVPORCH_REG] = 0x24,
- [DSIM_MHPORCH_REG] = 0x28,
- [DSIM_MSYNC_REG] = 0x2C,
- [DSIM_INTSRC_REG] = 0x34,
- [DSIM_INTMSK_REG] = 0x38,
- [DSIM_PKTHDR_REG] = 0x3C,
- [DSIM_PAYLOAD_REG] = 0x40,
- [DSIM_RXFIFO_REG] = 0x44,
- [DSIM_FIFOCTRL_REG] = 0x4C,
- [DSIM_PLLCTRL_REG] = 0x94,
- [DSIM_PHYCTRL_REG] = 0xA4,
- [DSIM_PHYTIMING_REG] = 0xB4,
- [DSIM_PHYTIMING1_REG] = 0xB8,
- [DSIM_PHYTIMING2_REG] = 0xBC,
+static const unsigned int exynos5433_reg_values[] = {
- [RESET_TYPE] = DSIM_FUNCRST,
- [PLL_TIMER] = 22200,
- [STOP_STATE_CNT] = 0xa,
- [PHYCTRL_ULPS_EXIT] = 0x190,
- [PHYCTRL_VREG_LP] = 1,
- [PHYCTRL_SLEW_UP] = 1,
- [PHYTIMING_LPX] = 0x07,
- [PHYTIMING_HS_EXIT] = 0x0c,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x2d,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x09,
- [PHYTIMING_HS_PREPARE] = 0x0b,
- [PHYTIMING_HS_ZERO] = 0x10,
- [PHYTIMING_HS_TRAIL] = 0x0c,
};
-static inline void exynos_dsi_write(struct exynos_dsi *dsi, enum reg_idx idx,
u32 val)
-{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- writel(val, dsi->reg_base + reg_ofs[idx]);
-}
-static inline u32 exynos_dsi_read(struct exynos_dsi *dsi, enum reg_idx idx) -{
- const unsigned int *reg_ofs;
- if (dsi->driver_data->reg_ofs == EXYNOS5433_REG_OFS)
reg_ofs = exynos5433_reg_ofs;
- else
reg_ofs = exynos_reg_ofs;
- return readl(dsi->reg_base + reg_ofs[idx]);
-}
-static void exynos_dsi_wait_for_reset(struct exynos_dsi *dsi) -{
- if (wait_for_completion_timeout(&dsi->completed, msecs_to_jiffies(300)))
return;
- dev_err(dsi->dev, "timeout waiting for reset\n");
-}
-static void exynos_dsi_reset(struct exynos_dsi *dsi) -{
- u32 reset_val = dsi->driver_data->reg_values[RESET_TYPE];
- reinit_completion(&dsi->completed);
- exynos_dsi_write(dsi, DSIM_SWRST_REG, reset_val);
-}
-#ifndef MHZ -#define MHZ (1000*1000) -#endif
-static unsigned long exynos_dsi_pll_find_pms(struct exynos_dsi *dsi,
unsigned long fin, unsigned long fout, u8 *p, u16 *m, u8 *s)
-{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- unsigned long best_freq = 0;
- u32 min_delta = 0xffffffff;
- u8 p_min, p_max;
- u8 _p, best_p;
- u16 _m, best_m;
- u8 _s, best_s;
- p_min = DIV_ROUND_UP(fin, (12 * MHZ));
- p_max = fin / (6 * MHZ);
- for (_p = p_min; _p <= p_max; ++_p) {
for (_s = 0; _s <= 5; ++_s) {
u64 tmp;
u32 delta;
tmp = (u64)fout * (_p << _s);
do_div(tmp, fin);
_m = tmp;
if (_m < 41 || _m > 125)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p);
if (tmp < 500 * MHZ ||
tmp > driver_data->max_freq * MHZ)
continue;
tmp = (u64)_m * fin;
do_div(tmp, _p << _s);
delta = abs(fout - tmp);
if (delta < min_delta) {
best_p = _p;
best_m = _m;
best_s = _s;
min_delta = delta;
best_freq = tmp;
}
}
- }
- if (best_freq) {
*p = best_p;
*m = best_m;
*s = best_s;
- }
- return best_freq;
-}
-static unsigned long exynos_dsi_set_pll(struct exynos_dsi *dsi,
unsigned long freq)
-{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- unsigned long fin, fout;
- int timeout;
- u8 p, s;
- u16 m;
- u32 reg;
- fin = dsi->pll_clk_rate;
- fout = exynos_dsi_pll_find_pms(dsi, fin, freq, &p, &m, &s);
- if (!fout) {
dev_err(dsi->dev,
"failed to find PLL PMS for requested frequency\n");
return 0;
- }
- dev_dbg(dsi->dev, "PLL freq %lu, (p %d, m %d, s %d)\n", fout, p, m, s);
- writel(driver_data->reg_values[PLL_TIMER],
dsi->reg_base + driver_data->plltmr_reg);
- reg = DSIM_PLL_EN | DSIM_PLL_P(p) | DSIM_PLL_M(m) | DSIM_PLL_S(s);
- if (driver_data->has_freqband) {
static const unsigned long freq_bands[] = {
100 * MHZ, 120 * MHZ, 160 * MHZ, 200 * MHZ,
270 * MHZ, 320 * MHZ, 390 * MHZ, 450 * MHZ,
510 * MHZ, 560 * MHZ, 640 * MHZ, 690 * MHZ,
770 * MHZ, 870 * MHZ, 950 * MHZ,
};
int band;
for (band = 0; band < ARRAY_SIZE(freq_bands); ++band)
if (fout < freq_bands[band])
break;
dev_dbg(dsi->dev, "band %d\n", band);
reg |= DSIM_FREQ_BAND(band);
- }
- exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg);
- timeout = 1000;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "PLL failed to stabilize\n");
return 0;
}
reg = exynos_dsi_read(dsi, DSIM_STATUS_REG);
- } while ((reg & DSIM_PLL_STABLE) == 0);
- return fout;
-}
-static int exynos_dsi_enable_clock(struct exynos_dsi *dsi) -{
- unsigned long hs_clk, byte_clk, esc_clk;
- unsigned long esc_div;
- u32 reg;
- hs_clk = exynos_dsi_set_pll(dsi, dsi->burst_clk_rate);
- if (!hs_clk) {
dev_err(dsi->dev, "failed to configure DSI PLL\n");
return -EFAULT;
- }
- byte_clk = hs_clk / 8;
- esc_div = DIV_ROUND_UP(byte_clk, dsi->esc_clk_rate);
- esc_clk = byte_clk / esc_div;
- if (esc_clk > 20 * MHZ) {
++esc_div;
esc_clk = byte_clk / esc_div;
- }
- dev_dbg(dsi->dev, "hs_clk = %lu, byte_clk = %lu, esc_clk = %lu\n",
hs_clk, byte_clk, esc_clk);
- reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_ESC_PRESCALER_MASK | DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA_MASK | DSIM_PLL_BYPASS
| DSIM_BYTE_CLK_SRC_MASK);
- reg |= DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN
| DSIM_ESC_PRESCALER(esc_div)
| DSIM_LANE_ESC_CLK_EN_CLK
| DSIM_LANE_ESC_CLK_EN_DATA(BIT(dsi->lanes) - 1)
| DSIM_BYTE_CLK_SRC(0)
| DSIM_TX_REQUEST_HSCLK;
- exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg);
- return 0;
-}
-static void exynos_dsi_set_phy_ctrl(struct exynos_dsi *dsi) -{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- const unsigned int *reg_values = driver_data->reg_values;
- u32 reg;
- if (driver_data->has_freqband)
return;
- /* B D-PHY: D-PHY Master & Slave Analog Block control */
- reg = DSIM_PHYCTRL_ULPS_EXIT(reg_values[PHYCTRL_ULPS_EXIT]);
- if (reg_values[PHYCTRL_VREG_LP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_VREG_LP;
- if (reg_values[PHYCTRL_SLEW_UP])
reg |= DSIM_PHYCTRL_B_DPHYCTL_SLEW_UP;
- exynos_dsi_write(dsi, DSIM_PHYCTRL_REG, reg);
- /*
* T LPX: Transmitted length of any Low-Power state period
* T HS-EXIT: Time that the transmitter drives LP-11 following a HS
* burst
*/
- reg = DSIM_PHYTIMING_LPX(reg_values[PHYTIMING_LPX]) |
DSIM_PHYTIMING_HS_EXIT(reg_values[PHYTIMING_HS_EXIT]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING_REG, reg);
- /*
* T CLK-PREPARE: Time that the transmitter drives the Clock Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T CLK-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Clock.
* T CLK_POST: Time that the transmitter continues to send HS clock
* after the last associated Data Lane has transitioned to LP Mode
* Interval is defined as the period from the end of T HS-TRAIL to
* the beginning of T CLK-TRAIL
* T CLK-TRAIL: Time that the transmitter drives the HS-0 state after
* the last payload clock bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING1_CLK_PREPARE(reg_values[PHYTIMING_CLK_PREPARE]) |
DSIM_PHYTIMING1_CLK_ZERO(reg_values[PHYTIMING_CLK_ZERO]) |
DSIM_PHYTIMING1_CLK_POST(reg_values[PHYTIMING_CLK_POST]) |
DSIM_PHYTIMING1_CLK_TRAIL(reg_values[PHYTIMING_CLK_TRAIL]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING1_REG, reg);
- /*
* T HS-PREPARE: Time that the transmitter drives the Data Lane LP-00
* Line state immediately before the HS-0 Line state starting the
* HS transmission
* T HS-ZERO: Time that the transmitter drives the HS-0 state prior to
* transmitting the Sync sequence.
* T HS-TRAIL: Time that the transmitter drives the flipped differential
* state after last payload data bit of a HS transmission burst
*/
- reg = DSIM_PHYTIMING2_HS_PREPARE(reg_values[PHYTIMING_HS_PREPARE]) |
DSIM_PHYTIMING2_HS_ZERO(reg_values[PHYTIMING_HS_ZERO]) |
DSIM_PHYTIMING2_HS_TRAIL(reg_values[PHYTIMING_HS_TRAIL]);
- exynos_dsi_write(dsi, DSIM_PHYTIMING2_REG, reg);
-}
-static void exynos_dsi_disable_clock(struct exynos_dsi *dsi) -{
- u32 reg;
- reg = exynos_dsi_read(dsi, DSIM_CLKCTRL_REG);
- reg &= ~(DSIM_LANE_ESC_CLK_EN_CLK | DSIM_LANE_ESC_CLK_EN_DATA_MASK
| DSIM_ESC_CLKEN | DSIM_BYTE_CLKEN);
- exynos_dsi_write(dsi, DSIM_CLKCTRL_REG, reg);
- reg = exynos_dsi_read(dsi, DSIM_PLLCTRL_REG);
- reg &= ~DSIM_PLL_EN;
- exynos_dsi_write(dsi, DSIM_PLLCTRL_REG, reg);
-}
-static void exynos_dsi_enable_lane(struct exynos_dsi *dsi, u32 lane) -{
- u32 reg = exynos_dsi_read(dsi, DSIM_CONFIG_REG);
- reg |= (DSIM_NUM_OF_DATA_LANE(dsi->lanes - 1) | DSIM_LANE_EN_CLK |
DSIM_LANE_EN(lane));
- exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg);
-}
-static int exynos_dsi_init_link(struct exynos_dsi *dsi) +static int exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int timeout;
- u32 reg;
- u32 lanes_mask;
- /* Initialize FIFO pointers */
- reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG);
- reg &= ~0x1f;
- exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- reg |= 0x1f;
- exynos_dsi_write(dsi, DSIM_FIFOCTRL_REG, reg);
- usleep_range(9000, 11000);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- struct exynos_drm_crtc *crtc;
- /* DSI configuration */
- reg = 0;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- /*
* The first bit of mode_flags specifies display configuration.
* If this bit is set[= MIPI_DSI_MODE_VIDEO], dsi will support video
* mode, otherwise it will support command mode.
*/
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg |= DSIM_VIDEO_MODE;
/*
* The user manual describes that following bits are ignored in
* command mode.
*/
if (!(dsi->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH))
reg |= DSIM_MFLUSH_VS;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
reg |= DSIM_SYNC_INFORM;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
reg |= DSIM_BURST_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
reg |= DSIM_AUTO_MODE;
if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
reg |= DSIM_HSE_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HFP))
reg |= DSIM_HFP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HBP))
reg |= DSIM_HBP_MODE;
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_HSA))
reg |= DSIM_HSA_MODE;
- }
- if (!(dsi->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
reg |= DSIM_EOT_DISABLE;
- switch (dsi->format) {
- case MIPI_DSI_FMT_RGB888:
reg |= DSIM_MAIN_PIX_FORMAT_RGB888;
break;
- case MIPI_DSI_FMT_RGB666:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666;
break;
- case MIPI_DSI_FMT_RGB666_PACKED:
reg |= DSIM_MAIN_PIX_FORMAT_RGB666_P;
break;
- case MIPI_DSI_FMT_RGB565:
reg |= DSIM_MAIN_PIX_FORMAT_RGB565;
break;
- default:
dev_err(dsi->dev, "invalid pixel format\n");
return -EINVAL;
- }
- /*
* Use non-continuous clock mode if the periparal wants and
* host controller supports
*
* In non-continous clock mode, host controller will turn off
* the HS clock between high-speed transmissions to reduce
* power consumption.
*/
- if (driver_data->has_clklane_stop &&
dsi->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
reg |= DSIM_CLKLANE_STOP;
- }
- exynos_dsi_write(dsi, DSIM_CONFIG_REG, reg);
- lanes_mask = BIT(dsi->lanes) - 1;
- exynos_dsi_enable_lane(dsi, lanes_mask);
- /* Check clock and data lane state are stop state */
- timeout = 100;
- do {
if (timeout-- == 0) {
dev_err(dsi->dev, "waiting for bus lanes timed out\n");
return -EFAULT;
}
reg = exynos_dsi_read(dsi, DSIM_STATUS_REG);
if ((reg & DSIM_STOP_STATE_DAT(lanes_mask))
!= DSIM_STOP_STATE_DAT(lanes_mask))
continue;
- } while (!(reg & (DSIM_STOP_STATE_CLK | DSIM_TX_READY_HS_CLK)));
- reg = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- reg &= ~DSIM_STOP_STATE_CNT_MASK;
- reg |= DSIM_STOP_STATE_CNT(driver_data->reg_values[STOP_STATE_CNT]);
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, reg);
- reg = DSIM_BTA_TIMEOUT(0xff) | DSIM_LPDR_TIMEOUT(0xffff);
- exynos_dsi_write(dsi, DSIM_TIMEOUT_REG, reg);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
return 0;
}
-static void exynos_dsi_set_display_mode(struct exynos_dsi *dsi) -{
- struct drm_display_mode *m = &dsi->mode;
- unsigned int num_bits_resol = dsi->driver_data->num_bits_resol;
- u32 reg;
- if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
reg = DSIM_CMD_ALLOW(0xf)
| DSIM_STABLE_VFP(m->vsync_start - m->vdisplay)
| DSIM_MAIN_VBP(m->vtotal - m->vsync_end);
exynos_dsi_write(dsi, DSIM_MVPORCH_REG, reg);
reg = DSIM_MAIN_HFP(m->hsync_start - m->hdisplay)
| DSIM_MAIN_HBP(m->htotal - m->hsync_end);
exynos_dsi_write(dsi, DSIM_MHPORCH_REG, reg);
reg = DSIM_MAIN_VSA(m->vsync_end - m->vsync_start)
| DSIM_MAIN_HSA(m->hsync_end - m->hsync_start);
exynos_dsi_write(dsi, DSIM_MSYNC_REG, reg);
- }
- reg = DSIM_MAIN_HRESOL(m->hdisplay, num_bits_resol) |
DSIM_MAIN_VRESOL(m->vdisplay, num_bits_resol);
- exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg);
- dev_dbg(dsi->dev, "LCD size = %dx%d\n", m->hdisplay, m->vdisplay);
-}
-static void exynos_dsi_set_display_enable(struct exynos_dsi *dsi, bool enable) -{
- u32 reg;
- reg = exynos_dsi_read(dsi, DSIM_MDRESOL_REG);
- if (enable)
reg |= DSIM_MAIN_STAND_BY;
- else
reg &= ~DSIM_MAIN_STAND_BY;
- exynos_dsi_write(dsi, DSIM_MDRESOL_REG, reg);
-}
-static int exynos_dsi_wait_for_hdr_fifo(struct exynos_dsi *dsi) -{
- int timeout = 2000;
- do {
u32 reg = exynos_dsi_read(dsi, DSIM_FIFOCTRL_REG);
if (!(reg & DSIM_SFR_HEADER_FULL))
return 0;
if (!cond_resched())
usleep_range(950, 1050);
- } while (--timeout);
- return -ETIMEDOUT;
-}
-static void exynos_dsi_set_cmd_lpm(struct exynos_dsi *dsi, bool lpm) -{
- u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- if (lpm)
v |= DSIM_CMD_LPDT_LP;
- else
v &= ~DSIM_CMD_LPDT_LP;
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v);
-}
-static void exynos_dsi_force_bta(struct exynos_dsi *dsi) -{
- u32 v = exynos_dsi_read(dsi, DSIM_ESCMODE_REG);
- v |= DSIM_FORCE_BTA;
- exynos_dsi_write(dsi, DSIM_ESCMODE_REG, v);
-}
-static void exynos_dsi_send_to_fifo(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- struct device *dev = dsi->dev;
- struct mipi_dsi_packet *pkt = &xfer->packet;
- const u8 *payload = pkt->payload + xfer->tx_done;
- u16 length = pkt->payload_length - xfer->tx_done;
- bool first = !xfer->tx_done;
- u32 reg;
- dev_dbg(dev, "< xfer %pK: tx len %u, done %u, rx len %u, done %u\n",
xfer, length, xfer->tx_done, xfer->rx_len, xfer->rx_done);
- if (length > DSI_TX_FIFO_SIZE)
length = DSI_TX_FIFO_SIZE;
- xfer->tx_done += length;
- /* Send payload */
- while (length >= 4) {
reg = get_unaligned_le32(payload);
exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg);
payload += 4;
length -= 4;
- }
- reg = 0;
- switch (length) {
- case 3:
reg |= payload[2] << 16;
fallthrough;
- case 2:
reg |= payload[1] << 8;
fallthrough;
- case 1:
reg |= payload[0];
exynos_dsi_write(dsi, DSIM_PAYLOAD_REG, reg);
break;
- }
- /* Send packet header */
- if (!first)
return;
- reg = get_unaligned_le32(pkt->header);
- if (exynos_dsi_wait_for_hdr_fifo(dsi)) {
dev_err(dev, "waiting for header FIFO timed out\n");
return;
- }
- if (NEQV(xfer->flags & MIPI_DSI_MSG_USE_LPM,
dsi->state & DSIM_STATE_CMD_LPM)) {
exynos_dsi_set_cmd_lpm(dsi, xfer->flags & MIPI_DSI_MSG_USE_LPM);
dsi->state ^= DSIM_STATE_CMD_LPM;
- }
- exynos_dsi_write(dsi, DSIM_PKTHDR_REG, reg);
- if (xfer->flags & MIPI_DSI_MSG_REQ_ACK)
exynos_dsi_force_bta(dsi);
-}
-static void exynos_dsi_read_from_fifo(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- u8 *payload = xfer->rx_payload + xfer->rx_done;
- bool first = !xfer->rx_done;
- struct device *dev = dsi->dev;
- u16 length;
- u32 reg;
- if (first) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
switch (reg & 0x3f) {
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
if (xfer->rx_len >= 2) {
payload[1] = reg >> 16;
++xfer->rx_done;
}
fallthrough;
case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
payload[0] = reg >> 8;
++xfer->rx_done;
xfer->rx_len = xfer->rx_done;
xfer->result = 0;
goto clear_fifo;
case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
dev_err(dev, "DSI Error Report: 0x%04x\n",
(reg >> 8) & 0xffff);
xfer->result = 0;
goto clear_fifo;
}
length = (reg >> 8) & 0xffff;
if (length > xfer->rx_len) {
dev_err(dev,
"response too long (%u > %u bytes), stripping\n",
xfer->rx_len, length);
length = xfer->rx_len;
} else if (length < xfer->rx_len)
xfer->rx_len = length;
- }
- length = xfer->rx_len - xfer->rx_done;
- xfer->rx_done += length;
- /* Receive payload */
- while (length >= 4) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
payload[0] = (reg >> 0) & 0xff;
payload[1] = (reg >> 8) & 0xff;
payload[2] = (reg >> 16) & 0xff;
payload[3] = (reg >> 24) & 0xff;
payload += 4;
length -= 4;
- }
- if (length) {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
switch (length) {
case 3:
payload[2] = (reg >> 16) & 0xff;
fallthrough;
case 2:
payload[1] = (reg >> 8) & 0xff;
fallthrough;
case 1:
payload[0] = reg & 0xff;
}
- }
- if (xfer->rx_done == xfer->rx_len)
xfer->result = 0;
-clear_fifo:
- length = DSI_RX_FIFO_SIZE / 4;
- do {
reg = exynos_dsi_read(dsi, DSIM_RXFIFO_REG);
if (reg == DSI_RX_FIFO_EMPTY)
break;
- } while (--length);
-}
-static void exynos_dsi_transfer_start(struct exynos_dsi *dsi) -{
- unsigned long flags;
- struct exynos_dsi_transfer *xfer;
- bool start = false;
-again:
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (xfer->packet.payload_length &&
xfer->tx_done == xfer->packet.payload_length)
/* waiting for RX */
return;
- exynos_dsi_send_to_fifo(dsi, xfer);
- if (xfer->packet.payload_length || xfer->rx_len)
return;
- xfer->result = 0;
- complete(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (start)
goto again;
-}
-static bool exynos_dsi_transfer_finish(struct exynos_dsi *dsi) -{
- struct exynos_dsi_transfer *xfer;
- unsigned long flags;
- bool start = true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (list_empty(&dsi->transfer_list)) {
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
return false;
- }
- xfer = list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- dev_dbg(dsi->dev,
"> xfer %pK, tx_len %zu, tx_done %u, rx_len %u, rx_done %u\n",
xfer, xfer->packet.payload_length, xfer->tx_done, xfer->rx_len,
xfer->rx_done);
- if (xfer->tx_done != xfer->packet.payload_length)
return true;
- if (xfer->rx_done != xfer->rx_len)
exynos_dsi_read_from_fifo(dsi, xfer);
- if (xfer->rx_done != xfer->rx_len)
return true;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- list_del_init(&xfer->list);
- start = !list_empty(&dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (!xfer->rx_len)
xfer->result = 0;
- complete(&xfer->completed);
- return start;
-}
-static void exynos_dsi_remove_transfer(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- unsigned long flags;
- bool start;
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- if (!list_empty(&dsi->transfer_list) &&
xfer == list_first_entry(&dsi->transfer_list,
struct exynos_dsi_transfer, list)) {
list_del_init(&xfer->list);
start = !list_empty(&dsi->transfer_list);
spin_unlock_irqrestore(&dsi->transfer_lock, flags);
if (start)
exynos_dsi_transfer_start(dsi);
return;
- }
- list_del_init(&xfer->list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
-}
-static int exynos_dsi_transfer(struct exynos_dsi *dsi,
struct exynos_dsi_transfer *xfer)
-{
- unsigned long flags;
- bool stopped;
- xfer->tx_done = 0;
- xfer->rx_done = 0;
- xfer->result = -ETIMEDOUT;
- init_completion(&xfer->completed);
- spin_lock_irqsave(&dsi->transfer_lock, flags);
- stopped = list_empty(&dsi->transfer_list);
- list_add_tail(&xfer->list, &dsi->transfer_list);
- spin_unlock_irqrestore(&dsi->transfer_lock, flags);
- if (stopped)
exynos_dsi_transfer_start(dsi);
- wait_for_completion_timeout(&xfer->completed,
msecs_to_jiffies(DSI_XFER_TIMEOUT_MS));
- if (xfer->result == -ETIMEDOUT) {
struct mipi_dsi_packet *pkt = &xfer->packet;
exynos_dsi_remove_transfer(dsi, xfer);
dev_err(dsi->dev, "xfer timed out: %*ph %*ph\n", 4, pkt->header,
(int)pkt->payload_length, pkt->payload);
return -ETIMEDOUT;
- }
- /* Also covers hardware timeout condition */
- return xfer->result;
-}
-static irqreturn_t exynos_dsi_irq(int irq, void *dev_id) -{
- struct exynos_dsi *dsi = dev_id;
- u32 status;
- status = exynos_dsi_read(dsi, DSIM_INTSRC_REG);
- if (!status) {
static unsigned long int j;
if (printk_timed_ratelimit(&j, 500))
dev_warn(dsi->dev, "spurious interrupt\n");
return IRQ_HANDLED;
- }
- exynos_dsi_write(dsi, DSIM_INTSRC_REG, status);
- if (status & DSIM_INT_SW_RST_RELEASE) {
u32 mask = ~(DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_SFR_HDR_FIFO_EMPTY | DSIM_INT_RX_ECC_ERR |
DSIM_INT_SW_RST_RELEASE);
exynos_dsi_write(dsi, DSIM_INTMSK_REG, mask);
complete(&dsi->completed);
return IRQ_HANDLED;
- }
- if (!(status & (DSIM_INT_RX_DONE | DSIM_INT_SFR_FIFO_EMPTY |
DSIM_INT_PLL_STABLE)))
return IRQ_HANDLED;
- if (exynos_dsi_transfer_finish(dsi))
exynos_dsi_transfer_start(dsi);
- return IRQ_HANDLED;
-}
-static irqreturn_t exynos_dsi_te_irq_handler(int irq, void *dev_id) -{
- struct exynos_dsi *dsi = dev_id;
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- if (ops && ops->te_handler &&
(dsi->state & DSIM_STATE_VIDOUT_AVAILABLE))
ops->te_handler(dsi->dsi_host.dev);
- return IRQ_HANDLED;
-}
-static void exynos_dsi_enable_irq(struct exynos_dsi *dsi) -{
- enable_irq(dsi->irq);
- if (gpio_is_valid(dsi->te_gpio))
enable_irq(gpio_to_irq(dsi->te_gpio));
-}
-static void exynos_dsi_disable_irq(struct exynos_dsi *dsi) -{
- if (gpio_is_valid(dsi->te_gpio))
disable_irq(gpio_to_irq(dsi->te_gpio));
- disable_irq(dsi->irq);
-}
-static int exynos_dsi_init(struct exynos_dsi *dsi) +static int exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
{
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- exynos_dsi_reset(dsi);
- exynos_dsi_enable_irq(dsi);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (driver_data->reg_values[RESET_TYPE] == DSIM_FUNCRST)
exynos_dsi_enable_lane(dsi, BIT(dsi->lanes) - 1);
- exynos_dsi_enable_clock(dsi);
- if (driver_data->wait_for_reset)
exynos_dsi_wait_for_reset(dsi);
- exynos_dsi_set_phy_ctrl(dsi);
- exynos_dsi_init_link(dsi);
if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
return 0;
}
-static int exynos_dsi_register_te_irq(struct exynos_dsi *dsi,
struct device *panel)
+static void exynos_dsi_te_handler(struct device *dev) {
- int ret;
- int te_gpio_irq;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- dsi->te_gpio = of_get_named_gpio(panel->of_node, "te-gpios", 0);
- if (dsi->te_gpio == -ENOENT)
return 0;
- if (!gpio_is_valid(dsi->te_gpio)) {
ret = dsi->te_gpio;
dev_err(dsi->dev, "cannot get te-gpios, %d\n", ret);
goto out;
- }
- ret = gpio_request(dsi->te_gpio, "te_gpio");
- if (ret) {
dev_err(dsi->dev, "gpio request failed with %d\n", ret);
goto out;
- }
- te_gpio_irq = gpio_to_irq(dsi->te_gpio);
- irq_set_status_flags(te_gpio_irq, IRQ_NOAUTOEN);
- ret = request_threaded_irq(te_gpio_irq, exynos_dsi_te_irq_handler, NULL,
IRQF_TRIGGER_RISING, "TE", dsi);
- if (ret) {
dev_err(dsi->dev, "request interrupt failed with %d\n", ret);
gpio_free(dsi->te_gpio);
goto out;
- }
-out:
- return ret;
- exynos_drm_crtc_te_handler(dsi->encoder.crtc);
}
-static void exynos_dsi_unregister_te_irq(struct exynos_dsi *dsi) -{
- if (gpio_is_valid(dsi->te_gpio)) {
free_irq(gpio_to_irq(dsi->te_gpio), dsi);
gpio_free(dsi->te_gpio);
dsi->te_gpio = -ENOENT;
- }
-}
-static void exynos_dsi_enable(struct exynos_dsi *dsi) -{
- struct drm_bridge *iter;
- int ret;
- if (dsi->state & DSIM_STATE_ENABLED)
return;
- pm_runtime_get_sync(dsi->dev);
- dsi->state |= DSIM_STATE_ENABLED;
- if (dsi->panel) {
ret = drm_panel_prepare(dsi->panel);
if (ret < 0)
goto err_put_sync;
- } else {
list_for_each_entry_reverse(iter, &dsi->bridge_chain,
chain_node) {
if (iter->funcs->pre_enable)
iter->funcs->pre_enable(iter);
}
- }
- exynos_dsi_set_display_mode(dsi);
- exynos_dsi_set_display_enable(dsi, true);
- if (dsi->panel) {
ret = drm_panel_enable(dsi->panel);
if (ret < 0)
goto err_display_disable;
- } else {
list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->enable)
iter->funcs->enable(iter);
}
- }
- dsi->state |= DSIM_STATE_VIDOUT_AVAILABLE;
- return;
-err_display_disable:
- exynos_dsi_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
-err_put_sync:
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put(dsi->dev);
-}
-static void exynos_dsi_disable(struct exynos_dsi *dsi) -{
- struct drm_bridge *iter;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return;
- dsi->state &= ~DSIM_STATE_VIDOUT_AVAILABLE;
- drm_panel_disable(dsi->panel);
- list_for_each_entry_reverse(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->disable)
iter->funcs->disable(iter);
- }
- exynos_dsi_set_display_enable(dsi, false);
- drm_panel_unprepare(dsi->panel);
- list_for_each_entry(iter, &dsi->bridge_chain, chain_node) {
if (iter->funcs->post_disable)
iter->funcs->post_disable(iter);
- }
- dsi->state &= ~DSIM_STATE_ENABLED;
- pm_runtime_put_sync(dsi->dev);
-}
-static enum drm_connector_status -exynos_dsi_detect(struct drm_connector *connector, bool force) -{
- return connector->status;
-} +static const struct samsung_dsim_host_ops exynos_dsi_host_ops = {
- .attach = exynos_dsi_host_attach,
- .detach = exynos_dsi_host_detach,
- .te_handler = exynos_dsi_te_handler,
+};
-static void exynos_dsi_connector_destroy(struct drm_connector *connector) -{
- drm_connector_unregister(connector);
- drm_connector_cleanup(connector);
- connector->dev = NULL;
-} +static const struct samsung_dsim_driver_data exynos3_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
-static const struct drm_connector_funcs exynos_dsi_connector_funcs = {
- .detect = exynos_dsi_detect,
- .fill_modes = drm_helper_probe_single_connector_modes,
- .destroy = exynos_dsi_connector_destroy,
- .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 samsung_dsim_driver_data exynos4_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
};
-static int exynos_dsi_get_modes(struct drm_connector *connector) -{
- struct exynos_dsi *dsi = connector_to_dsi(connector);
+static const struct samsung_dsim_driver_data exynos5_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x58,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
- if (dsi->panel)
return drm_panel_get_modes(dsi->panel, connector);
+static const struct samsung_dsim_driver_data exynos5433_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 5,
- .max_freq = 1500,
- .wait_for_reset = 0,
- .num_bits_resol = 12,
- .reg_values = exynos5433_reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
- return 0;
-} +static const struct samsung_dsim_driver_data exynos5422_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1500,
- .wait_for_reset = 1,
- .num_bits_resol = 12,
- .reg_values = exynos5422_reg_values,
- .host_ops = &exynos_dsi_host_ops,
+};
-static const struct drm_connector_helper_funcs exynos_dsi_connector_helper_funcs = {
- .get_modes = exynos_dsi_get_modes,
+static const struct of_device_id exynos_dsi_of_match[] = {
- { .compatible = "samsung,exynos3250-mipi-dsi",
.data = &exynos3_dsi_driver_data },
- { .compatible = "samsung,exynos4210-mipi-dsi",
.data = &exynos4_dsi_driver_data },
- { .compatible = "samsung,exynos5410-mipi-dsi",
.data = &exynos5_dsi_driver_data },
- { .compatible = "samsung,exynos5422-mipi-dsi",
.data = &exynos5422_dsi_driver_data },
- { .compatible = "samsung,exynos5433-mipi-dsi",
.data = &exynos5433_dsi_driver_data },
- { }
};
-static int exynos_dsi_create_connector(struct exynos_dsi *dsi) +static int exynos_dsi_bind(struct device *dev,
struct device *master, void *data)
{
- struct drm_connector *connector = &dsi->connector;
- struct drm_device *drm = dsi->bridge.dev;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_device *drm_dev = data;
- struct device_node *in_bridge_node;
- struct drm_bridge *in_bridge; int ret;
- connector->polled = DRM_CONNECTOR_POLL_HPD;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = drm_connector_init(drm, connector, &exynos_dsi_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
- if (ret) {
DRM_DEV_ERROR(dsi->dev,
"Failed to initialize connector with drm\n");
- ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
- if (ret < 0) return ret;
- }
- connector->status = connector_status_disconnected;
- drm_connector_helper_add(connector, &exynos_dsi_connector_helper_funcs);
- drm_connector_attach_encoder(connector, dsi->bridge.encoder);
- if (!drm->registered)
return 0;
- connector->funcs->reset(connector);
- drm_connector_register(connector);
- return 0;
-}
-static int exynos_dsi_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
-{
struct exynos_dsi *dsi = bridge->driver_private;
struct drm_encoder *encoder = bridge->encoder;
int ret;
if (!dsi->out_bridge && !dsi->panel)
return -EPROBE_DEFER;
if (dsi->out_bridge) {
ret = drm_bridge_attach(encoder, dsi->out_bridge,
bridge, flags);
if (ret)
return ret;
list_splice_init(&encoder->bridge_chain, &dsi->bridge_chain);
} else {
ret = exynos_dsi_create_connector(dsi);
if (ret)
return ret;
if (dsi->panel) {
dsi->connector.status = connector_status_connected;
}
}
return 0;
-}
-static void exynos_dsi_bridge_detach(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- struct drm_encoder *encoder = bridge->encoder;
- struct drm_device *drm = encoder->dev;
- if (dsi->panel) {
mutex_lock(&drm->mode_config.mutex);
exynos_dsi_disable(dsi);
dsi->panel = NULL;
dsi->connector.status = connector_status_disconnected;
mutex_unlock(&drm->mode_config.mutex);
- } else {
if (dsi->out_bridge->funcs->detach)
dsi->out_bridge->funcs->detach(dsi->out_bridge);
dsi->out_bridge = NULL;
INIT_LIST_HEAD(&dsi->bridge_chain);
- in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0);
- if (in_bridge_node) {
in_bridge = of_drm_find_bridge(in_bridge_node);
if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL, 0);
}of_node_put(in_bridge_node);
-}
-static void exynos_dsi_bridge_enable(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- exynos_dsi_enable(dsi);
-}
-static void exynos_dsi_bridge_disable(struct drm_bridge *bridge) -{
- struct exynos_dsi *dsi = bridge->driver_private;
- exynos_dsi_disable(dsi);
-}
-static void exynos_dsi_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
-{
- struct exynos_dsi *dsi = bridge->driver_private;
- /* The mode is set when actually enabling the device. */
- drm_mode_copy(&dsi->mode, adjusted_mode);
-}
-static const struct drm_bridge_funcs exynos_dsi_bridge_funcs = {
- .attach = exynos_dsi_bridge_attach,
- .detach = exynos_dsi_bridge_detach,
- .enable = exynos_dsi_bridge_enable,
- .disable = exynos_dsi_bridge_disable,
- .mode_set = exynos_dsi_bridge_mode_set,
-};
-static int exynos_dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi *dsi = host_to_dsi(host);
- const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
- struct drm_bridge *out_bridge;
- out_bridge = of_drm_find_bridge(device->dev.of_node);
- if (out_bridge) {
dsi->out_bridge = out_bridge;
- } else {
dsi->panel = of_drm_find_panel(device->dev.of_node);
if (IS_ERR(dsi->panel))
dsi->panel = NULL;
else
dsi->connector.status = connector_status_connected;
- }
- /*
* This is a temporary solution and should be made by more generic way.
*
* If attached panel device is for command mode one, dsi should register
* TE interrupt handler.
*/
- if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO)) {
int ret = exynos_dsi_register_te_irq(dsi, &device->dev);
if (ret)
return ret;
- }
- dsi->lanes = device->lanes;
- dsi->format = device->format;
- dsi->mode_flags = device->mode_flags;
- if (ops && ops->attach)
ops->attach(dsi->dsi_host.dev, device);
ret = samsung_dsim_bind(dsi->dsi, encoder);
if (ret)
goto err;
return 0;
-}
-static int exynos_dsi_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *device)
-{
struct exynos_dsi *dsi = host_to_dsi(host);
const struct exynos_dsi_host_ops *ops = dsi->driver_data->host_ops;
if (ops && ops->detach)
ops->detach(dsi->dsi_host.dev, device);
exynos_dsi_unregister_te_irq(dsi);
return 0;
+err:
- drm_encoder_cleanup(encoder);
- return ret;
}
-static ssize_t exynos_dsi_host_transfer(struct mipi_dsi_host *host,
const struct mipi_dsi_msg *msg)
+static void exynos_dsi_unbind(struct device *dev,
struct device *master, void *data)
{
- struct exynos_dsi *dsi = host_to_dsi(host);
- struct exynos_dsi_transfer xfer;
- int ret;
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- if (!(dsi->state & DSIM_STATE_ENABLED))
return -EINVAL;
- if (!(dsi->state & DSIM_STATE_INITIALIZED)) {
ret = exynos_dsi_init(dsi);
if (ret)
return ret;
dsi->state |= DSIM_STATE_INITIALIZED;
- }
- ret = mipi_dsi_create_packet(&xfer.packet, msg);
- if (ret < 0)
return ret;
- samsung_dsim_unbind(dsi->dsi);
- xfer.rx_len = msg->rx_len;
- xfer.rx_payload = msg->rx_buf;
- xfer.flags = msg->flags;
- ret = exynos_dsi_transfer(dsi, &xfer);
- return (ret < 0) ? ret : xfer.rx_done;
- drm_encoder_cleanup(encoder);
}
-static const struct mipi_dsi_host_ops exynos_dsi_ops = {
- .attach = exynos_dsi_host_attach,
- .detach = exynos_dsi_host_detach,
- .transfer = exynos_dsi_host_transfer,
+static const struct component_ops exynos_dsi_component_ops = {
- .bind = exynos_dsi_bind,
- .unbind = exynos_dsi_unbind,
};
-static int exynos_dsi_of_read_u32(const struct device_node *np,
const char *propname, u32 *out_value)
-{
- int ret = of_property_read_u32(np, propname, out_value);
- if (ret < 0)
pr_err("%pOF: failed to get '%s' property\n", np, propname);
- return ret;
-}
-static int exynos_dsi_parse_dt(struct exynos_dsi *dsi) -{
- struct device *dev = dsi->dev;
- struct device_node *node = dev->of_node;
- int ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,pll-clock-frequency",
&dsi->pll_clk_rate);
- if (ret < 0)
return ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,burst-clock-frequency",
&dsi->burst_clk_rate);
- if (ret < 0)
return ret;
- ret = exynos_dsi_of_read_u32(node, "samsung,esc-clock-frequency",
&dsi->esc_clk_rate);
- if (ret < 0)
return ret;
- return 0;
-}
-static struct exynos_dsi *__exynos_dsi_probe(struct platform_device *pdev) +static int exynos_dsi_probe(struct platform_device *pdev) {
- struct device *dev = &pdev->dev;
- struct drm_bridge *bridge;
- struct resource *res; struct exynos_dsi *dsi;
- int ret, i;
struct device *dev = &pdev->dev;
int ret;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL); if (!dsi)
return ERR_PTR(-ENOMEM);
return -ENOMEM;
- platform_set_drvdata(pdev, dsi);
- /* To be checked as invalid one */
- dsi->te_gpio = -ENOENT;
- dsi->dsi = samsung_dsim_probe(pdev);
- if (IS_ERR(dsi->dsi))
return PTR_ERR(dsi->dsi);
- init_completion(&dsi->completed);
- spin_lock_init(&dsi->transfer_lock);
- INIT_LIST_HEAD(&dsi->transfer_list);
- INIT_LIST_HEAD(&dsi->bridge_chain);
- pm_runtime_enable(dev);
- dsi->dsi_host.ops = &exynos_dsi_ops;
- dsi->dsi_host.dev = dev;
- dsi->dev = dev;
- dsi->driver_data = of_device_get_match_data(dev);
- dsi->supplies[0].supply = "vddcore";
- dsi->supplies[1].supply = "vddio";
- ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(dsi->supplies),
dsi->supplies);
- if (ret) {
if (ret != -EPROBE_DEFER)
dev_info(dev, "failed to get regulators: %d\n", ret);
return ERR_PTR(ret);
- }
- dsi->clks = devm_kcalloc(dev,
dsi->driver_data->num_clks, sizeof(*dsi->clks),
GFP_KERNEL);
- if (!dsi->clks)
return ERR_PTR(-ENOMEM);
- for (i = 0; i < dsi->driver_data->num_clks; i++) {
dsi->clks[i] = devm_clk_get(dev, clk_names[i]);
if (IS_ERR(dsi->clks[i])) {
if (strcmp(clk_names[i], "sclk_mipi") == 0) {
dsi->clks[i] = devm_clk_get(dev,
OLD_SCLK_MIPI_CLK_NAME);
if (!IS_ERR(dsi->clks[i]))
continue;
}
dev_info(dev, "failed to get the clock: %s\n",
clk_names[i]);
return ERR_PTR(PTR_ERR(dsi->clks[i]));
}
- }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- dsi->reg_base = devm_ioremap_resource(dev, res);
- if (IS_ERR(dsi->reg_base)) {
dev_err(dev, "failed to remap io region\n");
return dsi->reg_base;
- }
- dsi->phy = devm_phy_get(dev, "dsim");
- if (IS_ERR(dsi->phy)) {
dev_info(dev, "failed to get dsim phy\n");
return ERR_PTR(PTR_ERR(dsi->phy));
- }
- dsi->irq = platform_get_irq(pdev, 0);
- if (dsi->irq < 0)
return ERR_PTR(dsi->irq);
- irq_set_status_flags(dsi->irq, IRQ_NOAUTOEN);
- ret = devm_request_threaded_irq(dev, dsi->irq, NULL,
exynos_dsi_irq, IRQF_ONESHOT,
dev_name(dev), dsi);
- if (ret) {
dev_err(dev, "failed to request dsi irq\n");
return ERR_PTR(ret);
- }
- ret = exynos_dsi_parse_dt(dsi);
- ret = component_add(dev, &exynos_dsi_component_ops); if (ret)
return ERR_PTR(ret);
goto err_disable_runtime;
- ret = mipi_dsi_host_register(&dsi->dsi_host);
- if (ret)
return ERR_PTR(ret);
- return 0;
- bridge = &dsi->bridge;
- bridge->driver_private = dsi;
- bridge->funcs = &exynos_dsi_bridge_funcs;
- bridge->of_node = dev->of_node;
- drm_bridge_add(bridge);
+err_disable_runtime:
- pm_runtime_disable(dev);
- return dsi;
- return ret;
}
-static void __exynos_dsi_remove(struct exynos_dsi *dsi) +static int exynos_dsi_remove(struct platform_device *pdev) {
- drm_bridge_remove(&dsi->bridge);
- struct exynos_dsi *dsi = platform_get_drvdata(pdev);
- mipi_dsi_host_unregister(&dsi->dsi_host);
-}
-/*
- Probe/remove API, used from platforms based on the DRM bridge API.
- */
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev) -{
- return __exynos_dsi_probe(pdev);
-}
- pm_runtime_disable(&pdev->dev);
-void exynos_dsi_remove(struct exynos_dsi *dsi) -{
- return __exynos_dsi_remove(dsi);
-}
- samsung_dsim_remove(dsi->dsi);
-/*
- Bind/unbind API, used from platforms based on the component framework.
- */
-int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder) -{
- struct drm_bridge *previous = drm_bridge_chain_get_first_bridge(encoder);
- component_del(&pdev->dev, &exynos_dsi_component_ops);
- return drm_bridge_attach(encoder, &dsi->bridge, previous, 0);
-}
-void exynos_dsi_unbind(struct exynos_dsi *dsi) -{
- exynos_dsi_disable(dsi);
- return 0;
}
-int exynos_dsi_suspend(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_suspend(struct device *dev) {
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- usleep_range(10000, 20000);
- if (dsi->state & DSIM_STATE_INITIALIZED) {
dsi->state &= ~DSIM_STATE_INITIALIZED;
exynos_dsi_disable_clock(dsi);
exynos_dsi_disable_irq(dsi);
- }
- dsi->state &= ~DSIM_STATE_CMD_LPM;
- phy_power_off(dsi->phy);
- for (i = driver_data->num_clks - 1; i > -1; i--)
clk_disable_unprepare(dsi->clks[i]);
- ret = regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0)
dev_err(dsi->dev, "cannot disable regulators %d\n", ret);
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0;
- return samsung_dsim_suspend(dsi->dsi);
}
-int exynos_dsi_resume(struct exynos_dsi *dsi) +static int __maybe_unused exynos_dsi_resume(struct device *dev) {
- const struct exynos_dsi_driver_data *driver_data = dsi->driver_data;
- int ret, i;
- ret = regulator_bulk_enable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable regulators %d\n", ret);
return ret;
- }
- for (i = 0; i < driver_data->num_clks; i++) {
ret = clk_prepare_enable(dsi->clks[i]);
if (ret < 0)
goto err_clk;
- }
- ret = phy_power_on(dsi->phy);
- if (ret < 0) {
dev_err(dsi->dev, "cannot enable phy %d\n", ret);
goto err_clk;
- }
- struct exynos_dsi *dsi = dev_get_drvdata(dev);
- return 0;
- return samsung_dsim_resume(dsi->dsi);
+}
-err_clk:
- while (--i > -1)
clk_disable_unprepare(dsi->clks[i]);
- regulator_bulk_disable(ARRAY_SIZE(dsi->supplies), dsi->supplies);
+static const struct dev_pm_ops exynos_dsi_pm_ops = {
- SET_RUNTIME_PM_OPS(exynos_dsi_suspend, exynos_dsi_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
+};
- return ret;
-} +struct platform_driver dsi_driver = {
- .probe = exynos_dsi_probe,
- .remove = exynos_dsi_remove,
- .driver = {
.name = "exynos-dsi",
.owner = THIS_MODULE,
.pm = &exynos_dsi_pm_ops,
.of_match_table = exynos_dsi_of_match,
- },
+};
MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c b/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c deleted file mode 100644 index 79d9ec6ade45..000000000000 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi_pltfm.c +++ /dev/null @@ -1,333 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/*
- Samsung SoC MIPI DSI Master driver.
- Copyright (c) 2014 Samsung Electronics Co., Ltd
- Contacts: Tomasz Figa t.figa@samsung.com
- */
-#include <linux/component.h> -#include <linux/of_device.h> -#include <linux/of_graph.h> -#include <linux/pm_runtime.h>
-#include <drm/drm_bridge.h> -#include <drm/drm_encoder.h> -#include <drm/drm_mipi_dsi.h> -#include <drm/drm_probe_helper.h> -#include <drm/drm_simple_kms_helper.h>
-#include "exynos_drm_crtc.h" -#include "exynos_drm_drv.h" -#include "exynos_drm_dsi.h"
-enum {
- DSI_PORT_IN,
- DSI_PORT_OUT
-};
-struct exynos_dsi_pltfm {
- struct exynos_dsi *dsi;
- struct drm_encoder encoder;
-};
-static const unsigned int reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0x0af,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x06,
- [PHYTIMING_HS_EXIT] = 0x0b,
- [PHYTIMING_CLK_PREPARE] = 0x07,
- [PHYTIMING_CLK_ZERO] = 0x27,
- [PHYTIMING_CLK_POST] = 0x0d,
- [PHYTIMING_CLK_TRAIL] = 0x08,
- [PHYTIMING_HS_PREPARE] = 0x09,
- [PHYTIMING_HS_ZERO] = 0x0d,
- [PHYTIMING_HS_TRAIL] = 0x0b,
-};
-static const unsigned int exynos5422_reg_values[] = {
- [RESET_TYPE] = DSIM_SWRST,
- [PLL_TIMER] = 500,
- [STOP_STATE_CNT] = 0xf,
- [PHYCTRL_ULPS_EXIT] = 0xaf,
- [PHYCTRL_VREG_LP] = 0,
- [PHYCTRL_SLEW_UP] = 0,
- [PHYTIMING_LPX] = 0x08,
- [PHYTIMING_HS_EXIT] = 0x0d,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x30,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x0a,
- [PHYTIMING_HS_PREPARE] = 0x0c,
- [PHYTIMING_HS_ZERO] = 0x11,
- [PHYTIMING_HS_TRAIL] = 0x0d,
-};
-static const unsigned int exynos5433_reg_values[] = {
- [RESET_TYPE] = DSIM_FUNCRST,
- [PLL_TIMER] = 22200,
- [STOP_STATE_CNT] = 0xa,
- [PHYCTRL_ULPS_EXIT] = 0x190,
- [PHYCTRL_VREG_LP] = 1,
- [PHYCTRL_SLEW_UP] = 1,
- [PHYTIMING_LPX] = 0x07,
- [PHYTIMING_HS_EXIT] = 0x0c,
- [PHYTIMING_CLK_PREPARE] = 0x09,
- [PHYTIMING_CLK_ZERO] = 0x2d,
- [PHYTIMING_CLK_POST] = 0x0e,
- [PHYTIMING_CLK_TRAIL] = 0x09,
- [PHYTIMING_HS_PREPARE] = 0x0b,
- [PHYTIMING_HS_ZERO] = 0x10,
- [PHYTIMING_HS_TRAIL] = 0x0c,
-};
-static int __exynos_dsi_host_attach(struct device *dev,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- struct exynos_drm_crtc *crtc;
- mutex_lock(&drm->mode_config.mutex);
- crtc = exynos_drm_crtc_get_by_type(drm, EXYNOS_DISPLAY_TYPE_LCD);
- crtc->i80_mode = !(device->mode_flags & MIPI_DSI_MODE_VIDEO);
- mutex_unlock(&drm->mode_config.mutex);
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
-}
-static int __exynos_dsi_host_detach(struct device *dev,
struct mipi_dsi_device *device)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_device *drm = dsi->encoder.dev;
- if (drm->mode_config.poll_enabled)
drm_kms_helper_hotplug_event(drm);
- return 0;
-}
-static void __exynos_dsi_te_handler(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- exynos_drm_crtc_te_handler(dsi->encoder.crtc);
-}
-static const struct exynos_dsi_host_ops exynos_dsi_host_ops = {
- .attach = __exynos_dsi_host_attach,
- .detach = __exynos_dsi_host_detach,
- .te_handler = __exynos_dsi_te_handler,
-};
-static const struct exynos_dsi_driver_data exynos3_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos4_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x50,
- .has_freqband = 1,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5_dsi_driver_data = {
- .reg_ofs = EXYNOS_REG_OFS,
- .plltmr_reg = 0x58,
- .num_clks = 2,
- .max_freq = 1000,
- .wait_for_reset = 1,
- .num_bits_resol = 11,
- .reg_values = reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5433_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 5,
- .max_freq = 1500,
- .wait_for_reset = 0,
- .num_bits_resol = 12,
- .reg_values = exynos5433_reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct exynos_dsi_driver_data exynos5422_dsi_driver_data = {
- .reg_ofs = EXYNOS5433_REG_OFS,
- .plltmr_reg = 0xa0,
- .has_clklane_stop = 1,
- .num_clks = 2,
- .max_freq = 1500,
- .wait_for_reset = 1,
- .num_bits_resol = 12,
- .reg_values = exynos5422_reg_values,
- .host_ops = &exynos_dsi_host_ops,
-};
-static const struct of_device_id exynos_dsi_of_match[] = {
- { .compatible = "samsung,exynos3250-mipi-dsi",
.data = &exynos3_dsi_driver_data },
- { .compatible = "samsung,exynos4210-mipi-dsi",
.data = &exynos4_dsi_driver_data },
- { .compatible = "samsung,exynos5410-mipi-dsi",
.data = &exynos5_dsi_driver_data },
- { .compatible = "samsung,exynos5422-mipi-dsi",
.data = &exynos5422_dsi_driver_data },
- { .compatible = "samsung,exynos5433-mipi-dsi",
.data = &exynos5433_dsi_driver_data },
- { }
-};
-static int exynos_dsi_pltfm_bind(struct device *dev, struct device *master, void *data) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- struct drm_device *drm_dev = data;
- struct device_node *in_bridge_node;
- struct drm_bridge *in_bridge;
- int ret;
- drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS);
- ret = exynos_drm_set_possible_crtcs(encoder, EXYNOS_DISPLAY_TYPE_LCD);
- if (ret < 0)
return ret;
- in_bridge_node = of_graph_get_remote_node(dev->of_node, DSI_PORT_IN, 0);
- if (in_bridge_node) {
in_bridge = of_drm_find_bridge(in_bridge_node);
if (in_bridge)
drm_bridge_attach(encoder, in_bridge, NULL, 0);
of_node_put(in_bridge_node);
- }
- ret = exynos_dsi_bind(dsi->dsi, encoder);
- if (ret)
goto err;
- return 0;
-err:
- drm_encoder_cleanup(encoder);
- return ret;
-}
-static void exynos_dsi_pltfm_unbind(struct device *dev, struct device *master,
void *data)
-{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- struct drm_encoder *encoder = &dsi->encoder;
- exynos_dsi_unbind(dsi->dsi);
- drm_encoder_cleanup(encoder);
-}
-static const struct component_ops exynos_dsi_pltfm_component_ops = {
- .bind = exynos_dsi_pltfm_bind,
- .unbind = exynos_dsi_pltfm_unbind,
-};
-static int exynos_dsi_pltfm_probe(struct platform_device *pdev) -{
- struct exynos_dsi_pltfm *dsi;
- struct device *dev = &pdev->dev;
- int ret;
- dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
- if (!dsi)
return -ENOMEM;
- platform_set_drvdata(pdev, dsi);
- dsi->dsi = exynos_dsi_probe(pdev);
- if (IS_ERR(dsi->dsi))
return PTR_ERR(dsi->dsi);
- pm_runtime_enable(dev);
- ret = component_add(dev, &exynos_dsi_pltfm_component_ops);
- if (ret)
goto err_disable_runtime;
- return 0;
-err_disable_runtime:
- pm_runtime_disable(dev);
- return ret;
-}
-static int exynos_dsi_pltfm_remove(struct platform_device *pdev) -{
- struct exynos_dsi_pltfm *dsi = platform_get_drvdata(pdev);
- pm_runtime_disable(&pdev->dev);
- exynos_dsi_remove(dsi->dsi);
- component_del(&pdev->dev, &exynos_dsi_pltfm_component_ops);
- return 0;
-}
-static int __maybe_unused exynos_dsi_pltfm_suspend(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- return exynos_dsi_suspend(dsi->dsi);
-}
-static int __maybe_unused exynos_dsi_pltfm_resume(struct device *dev) -{
- struct exynos_dsi_pltfm *dsi = dev_get_drvdata(dev);
- return exynos_dsi_resume(dsi->dsi);
-}
-static const struct dev_pm_ops exynos_dsi_pm_ops = {
- SET_RUNTIME_PM_OPS(exynos_dsi_pltfm_suspend, exynos_dsi_pltfm_resume, NULL)
- SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
-};
-struct platform_driver dsi_driver = {
- .probe = exynos_dsi_pltfm_probe,
- .remove = exynos_dsi_pltfm_remove,
- .driver = {
.name = "exynos-dsi",
.owner = THIS_MODULE,
.pm = &exynos_dsi_pm_ops,
.of_match_table = exynos_dsi_of_match,
- },
-};
-MODULE_AUTHOR("Tomasz Figa t.figa@samsung.com"); -MODULE_AUTHOR("Andrzej Hajda a.hajda@samsung.com"); -MODULE_DESCRIPTION("Samsung SoC MIPI DSI Master"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.h b/include/drm/bridge/samsung-dsim.h similarity index 69% rename from drivers/gpu/drm/exynos/exynos_drm_dsi.h rename to include/drm/bridge/samsung-dsim.h index 8fa3276889de..be8b4913aa9c 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.h +++ b/include/drm/bridge/samsung-dsim.h @@ -3,7 +3,7 @@ #define __EXYNOS_DRM_DSI__
struct drm_encoder; -struct exynos_dsi; +struct samsung_dsim; struct platform_device; struct mipi_dsi_device;
@@ -12,13 +12,13 @@ enum exynos_reg_offset { EXYNOS5433_REG_OFS };
-struct exynos_dsi *exynos_dsi_probe(struct platform_device *pdev); -void exynos_dsi_remove(struct exynos_dsi *dsi); -int exynos_dsi_bind(struct exynos_dsi *dsi, struct drm_encoder *encoder); -void exynos_dsi_unbind(struct exynos_dsi *dsi); +struct samsung_dsim *samsung_dsim_probe(struct platform_device *pdev); +void samsung_dsim_remove(struct samsung_dsim *dsi); +int samsung_dsim_bind(struct samsung_dsim *dsi, struct drm_encoder *encoder); +void samsung_dsim_unbind(struct samsung_dsim *dsi);
-int exynos_dsi_suspend(struct exynos_dsi *dsi); -int exynos_dsi_resume(struct exynos_dsi *dsi); +int samsung_dsim_suspend(struct samsung_dsim *dsi); +int samsung_dsim_resume(struct samsung_dsim *dsi);
enum reg_value_idx { RESET_TYPE, @@ -42,13 +42,13 @@ enum reg_value_idx { #define DSIM_FUNCRST (1 << 16) #define DSIM_SWRST (1 << 0)
-struct exynos_dsi_host_ops { +struct samsung_dsim_host_ops { int (*attach)(struct device *dev, struct mipi_dsi_device *device); int (*detach)(struct device *dev, struct mipi_dsi_device *device); void (*te_handler)(struct device *dev); };
-struct exynos_dsi_driver_data { +struct samsung_dsim_driver_data { enum exynos_reg_offset reg_ofs; unsigned int plltmr_reg; unsigned int has_freqband:1; @@ -58,7 +58,7 @@ struct exynos_dsi_driver_data { unsigned int wait_for_reset; unsigned int num_bits_resol; const unsigned int *reg_values;
- const struct exynos_dsi_host_ops *host_ops;
- const struct samsung_dsim_host_ops *host_ops;
};
#endif /* __EXYNOS_DRM_DSI__ */
2.20.1
dri-devel mailing list dri-devel@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/dri-devel
-- Daniel Vetter Software Engineer, Intel Corporation http://blog.ffwll.ch
-- Pengutronix e.K. | Michael Tretter | Steuerwalder Str. 21 | https://www.pengutronix.de/ | 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
Hi Michael,
Thanks for your contribution.
20. 9. 11. 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos. Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Thanks, Inki Dae
to 13) and finally expose the API, split the code and move the platform independent driver to the bridges (patches 14 - 16). Hopefully this simplifies testing/bisecting and helps me to understand potential error reports.
Also I added host_ops for attach/detach and the tearing effect handler to make the calls into the platform code more visible.
Furthermore, the series should now apply to linux-next and correctly build the exynos_defconfig.
Thanks,
Michael
Changelog:
v2:
- rebase on linux-next
- verify with exynos_defconfig
- fix crashes reported by Marek Szyprowski Exynos3250-based Rinato
- reorder patches
- add host_ops for platform specific code
- roughly test component framework integration with dummy
Michael Tretter (16): drm/encoder: remove obsolete documentation of bridge drm/exynos: remove in_bridge_node from exynos_dsi drm/exynos: use exynos_dsi as drvdata drm/exynos: extract helper functions for probe drm/exynos: move dsi host registration to probe drm/exynos: shift register values to fields on write drm/exynos: use identifier instead of register offsets drm/exynos: add host_ops callback for platform drivers drm/exynos: add callback for tearing effect handler drm/exynos: implement a drm bridge drm/exynos: convert encoder functions to bridge function drm/exynos: configure mode on drm bridge drm/exynos: get encoder from bridge whenever possible drm/exynos: add API functions for platform drivers drm/exynos: split out platform specific code drm/exynos: move bridge driver to bridges
drivers/gpu/drm/bridge/Kconfig | 9 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/samsung-dsim.c | 1790 +++++++++++++++++++++ drivers/gpu/drm/exynos/Kconfig | 5 +- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 1927 ++--------------------- include/drm/bridge/samsung-dsim.h | 64 + include/drm/drm_encoder.h | 1 - 7 files changed, 2027 insertions(+), 1770 deletions(-) create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c create mode 100644 include/drm/bridge/samsung-dsim.h
On Mon, 09 Nov 2020 12:15:39 +0900, Inki Dae wrote:
- 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos.
I don't understand, why the MIPI-DSI bus device would be an encoder type and DSI to LVDS or MIPI-DSI to HDMI would be bridges. For example, the device tree documentation for the DSIM states that the DSIM receives RGB video as input and produces MIPI-DSI as output. Thus, the DSIM is basically a parallel RGB to MIPI-DSI bridge and the encoder is the LCD controller that encodes the video data as parallel RGB.
On the i.MX8MM, the LCDIF is already the encoder. On Exynos, the series implements the encoder in the platform glue, but in the end the encoder can probably be moved to the DECON.
Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Yes, the reason is that the driver should be shared between Exynos and i.MX8MM. It is the same IP and I don't see a reason why we should introduce another driver for the same IP if the driver works for both SoCs.
Michael
Hi Michael,
On 10.11.2020 09:13, Michael Tretter wrote:
On Mon, 09 Nov 2020 12:15:39 +0900, Inki Dae wrote:
- 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos.
I don't understand, why the MIPI-DSI bus device would be an encoder type and DSI to LVDS or MIPI-DSI to HDMI would be bridges. For example, the device tree documentation for the DSIM states that the DSIM receives RGB video as input and produces MIPI-DSI as output. Thus, the DSIM is basically a parallel RGB to MIPI-DSI bridge and the encoder is the LCD controller that encodes the video data as parallel RGB.
On the i.MX8MM, the LCDIF is already the encoder. On Exynos, the series implements the encoder in the platform glue, but in the end the encoder can probably be moved to the DECON.
This is probably the historical decision. That time when Exynos DSI driver has been implemented, support for DRM bridges wasn't ready enough to use to for such purpose.
Frankly, I'm still not convinced that the current DRM bridge framework provides everything needed to reimplement the Exynos DSI driver with all its features. There are a lots of corner cases and order-specific bits in turning on/off the display pipeline, which don't map nicely to the bridge pre_enable (called in post-order) and enable (called in pre-order) callbacks. Especially if you consider that there might be another bridge before and after.
I think that Andrzej Hajda already pointed those drawbacks of the current design. Last week I've spent some significant amount of time playing with exynos dsi code to check how to match its operations (especially the runtime power management) to this design with the current boards (Arndale with additional DSI->LVDS bridge and panel, Trats2 with DSI panel and TM2e with MIC 'in-bridge' and DSI panel), but without a success.
Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Yes, the reason is that the driver should be shared between Exynos and i.MX8MM. It is the same IP and I don't see a reason why we should introduce another driver for the same IP if the driver works for both SoCs.
I think that the easiest way to share this driver between Exynos and iMX would be to extract the low-level code (the code that plays with hardware registers) to the common plane, while keeping the separate DRM glue, device component and platform device parts. This way the Exynos will continue to use it in the current form, while iMX can start using it as DRM bridge. A bit similar approach is used with exynos_dp driver and analogix_dp bridge shared with other SoCs.
I hope that later, when the bridge related issue are resolved, the Exynos can be converted too, so the encoder creation moved to FIMD and Decon.
Best regards
20. 11. 10. 오후 5:13에 Michael Tretter 이(가) 쓴 글:
On Mon, 09 Nov 2020 12:15:39 +0900, Inki Dae wrote:
- 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos.
I don't understand, why the MIPI-DSI bus device would be an encoder type and DSI to LVDS or MIPI-DSI to HDMI would be bridges. For example, the device tree documentation for the DSIM states that the DSIM receives RGB video as input and produces MIPI-DSI as output. Thus, the DSIM is basically a parallel RGB to
MIPI-DSI receives RGB video as input and encodes it to MIPI packet and then transfers the packet to MIPI panel. And finally, MIPI panel decodes the packet and updates it - RGB data - on its SRAM.
MIPI-DSI driver programs how the RGB video should be made as MIPI packet. For more detail, you could refer to MIPI-DSI spec. This would be why we handle MIPI-DSI driver as an encoder like other ARM SoC DRM drivers did.
MIPI-DSI bridge and the encoder is the LCD controller that encodes the video data as parallel RGB.
On the i.MX8MM, the LCDIF is already the encoder. On Exynos, the series implements the encoder in the platform glue, but in the end the encoder can probably be moved to the DECON.
As you know, Display controller can transfer RGB video to various devices such as RGB panel, CPU panel, LVDS panel via LVDS bridge, MIPI panel via MIPI-DSI bus device, and so on like below,
Display Controller --> RGB panel or CPU panel. Display Controller --> LVDS bridge --> LVDS panel. Display Controller --> MIPI DSI bus device --> MIPI Panel. ...
Display controller drivers such as FIMD and DECON series in case of Exynos don't create an encoder driver-internally instead of it depends on Display pipeline configuration - what kind of Display panel is used. In fact, if the Display pipeline uses RGB panel or CPU panel without Display bus device such as MIPI-DSI, then FIMD and DECON drivers create an encoder for it internally - just we separated the code to consider other type of panels.
On the I.MX8MM, could you share how to handle an encoder if some board has only MIPI-DSI panel, and in this case, where is an encoder for it created? I looked into I.MX8MM DRM driver but didn't find MIPI-DSI driver. Seems that they support only parallel display, HDMI and LVDS panel.
Thanks, Inki Dae
Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Yes, the reason is that the driver should be shared between Exynos and i.MX8MM. It is the same IP and I don't see a reason why we should introduce another driver for the same IP if the driver works for both SoCs.
Michael
20. 11. 11. 오후 12:04에 Inki Dae 이(가) 쓴 글:
- 오후 5:13에 Michael Tretter 이(가) 쓴 글:
On Mon, 09 Nov 2020 12:15:39 +0900, Inki Dae wrote:
- 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos.
I don't understand, why the MIPI-DSI bus device would be an encoder type and DSI to LVDS or MIPI-DSI to HDMI would be bridges. For example, the device tree documentation for the DSIM states that the DSIM receives RGB video as input and produces MIPI-DSI as output. Thus, the DSIM is basically a parallel RGB to
MIPI-DSI receives RGB video as input and encodes it to MIPI packet and then transfers the packet to MIPI panel. And finally, MIPI panel decodes the packet and updates it - RGB data - on its SRAM.
MIPI-DSI driver programs how the RGB video should be made as MIPI packet. For more detail, you could refer to MIPI-DSI spec. This would be why we handle MIPI-DSI driver as an encoder like other ARM SoC DRM drivers did.
MIPI-DSI bridge and the encoder is the LCD controller that encodes the video data as parallel RGB.
On the i.MX8MM, the LCDIF is already the encoder. On Exynos, the series implements the encoder in the platform glue, but in the end the encoder can probably be moved to the DECON.
As you know, Display controller can transfer RGB video to various devices such as RGB panel, CPU panel, LVDS panel via LVDS bridge, MIPI panel via MIPI-DSI bus device, and so on like below,
Display Controller --> RGB panel or CPU panel. Display Controller --> LVDS bridge --> LVDS panel. Display Controller --> MIPI DSI bus device --> MIPI Panel. ...
Display controller drivers such as FIMD and DECON series in case of Exynos don't create an encoder driver-internally instead of it depends on Display pipeline configuration - what kind of Display panel is used. In fact, if the Display pipeline uses RGB panel or CPU panel without Display bus device such as MIPI-DSI, then FIMD and DECON drivers create an encoder for it internally - just we separated the code to consider other type of panels.
On the I.MX8MM, could you share how to handle an encoder if some board has only MIPI-DSI panel, and in this case, where is an encoder for it created? I looked into I.MX8MM DRM driver but didn't find MIPI-DSI driver. Seems that they support only parallel display, HDMI and LVDS panel.
One more thing, If I saw correctly, the LVDS driver of IMX DRM - lmx_ldb - creates an encoder internally like MIPI-DSI driver of Exynos DRM did.
Thanks, Inki Dae
Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Yes, the reason is that the driver should be shared between Exynos and i.MX8MM. It is the same IP and I don't see a reason why we should introduce another driver for the same IP if the driver works for both SoCs.
Michael
On Wed, 11 Nov 2020 12:11:15 +0900, Inki Dae wrote:
- 오후 12:04에 Inki Dae 이(가) 쓴 글:
- 오후 5:13에 Michael Tretter 이(가) 쓴 글:
On Mon, 09 Nov 2020 12:15:39 +0900, Inki Dae wrote:
- 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos.
I don't understand, why the MIPI-DSI bus device would be an encoder type and DSI to LVDS or MIPI-DSI to HDMI would be bridges. For example, the device tree documentation for the DSIM states that the DSIM receives RGB video as input and produces MIPI-DSI as output. Thus, the DSIM is basically a parallel RGB to
MIPI-DSI receives RGB video as input and encodes it to MIPI packet and then transfers the packet to MIPI panel. And finally, MIPI panel decodes the packet and updates it - RGB data - on its SRAM.
MIPI-DSI driver programs how the RGB video should be made as MIPI packet. For more detail, you could refer to MIPI-DSI spec. This would be why we handle MIPI-DSI driver as an encoder like other ARM SoC DRM drivers did.
MIPI-DSI bridge and the encoder is the LCD controller that encodes the video data as parallel RGB.
On the i.MX8MM, the LCDIF is already the encoder. On Exynos, the series implements the encoder in the platform glue, but in the end the encoder can probably be moved to the DECON.
As you know, Display controller can transfer RGB video to various devices such as RGB panel, CPU panel, LVDS panel via LVDS bridge, MIPI panel via MIPI-DSI bus device, and so on like below,
Display Controller --> RGB panel or CPU panel. Display Controller --> LVDS bridge --> LVDS panel. Display Controller --> MIPI DSI bus device --> MIPI Panel. ...
Display controller drivers such as FIMD and DECON series in case of Exynos don't create an encoder driver-internally instead of it depends on Display pipeline configuration - what kind of Display panel is used. In fact, if the Display pipeline uses RGB panel or CPU panel without Display bus device such as MIPI-DSI, then FIMD and DECON drivers create an encoder for it internally - just we separated the code to consider other type of panels.
What happens if I add a MIPI-DSI --> HDMI bridge to the Display pipeline? Then the Pipeline is
Display Controller --> MIPI DSI bus device --> HDMI bridge --> HDMI Panel
If the type of the Display panel decides which part of the pipeline provides the encoder, the HDMI bridge driver would be responsible for creating the encoder, right? Thus, the MIPI-DSI driver would not be responsible for creating the encoder and would also get the encoder from another driver. Therefore, I prefer to think of the Display Controller as the encoder and other bridges are just bridges.
BTW, this is exactly the Display pipeline that is used on the i.MX8MM EVK.
On the I.MX8MM, could you share how to handle an encoder if some board has only MIPI-DSI panel, and in this case, where is an encoder for it created? I looked into I.MX8MM DRM driver but didn't find MIPI-DSI driver. Seems that they support only parallel display, HDMI and LVDS panel.
One more thing, If I saw correctly, the LVDS driver of IMX DRM - lmx_ldb - creates an encoder internally like MIPI-DSI driver of Exynos DRM did.
Yes, but the IMX DRM driver is not used on the i.MX8MM. The i.MX8MM uses the LCDIF display controller instead of the IPU of the i.MX6. The driver for the LCDIF is the mxsfb driver, which in turn uses the drm_simple_display_pipe, which already provides the encoder. Therefore, to use a bridge driver with a driver based on a drm_simple_display_pipe, the bridge driver must accept other encoders.
Michael
Thanks, Inki Dae
Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Yes, the reason is that the driver should be shared between Exynos and i.MX8MM. It is the same IP and I don't see a reason why we should introduce another driver for the same IP if the driver works for both SoCs.
Michael
20. 11. 11. 오후 7:18에 Michael Tretter 이(가) 쓴 글:
On Wed, 11 Nov 2020 12:11:15 +0900, Inki Dae wrote:
- 오후 12:04에 Inki Dae 이(가) 쓴 글:
- 오후 5:13에 Michael Tretter 이(가) 쓴 글:
On Mon, 09 Nov 2020 12:15:39 +0900, Inki Dae wrote:
- 오후 10:53에 Michael Tretter 이(가) 쓴 글:
This is v2 of the series to convert the Exynos MIPI DSI driver into a drm bridge and make it usable with other drivers. Although the driver is converted, it still supports the component framework API to stay compliant with the Exynos DRM driver.
The Exynos MIPI DSI Phy is also found on the i.MX8M Mini. However, on the i.MX8M Mini, the bridge is driven by an LCDIF display controller instead of the Exynos Decon. The driver for the LCDIF does not use the component framework, but uses drm bridges.
I don't have any Exynos SoC to actually test the series. I build a dummy to test the bridge with a component driver, to make sure that at least the initialization is working. Furthermore, tested the driver as a bridge with a few additional unfinished patches on the i.MX8M Mini EVK. However, somebody should verify that the driver is still working on Exynos hardware.
I also changed the order of the patches to first make the driver more platform independent (patches 2 to 8), then convert to a drm bridge driver (patches 10
Just a fundamental question, A MIPI-DSI(Display Serial Interface) bus device would be one of an encoder type of devices not bridge such as DSI to LVDS and LVDS to DSI bridge devices, and also image enhancer and image compressor in case of Exynos.
I don't understand, why the MIPI-DSI bus device would be an encoder type and DSI to LVDS or MIPI-DSI to HDMI would be bridges. For example, the device tree documentation for the DSIM states that the DSIM receives RGB video as input and produces MIPI-DSI as output. Thus, the DSIM is basically a parallel RGB to
MIPI-DSI receives RGB video as input and encodes it to MIPI packet and then transfers the packet to MIPI panel. And finally, MIPI panel decodes the packet and updates it - RGB data - on its SRAM.
MIPI-DSI driver programs how the RGB video should be made as MIPI packet. For more detail, you could refer to MIPI-DSI spec. This would be why we handle MIPI-DSI driver as an encoder like other ARM SoC DRM drivers did.
MIPI-DSI bridge and the encoder is the LCD controller that encodes the video data as parallel RGB.
On the i.MX8MM, the LCDIF is already the encoder. On Exynos, the series implements the encoder in the platform glue, but in the end the encoder can probably be moved to the DECON.
As you know, Display controller can transfer RGB video to various devices such as RGB panel, CPU panel, LVDS panel via LVDS bridge, MIPI panel via MIPI-DSI bus device, and so on like below,
Display Controller --> RGB panel or CPU panel. Display Controller --> LVDS bridge --> LVDS panel. Display Controller --> MIPI DSI bus device --> MIPI Panel. ...
Display controller drivers such as FIMD and DECON series in case of Exynos don't create an encoder driver-internally instead of it depends on Display pipeline configuration - what kind of Display panel is used. In fact, if the Display pipeline uses RGB panel or CPU panel without Display bus device such as MIPI-DSI, then FIMD and DECON drivers create an encoder for it internally - just we separated the code to consider other type of panels.
What happens if I add a MIPI-DSI --> HDMI bridge to the Display pipeline? Then the Pipeline is
Display Controller --> MIPI DSI bus device --> HDMI bridge --> HDMI Panel
If the type of the Display panel decides which part of the pipeline provides the encoder, the HDMI bridge driver would be responsible for creating the
HDMI bridge driver will create only an bridge because this is not encoder device but bridge device such as LVDS bridge.
Display Controller(CRTC) --> MIPI DSI bus device(Encoder) --> HDMI bridge(Bridge) --> HDMI Panel(Connector)
encoder, right? Thus, the MIPI-DSI driver would not be responsible for creating the encoder and would also get the encoder from another driver.
I think MIPI-DSI is an encoder device as I mentioned earlier so MIPI-DSI driver would be responsible for creating the encoder.
Therefore, I prefer to think of the Display Controller as the encoder and other bridges are just bridges.
So crtc driver will create an encoder internally in case of parallel RGB Panel *because encoder device isn't used*. On the other hand, each encoder driver will be an encoder in case of other type of panel - which is needed for Display bus device such as MIPI-DSI device.
Thanks, Inki Dae
BTW, this is exactly the Display pipeline that is used on the i.MX8MM EVK.
On the I.MX8MM, could you share how to handle an encoder if some board has only MIPI-DSI panel, and in this case, where is an encoder for it created? I looked into I.MX8MM DRM driver but didn't find MIPI-DSI driver. Seems that they support only parallel display, HDMI and LVDS panel.
One more thing, If I saw correctly, the LVDS driver of IMX DRM - lmx_ldb - creates an encoder internally like MIPI-DSI driver of Exynos DRM did.
Yes, but the IMX DRM driver is not used on the i.MX8MM. The i.MX8MM uses the LCDIF display controller instead of the IPU of the i.MX6. The driver for the LCDIF is the mxsfb driver, which in turn uses the drm_simple_display_pipe, which already provides the encoder. Therefore, to use a bridge driver with a driver based on a drm_simple_display_pipe, the bridge driver must accept other encoders.
Michael
Thanks, Inki Dae
Why do you want to convert such MIPI-DSI driver to bridge type of driver? Seems not sensible. The reason would be just to share MIPI-DSI phy driver for Exynos with i.MX8M Mini?
Yes, the reason is that the driver should be shared between Exynos and i.MX8MM. It is the same IP and I don't see a reason why we should introduce another driver for the same IP if the driver works for both SoCs.
Michael
dri-devel@lists.freedesktop.org