From patchwork Tue Dec 7 14:27:17 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kevin Tang X-Patchwork-Id: 12661947 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 30364C433F5 for ; Tue, 7 Dec 2021 14:28:42 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 9ABFC7AD73; Tue, 7 Dec 2021 14:28:41 +0000 (UTC) Received: from mail-pl1-x62f.google.com (mail-pl1-x62f.google.com [IPv6:2607:f8b0:4864:20::62f]) by gabe.freedesktop.org (Postfix) with ESMTPS id EAD807AD73 for ; Tue, 7 Dec 2021 14:28:40 +0000 (UTC) Received: by mail-pl1-x62f.google.com with SMTP id y8so9569841plg.1 for ; Tue, 07 Dec 2021 06:28:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=AG+In2ijRFca34gowRFyNdplbeSZ3GYd9SgUbmud8aE=; b=Ewnfn0O7Av/1YGecXp5nVtci1kF6Fxl6xcpRRQYtGB9GXKOgv8LoNi6kft4HPW9gIC 1o48cAzSPzqNt0f3diQtGmOr8ODxXX/nozRUFezNCEx3T1vlelFA17L8kFw/2+8ekDso gLfFZGq7ljz2qiIHfk1znm5b7Wlihy+JD/SibJDYOKSmY9i8mpJO7uSJy/NB7TAxIkf0 SRbbMKJwp9J7qIL+CZRQRwrFCADUVUbPrv+ERWX6dtxVbIzfAnfhC1/uW+2S2mTdY2hr XcKUvtpDmDfFxyLjRb07dTM1oturHXthJUhwIsZWK5Vqb3ujMEeTAf3y/DtnJQUq18s+ L0dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=AG+In2ijRFca34gowRFyNdplbeSZ3GYd9SgUbmud8aE=; b=wXqMeR3mBOoYoRzk8Nxg6xx+u68xtzsmMYiNVd2JWLoPHU/TSTL0k1G1keh7fbz+Ti Qx+Gwl6vkMBqIcVXyFohuGXm9r35AJpVPRTlsJZHwNwdXATl8vmG9D4ALwbIhIBw5EEg Yrbw5JBMHrA3FDGB4Y0EU5svGeY30AnQolWgzw355zPCMtI7opo9xUn5FxWz3elIKXyk 1glx0Hvts2GaeWcTkqfnlUtVfo2bhfys8wNv0H9gzNaesRfogiNADBJZKUeP+fgbdaN+ x3wVUOEX2Ot5IU6yw77rS3tlI8xO25TI/b2cOOTmsgq3eNpK4VSlQ4aLD+dud/lksemy EOgQ== X-Gm-Message-State: AOAM532PyjqamPuJTignLg48YPHWZV8VAOxzeAHmRtbL7idyzd3Yfx63 ze8ENtPFPP90w9MJYlfPzrs= X-Google-Smtp-Source: ABdhPJxw+E4P9EcRLeFHa2WQ76CsHwO8f8M6Of3Bq8Z8kMuQluijAgJOkf9Gls/pARA6Ril7/epNFw== X-Received: by 2002:a17:90a:fe0a:: with SMTP id ck10mr7033774pjb.216.1638887320298; Tue, 07 Dec 2021 06:28:40 -0800 (PST) Received: from nj08008nbu.spreadtrum.com ([103.6.5.6]) by smtp.gmail.com with ESMTPSA id d20sm16373247pfl.88.2021.12.07.06.28.32 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 07 Dec 2021 06:28:39 -0800 (PST) From: Kevin Tang To: maarten.lankhorst@linux.intel.com, mripard@kernel.org, sean@poorly.run, airlied@linux.ie, daniel@ffwll.ch, robh+dt@kernel.org, mark.rutland@arm.com, kevin3.tang@gmail.com, pony1.wu@gmail.com Subject: [PATCH v8 6/6] drm/sprd: add Unisoc's drm mipi dsi&dphy driver Date: Tue, 7 Dec 2021 22:27:17 +0800 Message-Id: <20211207142717.30296-7-kevin3.tang@gmail.com> X-Mailer: git-send-email 2.29.0 In-Reply-To: <20211207142717.30296-1-kevin3.tang@gmail.com> References: <20211207142717.30296-1-kevin3.tang@gmail.com> MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: orsonzhai@gmail.com, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, zhang.lyra@gmail.com Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" 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 Cc: Chunyan Zhang Signed-off-by: Kevin Tang --- drivers/gpu/drm/sprd/Kconfig | 1 + drivers/gpu/drm/sprd/Makefile | 8 +- drivers/gpu/drm/sprd/megacores_pll.c | 305 ++++++++ drivers/gpu/drm/sprd/sprd_dpu.c | 13 + drivers/gpu/drm/sprd/sprd_drm.c | 1 + drivers/gpu/drm/sprd/sprd_drm.h | 1 + drivers/gpu/drm/sprd/sprd_dsi.c | 1073 ++++++++++++++++++++++++++ drivers/gpu/drm/sprd/sprd_dsi.h | 126 +++ 8 files changed, 1526 insertions(+), 2 deletions(-) create mode 100644 drivers/gpu/drm/sprd/megacores_pll.c create mode 100644 drivers/gpu/drm/sprd/sprd_dsi.c create mode 100644 drivers/gpu/drm/sprd/sprd_dsi.h diff --git a/drivers/gpu/drm/sprd/Kconfig b/drivers/gpu/drm/sprd/Kconfig index 37762c333..3edeaeca0 100644 --- a/drivers/gpu/drm/sprd/Kconfig +++ b/drivers/gpu/drm/sprd/Kconfig @@ -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. diff --git a/drivers/gpu/drm/sprd/Makefile b/drivers/gpu/drm/sprd/Makefile index ab12b95e6..e82e6a6f8 100644 --- a/drivers/gpu/drm/sprd/Makefile +++ b/drivers/gpu/drm/sprd/Makefile @@ -1,4 +1,8 @@ # 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 diff --git a/drivers/gpu/drm/sprd/megacores_pll.c b/drivers/gpu/drm/sprd/megacores_pll.c new file mode 100644 index 000000000..3091dfdc1 --- /dev/null +++ b/drivers/gpu/drm/sprd/megacores_pll.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Unisoc Inc. + */ + +#include +#include +#include +#include +#include +#include + +#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. + */ +} diff --git a/drivers/gpu/drm/sprd/sprd_dpu.c b/drivers/gpu/drm/sprd/sprd_dpu.c index 1d10d0998..06a3414ee 100644 --- a/drivers/gpu/drm/sprd/sprd_dpu.c +++ b/drivers/gpu/drm/sprd/sprd_dpu.c @@ -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); } diff --git a/drivers/gpu/drm/sprd/sprd_drm.c b/drivers/gpu/drm/sprd/sprd_drm.c index 59b9e54f7..a077e2d4d 100644 --- a/drivers/gpu/drm/sprd/sprd_drm.c +++ b/drivers/gpu/drm/sprd/sprd_drm.c @@ -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) diff --git a/drivers/gpu/drm/sprd/sprd_drm.h b/drivers/gpu/drm/sprd/sprd_drm.h index 85d4a8b9f..95d1b972f 100644 --- a/drivers/gpu/drm/sprd/sprd_drm.h +++ b/drivers/gpu/drm/sprd/sprd_drm.h @@ -14,5 +14,6 @@ struct sprd_drm { }; extern struct platform_driver sprd_dpu_driver; +extern struct platform_driver sprd_dsi_driver; #endif /* _SPRD_DRM_H_ */ diff --git a/drivers/gpu/drm/sprd/sprd_dsi.c b/drivers/gpu/drm/sprd/sprd_dsi.c new file mode 100644 index 000000000..911b3cddc --- /dev/null +++ b/drivers/gpu/drm/sprd/sprd_dsi.c @@ -0,0 +1,1073 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Unisoc Inc. + */ + +#include +#include +#include +#include +#include +#include +#include