linux-postmarketos-qcom-sm8550: nt37705: support 48/90/120/165 Hz
Expose the five timings of the vendor CSOT panel (48, 60, 90, 120 and 165 Hz, 60 preferred). All of them share porches, PHY timings and the DSI clock; only the timing-switch commands differ (2f XX and e0 YY on page 08), so prepare() picks them from the refresh rate of the CRTC mode. Changing the rate is a full modeset (panel power cycle). Tested on hardware: all rates selectable from GNOME display settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ maintainer="snlavaise <snlavaise@gmail.com>"
|
||||
_flavor="postmarketos-qcom-sm8550"
|
||||
pkgname=linux-$_flavor
|
||||
pkgver=7.2
|
||||
pkgrel=48
|
||||
pkgrel=49
|
||||
pkgdesc="Mainline kernel for Qualcomm SM8550 (Snapdragon 8 Gen 2) + Motorola rtwo"
|
||||
arch="aarch64"
|
||||
_carch="arm64"
|
||||
@@ -174,6 +174,6 @@ cf4889c4dca8d3bac7544d71284e6b592d6014d9ad94badfc6d2cbe56a8402c61e4c0811c2f48d33
|
||||
7673bec5661e692721bce41e72a330180e653d2b1b0a5cb3d36bdc869ad4a32ce23a6048d01c9cf2cf5c78c2a7e33899a0a149c65e9754d9d1c2cd71b542b84c msm-dsi-keep-panel-dsc-config.patch
|
||||
3268835bcb539a77d16c888d933c856df6bb591861d30c7c61c6e0ab70751bac6248961ec30fedf9a62bebdcd63113cf1dd14e58a1b53262493d5e0e90dbbe3f msm-dp-ignore-invalid-lttpr-caps.patch
|
||||
4f5ea9bfe01a6df166fdad601f6bd74d4b39049be924e8b4df58a8acbba7e9ebfc568bd81df70354209642f68e113804b9d1b62cbd3c83c319fae5fefcf07595 msm-dp-no-push-idle-when-link-down.patch
|
||||
e8df16de9b413fedd0d36414311216e68e69363e1bfbba8c7b892120f860d217b58ceeffeb92566a5617177b0f190a64e9e2284d6d4285e72db35cf40770e41a panel-novatek-nt37705.c
|
||||
f2ec70d834577378c564e8cab51879f14815693a04af5382b3a778a598a1b3c86585ff9dadf004fa526920778d6540cc44db769a4f75c2af883dcf52c2b70af8 panel-novatek-nt37705.c
|
||||
dd95e80f62879d5167e7bfc7d74b4a64669a43dbf5aacb6a13e62d1ed4a88677ef1042f8cecc46e05e3d12ba75f070a5927df308c90fe846af93b78c6b310e80 typec-mux-ps5169.c
|
||||
"
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
* Sequences transcribed byte-by-byte from the vendor device tree node
|
||||
* "qcom,mdss_dsi_mot_csot_nt37705_667_curve_1080x2400_dsc_cmd_v1"
|
||||
* (panel name mipi_mot_cmd_csot_1080p_dsc_667, panel supplier "csot"),
|
||||
* see pmos/panel-nt37705-v1-dump.md. Only the 60 Hz timing (timing@0, 2f01 +
|
||||
* e000) is exposed for the first bring-up. The other rates only differ in
|
||||
* their timing-switch-command: 120 Hz 2f02/e041, 90 Hz 2f04/e000,
|
||||
* 48 Hz 2f05/e000, 165 Hz 2f03/e041 (same porches and DSI clock).
|
||||
* see pmos/panel-nt37705-v1-dump.md. The stock node has five timings
|
||||
* (48/60/90/120/165 Hz) with the same porches, PHY timings and DSI clock;
|
||||
* their on-commands only differ in two bytes, the frame rate select (0x2f)
|
||||
* and the first 0xe0 parameter of CMD page 8. All five are exposed; 60 Hz
|
||||
* stays the preferred mode.
|
||||
*
|
||||
* DSC: besides the Novatek MCS setup in the on command (0x90 enable, 0x91
|
||||
* parameters), the standard PPS and compression-mode packets are sent after
|
||||
@@ -30,6 +31,9 @@
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#include <drm/display/drm_dsc.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/display/drm_dsc_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_modes.h>
|
||||
@@ -41,6 +45,7 @@ struct nt37705_csot {
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct regulator_bulk_data *supplies;
|
||||
struct drm_connector *connector;
|
||||
ktime_t off_since;
|
||||
};
|
||||
|
||||
@@ -201,10 +206,29 @@ static void nt37705_csot_write_dsc_params(struct mipi_dsi_multi_context *dsi_ctx
|
||||
}
|
||||
|
||||
/*
|
||||
* Full 60 Hz on-command from timing@0 of the stock node, ending with
|
||||
* Per-rate bytes of the stock on-command (and timing-switch-command):
|
||||
* 0x2f frame rate select, and the 0xe0 parameter written on CMD page 8.
|
||||
*/
|
||||
struct nt37705_csot_rate {
|
||||
unsigned int vrefresh;
|
||||
u8 frame_rate;
|
||||
u8 e0;
|
||||
};
|
||||
|
||||
static const struct nt37705_csot_rate nt37705_csot_rates[] = {
|
||||
{ .vrefresh = 60, .frame_rate = 0x01, .e0 = 0x00 }, /* timing@0 */
|
||||
{ .vrefresh = 120, .frame_rate = 0x02, .e0 = 0x41 }, /* timing@1 */
|
||||
{ .vrefresh = 90, .frame_rate = 0x04, .e0 = 0x00 }, /* timing@2 */
|
||||
{ .vrefresh = 48, .frame_rate = 0x05, .e0 = 0x00 }, /* timing@3 */
|
||||
{ .vrefresh = 165, .frame_rate = 0x03, .e0 = 0x41 }, /* timing@4 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Full on-command from the stock node, ending with
|
||||
* sleep-out (100 ms) and display-on as in the vendor sequence.
|
||||
*/
|
||||
static int nt37705_csot_on(struct nt37705_csot *ctx)
|
||||
static int nt37705_csot_on(struct nt37705_csot *ctx,
|
||||
const struct nt37705_csot_rate *rate)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = ctx->dsi;
|
||||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
|
||||
@@ -240,7 +264,7 @@ static int nt37705_csot_on(struct nt37705_csot *ctx)
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x02);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfe, 0x03);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xf0, 0x55, 0xaa, 0x52, 0x08, 0x08);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xe0, 0x00);
|
||||
mipi_dsi_dcs_write_var_seq_multi(&dsi_ctx, 0xe0, rate->e0);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xff, 0xaa, 0x55, 0xa5, 0x81);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x6f, 0x1e);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0xfb, 0x0f);
|
||||
@@ -273,7 +297,7 @@ static int nt37705_csot_on(struct nt37705_csot *ctx)
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x17, 0x10);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2a, 0x00, 0x00, 0x04, 0x37);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2b, 0x00, 0x00, 0x09, 0x5f);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x2f, 0x01);
|
||||
mipi_dsi_dcs_write_var_seq_multi(&dsi_ctx, 0x2f, rate->frame_rate);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x5f, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x35, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(&dsi_ctx, 0x51, 0x00, 0x00);
|
||||
@@ -327,6 +351,34 @@ static int nt37705_csot_off(struct nt37705_csot *ctx)
|
||||
return dsi_ctx.accum_err;
|
||||
}
|
||||
|
||||
/*
|
||||
* drm_panel callbacks do not get the mode. prepare() runs from the bridge
|
||||
* chain's pre_enable during an atomic commit, after the new states have
|
||||
* been swapped in, so the connector passed to get_modes() leads to the mode
|
||||
* being set. Anything unexpected falls back to 60 Hz.
|
||||
*/
|
||||
static const struct nt37705_csot_rate *
|
||||
nt37705_csot_current_rate(struct nt37705_csot *ctx)
|
||||
{
|
||||
struct drm_connector *connector = ctx->connector;
|
||||
struct drm_crtc *crtc;
|
||||
int vrefresh, i;
|
||||
|
||||
if (!connector || !connector->state || !connector->state->crtc)
|
||||
return &nt37705_csot_rates[0];
|
||||
|
||||
crtc = connector->state->crtc;
|
||||
if (!crtc->state)
|
||||
return &nt37705_csot_rates[0];
|
||||
|
||||
vrefresh = drm_mode_vrefresh(&crtc->state->mode);
|
||||
for (i = 0; i < ARRAY_SIZE(nt37705_csot_rates); i++)
|
||||
if (nt37705_csot_rates[i].vrefresh == vrefresh)
|
||||
return &nt37705_csot_rates[i];
|
||||
|
||||
return &nt37705_csot_rates[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Everything is sent from prepare (DSI host already up thanks to
|
||||
* prepare_prev_first), followed by the standard PPS and compression-mode
|
||||
@@ -338,6 +390,7 @@ static int nt37705_csot_prepare(struct drm_panel *panel)
|
||||
{
|
||||
struct nt37705_csot *ctx = to_nt37705_csot(panel);
|
||||
struct device *dev = &ctx->dsi->dev;
|
||||
const struct nt37705_csot_rate *rate = nt37705_csot_current_rate(ctx);
|
||||
struct drm_dsc_picture_parameter_set pps;
|
||||
s64 elapsed;
|
||||
int ret;
|
||||
@@ -353,7 +406,9 @@ static int nt37705_csot_prepare(struct drm_panel *panel)
|
||||
|
||||
nt37705_csot_reset(ctx);
|
||||
|
||||
ret = nt37705_csot_on(ctx);
|
||||
dev_info(dev, "on at %u Hz\n", rate->vrefresh);
|
||||
|
||||
ret = nt37705_csot_on(ctx, rate);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to initialize panel: %d\n", ret);
|
||||
goto err;
|
||||
@@ -409,28 +464,56 @@ static int nt37705_csot_unprepare(struct drm_panel *panel)
|
||||
}
|
||||
|
||||
/*
|
||||
* 60 Hz modeline, geometry from the stock timing@0:
|
||||
* Geometry from the stock timings, identical for every rate:
|
||||
* hbp/hfp/hpw = 0x20/0x20/0x10, vbp/vfp/vpw = 0x04/0x0c/0x04 (1080x2400).
|
||||
*/
|
||||
static const struct drm_display_mode nt37705_csot_mode = {
|
||||
.clock = (1080 + 32 + 16 + 32) * (2400 + 4 + 12 + 4) * 60 / 1000,
|
||||
.hdisplay = 1080,
|
||||
.hsync_start = 1080 + 32,
|
||||
.hsync_end = 1080 + 32 + 16,
|
||||
.htotal = 1080 + 32 + 16 + 32,
|
||||
.vdisplay = 2400,
|
||||
.vsync_start = 2400 + 4,
|
||||
.vsync_end = 2400 + 4 + 12,
|
||||
.vtotal = 2400 + 4 + 12 + 4,
|
||||
.width_mm = 69,
|
||||
.height_mm = 154,
|
||||
.type = DRM_MODE_TYPE_DRIVER,
|
||||
#define NT37705_CSOT_MODE(hz) { \
|
||||
.clock = (1080 + 32 + 16 + 32) * (2400 + 4 + 12 + 4) * (hz) / 1000, \
|
||||
.hdisplay = 1080, \
|
||||
.hsync_start = 1080 + 32, \
|
||||
.hsync_end = 1080 + 32 + 16, \
|
||||
.htotal = 1080 + 32 + 16 + 32, \
|
||||
.vdisplay = 2400, \
|
||||
.vsync_start = 2400 + 4, \
|
||||
.vsync_end = 2400 + 4 + 12, \
|
||||
.vtotal = 2400 + 4 + 12 + 4, \
|
||||
.width_mm = 69, \
|
||||
.height_mm = 154, \
|
||||
.type = DRM_MODE_TYPE_DRIVER, \
|
||||
}
|
||||
|
||||
/* Same order as nt37705_csot_rates[]; the first one is the preferred mode. */
|
||||
static const struct drm_display_mode nt37705_csot_modes[] = {
|
||||
NT37705_CSOT_MODE(60),
|
||||
NT37705_CSOT_MODE(120),
|
||||
NT37705_CSOT_MODE(90),
|
||||
NT37705_CSOT_MODE(48),
|
||||
NT37705_CSOT_MODE(165),
|
||||
};
|
||||
|
||||
static int nt37705_csot_get_modes(struct drm_panel *panel,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
return drm_connector_helper_get_modes_fixed(connector, &nt37705_csot_mode);
|
||||
struct nt37705_csot *ctx = to_nt37705_csot(panel);
|
||||
struct drm_display_mode *mode;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(nt37705_csot_modes); i++) {
|
||||
mode = drm_mode_duplicate(connector->dev, &nt37705_csot_modes[i]);
|
||||
if (!mode)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_mode_set_name(mode);
|
||||
if (i == 0)
|
||||
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
||||
drm_mode_probed_add(connector, mode);
|
||||
}
|
||||
|
||||
connector->display_info.width_mm = nt37705_csot_modes[0].width_mm;
|
||||
connector->display_info.height_mm = nt37705_csot_modes[0].height_mm;
|
||||
ctx->connector = connector;
|
||||
|
||||
return ARRAY_SIZE(nt37705_csot_modes);
|
||||
}
|
||||
|
||||
static const struct drm_panel_funcs nt37705_csot_panel_funcs = {
|
||||
|
||||
Reference in New Issue
Block a user