Skip to content

Commit 7733b33

Browse files
committed
lk: Add basic panel header generator for LK bootloader
NOTE: Only video mode panels are supported properly at the moment.
1 parent f233a71 commit 7733b33

File tree

3 files changed

+231
-1
lines changed

3 files changed

+231
-1
lines changed

lk.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
from __future__ import annotations
3+
4+
import datetime
5+
6+
import wrap
7+
from panel import Panel, Mode, CommandSequence, LaneMap, TrafficMode
8+
9+
10+
def generate_commands(p: Panel, cmd_name: str) -> str:
11+
cmd: CommandSequence = p.cmds[cmd_name]
12+
13+
cmds = ""
14+
struct = f"static struct mipi_dsi_cmd {p.id}_{cmd_name}_command[] = {{\n"
15+
16+
s = ""
17+
i = 0
18+
for c in cmd.seq:
19+
b = bytearray()
20+
long = c.type.is_long
21+
if long:
22+
b += int.to_bytes(len(c.payload), 2, 'little') # Word count (WC)
23+
else:
24+
assert len(c.payload) <= 2, f"Payload too long: {len(c.payload)}"
25+
itr = iter(c.payload)
26+
b.append(next(itr, 0))
27+
b.append(next(itr, 0))
28+
29+
b.append(c.type.value | c.vc << 6)
30+
b.append(int(c.ack) << 5 | int(long) << 6 | int(c.last) << 7)
31+
32+
if long:
33+
b += bytes(c.payload)
34+
35+
# DMA command size must be multiple of 4
36+
mod = len(b) % 4
37+
if mod != 0:
38+
b += bytes([0xff] * (4 - mod))
39+
40+
name = f'{p.id}_{cmd_name}_cmd_{i}'
41+
cmds += f'static char {name}[] = {{\n'
42+
cmds += wrap.join('\t', ',', '', [f'{byte:#04x}' for byte in b], wrap=54)
43+
cmds += '\n};\n'
44+
45+
struct += f'\t{{ sizeof({name}), {name}, {c.wait} }},\n'
46+
i += 1
47+
48+
struct += '};'
49+
50+
return cmds + '\n' + struct
51+
52+
53+
def generate_cmd_info(p: Panel) -> str:
54+
s = f'static struct commandpanel_info {p.id}_command_panel = {{\n'
55+
if p.mode != Mode.CMD_MODE:
56+
return s + '\t/* Unused, this is a video mode panel */\n};'
57+
58+
s += '\t/* FIXME: This is a command mode panel */\n'
59+
return s + '};'
60+
61+
62+
def generate_video_info(p: Panel) -> str:
63+
s = f'static struct videopanel_info {p.id}_video_panel = {{\n'
64+
65+
s += f'''\
66+
.hsync_pulse = {int('MIPI_DSI_MODE_VIDEO_HSE' in p.flags)},
67+
.hfp_power_mode = {int(p.hfp_power_mode)},
68+
.hbp_power_mode = {int(p.hbp_power_mode)},
69+
.hsa_power_mode = {int(p.hsa_power_mode)},
70+
.bllp_eof_power_mode = {int(p.bllp_eof_power_mode)},
71+
.bllp_power_mode = {int(p.bllp_power_mode)},
72+
.traffic_mode = {list(TrafficMode.__members__.values()).index(p.traffic_mode)},
73+
/* This is bllp_eof_power_mode and bllp_power_mode combined */
74+
.bllp_eof_power = {int(p.bllp_eof_power_mode)} << 3 | {int(p.bllp_power_mode)} << 0,
75+
'''
76+
return s + '};'
77+
78+
79+
def generate_reset_seq(p: Panel) -> str:
80+
if not p.reset_seq:
81+
return ''
82+
83+
return f'''
84+
static struct panel_reset_sequence {p.id}_reset_seq = {{
85+
.pin_state = {{ {', '.join(str(res[0]) for res in p.reset_seq)} }},
86+
.sleep = {{ {', '.join(str(res[1]) for res in p.reset_seq)} }},
87+
.pin_direction = 2,
88+
}};
89+
'''
90+
91+
92+
def generate_backlight(p: Panel) -> str:
93+
if not p.backlight:
94+
return ''
95+
96+
return f'''
97+
static struct backlight {p.id}_backlight = {{
98+
.bl_interface_type = BL_{p.backlight.name},
99+
.bl_min_level = 1,
100+
.bl_max_level = {p.max_brightness},
101+
}};
102+
'''
103+
104+
105+
def generate_lk_driver(p: Panel) -> None:
106+
if 'sim' in p.id:
107+
return
108+
109+
define = f'_PANEL_{p.id.upper()}_H_'
110+
111+
with open(f'{p.id}/lk_panel_{p.id}.h', 'w') as f:
112+
f.write(f'''\
113+
// SPDX-License-Identifier: GPL-2.0-only
114+
// Copyright (c) {datetime.date.today().year} FIXME
115+
// Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree:
116+
// Copyright (c) 2014, The Linux Foundation. All rights reserved. (FIXME)
117+
118+
#ifndef {define}
119+
#define {define}
120+
121+
#include <mipi_dsi.h>
122+
#include <panel_display.h>
123+
#include <panel.h>
124+
#include <string.h>
125+
126+
static struct panel_config {p.id}_panel_data = {{
127+
.panel_node_id = "{p.node_name}",
128+
.panel_controller = "dsi:0",
129+
.panel_compatible = "qcom,mdss-dsi-panel",
130+
.panel_type = {int(p.mode == Mode.CMD_MODE)},
131+
.panel_destination = "DISPLAY_1",
132+
/* .panel_orientation not supported yet */
133+
.panel_framerate = {p.framerate},
134+
.panel_lp11_init = {int(p.lp11_init)},
135+
.panel_init_delay = {p.init_delay},
136+
}};
137+
138+
static struct panel_resolution {p.id}_panel_res = {{
139+
.panel_width = {p.h.px},
140+
.panel_height = {p.v.px},
141+
.hfront_porch = {p.h.fp},
142+
.hback_porch = {p.h.bp},
143+
.hpulse_width = {p.h.pw},
144+
.hsync_skew = {p.hsync_skew},
145+
.vfront_porch = {p.v.fp},
146+
.vback_porch = {p.v.bp},
147+
.vpulse_width = {p.v.pw},
148+
/* Borders not supported yet */
149+
}};
150+
151+
static struct color_info {p.id}_color = {{
152+
.color_format = {p.bpp},
153+
.color_order = DSI_RGB_SWAP_RGB,
154+
.underflow_color = 0xff,
155+
/* Borders and pixel packing not supported yet */
156+
}};
157+
158+
{generate_commands(p, 'on')}
159+
160+
{generate_commands(p, 'off')}
161+
162+
static struct command_state {p.id}_state = {{
163+
.oncommand_state = {int(p.cmds['on'].state == CommandSequence.State.HS_MODE)},
164+
.offcommand_state = {int(p.cmds['off'].state == CommandSequence.State.HS_MODE)},
165+
}};
166+
167+
{generate_cmd_info(p)}
168+
169+
{generate_video_info(p)}
170+
171+
static struct lane_configuration {p.id}_lane_config = {{
172+
.dsi_lanes = {p.lanes},
173+
.dsi_lanemap = {list(LaneMap.__members__.values()).index(p.lane_map)},
174+
.lane0_state = {int(p.lanes > 0)},
175+
.lane1_state = {int(p.lanes > 1)},
176+
.lane2_state = {int(p.lanes > 2)},
177+
.lane3_state = {int(p.lanes > 3)},
178+
.force_clk_lane_hs = {int('MIPI_DSI_CLOCK_NON_CONTINUOUS' not in p.flags)},
179+
}};
180+
181+
static const uint32_t {p.id}_timings[] = {{
182+
{', '.join(f'{byte:#04x}' for byte in p.timings)}
183+
}};
184+
185+
static struct panel_timing {p.id}_timing_info = {{
186+
.tclk_post = {p.tclk_post:#04x},
187+
.tclk_pre = {p.tclk_pre:#04x},
188+
}};
189+
{generate_reset_seq(p)}{generate_backlight(p)}
190+
{wrap.join(f'static inline void panel_{p.id}_select(', ',', ')',
191+
['struct panel_struct *panel', 'struct msm_panel_info *pinfo',
192+
'struct mdss_dsi_phy_ctrl *phy_db'])}
193+
{{
194+
panel->paneldata = &{p.id}_panel_data;
195+
panel->panelres = &{p.id}_panel_res;
196+
panel->color = &{p.id}_color;
197+
panel->videopanel = &{p.id}_video_panel;
198+
panel->commandpanel = &{p.id}_command_panel;
199+
panel->state = &{p.id}_state;
200+
panel->laneconfig = &{p.id}_lane_config;
201+
panel->paneltiminginfo = &{p.id}_timing_info;
202+
panel->panelresetseq = {f'&{p.id}_reset_seq' if p.reset_seq else 'NULL'};
203+
panel->backlightinfo = {f'&{p.id}_backlight' if p.backlight else 'NULL'};
204+
pinfo->mipi.panel_cmds = {p.id}_on_command;
205+
pinfo->mipi.num_of_panel_cmds = ARRAY_SIZE({p.id}_on_command);
206+
memcpy(phy_db->timing, {p.id}_timings, TIMING_SIZE);
207+
phy_db->regulator_mode = {'DSI_PHY_REGULATOR_LDO_MODE' if p.ldo_mode else 'DSI_PHY_REGULATOR_DCDC_MODE'};
208+
}}
209+
210+
#endif /* {define} */
211+
''')

lmdpdg.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from driver import generate_driver
1111
from dtsi import generate_panel_dtsi
1212
from fdt2 import Fdt2
13+
from lk import generate_lk_driver
1314
from panel import Panel
1415
from simple import generate_panel_simple
1516

@@ -27,6 +28,7 @@ def generate(p: Panel, options: generator.Options) -> None:
2728
generate_panel_simple(p)
2829
generate_driver(p, options)
2930
generate_panel_dtsi(p, options)
31+
generate_lk_driver(p)
3032

3133

3234
parser = argparse.ArgumentParser(

panel.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ def _find_mode_node(fdt: Fdt2, node: int) -> int:
207207
class Panel:
208208
def __init__(self, name: str, fdt: Fdt2, node: int) -> None:
209209
self.name = name
210-
self.id = _remove_before(_remove_prefixes(fdt.get_name(node), 'qcom,mdss_dsi_', 'ss_dsi_panel_', 'mot_').lower(), ',')
210+
self.node_name = fdt.get_name(node)
211+
self.id = _remove_before(_remove_prefixes(self.node_name, 'qcom,mdss_dsi_', 'ss_dsi_panel_', 'mot_').lower(), ',')
211212
print(f'Parsing: {self.id} ({name})')
212213
self.short_id = _replace_all(self.id, '_panel', '_video', '_vid', '_cmd',
213214
'_hd', '_qhd', '_720p', '_1080p',
@@ -283,6 +284,22 @@ def __init__(self, name: str, fdt: Fdt2, node: int) -> None:
283284
dsi_ctrl = fdt.node_offset_by_phandle(dsi_ctrl)
284285
self.ldo_mode = fdt.getprop_or_none(dsi_ctrl, 'qcom,regulator-ldo-mode') is not None
285286

287+
# Timings are usually calculated by the driver except for downstream and LK
288+
p = fdt.getprop_or_none(node, 'qcom,mdss-dsi-panel-timings')
289+
self.timings = bytes(p) if p else bytes()
290+
self.tclk_post = fdt.getprop_int32(node, 'qcom,mdss-dsi-t-clk-post')
291+
self.tclk_pre = fdt.getprop_int32(node, 'qcom,mdss-dsi-t-clk-pre')
292+
293+
# Additional weird values used by downstream and LK
294+
self.hsync_skew = fdt.getprop_int32(node, 'qcom,mdss-dsi-h-sync-skew')
295+
self.hfp_power_mode = fdt.getprop_or_none(node, 'qcom,mdss-dsi-hfp-power-mode') is not None
296+
self.hsa_power_mode = fdt.getprop_or_none(node, 'qcom,mdss-dsi-hsa-power-mode') is not None
297+
self.hbp_power_mode = fdt.getprop_or_none(node, 'qcom,mdss-dsi-hbp-power-mode') is not None
298+
self.bllp_power_mode = fdt.getprop_or_none(node, 'qcom,mdss-dsi-bllp-power-mode') is not None
299+
self.bllp_eof_power_mode = fdt.getprop_or_none(node, 'qcom,mdss-dsi-bllp-eof-power-mode') is not None
300+
self.lp11_init = fdt.getprop_or_none(node, 'qcom,mdss-dsi-lp11-init') is not None
301+
self.init_delay = fdt.getprop_int32(node, 'qcom,mdss-dsi-init-delay-us')
302+
286303
@staticmethod
287304
def parse(fdt: Fdt2, node: int) -> Panel:
288305
name = fdt.getprop_or_none(node, 'qcom,mdss-dsi-panel-name')

0 commit comments

Comments
 (0)