summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorFancy Fang <chen.fang@nxp.com>2018-06-06 23:46:37 +0800
committerJason Liu <jason.hui.liu@nxp.com>2019-02-12 10:32:06 +0800
commit941ef8c5fc870b72d18c1ec5abc69c5523647cba (patch)
tree062a6c0ca400434606abab443e9e09010e157acf /drivers
parentbeb9d163c755fe7ae960b79d85c0c1436a8dc057 (diff)
MLK-18535-8 drm/imx: sec_mipi_dsim-imx: add DSIM host driver
The Samsung DSIM host driver mainly focuses on the config related with soc platforms. And the controller itself config has been moved to the sec-dsim bridge driver. Signed-off-by: Fancy Fang <chen.fang@nxp.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/gpu/drm/bridge/sec-dsim.c22
-rw-r--r--drivers/gpu/drm/imx/Kconfig9
-rw-r--r--drivers/gpu/drm/imx/Makefile1
-rw-r--r--drivers/gpu/drm/imx/sec_mipi_dsim-imx.c311
4 files changed, 323 insertions, 20 deletions
diff --git a/drivers/gpu/drm/bridge/sec-dsim.c b/drivers/gpu/drm/bridge/sec-dsim.c
index dfd71acabfc5..df76bf2c40e7 100644
--- a/drivers/gpu/drm/bridge/sec-dsim.c
+++ b/drivers/gpu/drm/bridge/sec-dsim.c
@@ -352,7 +352,6 @@ static int sec_mipi_dsim_bridge_attach(struct drm_bridge *bridge)
struct device *dev = dsim->dev;
struct device_node *np = dev->of_node;
struct device_node *endpoint, *remote;
- struct drm_device *drm_dev = bridge->dev;
struct drm_bridge *next = NULL;
struct drm_encoder *encoder = dsim->encoder;
@@ -396,7 +395,7 @@ static int sec_mipi_dsim_bridge_attach(struct drm_bridge *bridge)
dsim->next = next;
next->encoder = encoder;
- ret = drm_bridge_attach(drm_dev, next);
+ ret = drm_bridge_attach(encoder, bridge, next);
if (ret) {
dev_err(dev, "Unable to attach bridge %s: %d\n",
remote->name, ret);
@@ -410,19 +409,6 @@ static int sec_mipi_dsim_bridge_attach(struct drm_bridge *bridge)
return 0;
}
-static void sec_mipi_dsim_bridge_detach(struct drm_bridge *bridge)
-{
- struct sec_mipi_dsim *dsim = bridge->driver_private;
-
- if (bridge->next) {
- drm_bridge_detach(dsim->next);
-
- bridge->next->encoder = NULL;
- bridge->next = NULL;
- dsim->next = NULL;
- }
-}
-
static int sec_mipi_dsim_config_pll(struct sec_mipi_dsim *dsim)
{
int ret;
@@ -824,7 +810,6 @@ static void sec_mipi_dsim_bridge_mode_set(struct drm_bridge *bridge,
static const struct drm_bridge_funcs sec_mipi_dsim_bridge_funcs = {
.attach = sec_mipi_dsim_bridge_attach,
- .detach = sec_mipi_dsim_bridge_detach,
.enable = sec_mipi_dsim_bridge_enable,
.disable = sec_mipi_dsim_bridge_disable,
.mode_set = sec_mipi_dsim_bridge_mode_set,
@@ -1057,7 +1042,6 @@ static enum drm_connector_status
}
static const struct drm_connector_funcs sec_mipi_dsim_connector_funcs = {
- .dpms = drm_atomic_helper_connector_dpms,
.detect = sec_mipi_dsim_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
@@ -1168,7 +1152,7 @@ int sec_mipi_dsim_bind(struct device *dev, struct device *master, void *data,
dev_set_drvdata(dev, dsim);
/* attach sec dsim bridge and its next bridge if exists */
- ret = drm_bridge_attach(drm_dev, bridge);
+ ret = drm_bridge_attach(encoder, bridge, NULL);
if (ret) {
dev_err(dev, "Failed to attach bridge: %s\n", dev_name(dev));
mipi_dsi_host_unregister(&dsim->dsi_host);
@@ -1222,8 +1206,6 @@ void sec_mipi_dsim_unbind(struct device *dev, struct device *master, void *data)
dsim->panel = NULL;
}
- drm_bridge_detach(dsim->bridge);
-
mipi_dsi_host_unregister(&dsim->dsi_host);
}
EXPORT_SYMBOL(sec_mipi_dsim_unbind);
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 1dff27587c8f..402d7bee5d67 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -54,6 +54,15 @@ config DRM_IMX_NWL_DSI
Choose this to enable the internal NWL MIPI DSI controller
found on i.MX8 processors.
+config DRM_IMX_SEC_DSIM
+ tristate "Support for Samsung MIPI DSIM displays"
+ depends on DRM_IMX
+ select MFD_SYSCON
+ select DRM_SEC_MIPI_DSIM
+ help
+ Choose this to enable the internal SEC MIPI DSIM controller
+ found on i.MX platform.
+
source "drivers/gpu/drm/imx/ipuv3/Kconfig"
source "drivers/gpu/drm/imx/dpu/Kconfig"
source "drivers/gpu/drm/imx/hdp/Kconfig"
diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile
index a98c25608ad6..9f1a39f39959 100644
--- a/drivers/gpu/drm/imx/Makefile
+++ b/drivers/gpu/drm/imx/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o
obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o
obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o
obj-$(CONFIG_DRM_IMX_NWL_DSI) += nwl_dsi-imx.o
+obj-$(CONFIG_DRM_IMX_SEC_DSIM) += sec_mipi_dsim-imx.o
obj-$(CONFIG_DRM_IMX_IPUV3) += ipuv3/
obj-$(CONFIG_DRM_IMX_DPU) += dpu/
obj-$(CONFIG_DRM_IMX_HDP) += hdp/
diff --git a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
new file mode 100644
index 000000000000..245329eeea65
--- /dev/null
+++ b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
@@ -0,0 +1,311 @@
+/*
+ * Samsung MIPI DSI Host Controller on IMX
+ *
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <drm/bridge/sec_mipi_dsim.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME "imx_sec_dsim_drv"
+
+/* Dispmix Control & GPR Registers */
+#define DISPLAY_MIX_SFT_RSTN_CSR 0X00
+ #define MIPI_DSI_I_PRESETn_SFT_EN BIT(5)
+#define DISPLAY_MIX_CLK_EN_CSR 0x04
+ #define MIPI_DSI_PCLK_SFT_EN BIT(8)
+ #define MIPI_DSI_CLKREF_SFT_EN BIT(9)
+#define GPR_MIPI_RESET_DIV 0x08
+ /* Clock & Data lanes reset: Active Low */
+ #define GPR_MIPI_S_RESETN BIT(16)
+ #define GPR_MIPI_M_RESETN BIT(17)
+
+struct imx_sec_dsim_device {
+ struct device *dev;
+ struct drm_encoder encoder;
+ struct regmap *gpr;
+};
+
+#define enc_to_dsim(enc) container_of(enc, struct imx_sec_dsim_device, encoder)
+
+static struct imx_sec_dsim_device *dsim_dev;
+
+static void disp_mix_dsim_soft_reset_release(struct regmap *gpr, bool release)
+{
+ if (release)
+ /* release dsi blk reset */
+ regmap_update_bits(gpr, DISPLAY_MIX_SFT_RSTN_CSR,
+ MIPI_DSI_I_PRESETn_SFT_EN,
+ MIPI_DSI_I_PRESETn_SFT_EN);
+ else
+ regmap_update_bits(gpr, DISPLAY_MIX_SFT_RSTN_CSR,
+ MIPI_DSI_I_PRESETn_SFT_EN,
+ 0x0);
+}
+
+static void disp_mix_dsim_clks_enable(struct regmap *gpr, bool enable)
+{
+ if (enable)
+ regmap_update_bits(gpr, DISPLAY_MIX_CLK_EN_CSR,
+ MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN,
+ MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN);
+ else
+ regmap_update_bits(gpr, DISPLAY_MIX_CLK_EN_CSR,
+ MIPI_DSI_PCLK_SFT_EN | MIPI_DSI_CLKREF_SFT_EN,
+ 0x0);
+}
+
+static void imx_sec_dsim_lanes_reset(struct regmap *gpr, bool reset)
+{
+ if (!reset)
+ /* release lanes reset */
+ regmap_update_bits(gpr, GPR_MIPI_RESET_DIV,
+ GPR_MIPI_S_RESETN | GPR_MIPI_M_RESETN,
+ GPR_MIPI_S_RESETN | GPR_MIPI_M_RESETN);
+ else
+ /* reset lanes */
+ regmap_update_bits(gpr, GPR_MIPI_RESET_DIV,
+ GPR_MIPI_S_RESETN | GPR_MIPI_M_RESETN,
+ 0x0);
+}
+
+static void imx_sec_dsim_encoder_helper_enable(struct drm_encoder *encoder)
+{
+ struct imx_sec_dsim_device *dsim_dev = enc_to_dsim(encoder);
+
+ pm_runtime_get_sync(dsim_dev->dev);
+
+ imx_sec_dsim_lanes_reset(dsim_dev->gpr, false);
+}
+
+static void imx_sec_dsim_encoder_helper_disable(struct drm_encoder *encoder)
+{
+ struct imx_sec_dsim_device *dsim_dev = enc_to_dsim(encoder);
+
+ imx_sec_dsim_lanes_reset(dsim_dev->gpr, true);
+
+ pm_runtime_put_sync(dsim_dev->dev);
+}
+
+static int imx_sec_dsim_encoder_helper_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+
+ /* sec dsim can only accept active hight DE */
+ imx_crtc_state->bus_flags |= DRM_BUS_FLAG_DE_HIGH;
+
+ /* For the dotclock polarity, default is neg edge;
+ * and in the dsim spec, there is no explict words
+ * to illustrate the dotclock polarity requirement.
+ */
+ imx_crtc_state->bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE;
+
+ imx_crtc_state->bus_format = adjusted_mode->private_flags & 0xffff;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs imx_sec_dsim_encoder_helper_funcs = {
+ .enable = imx_sec_dsim_encoder_helper_enable,
+ .disable = imx_sec_dsim_encoder_helper_disable,
+ .atomic_check = imx_sec_dsim_encoder_helper_atomic_check,
+};
+
+static const struct drm_encoder_funcs imx_sec_dsim_encoder_funcs = {
+ .destroy = imx_drm_encoder_destroy,
+};
+
+static const struct sec_mipi_dsim_plat_data imx8mm_mipi_dsim_plat_data = {
+ .version = 0x1060200,
+ .max_data_lanes = 4,
+ .max_data_rate = 1500000000ULL,
+ .mode_valid = NULL,
+};
+
+static const struct of_device_id imx_sec_dsim_dt_ids[] = {
+ {
+ .compatible = "fsl,imx8mm-mipi-dsim",
+ .data = &imx8mm_mipi_dsim_plat_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sec_dsim_dt_ids);
+
+static int imx_sec_dsim_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ int ret, irq;
+ struct resource *res;
+ struct drm_device *drm_dev = data;
+ struct platform_device *pdev = to_platform_device(dev);
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *of_id = of_match_device(imx_sec_dsim_dt_ids,
+ dev);
+ const struct sec_mipi_dsim_plat_data *pdata = of_id->data;
+ struct drm_encoder *encoder;
+
+ dev_dbg(dev, "%s: dsim bind begin\n", __func__);
+ dsim_dev = devm_kzalloc(dev, sizeof(*dsim_dev), GFP_KERNEL);
+ if (!dsim_dev) {
+ dev_err(dev, "Unable to allocate 'dsim_dev'\n");
+ return -ENOMEM;
+ }
+
+ dsim_dev->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -ENODEV;
+
+ dsim_dev->gpr = syscon_regmap_lookup_by_phandle(np, "dsi-gpr");
+ if (IS_ERR(dsim_dev->gpr))
+ return PTR_ERR(dsim_dev->gpr);
+
+ encoder = &dsim_dev->encoder;
+ ret = imx_drm_encoder_parse_of(drm_dev, encoder, np);
+ if (ret)
+ return ret;
+
+ drm_encoder_helper_add(encoder, &imx_sec_dsim_encoder_helper_funcs);
+
+ ret = drm_encoder_init(drm_dev, encoder,
+ &imx_sec_dsim_encoder_funcs,
+ DRM_MODE_ENCODER_DSI, dev_name(dev));
+ if (ret)
+ return ret;
+
+ /* bind sec dsim bridge */
+ ret = sec_mipi_dsim_bind(dev, master, data, encoder, res, irq, pdata);
+ if (ret) {
+ dev_err(dev, "failed to bind sec dsim bridge: %d\n", ret);
+ drm_encoder_cleanup(encoder);
+ return ret;
+ }
+
+ pm_runtime_enable(dev);
+
+ /* Pull dsim out of reset */
+ disp_mix_dsim_soft_reset_release(dsim_dev->gpr, true);
+ disp_mix_dsim_clks_enable(dsim_dev->gpr, true);
+ imx_sec_dsim_lanes_reset(dsim_dev->gpr, false);
+
+ dev_dbg(dev, "%s: dsim bind end\n", __func__);
+
+ return 0;
+}
+
+static void imx_sec_dsim_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ pm_runtime_disable(dev);
+
+ sec_mipi_dsim_unbind(dev, master, data);
+
+ drm_encoder_cleanup(&dsim_dev->encoder);
+}
+
+static const struct component_ops imx_sec_dsim_ops = {
+ .bind = imx_sec_dsim_bind,
+ .unbind = imx_sec_dsim_unbind,
+};
+
+static int imx_sec_dsim_probe(struct platform_device *pdev)
+{
+ dev_dbg(&pdev->dev, "%s: dsim probe begin\n", __func__);
+
+ return component_add(&pdev->dev, &imx_sec_dsim_ops);
+}
+
+static int imx_sec_dsim_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx_sec_dsim_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_sec_dsim_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int imx_sec_dsim_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int imx_sec_dsim_runtime_suspend(struct device *dev)
+{
+ sec_mipi_dsim_suspend(dev);
+
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ return 0;
+}
+
+static int imx_sec_dsim_runtime_resume(struct device *dev)
+{
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ sec_mipi_dsim_resume(dev);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx_sec_dsim_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(imx_sec_dsim_suspend,
+ imx_sec_dsim_resume)
+ SET_RUNTIME_PM_OPS(imx_sec_dsim_runtime_suspend,
+ imx_sec_dsim_runtime_resume,
+ NULL)
+};
+
+struct platform_driver imx_sec_dsim_driver = {
+ .probe = imx_sec_dsim_probe,
+ .remove = imx_sec_dsim_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = imx_sec_dsim_dt_ids,
+ .pm = &imx_sec_dsim_pm_ops,
+ },
+};
+
+module_platform_driver(imx_sec_dsim_driver);
+
+MODULE_DESCRIPTION("NXP i.MX MIPI DSI Host Controller driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");