Skip to content
Snippets Groups Projects
dw-mipi-dsi.c 34.4 KiB
Newer Older
	dw_mipi_dsi_wait_for_two_frames(adjusted_mode);

	/* Switch to cmd mode for panel-bridge pre_enable & panel prepare */
	dw_mipi_dsi_set_mode(dsi, 0);

	if (phy_ops->power_on)
		phy_ops->power_on(dsi->plat_data->priv_data);
static void dw_mipi_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
						 struct drm_bridge_state *old_bridge_state)
{
	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);

	/* Power up the dsi ctl into a command mode */
	dw_mipi_dsi_mode_set(dsi, &dsi->mode);
	if (dsi->slave)
		dw_mipi_dsi_mode_set(dsi->slave, &dsi->mode);
}

static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge,
					const struct drm_display_mode *mode,
					const struct drm_display_mode *adjusted_mode)
{
	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);

	/* Store the display mode for later use in pre_enable callback */
	drm_mode_copy(&dsi->mode, adjusted_mode);
static void dw_mipi_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
					     struct drm_bridge_state *old_bridge_state)
{
	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);

	/* Switch to video mode for panel-bridge enable & panel enable */
	dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
	if (dsi->slave)
		dw_mipi_dsi_set_mode(dsi->slave, MIPI_DSI_MODE_VIDEO);
}

static enum drm_mode_status
dw_mipi_dsi_bridge_mode_valid(struct drm_bridge *bridge,
			      const struct drm_display_info *info,
			      const struct drm_display_mode *mode)
{
	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
	const struct dw_mipi_dsi_plat_data *pdata = dsi->plat_data;
	enum drm_mode_status mode_status = MODE_OK;

	if (pdata->mode_valid)
		mode_status = pdata->mode_valid(pdata->priv_data, mode,
						dsi->mode_flags,
						dw_mipi_dsi_get_lanes(dsi),
						dsi->format);
static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge,
				     enum drm_bridge_attach_flags flags)
{
	struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);

	if (!bridge->encoder) {
		DRM_ERROR("Parent encoder object not found\n");
		return -ENODEV;
	}

	/* Set the encoder type as caller does not know it */
	bridge->encoder->encoder_type = DRM_MODE_ENCODER_DSI;

	/* Attach the panel-bridge to the dsi bridge */
	return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
				 flags);
static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
	.atomic_duplicate_state	= drm_atomic_helper_bridge_duplicate_state,
	.atomic_destroy_state	= drm_atomic_helper_bridge_destroy_state,
	.atomic_get_input_bus_fmts = dw_mipi_dsi_bridge_atomic_get_input_bus_fmts,
	.atomic_check		= dw_mipi_dsi_bridge_atomic_check,
	.atomic_reset		= drm_atomic_helper_bridge_reset,
	.atomic_pre_enable	= dw_mipi_dsi_bridge_atomic_pre_enable,
	.atomic_enable		= dw_mipi_dsi_bridge_atomic_enable,
	.atomic_post_disable	= dw_mipi_dsi_bridge_post_atomic_disable,
	.mode_set		= dw_mipi_dsi_bridge_mode_set,
	.mode_valid		= dw_mipi_dsi_bridge_mode_valid,
	.attach			= dw_mipi_dsi_bridge_attach,
static int dw_mipi_dsi_debugfs_write(void *data, u64 val)
{
	struct debugfs_entries *vpg = data;
	struct dw_mipi_dsi *dsi;
	u32 mode_cfg;

	if (!vpg)
		return -ENODEV;

	dsi = vpg->dsi;

	*vpg->reg = (bool)val;

	mode_cfg = dsi_read(dsi, DSI_VID_MODE_CFG);

	if (*vpg->reg)
		mode_cfg |= vpg->mask;
	else
		mode_cfg &= ~vpg->mask;

	dsi_write(dsi, DSI_VID_MODE_CFG, mode_cfg);

	return 0;
}

static int dw_mipi_dsi_debugfs_show(void *data, u64 *val)
{
	struct debugfs_entries *vpg = data;

	if (!vpg)
		return -ENODEV;

	*val = *vpg->reg;

	return 0;
}

DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_mipi_dsi_debugfs_show,
			 dw_mipi_dsi_debugfs_write, "%llu\n");

static void debugfs_create_files(void *data)
{
	struct dw_mipi_dsi *dsi = data;
	struct debugfs_entries debugfs[] = {
		REGISTER(vpg, VID_MODE_VPG_ENABLE, dsi),
		REGISTER(vpg_horizontal, VID_MODE_VPG_HORIZONTAL, dsi),
		REGISTER(vpg_ber_pattern, VID_MODE_VPG_MODE, dsi),
	};
	int i;

	dsi->debugfs_vpg = kmemdup(debugfs, sizeof(debugfs), GFP_KERNEL);
	if (!dsi->debugfs_vpg)
		return;

	for (i = 0; i < ARRAY_SIZE(debugfs); i++)
		debugfs_create_file(dsi->debugfs_vpg[i].name, 0644,
				    dsi->debugfs, &dsi->debugfs_vpg[i],
				    &fops_x32);
}

static void dw_mipi_dsi_debugfs_init(struct dw_mipi_dsi *dsi)
{
	dsi->debugfs = debugfs_create_dir(dev_name(dsi->dev), NULL);
	if (IS_ERR(dsi->debugfs)) {
		dev_err(dsi->dev, "failed to create debugfs root\n");
		return;
	}

}

static void dw_mipi_dsi_debugfs_remove(struct dw_mipi_dsi *dsi)
{
	debugfs_remove_recursive(dsi->debugfs);
}

#else

static void dw_mipi_dsi_debugfs_init(struct dw_mipi_dsi *dsi) { }
static void dw_mipi_dsi_debugfs_remove(struct dw_mipi_dsi *dsi) { }

#endif /* CONFIG_DEBUG_FS */

static struct dw_mipi_dsi *
__dw_mipi_dsi_probe(struct platform_device *pdev,
		    const struct dw_mipi_dsi_plat_data *plat_data)
{
	struct device *dev = &pdev->dev;
	struct reset_control *apb_rst;
	struct dw_mipi_dsi *dsi;
	int ret;

	dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
	if (!dsi)
		return ERR_PTR(-ENOMEM);

	dsi->dev = dev;
	dsi->plat_data = plat_data;

	if (!plat_data->phy_ops->init || !plat_data->phy_ops->get_lane_mbps ||
	    !plat_data->phy_ops->get_timing) {
		DRM_ERROR("Phy not properly configured\n");
		return ERR_PTR(-ENODEV);
	}

	if (!plat_data->base) {
		dsi->base = devm_platform_ioremap_resource(pdev, 0);
		if (IS_ERR(dsi->base))
			return ERR_PTR(-ENODEV);

	} else {
		dsi->base = plat_data->base;
	}

	dsi->pclk = devm_clk_get(dev, "pclk");
	if (IS_ERR(dsi->pclk)) {
		ret = PTR_ERR(dsi->pclk);
		dev_err(dev, "Unable to get pclk: %d\n", ret);
		return ERR_PTR(ret);
	}

	/*
	 * Note that the reset was not defined in the initial device tree, so
	 * we have to be prepared for it not being found.
	 */
	apb_rst = devm_reset_control_get_optional_exclusive(dev, "apb");
	if (IS_ERR(apb_rst)) {
		ret = PTR_ERR(apb_rst);
			dev_err(dev, "Unable to get reset control: %d\n", ret);
	}

	if (apb_rst) {
		ret = clk_prepare_enable(dsi->pclk);
		if (ret) {
			dev_err(dev, "%s: Failed to enable pclk\n", __func__);
			return ERR_PTR(ret);
		}

		reset_control_assert(apb_rst);
		usleep_range(10, 20);
		reset_control_deassert(apb_rst);

		clk_disable_unprepare(dsi->pclk);
	}

	dw_mipi_dsi_debugfs_init(dsi);
	pm_runtime_enable(dev);

	dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
	dsi->dsi_host.dev = dev;
	ret = mipi_dsi_host_register(&dsi->dsi_host);
	if (ret) {
		dev_err(dev, "Failed to register MIPI host: %d\n", ret);
		dw_mipi_dsi_debugfs_remove(dsi);
		return ERR_PTR(ret);
	}

	dsi->bridge.driver_private = dsi;
	dsi->bridge.funcs = &dw_mipi_dsi_bridge_funcs;
	dsi->bridge.of_node = pdev->dev.of_node;

	return dsi;
}

static void __dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi)
{
	pm_runtime_disable(dsi->dev);
	dw_mipi_dsi_debugfs_remove(dsi);
void dw_mipi_dsi_set_slave(struct dw_mipi_dsi *dsi, struct dw_mipi_dsi *slave)
{
	/* introduce controllers to each other */
	dsi->slave = slave;
	dsi->slave->master = dsi;

	/* migrate settings for already attached displays */
	dsi->slave->lanes = dsi->lanes;
	dsi->slave->channel = dsi->channel;
	dsi->slave->format = dsi->format;
	dsi->slave->mode_flags = dsi->mode_flags;
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_set_slave);

struct drm_bridge *dw_mipi_dsi_get_bridge(struct dw_mipi_dsi *dsi)
{
	return &dsi->bridge;
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_get_bridge);

/*
 * Probe/remove API, used from platforms based on the DRM bridge API.
 */
struct dw_mipi_dsi *
dw_mipi_dsi_probe(struct platform_device *pdev,
		  const struct dw_mipi_dsi_plat_data *plat_data)
	return __dw_mipi_dsi_probe(pdev, plat_data);
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_probe);

void dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi)
{
	__dw_mipi_dsi_remove(dsi);
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_remove);

/*
 * Bind/unbind API, used from platforms based on the component framework.
 */
int dw_mipi_dsi_bind(struct dw_mipi_dsi *dsi, struct drm_encoder *encoder)
	return drm_bridge_attach(encoder, &dsi->bridge, NULL, 0);
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_bind);

void dw_mipi_dsi_unbind(struct dw_mipi_dsi *dsi)
{
}
EXPORT_SYMBOL_GPL(dw_mipi_dsi_unbind);

MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
MODULE_DESCRIPTION("DW MIPI DSI host controller driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:dw-mipi-dsi");