1
0
Fork 0

MLK-16347-4: drm/imx: Add mipi-dsi driver for mx8

Add support for the NorthWest Logic MIPI-DSI controller found
in the following i.MX8 platforms: i.MX8qm, i.MX8qxp and i.MX8mq.
This is the MIPI-DSI encoder containing the platform specific changes
and it uses the NWL MIPI-DSI bridge.
Currently only qm and qxp are tested with this driver. The mq support
will be added later.

Signed-off-by: Robert Chiras <robert.chiras@nxp.com>
pull/10/head
Robert Chiras 2017-07-14 15:31:09 +03:00 committed by Jason Liu
parent 31b60515e3
commit 228dc18bbc
4 changed files with 800 additions and 2 deletions

View File

@ -0,0 +1,120 @@
NXP specific extensions to the Northwest Logic MIPI-DSI
================================
Platform specific extentions for the NWL MIPI-DSI host controller found in
MX8 platforms.
Required properties:
- compatible: "fsl,<chip>-mipi-dsi"
The following strings are expected:
"fsl,imx8qm-mipi-dsi"
"fsl,imx8qxp-mipi-dsi"
- reg: the register range of the MIPI-DSI controller
- interrupts: the interrupt number for this module
- clock, clock-names: phandles to the MIPI-DSI clocks
The following clocks are expected on all platforms:
"phy_ref" - PHY_REF clock
"tx_esc" - TX_ESC clock (used in escape mode)
"rx_esc" - RX_ESC clock (used in escape mode)
The following clocks are expected on i.MX8qm and i.MX8qxp:
"bypass" - bypass clock
"pixel" - pixel clock (for the pixel link)
- assigned-clocks: phandles to clocks that requires initial configuration
- assigned-clock-rates: rates of the clocks that requires initial configuration
The following clocks needs to have an initial configuration:
"tx_esc" and "rx_esc"
- port: input and output port nodes with endpoint definitions as
defined in Documentation/devicetree/bindings/graph.txt;
the input port should be connected to a display
interface and the output port should be connected to a
panel or a bridge input port
- phys: phandle to the phy module representing the DPHY
inside MIPI-DSI IP block
- phy-names: should be "dphy"
Optional properties:
- power-domains phandle to the power domain
- interrupt-parent phandle to the interrupt parent, if there is one;
usually, on i.MX8qm and i.MX8qxp there is an irq
steer handling the MIPI DSI interrupts
- csr phandle to the CSR register set (required on i.MX8qm
and i.MX8qxp for the reset functions)
- assigned-clock-parents phandles to parent clocks that needs to be assigned as
parents to clocks defined in assigned-clocks
Example:
mipi_dsi1: mipi_dsi@56228000 {
compatible = "fsl,imx8qm-mipi-dsi";
reg = <0x0 0x56228000 0x0 0x1000>;
interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&irqsteer_dsi0>;
clocks =
<&clk IMX8QM_MIPI0_PXL_CLK>,
<&clk IMX8QM_MIPI0_BYPASS_CLK>,
<&clk IMX8QM_CLK_DUMMY>,
<&clk IMX8QM_MIPI0_DSI_TX_ESC_CLK>,
<&clk IMX8QM_MIPI0_DSI_RX_ESC_CLK>;
clock-names = "pixel", "bypass", "phy_ref", "tx_esc", "rx_esc";
assigned-clocks = <&clk IMX8QM_MIPI0_DSI_TX_ESC_DIV>,
<&clk IMX8QM_MIPI0_DSI_RX_ESC_DIV>;
assigned-clock-rates = <18000000>, <72000000>;
power-domains = <&pd_mipi0>;
csr = <&mipi_dsi_csr1>;
phys = <&mipi_dsi_phy1>;
phy-names = "dphy";
port@0 {
mipi_dsi1_in: endpoint {
remote-endpoint = <&dpu1_disp0_mipi_dsi>;
};
};
port@1 {
mipi_dsi1_out: endpoint {
remote-endpoint = <&adv7535_1_in>;
};
};
};
Another example, for a platform with a complex clock tree, like 8QXP:
mipi_dsi1: mipi_dsi@56228000 {
compatible = "fsl,imx8qxp-mipi-dsi";
reg = <0x0 0x56228000 0x0 0x300>;
interrupts = <16 IRQ_TYPE_LEVEL_HIGH>;
interrupt-parent = <&irqsteer_mipi_lvds0>;
clocks =
<&clk IMX8QXP_MIPI0_PIXEL_CLK>,
<&clk IMX8QXP_MIPI0_BYPASS_CLK>,
<&clk IMX8QXP_CLK_DUMMY>,
<&clk IMX8QXP_MIPI0_DSI_TX_ESC_CLK>,
<&clk IMX8QXP_MIPI0_DSI_RX_ESC_CLK>;
clock-names = "pixel", "bypass", "phy_ref", "tx_esc", "rx_esc";
assigned-clocks =
<&clk IMX8QXP_MIPI0_DSI_TX_ESC_SEL>,
<&clk IMX8QXP_MIPI0_DSI_RX_ESC_SEL>,
<&clk IMX8QXP_MIPI0_DSI_TX_ESC_CLK>,
<&clk IMX8QXP_MIPI0_DSI_RX_ESC_CLK>;
assigned-clock-rates = <0>, <0>, <18000000>, <72000000>;
assigned-clock-parents =
<&clk IMX8QXP_MIPI0_DSI_PLL_DIV2_CLK>,
<&clk IMX8QXP_MIPI0_DSI_PLL_DIV2_CLK>;
power-domains = <&pd_mipi_dsi0>;
csr = <&mipi_dsi_csr1>;
phys = <&mipi_dsi_phy1>;
phy-names = "dphy";
port@0 {
mipi_dsi1_in: endpoint {
remote-endpoint = <&dpu_disp0_mipi_dsi>;
};
};
port@1 {
mipi_dsi1_out: endpoint {
remote-endpoint = <&adv7535_1_in>;
};
};
};
* Here, we set the clock parents for the *_SEL clocks (which are the sources of
the *_CLK clocks) and also the clock rate of the *_CLK clocks.

View File

@ -45,6 +45,15 @@ config DRM_IMX_HDMI
help
Choose this if you want to use HDMI on i.MX6.
config DRM_IMX_NWL_DSI
tristate "Support for Northwest Logic MIPI DSI displays"
depends on DRM_IMX
select MFD_SYSCON
select DRM_NWL_DSI
help
Choose this to enable the internal NWL MIPI DSI controller
found on i.MX8 processors.
source "drivers/gpu/drm/imx/ipuv3/Kconfig"
source "drivers/gpu/drm/imx/dpu/Kconfig"
source "drivers/gpu/drm/imx/hdp/Kconfig"

View File

@ -7,9 +7,8 @@ obj-$(CONFIG_DRM_IMX) += imxdrm.o
obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += parallel-display.o
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_IPUV3) += ipuv3/
obj-$(CONFIG_DRM_IMX_DPU) += dpu/
obj-$(CONFIG_DRM_IMX_HDP) += hdp/

View File

@ -0,0 +1,670 @@
/*
* i.MX drm driver - Northwest Logic MIPI DSI display driver
*
* Copyright (C) 2017 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 <drm/bridge/nwl_dsi.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_of.h>
#include <drm/drmP.h>
#include <linux/clk.h>
#include <linux/component.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-mixel-mipi-dsi.h>
#include <linux/regmap.h>
#include <soc/imx8/sc/sci.h>
#include <video/videomode.h>
#include "imx-drm.h"
#define DRIVER_NAME "nwl_dsi-imx"
#define DC_ID(x) SC_R_DC_ ## x
#define MIPI_ID(x) SC_R_MIPI_ ## x
#define SYNC_CTRL(x) SC_C_SYNC_CTRL ## x
#define PXL_VLD(x) SC_C_PXL_LINK_MST ## x ## _VLD
#define PXL_ADDR(x) SC_C_PXL_LINK_MST ## x ## _ADDR
/* Possible clocks */
#define CLK_PIXEL "pixel"
#define CLK_BYPASS "bypass"
#define CLK_PHYREF "phy_ref"
/* Possible valid PHY reference clock rates*/
u32 phyref_rates[] =
{
27000000,
24000000
};
struct imx_mipi_dsi {
struct drm_encoder encoder;
struct device *dev;
struct phy *phy;
/* Optional external regs */
struct regmap *csr;
/* Optional clocks */
struct clk_config *clk_config;
size_t clk_num;
u32 tx_ulps_reg;
u32 pxl2dpi_reg;
u32 phyref_rate;
u32 instance;
bool enabled;
};
struct clk_config {
const char *id;
struct clk *clk;
bool present;
u32 rate;
};
enum imx_ext_regs {
IMX_REG_CSR = BIT(0),
};
struct devtype {
int (*poweron)(struct imx_mipi_dsi *);
int (*poweroff)(struct imx_mipi_dsi *);
u32 ext_regs; /* required external registers */
u32 tx_ulps_reg;
u32 pxl2dpi_reg;
u8 max_instances;
struct clk_config clk_config[3];
};
static int imx8qm_dsi_poweron(struct imx_mipi_dsi *dsi);
static int imx8qm_dsi_poweroff(struct imx_mipi_dsi *dsi);
static struct devtype imx8qm_dev = {
.poweron = &imx8qm_dsi_poweron,
.poweroff = &imx8qm_dsi_poweroff,
.clk_config = {
{ .id = CLK_PIXEL, .present = true },
{ .id = CLK_BYPASS, .present = true },
{ .id = CLK_PHYREF, .present = true },
},
.ext_regs = IMX_REG_CSR,
.tx_ulps_reg = 0x00,
.pxl2dpi_reg = 0x04,
.max_instances = 2,
};
static int imx8qxp_dsi_poweron(struct imx_mipi_dsi *dsi);
static int imx8qxp_dsi_poweroff(struct imx_mipi_dsi *dsi);
static struct devtype imx8qxp_dev = {
.poweron = &imx8qxp_dsi_poweron,
.poweroff = &imx8qxp_dsi_poweroff,
.clk_config = {
{ .id = CLK_PIXEL, .present = true },
{ .id = CLK_BYPASS, .present = true },
{ .id = CLK_PHYREF, .present = true },
},
.ext_regs = IMX_REG_CSR,
.tx_ulps_reg = 0x30,
.pxl2dpi_reg = 0x40,
.max_instances = 2,
};
static const struct of_device_id imx_nwl_dsi_dt_ids[] = {
{ .compatible = "fsl,imx8qm-mipi-dsi", .data = &imx8qm_dev, },
{ .compatible = "fsl,imx8qxp-mipi-dsi", .data = &imx8qxp_dev, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_nwl_dsi_dt_ids);
static inline struct imx_mipi_dsi *encoder_to_dsi(struct drm_encoder *encoder)
{
return container_of(encoder, struct imx_mipi_dsi, encoder);
}
static void imx_nwl_dsi_set_clocks(struct imx_mipi_dsi *dsi, bool enable)
{
struct device *dev = dsi->dev;
const char *id;
struct clk *clk;
unsigned long rate;
size_t i;
for (i = 0; i < dsi->clk_num; i++) {
if (!dsi->clk_config[i].present)
continue;
id = dsi->clk_config[i].id;
clk = dsi->clk_config[i].clk;
rate = dsi->clk_config[i].rate;
/* BYPASS clk must have the same rate as PHY_REF clk */
if (!strcmp(id, CLK_BYPASS))
rate = dsi->phyref_rate;
if (enable) {
if (rate > 0)
clk_set_rate(clk, rate);
clk_enable(clk);
rate = clk_get_rate(clk);
DRM_DEV_DEBUG_DRIVER(dev,
"Enabled %s clk (rate=%lu)\n", id, rate);
} else {
clk_disable(clk);
DRM_DEV_DEBUG_DRIVER(dev, "Disabled %s clk\n", id);
}
}
}
/*
* v2 is true for QXP
* On QM, we have 2 DPUs, each one with a MIPI-DSI link
* On QXP, we have 1 DPU with two MIPI-DSI links
* Because of this, we will have different initialization
* paths for MIPI0 and MIPI1 on QM vs QXP
*/
static int imx8q_dsi_poweron(struct imx_mipi_dsi *dsi, bool v2)
{
struct device *dev = dsi->dev;
int ret = 0;
sc_err_t sci_err = 0;
sc_ipc_t ipc_handle = 0;
u32 inst = dsi->instance;
u32 mu_id;
sc_rsrc_t mipi_id, dc_id;
sc_ctrl_t mipi_ctrl;
sci_err = sc_ipc_getMuID(&mu_id);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev, "Failed to get MU ID (%d)\n", sci_err);
return -ENODEV;
}
sci_err = sc_ipc_open(&ipc_handle, mu_id);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev, "Failed to open IPC (%d)\n", sci_err);
return -ENODEV;
}
mipi_id = inst?MIPI_ID(1):MIPI_ID(0);
dc_id = (!v2 && inst)?DC_ID(1):DC_ID(0);
DRM_DEV_DEBUG_DRIVER(dev, "MIPI ID: %d DC ID: %d\n",
mipi_id,
dc_id);
/* Initialize Pixel Link */
mipi_ctrl = (v2 && inst)?PXL_ADDR(2):PXL_ADDR(1);
sci_err = sc_misc_set_control(ipc_handle,
dc_id,
mipi_ctrl,
0);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev,
"Failed to set SC_C_PXL_LINK_MST%d_ADDR (%d)\n",
inst,
sci_err);
ret = -ENODEV;
goto err_ipc;
}
mipi_ctrl = (v2 && inst)?PXL_VLD(2):PXL_VLD(1);
sci_err = sc_misc_set_control(ipc_handle,
dc_id,
mipi_ctrl,
1);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev,
"Failed to set SC_C_PXL_LINK_MST%d_VLD (%d)\n",
inst + 1,
sci_err);
ret = -ENODEV;
goto err_ipc;
}
mipi_ctrl = (v2 && inst)?SYNC_CTRL(1):SYNC_CTRL(0);
sci_err = sc_misc_set_control(ipc_handle,
dc_id,
mipi_ctrl,
1);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev,
"Failed to set SC_C_SYNC_CTRL%d (%d)\n",
inst,
sci_err);
ret = -ENODEV;
goto err_ipc;
}
if (v2) {
sci_err = sc_misc_set_control(ipc_handle,
mipi_id, SC_C_MODE, 0);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to set SC_C_MODE (%d)\n",
sci_err);
sci_err = sc_misc_set_control(ipc_handle,
mipi_id, SC_C_DUAL_MODE, 0);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to set SC_C_DUAL_MODE (%d)\n",
sci_err);
sci_err = sc_misc_set_control(ipc_handle,
mipi_id, SC_C_PXL_LINK_SEL, inst);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to set SC_C_PXL_LINK_SEL (%d)\n",
sci_err);
}
/* Assert DPI and MIPI bits */
sci_err = sc_misc_set_control(ipc_handle,
mipi_id,
SC_C_DPI_RESET,
1);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev,
"Failed to assert DPI reset (%d)\n",
sci_err);
ret = -ENODEV;
goto err_ipc;
}
sci_err = sc_misc_set_control(ipc_handle,
mipi_id,
SC_C_MIPI_RESET,
1);
if (sci_err != SC_ERR_NONE) {
DRM_DEV_ERROR(dev,
"Failed to assert MIPI reset (%d)\n",
sci_err);
ret = -ENODEV;
goto err_ipc;
}
regmap_write(dsi->csr,
dsi->tx_ulps_reg,
0);
regmap_write(dsi->csr,
dsi->pxl2dpi_reg,
DPI_24_BIT);
sc_ipc_close(ipc_handle);
return ret;
err_ipc:
sc_ipc_close(ipc_handle);
return ret;
}
static int imx8q_dsi_poweroff(struct imx_mipi_dsi *dsi, bool v2)
{
struct device *dev = dsi->dev;
sc_err_t sci_err = 0;
sc_ipc_t ipc_handle = 0;
u32 mu_id;
u32 inst = dsi->instance;
sc_rsrc_t mipi_id, dc_id;
mipi_id = (inst)?SC_R_MIPI_1:SC_R_MIPI_0;
if (v2)
dc_id = SC_R_DC_0;
else
dc_id = (inst)?SC_R_DC_1:SC_R_DC_0;
/* Deassert DPI and MIPI bits */
if (sc_ipc_getMuID(&mu_id) == SC_ERR_NONE &&
sc_ipc_open(&ipc_handle, mu_id) == SC_ERR_NONE) {
sci_err = sc_misc_set_control(ipc_handle,
mipi_id, SC_C_DPI_RESET, 0);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to deassert DPI reset (%d)\n",
sci_err);
sci_err = sc_misc_set_control(ipc_handle,
mipi_id, SC_C_MIPI_RESET, 0);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to deassert MIPI reset (%d)\n",
sci_err);
sci_err = sc_misc_set_control(ipc_handle,
dc_id, SC_C_SYNC_CTRL0, 0);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to reset SC_C_SYNC_CTRL0 (%d)\n",
sci_err);
sci_err = sc_misc_set_control(ipc_handle,
dc_id, SC_C_PXL_LINK_MST1_VLD, 0);
if (sci_err != SC_ERR_NONE)
DRM_DEV_ERROR(dev,
"Failed to reset SC_C_SYNC_CTRL0 (%d)\n",
sci_err);
}
return 0;
}
static int imx8qm_dsi_poweron(struct imx_mipi_dsi *dsi)
{
return imx8q_dsi_poweron(dsi, false);
}
static int imx8qm_dsi_poweroff(struct imx_mipi_dsi *dsi)
{
return imx8q_dsi_poweroff(dsi, false);
}
static int imx8qxp_dsi_poweron(struct imx_mipi_dsi *dsi)
{
return imx8q_dsi_poweron(dsi, true);
}
static int imx8qxp_dsi_poweroff(struct imx_mipi_dsi *dsi)
{
return imx8q_dsi_poweroff(dsi, true);
}
static void imx_nwl_dsi_encoder_enable(struct drm_encoder *encoder)
{
struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
struct device *dev = dsi->dev;
const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
dev);
const struct devtype *devtype = of_id->data;
int ret;
if (dsi->enabled)
return;
DRM_DEV_INFO(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
imx_nwl_dsi_set_clocks(dsi, true);
ret = devtype->poweron(dsi);
if (ret < 0) {
DRM_DEV_ERROR(dev, "Failed to power on DSI (%d)\n", ret);
return;
}
dsi->enabled = true;
}
static void imx_nwl_dsi_encoder_disable(struct drm_encoder *encoder)
{
struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
struct device *dev = dsi->dev;
const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
dev);
const struct devtype *devtype = of_id->data;
if (!dsi->enabled)
return;
DRM_DEV_INFO(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
devtype->poweroff(dsi);
imx_nwl_dsi_set_clocks(dsi, false);
dsi->enabled = false;
}
/*
* This function will try the required phy speed for current mode
* If the phy speed can be achieved, the phy will save the speed
* configuration
*/
static int imx_nwl_try_phy_speed(struct imx_mipi_dsi *dsi,
struct drm_display_mode *mode)
{
struct device *dev = dsi->dev;
unsigned long pixclock;
unsigned long bit_clk;
size_t i, num_rates = ARRAY_SIZE(phyref_rates);
int ret = 0;
pixclock = mode->clock * 1000;
bit_clk = nwl_dsi_get_bit_clock(&dsi->encoder, pixclock);
for (i = 0; i < num_rates; i++) {
dsi->phyref_rate = phyref_rates[i];
DRM_DEV_DEBUG_DRIVER(dev, "Trying PHY ref rate: %u\n",
dsi->phyref_rate);
ret = mixel_phy_mipi_set_phy_speed(dsi->phy,
bit_clk,
dsi->phyref_rate);
/* Pick the first non-failing rate */
if (!ret)
break;
}
if (ret < 0) {
DRM_DEV_DEBUG_DRIVER(dev,
"Cannot setup PHY for mode: %ux%u @%d kHz\n",
mode->hdisplay,
mode->vdisplay,
mode->clock);
DRM_DEV_DEBUG_DRIVER(dev, "PHY_REF clk: %u, bit clk: %lu\n",
dsi->phyref_rate, bit_clk);
}
return ret;
}
static int imx_nwl_dsi_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
struct imx_mipi_dsi *dsi = encoder_to_dsi(encoder);
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
/* Try to see if the phy can satisfy the current mode */
return imx_nwl_try_phy_speed(dsi, &crtc_state->adjusted_mode);
}
static const struct drm_encoder_helper_funcs
imx_nwl_dsi_encoder_helper_funcs = {
.enable = imx_nwl_dsi_encoder_enable,
.disable = imx_nwl_dsi_encoder_disable,
.atomic_check = imx_nwl_dsi_encoder_atomic_check,
};
static void imx_nwl_dsi_encoder_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
}
static const struct drm_encoder_funcs imx_nwl_dsi_encoder_funcs = {
.destroy = imx_nwl_dsi_encoder_destroy,
};
static int imx_nwl_dsi_bind(struct device *dev,
struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
struct drm_device *drm = data;
struct imx_mipi_dsi *dsi;
struct device_node *np = dev->of_node;
const struct of_device_id *of_id = of_match_device(imx_nwl_dsi_dt_ids,
dev);
const struct devtype *devtype = of_id->data;
struct resource *res;
int irq;
struct clk *clk;
const char *clk_id;
size_t i, clk_config_sz;
int ret;
if (!np)
return -ENODEV;
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
if (!dsi)
return -ENOMEM;
dsi->dev = dev;
dsi->instance = of_alias_get_id(np, "mipi_dsi");
if (dsi->instance < 0) {
dev_err(dev, "No mipi_dsi alias found!");
return dsi->instance;
}
if (dsi->instance > devtype->max_instances - 1) {
dev_err(dev, "Too many instances! (cur: %d, max: %d)\n",
dsi->instance, devtype->max_instances);
return -ENODEV;
}
DRM_DEV_INFO(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
dsi->phy = devm_phy_get(dev, "dphy");
if (IS_ERR(dsi->phy)) {
ret = PTR_ERR(dsi->phy);
dev_err(dev, "Could not get PHY (%d)\n", ret);
return ret;
}
/* Look for optional clocks */
dsi->clk_num = ARRAY_SIZE(devtype->clk_config);
dsi->clk_config = devm_kcalloc(dev,
dsi->clk_num,
sizeof(struct clk_config),
GFP_KERNEL);
clk_config_sz = dsi->clk_num * sizeof(struct clk_config);
memcpy(dsi->clk_config, devtype->clk_config, clk_config_sz);
for (i = 0; i < dsi->clk_num; i++) {
if (!dsi->clk_config[i].present)
continue;
clk_id = dsi->clk_config[i].id;
clk = devm_clk_get(dev, clk_id);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
dev_err(dev, "Failed to get %s clock (%d)\n",
clk_id, ret);
return ret;
}
clk_prepare(clk);
dsi->clk_config[i].clk = clk;
}
/* Look for optional regmaps */
dsi->csr = syscon_regmap_lookup_by_phandle(np, "csr");
if (IS_ERR(dsi->csr) && (devtype->ext_regs & IMX_REG_CSR)) {
ret = PTR_ERR(dsi->csr);
dev_err(dev, "Failed to get CSR regmap (%d)\n", ret);
return ret;
}
dsi->tx_ulps_reg = devtype->tx_ulps_reg;
dsi->pxl2dpi_reg = devtype->pxl2dpi_reg;
platform_set_drvdata(pdev, dsi);
ret = imx_drm_encoder_parse_of(drm, &dsi->encoder, dev->of_node);
if (ret)
return ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EBUSY;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
DRM_DEV_ERROR(dev, "Failed to get device IRQ!\n");
return -EINVAL;
}
drm_encoder_helper_add(&dsi->encoder,
&imx_nwl_dsi_encoder_helper_funcs);
ret = drm_encoder_init(drm,
&dsi->encoder,
&imx_nwl_dsi_encoder_funcs,
DRM_MODE_ENCODER_DSI,
NULL);
if (ret) {
DRM_DEV_ERROR(dev, "failed to init DSI encoder (%d)\n", ret);
return ret;
}
/* Now, bind our NWL MIPI-DSI bridge */
ret = nwl_dsi_bind(dev, &dsi->encoder, dsi->phy, res, irq);
if (ret)
drm_encoder_cleanup(&dsi->encoder);
return ret;
}
static void imx_nwl_dsi_unbind(struct device *dev,
struct device *master,
void *data)
{
struct imx_mipi_dsi *dsi = dev_get_drvdata(dev);
DRM_DEV_INFO(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
imx_nwl_dsi_encoder_disable(&dsi->encoder);
drm_encoder_cleanup(&dsi->encoder);
nwl_dsi_unbind(dsi->encoder.bridge);
}
static const struct component_ops imx_nwl_dsi_component_ops = {
.bind = imx_nwl_dsi_bind,
.unbind = imx_nwl_dsi_unbind,
};
static int imx_nwl_dsi_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &imx_nwl_dsi_component_ops);
}
static int imx_nwl_dsi_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &imx_nwl_dsi_component_ops);
return 0;
}
static struct platform_driver imx_nwl_dsi_driver = {
.probe = imx_nwl_dsi_probe,
.remove = imx_nwl_dsi_remove,
.driver = {
.of_match_table = imx_nwl_dsi_dt_ids,
.name = DRIVER_NAME,
},
};
module_platform_driver(imx_nwl_dsi_driver);
MODULE_AUTHOR("NXP Semiconductor");
MODULE_DESCRIPTION("i.MX Northwest Logic MIPI-DSI driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRIVER_NAME);