Commit 1c66496b authored by Kevin Tang's avatar Kevin Tang Committed by Maarten Lankhorst
Browse files

drm/sprd: add Unisoc's drm mipi dsi&dphy driver



Adds dsi host controller support for the Unisoc's display subsystem.
Adds dsi phy support for the Unisoc's display subsystem.
Only MIPI DSI Displays supported, DP/TV/HMDI will be support
in the feature.

v1:
  - Remove dphy and dsi graph binding, merge the dphy driver into the dsi.

v2:
  - Use drm_xxx to replace all DRM_XXX.
  - Use kzalloc to replace devm_kzalloc for sprd_dsi structure init.

v4:
  - Use drmm_helpers to allocate encoder.
  - Move allocate encoder and connector to bind function.

v5:
  - Drop the dsi ip file prefix.
  - Fix the checkpatch warnings.
  - Add Signed-off-by for dsi&dphy patch.
  - Use the mode_flags of mipi_dsi_device to setup crtc DPI and EDPI mode.

v6:
  - Redesign the way to access the dsi register.
  - Reduce the dsi_context member variables.

v7:
  - Fix codeing style issue by checkpatch.
  - Drop the pll registers structure define.
  - Use bridge API instead of drm panel API.
  - Register mipi_dsi_host on probe phase;
  - Remove some unused function.

v8:
  - Fix missing signed-off-by.
  - Move component_add to dsi_host.attach callback.

Cc: Orson Zhai <orsonzhai@gmail.com>
Cc: Chunyan Zhang <zhang.lyra@gmail.com>
Signed-off-by: default avatarKevin Tang <kevin.tang@unisoc.com>
Signed-off-by: default avatarMaarten Lankhorst <maarten.lankhorst@linux.intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20211207142717.30296-7-kevin3.tang@gmail.com
parent 2295bbd3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ config DRM_SPRD
	select DRM_GEM_CMA_HELPER
	select DRM_KMS_CMA_HELPER
	select DRM_KMS_HELPER
	select DRM_MIPI_DSI
	select VIDEOMODE_HELPERS
	help
	  Choose this option if you have a Unisoc chipset.
+6 −2
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0

obj-y := sprd_drm.o \
	sprd_dpu.o
sprd-drm-y := sprd_drm.o \
	sprd_dpu.o \
	sprd_dsi.o \
	megacores_pll.o

obj-$(CONFIG_DRM_SPRD) += sprd-drm.o
 No newline at end of file
+305 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 Unisoc Inc.
 */

#include <asm/div64.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/regmap.h>
#include <linux/string.h>

#include "sprd_dsi.h"

#define L						0
#define H						1
#define CLK						0
#define DATA					1
#define INFINITY				0xffffffff
#define MIN_OUTPUT_FREQ			(100)

#define AVERAGE(a, b) (min(a, b) + abs((b) - (a)) / 2)

/* sharkle */
#define VCO_BAND_LOW	750
#define VCO_BAND_MID	1100
#define VCO_BAND_HIGH	1500
#define PHY_REF_CLK	26000

static int dphy_calc_pll_param(struct dphy_pll *pll)
{
	const u32 khz = 1000;
	const u32 mhz = 1000000;
	const unsigned long long factor = 100;
	unsigned long long tmp;
	int i;

	pll->potential_fvco = pll->freq / khz;
	pll->ref_clk = PHY_REF_CLK / khz;

	for (i = 0; i < 4; ++i) {
		if (pll->potential_fvco >= VCO_BAND_LOW &&
		    pll->potential_fvco <= VCO_BAND_HIGH) {
			pll->fvco = pll->potential_fvco;
			pll->out_sel = BIT(i);
			break;
		}
		pll->potential_fvco <<= 1;
	}
	if (pll->fvco == 0)
		return -EINVAL;

	if (pll->fvco >= VCO_BAND_LOW && pll->fvco <= VCO_BAND_MID) {
		/* vco band control */
		pll->vco_band = 0x0;
		/* low pass filter control */
		pll->lpf_sel = 1;
	} else if (pll->fvco > VCO_BAND_MID && pll->fvco <= VCO_BAND_HIGH) {
		pll->vco_band = 0x1;
		pll->lpf_sel = 0;
	} else {
		return -EINVAL;
	}

	pll->nint = pll->fvco / pll->ref_clk;
	tmp = pll->fvco * factor * mhz;
	do_div(tmp, pll->ref_clk);
	tmp = tmp - pll->nint * factor * mhz;
	tmp *= BIT(20);
	do_div(tmp, 100000000);
	pll->kint = (u32)tmp;
	pll->refin = 3; /* pre-divider bypass */
	pll->sdm_en = true; /* use fraction N PLL */
	pll->fdk_s = 0x1; /* fraction */
	pll->cp_s = 0x0;
	pll->det_delay = 0x1;

	return 0;
}

static void dphy_set_pll_reg(struct dphy_pll *pll, struct regmap *regmap)
{
	u8 reg_val[9] = {0};
	int i;

	u8 reg_addr[] = {
		0x03, 0x04, 0x06, 0x08, 0x09,
		0x0a, 0x0b, 0x0e, 0x0f
	};

	reg_val[0] = 1 | (1 << 1) |  (pll->lpf_sel << 2);
	reg_val[1] = pll->div | (1 << 3) | (pll->cp_s << 5) | (pll->fdk_s << 7);
	reg_val[2] = pll->nint;
	reg_val[3] = pll->vco_band | (pll->sdm_en << 1) | (pll->refin << 2);
	reg_val[4] = pll->kint >> 12;
	reg_val[5] = pll->kint >> 4;
	reg_val[6] = pll->out_sel | ((pll->kint << 4) & 0xf);
	reg_val[7] = 1 << 4;
	reg_val[8] = pll->det_delay;

	for (i = 0; i < sizeof(reg_addr); ++i) {
		regmap_write(regmap, reg_addr[i], reg_val[i]);
		DRM_DEBUG("%02x: %02x\n", reg_addr[i], reg_val[i]);
	}
}

int dphy_pll_config(struct dsi_context *ctx)
{
	struct sprd_dsi *dsi = container_of(ctx, struct sprd_dsi, ctx);
	struct regmap *regmap = ctx->regmap;
	struct dphy_pll *pll = &ctx->pll;
	int ret;

	pll->freq = dsi->slave->hs_rate;

	/* FREQ = 26M * (NINT + KINT / 2^20) / out_sel */
	ret = dphy_calc_pll_param(pll);
	if (ret) {
		drm_err(dsi->drm, "failed to calculate dphy pll parameters\n");
		return ret;
	}
	dphy_set_pll_reg(pll, regmap);

	return 0;
}

static void dphy_set_timing_reg(struct regmap *regmap, int type, u8 val[])
{
	switch (type) {
	case REQUEST_TIME:
		regmap_write(regmap, 0x31, val[CLK]);
		regmap_write(regmap, 0x41, val[DATA]);
		regmap_write(regmap, 0x51, val[DATA]);
		regmap_write(regmap, 0x61, val[DATA]);
		regmap_write(regmap, 0x71, val[DATA]);

		regmap_write(regmap, 0x90, val[CLK]);
		regmap_write(regmap, 0xa0, val[DATA]);
		regmap_write(regmap, 0xb0, val[DATA]);
		regmap_write(regmap, 0xc0, val[DATA]);
		regmap_write(regmap, 0xd0, val[DATA]);
		break;
	case PREPARE_TIME:
		regmap_write(regmap, 0x32, val[CLK]);
		regmap_write(regmap, 0x42, val[DATA]);
		regmap_write(regmap, 0x52, val[DATA]);
		regmap_write(regmap, 0x62, val[DATA]);
		regmap_write(regmap, 0x72, val[DATA]);

		regmap_write(regmap, 0x91, val[CLK]);
		regmap_write(regmap, 0xa1, val[DATA]);
		regmap_write(regmap, 0xb1, val[DATA]);
		regmap_write(regmap, 0xc1, val[DATA]);
		regmap_write(regmap, 0xd1, val[DATA]);
		break;
	case ZERO_TIME:
		regmap_write(regmap, 0x33, val[CLK]);
		regmap_write(regmap, 0x43, val[DATA]);
		regmap_write(regmap, 0x53, val[DATA]);
		regmap_write(regmap, 0x63, val[DATA]);
		regmap_write(regmap, 0x73, val[DATA]);

		regmap_write(regmap, 0x92, val[CLK]);
		regmap_write(regmap, 0xa2, val[DATA]);
		regmap_write(regmap, 0xb2, val[DATA]);
		regmap_write(regmap, 0xc2, val[DATA]);
		regmap_write(regmap, 0xd2, val[DATA]);
		break;
	case TRAIL_TIME:
		regmap_write(regmap, 0x34, val[CLK]);
		regmap_write(regmap, 0x44, val[DATA]);
		regmap_write(regmap, 0x54, val[DATA]);
		regmap_write(regmap, 0x64, val[DATA]);
		regmap_write(regmap, 0x74, val[DATA]);

		regmap_write(regmap, 0x93, val[CLK]);
		regmap_write(regmap, 0xa3, val[DATA]);
		regmap_write(regmap, 0xb3, val[DATA]);
		regmap_write(regmap, 0xc3, val[DATA]);
		regmap_write(regmap, 0xd3, val[DATA]);
		break;
	case EXIT_TIME:
		regmap_write(regmap, 0x36, val[CLK]);
		regmap_write(regmap, 0x46, val[DATA]);
		regmap_write(regmap, 0x56, val[DATA]);
		regmap_write(regmap, 0x66, val[DATA]);
		regmap_write(regmap, 0x76, val[DATA]);

		regmap_write(regmap, 0x95, val[CLK]);
		regmap_write(regmap, 0xA5, val[DATA]);
		regmap_write(regmap, 0xB5, val[DATA]);
		regmap_write(regmap, 0xc5, val[DATA]);
		regmap_write(regmap, 0xd5, val[DATA]);
		break;
	case CLKPOST_TIME:
		regmap_write(regmap, 0x35, val[CLK]);
		regmap_write(regmap, 0x94, val[CLK]);
		break;

	/* the following just use default value */
	case SETTLE_TIME:
		fallthrough;
	case TA_GET:
		fallthrough;
	case TA_GO:
		fallthrough;
	case TA_SURE:
		fallthrough;
	default:
		break;
	}
}

void dphy_timing_config(struct dsi_context *ctx)
{
	struct regmap *regmap = ctx->regmap;
	struct dphy_pll *pll = &ctx->pll;
	const u32 factor = 2;
	const u32 scale = 100;
	u32 t_ui, t_byteck, t_half_byteck;
	u32 range[2], constant;
	u8 val[2];
	u32 tmp = 0;

	/* t_ui: 1 ui, byteck: 8 ui, half byteck: 4 ui */
	t_ui = 1000 * scale / (pll->freq / 1000);
	t_byteck = t_ui << 3;
	t_half_byteck = t_ui << 2;
	constant = t_ui << 1;

	/* REQUEST_TIME: HS T-LPX: LP-01
	 * For T-LPX, mipi spec defined min value is 50ns,
	 * but maybe it shouldn't be too small, because BTA,
	 * LP-10, LP-00, LP-01, all of this is related to T-LPX.
	 */
	range[L] = 50 * scale;
	range[H] = INFINITY;
	val[CLK] = DIV_ROUND_UP(range[L] * (factor << 1), t_byteck) - 2;
	val[DATA] = val[CLK];
	dphy_set_timing_reg(regmap, REQUEST_TIME, val);

	/* PREPARE_TIME: HS sequence: LP-00 */
	range[L] = 38 * scale;
	range[H] = 95 * scale;
	tmp = AVERAGE(range[L], range[H]);
	val[CLK] = DIV_ROUND_UP(AVERAGE(range[L], range[H]), t_half_byteck) - 1;
	range[L] = 40 * scale + 4 * t_ui;
	range[H] = 85 * scale + 6 * t_ui;
	tmp |= AVERAGE(range[L], range[H]) << 16;
	val[DATA] = DIV_ROUND_UP(AVERAGE(range[L], range[H]), t_half_byteck) - 1;
	dphy_set_timing_reg(regmap, PREPARE_TIME, val);

	/* ZERO_TIME: HS-ZERO */
	range[L] = 300 * scale;
	range[H] = INFINITY;
	val[CLK] = DIV_ROUND_UP(range[L] * factor + (tmp & 0xffff)
			- 525 * t_byteck / 100, t_byteck) - 2;
	range[L] = 145 * scale + 10 * t_ui;
	val[DATA] = DIV_ROUND_UP(range[L] * factor
			+ ((tmp >> 16) & 0xffff) - 525 * t_byteck / 100,
			t_byteck) - 2;
	dphy_set_timing_reg(regmap, ZERO_TIME, val);

	/* TRAIL_TIME: HS-TRAIL */
	range[L] = 60 * scale;
	range[H] = INFINITY;
	val[CLK] = DIV_ROUND_UP(range[L] * factor - constant, t_half_byteck);
	range[L] = max(8 * t_ui, 60 * scale + 4 * t_ui);
	val[DATA] = DIV_ROUND_UP(range[L] * 3 / 2 - constant, t_half_byteck) - 2;
	dphy_set_timing_reg(regmap, TRAIL_TIME, val);

	/* EXIT_TIME: */
	range[L] = 100 * scale;
	range[H] = INFINITY;
	val[CLK] = DIV_ROUND_UP(range[L] * factor, t_byteck) - 2;
	val[DATA] = val[CLK];
	dphy_set_timing_reg(regmap, EXIT_TIME, val);

	/* CLKPOST_TIME: */
	range[L] = 60 * scale + 52 * t_ui;
	range[H] = INFINITY;
	val[CLK] = DIV_ROUND_UP(range[L] * factor, t_byteck) - 2;
	val[DATA] = val[CLK];
	dphy_set_timing_reg(regmap, CLKPOST_TIME, val);

	/* SETTLE_TIME:
	 * This time is used for receiver. So for transmitter,
	 * it can be ignored.
	 */

	/* TA_GO:
	 * transmitter drives bridge state(LP-00) before releasing control,
	 * reg 0x1f default value: 0x04, which is good.
	 */

	/* TA_SURE:
	 * After LP-10 state and before bridge state(LP-00),
	 * reg 0x20 default value: 0x01, which is good.
	 */

	/* TA_GET:
	 * receiver drives Bridge state(LP-00) before releasing control
	 * reg 0x21 default value: 0x03, which is good.
	 */
}
+13 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@

#include "sprd_drm.h"
#include "sprd_dpu.h"
#include "sprd_dsi.h"

/* Global control registers */
#define REG_DPU_CTRL	0x04
@@ -618,9 +619,21 @@ static void sprd_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
	struct sprd_dpu *dpu = to_sprd_crtc(crtc);
	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
	struct drm_encoder *encoder;
	struct sprd_dsi *dsi;

	drm_display_mode_to_videomode(mode, &dpu->ctx.vm);

	drm_for_each_encoder_mask(encoder, crtc->dev,
				  crtc->state->encoder_mask) {
		dsi = encoder_to_dsi(encoder);

		if (dsi->slave->mode_flags & MIPI_DSI_MODE_VIDEO)
			dpu->ctx.if_type = SPRD_DPU_IF_DPI;
		else
			dpu->ctx.if_type = SPRD_DPU_IF_EDPI;
	}

	sprd_dpi_init(dpu);
}

+1 −0
Original line number Diff line number Diff line
@@ -181,6 +181,7 @@ static struct platform_driver sprd_drm_driver = {
static struct platform_driver *sprd_drm_drivers[]  = {
	&sprd_drm_driver,
	&sprd_dpu_driver,
	&sprd_dsi_driver,
};

static int __init sprd_drm_init(void)
Loading