├── .gitattributes ├── generator.py ├── wrap.py ├── tools ├── unpackqcdt.py └── unpackbootimg.py ├── simple.py ├── dtsi.py ├── fdt2.py ├── lmdpdg.py ├── .gitignore ├── README.md ├── lk.py ├── mipi.py ├── panel.py ├── driver.py └── COPYING /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.py text diff=python 3 | -------------------------------------------------------------------------------- /generator.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from __future__ import annotations 3 | 4 | from dataclasses import dataclass 5 | from enum import Flag, auto 6 | from typing import List, Optional, TextIO, Dict 7 | 8 | 9 | class GpioFlag(Flag): 10 | ACTIVE_HIGH = 0 11 | ACTIVE_LOW = auto() 12 | 13 | 14 | @dataclass(init=False) 15 | class Options: 16 | dtb: List[TextIO] 17 | regulator: Optional[List[str]] 18 | backlight: bool 19 | backlight_gpio: bool 20 | backlight_fallback_dcs: bool 21 | dcs_get_brightness: bool 22 | ignore_wait: int 23 | dumb_dcs: bool 24 | 25 | # Added by panel driver generator 26 | compatible: str 27 | gpios: Dict[str, GpioFlag] 28 | -------------------------------------------------------------------------------- /wrap.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from __future__ import annotations 3 | 4 | from typing import List 5 | 6 | 7 | def _width(s: str) -> int: 8 | return len(s) + s.count('\t') * 7 9 | 10 | 11 | # Wrap to new line if long (without alignment) 12 | def simple(items: List[str], wrap: int = 80) -> str: 13 | s = '' 14 | for i in items: 15 | if _width(s) + len(i) + 1 > wrap: 16 | s += '\n' 17 | elif s: 18 | s += ' ' 19 | s += i 20 | return s 21 | 22 | 23 | # Wrap long argument lists if necessary and align them properly. 24 | # e.g. join('static int panel_get_modes(', ',', ')', ['struct drm_panel *panel', 'struct drm_connector *connector']) 25 | # results in static int panel_get_modes(struct drm_panel *panel, 26 | # struct drm_connector *connector) 27 | def join(prefix: str, sep: str, end: str, items: List[str], force: int = -1, wrap: int = 80) -> str: 28 | s = prefix + (sep + ' ').join(items) + end 29 | if _width(s) <= wrap: 30 | return s 31 | 32 | align = _width(prefix) 33 | wrap -= align 34 | indent = '\t' * (align // 8) + ' ' * (align % 8) 35 | 36 | s = '' 37 | line = '' 38 | 39 | last = len(items) - 1 40 | for i, item in enumerate(items): 41 | if i == last: 42 | sep = end 43 | 44 | if line: 45 | if force == i or _width(line) + len(item) + len(sep) > wrap: 46 | s += prefix + line + '\n' 47 | prefix = indent 48 | line = '' 49 | else: 50 | line += ' ' 51 | line += item + sep 52 | 53 | s += prefix + line 54 | return s 55 | -------------------------------------------------------------------------------- /tools/unpackqcdt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import struct 3 | import sys 4 | from dataclasses import dataclass 5 | from typing import Tuple 6 | 7 | QCDT_MAGIC = 'QCDT'.encode() 8 | 9 | HEADER_FORMAT = '<4sII' 10 | HEADER_SIZE = struct.calcsize(HEADER_FORMAT) 11 | 12 | PAGE_SIZE = 2048 13 | ENTRY_FORMATS = ( 14 | '= 2: 53 | r.soc_rev = entry[3] 54 | if version >= 3: 55 | r.pmic = (entry[4], entry[5], entry[6], entry[7]) 56 | 57 | print(r) 58 | records.append(r) 59 | 60 | for r in records: 61 | with open(f'plat_{r.plat_id:x}-var_{r.variant_id:x}-sub_{r.subtype_id:x}.dtb', 'wb') as f: 62 | f.write(b[r.offset:r.offset + r.size]) 63 | -------------------------------------------------------------------------------- /simple.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from __future__ import annotations 3 | 4 | import wrap 5 | from panel import Panel 6 | 7 | 8 | def generate_mode(p: Panel) -> str: 9 | return f'''\ 10 | static const struct drm_display_mode {p.short_id}_mode = {{ 11 | .clock = ({p.h.px} + {p.h.fp} + {p.h.pw} + {p.h.bp}) * ({p.v.px} + {p.v.fp} + {p.v.pw} + {p.v.bp}) * {p.framerate} / 1000, 12 | .hdisplay = {p.h.px}, 13 | .hsync_start = {p.h.px} + {p.h.fp}, 14 | .hsync_end = {p.h.px} + {p.h.fp} + {p.h.pw}, 15 | .htotal = {p.h.px} + {p.h.fp} + {p.h.pw} + {p.h.bp}, 16 | .vdisplay = {p.v.px}, 17 | .vsync_start = {p.v.px} + {p.v.fp}, 18 | .vsync_end = {p.v.px} + {p.v.fp} + {p.v.pw}, 19 | .vtotal = {p.v.px} + {p.v.fp} + {p.v.pw} + {p.v.bp}, 20 | .width_mm = {p.h.size}, 21 | .height_mm = {p.v.size}, 22 | .type = DRM_MODE_TYPE_DRIVER, 23 | }}; 24 | ''' 25 | 26 | 27 | def generate_panel_simple(p: Panel) -> None: 28 | name = p.short_id.replace('_', '-') 29 | with open(f'{p.id}/panel-simple-{name}.c', 'w') as f: 30 | f.write(f'''\ 31 | // SPDX-License-Identifier: GPL-2.0-only 32 | // Copyright (c) 2013, The Linux Foundation. All rights reserved. 33 | 34 | {generate_mode(p)} 35 | static const struct panel_desc_dsi {p.short_id} = {{ 36 | .desc = {{ 37 | .modes = &{p.short_id}_mode, 38 | .num_modes = 1, 39 | .bpc = {int(p.bpp / 3)}, 40 | .size = {{ 41 | .width = {p.h.size}, 42 | .height = {p.v.size}, 43 | }}, 44 | .connector_type = DRM_MODE_CONNECTOR_DSI, 45 | }}, 46 | {wrap.join(' .flags = ', ' |', ',', p.flags)} 47 | .format = {p.format}, 48 | .lanes = {p.lanes}, 49 | }}; 50 | ''') 51 | -------------------------------------------------------------------------------- /tools/unpackbootimg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | import struct 4 | import sys 5 | 6 | BOOT_MAGIC = 'ANDROID!'.encode() 7 | 8 | HEADER_FORMAT = '<8s10I16s512s32s1024s' 9 | HEADER_SIZE = struct.calcsize(HEADER_FORMAT) 10 | 11 | 12 | def extract_file(f, name, pos, size): 13 | f.seek(pos) 14 | with open(name, 'wb') as o: 15 | o.write(f.read(size)) 16 | 17 | 18 | def unpack_image(f): 19 | header = struct.unpack(HEADER_FORMAT, f.read(HEADER_SIZE)[:HEADER_SIZE]) 20 | 21 | # Ensure this is an Android boot image 22 | if header[0] != BOOT_MAGIC: 23 | print("Image does not appear to be an Android boot image") 24 | exit(1) 25 | 26 | page_size = header[8] 27 | page_mask = page_size - 1 28 | 29 | offset = page_size 30 | 31 | kernel_size = header[1] 32 | if kernel_size: 33 | extract_file(f, 'kernel.img', offset, kernel_size) 34 | offset += (kernel_size + page_mask) & ~page_mask 35 | 36 | ramdisk_size = header[3] 37 | if ramdisk_size: 38 | extract_file(f, 'ramdisk.img', offset, ramdisk_size) 39 | offset += (ramdisk_size + page_mask) & ~page_mask 40 | 41 | second_size = header[5] 42 | if second_size: 43 | extract_file(f, 'second.img', offset, second_size) 44 | offset += (second_size + page_mask) & ~page_mask 45 | 46 | dtb_size = header[9] 47 | if dtb_size > 1: 48 | extract_file(f, 'dtb.img', offset, dtb_size) 49 | offset += (dtb_size + page_mask) & ~page_mask 50 | 51 | # Extract command line 52 | cmdline = header[12].decode().rstrip('\0') + header[14].decode().rstrip('\0') 53 | 54 | with open('cmdline.txt', 'w') as o: 55 | o.write(cmdline) 56 | 57 | 58 | with open(sys.argv[1], 'rb') as f: 59 | unpack_image(f) 60 | -------------------------------------------------------------------------------- /dtsi.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from generator import Options, GpioFlag 3 | from panel import Panel, BacklightControl 4 | 5 | 6 | def generate_backlight(p: Panel): 7 | if p.backlight == BacklightControl.DCS: 8 | return "" 9 | return "\t\tbacklight = <&backlight>;\n" 10 | 11 | 12 | def generate_supplies(options: Options): 13 | s = "" 14 | if options.regulator: 15 | for r in options.regulator: 16 | s += f"\t\t{r}-supply = <&...>;\n" 17 | return s 18 | 19 | 20 | def generate_gpios(options: Options): 21 | s = "" 22 | for name, flags in options.gpios.items(): 23 | flags = "GPIO_ACTIVE_LOW" if flags & GpioFlag.ACTIVE_LOW else "GPIO_ACTIVE_HIGH" 24 | s += f"\t\t{name}-gpios = <&tlmm XY {flags}>;\n" 25 | return s 26 | 27 | 28 | def generate_panel_dtsi(p: Panel, options: Options) -> None: 29 | name = p.short_id.replace('_', '-') 30 | with open(f'{p.id}/panel-{name}.dtsi', 'w') as f: 31 | if p.cphy_mode: 32 | f.write('''\ 33 | #include 34 | 35 | ''') 36 | f.write(f'''\ 37 | &mdss_dsi0 {{ 38 | panel@0 {{ 39 | compatible = "{options.compatible}"; 40 | reg = <0>; 41 | 42 | {generate_backlight(p)}\ 43 | {generate_supplies(options)}\ 44 | {generate_gpios(options)}\ 45 | 46 | port {{ 47 | panel_in: endpoint {{ 48 | remote-endpoint = <&mdss_dsi0_out>; 49 | }}; 50 | }}; 51 | }}; 52 | }}; 53 | 54 | &mdss_dsi0_out {{ 55 | data-lanes = <{' '.join(map(str, p.lane_map.phys2log[:p.lanes]))}>; 56 | remote-endpoint = <&panel_in>; 57 | }}; 58 | ''') 59 | 60 | if p.ldo_mode: 61 | f.write(''' 62 | &mdss_dsi0_phy { 63 | qcom,dsi-phy-regulator-ldo-mode; 64 | }; 65 | ''') 66 | if p.cphy_mode: 67 | f.write(''' 68 | &mdss_dsi0_phy { 69 | phy-type = ; 70 | }; 71 | ''') 72 | -------------------------------------------------------------------------------- /fdt2.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | 3 | # Extend libfdt with some utility methods 4 | from libfdt import * 5 | 6 | 7 | class Fdt2(FdtRo): 8 | def find_by_compatible(self, compatible): 9 | offset = -1 10 | while True: 11 | offset = check_err( 12 | fdt_node_offset_by_compatible(self._fdt, offset, compatible), 13 | [FDT_ERR_NOTFOUND]) 14 | if offset == -FDT_ERR_NOTFOUND: 15 | break 16 | yield offset 17 | 18 | def subnodes(self, parent): 19 | offset = self.first_subnode(parent, [FDT_ERR_NOTFOUND]) 20 | while offset != -FDT_ERR_NOTFOUND: 21 | yield offset 22 | offset = self.next_subnode(offset, [FDT_ERR_NOTFOUND]) 23 | 24 | def subnode_or_none(self, parentoffset, name): 25 | offset = self.subnode_offset(parentoffset, name, [FDT_ERR_NOTFOUND]) 26 | if offset == -FDT_ERR_NOTFOUND: 27 | return None 28 | return offset 29 | 30 | def getprop(self, nodeoffset, prop_name, quiet=()): 31 | try: 32 | return super().getprop(nodeoffset, prop_name, quiet) 33 | except: 34 | print(f"ERROR: Failed to get property: {prop_name}") 35 | raise 36 | 37 | def getprop_or_none(self, nodeoffset, prop_name): 38 | prop = self.getprop(nodeoffset, prop_name, [FDT_ERR_NOTFOUND]) 39 | if prop == -FDT_ERR_NOTFOUND: 40 | return None 41 | return prop 42 | 43 | def getprop_uint32(self, nodeoffset, prop_name, default=0, ignore_empty=False): 44 | prop = self.getprop(nodeoffset, prop_name, [FDT_ERR_NOTFOUND]) 45 | if prop == -FDT_ERR_NOTFOUND: 46 | return default 47 | if ignore_empty and len(prop) == 0: 48 | return default 49 | return prop.as_uint32() 50 | 51 | 52 | def property_is_str(self): 53 | return self[-1] == 0 and 0 not in self[:-1] 54 | 55 | 56 | def property_as_uint32_array(self): 57 | num = int(len(self) / 4) 58 | return list(struct.unpack('>' + ('L' * num), self)) 59 | 60 | 61 | Property.is_str = property_is_str 62 | Property.as_uint32_array = property_as_uint32_array 63 | -------------------------------------------------------------------------------- /lmdpdg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | import argparse 4 | import os 5 | import shutil 6 | import sys 7 | import traceback 8 | 9 | import generator 10 | from driver import generate_driver 11 | from dtsi import generate_panel_dtsi 12 | from fdt2 import Fdt2 13 | from lk import generate_lk_driver 14 | from panel import Panel 15 | from simple import generate_panel_simple 16 | 17 | 18 | def generate(p: Panel, options: generator.Options) -> None: 19 | print(f"Generating: {p.id} ({p.name})") 20 | 21 | if os.path.exists(p.id): 22 | shutil.rmtree(p.id) 23 | os.mkdir(p.id) 24 | 25 | if not args.backlight: 26 | p.backlight = None 27 | 28 | generate_panel_simple(p) 29 | generate_driver(p, options) 30 | generate_panel_dtsi(p, options) 31 | generate_lk_driver(p) 32 | 33 | 34 | parser = argparse.ArgumentParser( 35 | description="Generate Linux DRM panel driver based on (downstream) MDSS DSI device tree") 36 | parser.add_argument('dtb', nargs='+', type=argparse.FileType('rb'), help="Device tree blobs to parse") 37 | parser.add_argument('-r', '--regulator', action='append', nargs='?', const='power', help=""" 38 | Enable one or multiple regulators with the specified name in the generated panel driver. 39 | Some panels require additional power supplies to be enabled to work properly. 40 | """) 41 | parser.add_argument('--backlight-gpio', action='store_true', help=""" 42 | Enable/disable backlight with an extra GPIO (works only for MIPI DCS backlight) 43 | """) 44 | parser.add_argument('--no-backlight', dest='backlight', action='store_false', default=True, help=""" 45 | Do not generate any backlight/brightness related code. 46 | """) 47 | parser.add_argument('--backlight-fallback-dcs', action='store_true', help=""" 48 | Generate code for both custom backlight and DCS backlight. DCS backlight 49 | is used if no 'backlight' is defined in the device tree. 50 | """) 51 | parser.add_argument('--dcs-no-get-brightness', dest='dcs_get_brightness', action='store_false', default=True, help=""" 52 | Do not generate get_brightness() function for DCS backlight/brightness code. 53 | Some panels do not implement the MIPI DCS Get Display Brightness command correctly. 54 | """) 55 | parser.add_argument('--ignore-wait', type=int, default=0, help=""" 56 | Ignore wait in command sequences that is smaller that the specified value. 57 | Some device trees add a useless 1ms wait after each command, making the driver 58 | unnecessarily verbose. 59 | """) 60 | parser.add_argument('--dumb-dcs', dest='dumb_dcs', action='store_true', help=""" 61 | Do not attempt to interpret DCS commands. Some panels use arbitrary DCS commands 62 | to write to registers, which conflict with commands specified in the MIPI DCS 63 | specification. This option stops interpretation of DCS commands, except for 64 | enter/exit_sleep_mode and set_display_on/off (which should be supported by 65 | any panel ideally). 66 | """) 67 | args = parser.parse_args(namespace=generator.Options()) 68 | 69 | for f in args.dtb: 70 | with f: 71 | print(f"Parsing: {f.name}") 72 | fdt = Fdt2(f.read()) 73 | 74 | found = False 75 | for offset in Panel.find(fdt): 76 | try: 77 | panel = Panel.parse(fdt, offset) 78 | if panel: 79 | generate(panel, args) 80 | found = True 81 | except: 82 | traceback.print_exc(file=sys.stdout) 83 | 84 | if not found: 85 | print(f"{f.name} does not contain any usable panel specifications") 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python ### 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # celery beat schedule file 96 | celerybeat-schedule 97 | 98 | # SageMath parsed files 99 | *.sage.py 100 | 101 | # Environments 102 | .env 103 | .venv 104 | env/ 105 | venv/ 106 | ENV/ 107 | env.bak/ 108 | venv.bak/ 109 | 110 | # Spyder project settings 111 | .spyderproject 112 | .spyproject 113 | 114 | # Rope project settings 115 | .ropeproject 116 | 117 | # mkdocs documentation 118 | /site 119 | 120 | # mypy 121 | .mypy_cache/ 122 | .dmypy.json 123 | dmypy.json 124 | 125 | # Pyre type checker 126 | .pyre/ 127 | 128 | ### Pycharm ### 129 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 130 | 131 | *.iml 132 | 133 | ## Directory-based project format: 134 | .idea/ 135 | # if you remove the above rule, at least ignore the following: 136 | 137 | # User-specific stuff 138 | #.idea/**/workspace.xml 139 | #.idea/**/tasks.xml 140 | #.idea/**/usage.statistics.xml 141 | #.idea/**/dictionaries 142 | #.idea/**/shelf 143 | 144 | # Generated files 145 | #.idea/**/contentModel.xml 146 | 147 | # Sensitive or high-churn files 148 | #.idea/**/dataSources/ 149 | #.idea/**/dataSources.ids 150 | #.idea/**/dataSources.local.xml 151 | #.idea/**/sqlDataSources.xml 152 | #.idea/**/dynamic.xml 153 | #.idea/**/uiDesigner.xml 154 | #.idea/**/dbnavigator.xml 155 | 156 | # Gradle 157 | #.idea/**/gradle.xml 158 | #.idea/**/libraries 159 | 160 | # CMake 161 | cmake-build-*/ 162 | 163 | # Mongo Explorer plugin 164 | .idea/**/mongoSettings.xml 165 | 166 | # File-based project format 167 | *.iws 168 | 169 | # IntelliJ 170 | out/ 171 | 172 | # mpeltonen/sbt-idea plugin 173 | .idea_modules/ 174 | 175 | # JIRA plugin 176 | atlassian-ide-plugin.xml 177 | 178 | # Cursive Clojure plugin 179 | .idea/replstate.xml 180 | 181 | # Crashlytics plugin (for Android Studio and IntelliJ) 182 | com_crashlytics_export_strings.xml 183 | crashlytics.properties 184 | crashlytics-build.properties 185 | fabric.properties 186 | 187 | # Editor-based Rest Client 188 | .idea/httpRequests 189 | 190 | # Android studio 3.1+ serialized cache file 191 | .idea/caches/build_file_checksums.ser 192 | 193 | ### Project ### 194 | run/ 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linux-mdss-dsi-panel-driver-generator (lmdpdg) 2 | The downstream kernel for Qualcomm-based Android devices describes panel 3 | properties and initialization sequences in the device tree. 4 | (See [DSI Panel Driver Porting] for details) 5 | 6 | This tool uses the information provided in the device tree to automatically 7 | generate a simple DRM panel driver, for use in mainline together with Freedreno. 8 | As far as possible, it attempts to generate _clean_ C code, so that only minimal 9 | changes are necessary for upstreaming the generated panel driver. 10 | 11 | ## Preparation 12 | ### Requirements 13 | - Python 3.7+ 14 | - [pylibfdt] compiled for Python 3 15 | 16 | ### Extracting device tree blob (DTB) 17 | lmdpdg operates on the compiled device tree blob (dtb), not the original source 18 | file from the kernel source. This means that it can be easily used even when the 19 | kernel source is not available. 20 | 21 | The device tree blob can be easily extracted from a stock boot image (`boot.img`): 22 | 23 | ```shell 24 | $ tools/unpackbootimg.py boot.img 25 | $ tools/unpackqcdt.py dt.img 26 | ``` 27 | 28 | This will produce multiple `*.dtb` files in the correct directory. 29 | You will need to try multiple (or all) of them to find the correct one. 30 | 31 | ### Compiling from source `.dtsi` 32 | If you have only the source `.dtsi` for the panel, or would like to make changes 33 | in it, you can still use it as input for this tool. You just need to pretend 34 | to have a full device tree blob by setting up the needed device nodes: 35 | 36 | ```dts 37 | /dts-v1/; 38 | 39 | / { 40 | mdp { 41 | compatible = "qcom,mdss_mdp"; 42 | 43 | /* Add your panels here */ 44 | dsi_sim_vid: qcom,mdss_dsi_sim_video { 45 | qcom,mdss-dsi-panel-name = "Simulator video mode dsi panel"; 46 | //qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; 47 | qcom,mdss-dsi-panel-type = "dsi_video_mode"; 48 | /* ... */ 49 | }; 50 | }; 51 | }; 52 | ``` 53 | 54 | Comment out entries that refer to other devices (see above). 55 | 56 | Compile it using [dtc]: `dtc -O your.dtb your.dts`. That's it! Yay! 57 | 58 | ## Usage 59 | Got the device tree blob? Then you are ready to go: 60 | 61 | ```shell 62 | $ ./lmdpdg.py 63 | ``` 64 | 65 | The generator has a couple of command line options that can be used to generate 66 | additional code (e.g. to enable a regulator to power on the panel). 67 | The script will gladly inform you about available options if you pass `--help`. 68 | 69 | Currently there are 4 files generated: 70 | - `panel-xyz.c`: The main (full) panel driver for Linux. 71 | - `panel-simple-xyz.c`: A snippet to use for `panel-simple.c` in Linux. 72 | Can be used if the full panel driver is causing problems. 73 | - `panel-xyz.dtsi`: An example for the relevant panel setup in the device tree. 74 | - `lk_panel_xyz.h`: A panel header for Qualcomm's Little Kernel (LK) bootloader. 75 | Can be used to turn the display on for splash screens there. 76 | 77 | ### Making final edits 78 | In most cases, the driver should work as-is, no changes required. 79 | If you would like to use it permanently, or even upstream it, here are a few 80 | things that you may want to update: 81 | 82 | - The `compatible` string in the device tree match table 83 | - `MODULE_AUTHOR` 84 | - `MODULE_DESCRIPTION` (eventually) 85 | - License header 86 | - If you have comments in your device tree source file 87 | (e.g. for the on/off command sequence), you may want to apply them to the 88 | driver to make the code a bit less magic. 89 | 90 | Adding it to `drivers/gpu/drm/panel`, together with adding needed `Kconfig` 91 | and `Makefile` entries should be straightforward. 92 | 93 | ## Warning 94 | This tool is like a human: it can make mistakes. Nobody knows what will happen 95 | to your panel if you send it bad commands or turn it on incorrectly. In most 96 | cases it will just refuse to work. 97 | 98 | However, this tool is mainly intended as a helping hand when porting new panels. 99 | You should verify that its output makes sense before using the generated driver. 100 | 101 | ## Questions? 102 | Feel free to open an issue! :) 103 | 104 | [dtc]: https://git.kernel.org/pub/scm/utils/dtc/dtc.git 105 | [pylibfdt]: https://git.kernel.org/pub/scm/utils/dtc/dtc.git 106 | [DSI Panel Driver Porting]: https://github.com/freedreno/freedreno/wiki/DSI-Panel-Driver-Porting 107 | -------------------------------------------------------------------------------- /lk.py: -------------------------------------------------------------------------------- 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('MIPI_DSI_MODE_VIDEO_NO_HFP' in p.flags)}, 68 | .hbp_power_mode = {int('MIPI_DSI_MODE_VIDEO_NO_HBP' in p.flags)}, 69 | .hsa_power_mode = {int('MIPI_DSI_MODE_VIDEO_NO_HSA' in p.flags)}, 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 122 | #include 123 | #include 124 | #include 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_on_cmds = {p.id}_on_command; 205 | pinfo->mipi.panel_off_cmds = {p.id}_off_command; 206 | pinfo->mipi.num_of_panel_on_cmds = ARRAY_SIZE({p.id}_on_command); 207 | pinfo->mipi.num_of_panel_off_cmds = ARRAY_SIZE({p.id}_off_command); 208 | memcpy(phy_db->timing, {p.id}_timings, TIMING_SIZE); 209 | phy_db->regulator_mode = {'DSI_PHY_REGULATOR_LDO_MODE' if p.ldo_mode else 'DSI_PHY_REGULATOR_DCDC_MODE'}; 210 | }} 211 | 212 | #endif /* {define} */ 213 | ''') 214 | -------------------------------------------------------------------------------- /mipi.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | # 3 | # Enums originally taken from: 4 | # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/video/mipi_display.h 5 | # 6 | # Defines for Mobile Industry Processor Interface (MIPI(R)) 7 | # Display Working Group standards: DSI, DCS, DBI, DPI 8 | # 9 | # Copyright (C) 2010 Guennadi Liakhovetski 10 | # Copyright (C) 2006 Nokia Corporation 11 | # Author: Imre Deak 12 | # 13 | # Some code adapted from: 14 | # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/drm_mipi_dsi.c 15 | # 16 | # Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd. 17 | # Andrzej Hajda 18 | # 19 | from __future__ import annotations 20 | 21 | from enum import IntEnum, unique, Enum 22 | from typing import List, Optional, Union, Tuple 23 | 24 | from generator import Options 25 | 26 | import wrap 27 | 28 | 29 | def _hex_fill(i: int, size: int = 1) -> str: 30 | return f'{i:#0{size * 2 + 2}x}' 31 | 32 | 33 | def _get_params_hex(b: bytes) -> List[str]: 34 | return [_hex_fill(i) for i in b] 35 | 36 | 37 | def _get_params_int(size: int, byteorder: str): 38 | def _get_params(b: bytes) -> List[str]: 39 | if len(b) < size: 40 | return [_hex_fill(int.from_bytes(b, byteorder=byteorder), size)] 41 | 42 | itr = iter(b) 43 | return [_hex_fill(int.from_bytes(t, byteorder=byteorder), size) for t in zip(*[itr] * size)] 44 | 45 | return _get_params 46 | 47 | 48 | @unique 49 | class TearMode(IntEnum): 50 | VBLANK = 0 51 | VHBLANK = 1 52 | 53 | @property 54 | def identifier(self): 55 | return 'MIPI_DSI_DCS_TEAR_MODE_' + self.name 56 | 57 | @staticmethod 58 | def get_params(b: bytes) -> List[str]: 59 | return [TearMode(b[0]).identifier] 60 | 61 | 62 | # MIPI DCS commands 63 | @unique 64 | class DCSCommand(Enum): 65 | NOP = 0x00, 0, 'mipi_dsi_dcs_nop_multi' 66 | SOFT_RESET = 0x01, 0, 'mipi_dsi_dcs_soft_reset_multi' 67 | # GET_COMPRESSION_MODE = 0x03, 68 | # GET_DISPLAY_ID = 0x04, 69 | # GET_ERROR_COUNT_ON_DSI = 0x05, 70 | # GET_RED_CHANNEL = 0x06, 71 | # GET_GREEN_CHANNEL = 0x07, 72 | # GET_BLUE_CHANNEL = 0x08, 73 | # GET_DISPLAY_STATUS = 0x09, 74 | # GET_POWER_MODE = 0x0A, 75 | # GET_ADDRESS_MODE = 0x0B, 76 | # GET_PIXEL_FORMAT = 0x0C, 77 | # GET_DISPLAY_MODE = 0x0D, 78 | # GET_SIGNAL_MODE = 0x0E, 79 | # GET_DIAGNOSTIC_RESULT = 0x0F, 80 | ENTER_SLEEP_MODE = 0x10, 0, 'mipi_dsi_dcs_enter_sleep_mode_multi' 81 | EXIT_SLEEP_MODE = 0x11, 0, 'mipi_dsi_dcs_exit_sleep_mode_multi' 82 | ENTER_PARTIAL_MODE = 0x12, 0, 83 | ENTER_NORMAL_MODE = 0x13, 0, 84 | # GET_IMAGE_CHECKSUM_RGB = 0x14, 85 | # GET_IMAGE_CHECKSUM_CT = 0x15, 86 | EXIT_INVERT_MODE = 0x20, 0, 87 | ENTER_INVERT_MODE = 0x21, 0, 88 | SET_GAMMA_CURVE = 0x26, 1, 89 | SET_DISPLAY_OFF = 0x28, 0, 'mipi_dsi_dcs_set_display_off_multi' 90 | SET_DISPLAY_ON = 0x29, 0, 'mipi_dsi_dcs_set_display_on_multi' 91 | SET_COLUMN_ADDRESS = 0x2A, 4, 'mipi_dsi_dcs_set_column_address_multi', _get_params_int(2, 'big') 92 | SET_PAGE_ADDRESS = 0x2B, 4, 'mipi_dsi_dcs_set_page_address_multi', _get_params_int(2, 'big') 93 | WRITE_MEMORY_START = 0x2C, 94 | WRITE_LUT = 0x2D, 95 | READ_MEMORY_START = 0x2E, 96 | SET_PARTIAL_ROWS = 0x30, 97 | SET_PARTIAL_COLUMNS = 0x31, 98 | SET_SCROLL_AREA = 0x33, 6, 99 | SET_TEAR_OFF = 0x34, 0, 'mipi_dsi_dcs_set_tear_off_multi' 100 | SET_TEAR_ON = 0x35, 1, 'mipi_dsi_dcs_set_tear_on_multi', TearMode.get_params 101 | SET_ADDRESS_MODE = 0x36, 1, 102 | SET_SCROLL_START = 0x37, 2, 103 | EXIT_IDLE_MODE = 0x38, 0, 104 | ENTER_IDLE_MODE = 0x39, 0, 105 | SET_PIXEL_FORMAT = 0x3A, 1, 'mipi_dsi_dcs_set_pixel_format_multi' 106 | WRITE_MEMORY_CONTINUE = 0x3C, 107 | SET_3D_CONTROL = 0x3D, 108 | READ_MEMORY_CONTINUE = 0x3E, 109 | # GET_3D_CONTROL = 0x3F, 110 | SET_VSYNC_TIMING = 0x40 111 | SET_TEAR_SCANLINE = 0x44, 2, 'mipi_dsi_dcs_set_tear_scanline_multi', _get_params_int(2, 'big') 112 | GET_SCANLINE = 0x45, 113 | SET_DISPLAY_BRIGHTNESS = 0x51, (1, 2), 'mipi_dsi_dcs_set_display_brightness_multi', _get_params_int(2, 'little') 114 | # GET_DISPLAY_BRIGHTNESS = 0x52, 115 | WRITE_CONTROL_DISPLAY = 0x53, 1, 116 | # GET_CONTROL_DISPLAY = 0x54, 117 | WRITE_POWER_SAVE = 0x55, 1, 118 | # GET_POWER_SAVE = 0x56, 119 | SET_CABC_MIN_BRIGHTNESS = 0x5E, 120 | # GET_CABC_MIN_BRIGHTNESS = 0x5F, 121 | READ_DDB_START = 0xA1, 122 | READ_PPS_START = 0xA2, 123 | READ_DDB_CONTINUE = 0xA8, 124 | READ_PPS_CONTINUE = 0xA9, 125 | 126 | def __new__(cls, value: int, nargs: Union[int, Tuple[int]] = (), method: str = None, 127 | _get_params=_get_params_hex) -> DCSCommand: 128 | obj = object.__new__(cls) 129 | obj._value_ = value 130 | obj.nargs = nargs if isinstance(nargs, tuple) else (nargs,) 131 | obj.method = method 132 | obj._get_params = _get_params 133 | return obj 134 | 135 | @property 136 | def identifier(self): 137 | return 'MIPI_DCS_' + self.name 138 | 139 | def get_params(self, b: bytes): 140 | params = self._get_params(b) 141 | params.insert(0, '&dsi_ctx') 142 | return params 143 | 144 | @staticmethod 145 | def find(payload: bytes, dumb: bool) -> Optional[DCSCommand]: 146 | try: 147 | dcs = DCSCommand(payload[0]) 148 | except ValueError: 149 | # Not a specified DCS command 150 | return None 151 | 152 | if dumb and dcs not in DCSCommand._DUMB_ALLOWED: 153 | return None 154 | 155 | if dcs.nargs and len(payload) - 1 not in dcs.nargs: 156 | # Argument count does not match. Weird. 157 | expected_args = " or ".join(str(i) for i in dcs.nargs) 158 | print(f"WARNING: DCS command {dcs.name} with incorrect argument count " 159 | f"(expected: {expected_args}, is: {len(payload) - 1}). Consider using --dumb-dcs") 160 | return None 161 | 162 | try: 163 | # Try to parse the argument(s) 164 | dcs.get_params(payload[1:]) 165 | except ValueError as e: 166 | # Not a valid argument. Weird. 167 | print(f"WARNING: DCS command {dcs.name} with invalid arguments {payload[1:]}: {e} Consider using --dumb-dcs") 168 | return None 169 | 170 | return dcs 171 | 172 | 173 | DCSCommand._DUMB_ALLOWED = [DCSCommand.ENTER_SLEEP_MODE, DCSCommand.EXIT_SLEEP_MODE, 174 | DCSCommand.SET_DISPLAY_ON, DCSCommand.SET_DISPLAY_OFF] 175 | 176 | 177 | def _generate_call(method: str, args: List[str]) -> str: 178 | return wrap.join(f'\t{method}(', ',', ');', args) 179 | 180 | 181 | def _generate_generic_write(t: Transaction, payload: bytes, options: Options) -> str: 182 | # TODO: Warn when downstream uses LONG_WRITE but mainline would use SHORT 183 | params = _get_params_hex(payload) 184 | params.insert(0, '&dsi_ctx') 185 | return wrap.join('\tmipi_dsi_generic_write_seq_multi(', ',', ');', params, force=2) 186 | 187 | 188 | def _generate_dcs_write(t: Transaction, payload: bytes, options: Options) -> str: 189 | # TODO: Warn when downstream uses LONG_WRITE but mainline would use SHORT 190 | 191 | dcs = DCSCommand.find(payload, options.dumb_dcs) 192 | if dcs and dcs.method: 193 | return _generate_call(dcs.method, dcs.get_params(payload[1:])) 194 | 195 | params = _get_params_hex(payload) 196 | if dcs: 197 | params[0] = dcs.identifier 198 | params.insert(0, '&dsi_ctx') 199 | 200 | return wrap.join('\tmipi_dsi_dcs_write_seq_multi(', ',', ');', params, force=2) 201 | 202 | 203 | def _generate_peripheral(t: Transaction, payload: bytes, options: Options) -> str: 204 | if t == Transaction.TURN_ON_PERIPHERAL: 205 | return _generate_call('mipi_dsi_turn_on_peripheral_multi', ['&dsi_ctx']) 206 | elif t == Transaction.SHUTDOWN_PERIPHERAL: 207 | return _generate_call('mipi_dsi_shutdown_peripheral_multi', ['&dsi_ctx']) 208 | else: 209 | raise ValueError(t) 210 | 211 | 212 | def _generate_compression_mode(t: Transaction, payload: bytes, options: Options) -> str: 213 | return _generate_call('mipi_dsi_compression_mode_multi', ['&dsi_ctx', str(bool(payload[0])).lower()]) 214 | 215 | 216 | def _generate_ignore(t: Transaction, payload: bytes, options: Options) -> str: 217 | print(f"WARNING: Ignoring weird {t.name}") 218 | return f"\t// WARNING: Ignoring weird {t.name}" 219 | 220 | 221 | def _generate_fallback(t: Transaction, payload: bytes, options: Options) -> str: 222 | raise ValueError(t.name + ' is not supported') 223 | 224 | 225 | # MIPI DSI Processor-to-Peripheral transaction types 226 | @unique 227 | class Transaction(Enum): 228 | V_SYNC_START = 0x01, 229 | V_SYNC_END = 0x11, 230 | H_SYNC_START = 0x21, 231 | H_SYNC_END = 0x31, 232 | 233 | COMPRESSION_MODE = 0x07, 1, _generate_compression_mode 234 | END_OF_TRANSMISSION = 0x08, 235 | 236 | COLOR_MODE_OFF = 0x02, 237 | COLOR_MODE_ON = 0x12, 238 | SHUTDOWN_PERIPHERAL = 0x22, 0, _generate_peripheral 239 | TURN_ON_PERIPHERAL = 0x32, 0, _generate_peripheral 240 | 241 | GENERIC_SHORT_WRITE_0_PARAM = 0x03, 0, _generate_generic_write 242 | GENERIC_SHORT_WRITE_1_PARAM = 0x13, 1, _generate_generic_write 243 | GENERIC_SHORT_WRITE_2_PARAM = 0x23, 2, _generate_generic_write 244 | 245 | GENERIC_READ_REQUEST_0_PARAM = 0x04, 246 | GENERIC_READ_REQUEST_1_PARAM = 0x14, 247 | GENERIC_READ_REQUEST_2_PARAM = 0x24, 248 | 249 | DCS_SHORT_WRITE = 0x05, 0, _generate_dcs_write 250 | DCS_SHORT_WRITE_PARAM = 0x15, 1, _generate_dcs_write 251 | 252 | DCS_READ = 0x06, 253 | EXECUTE_QUEUE = 0x16, 254 | 255 | SET_MAXIMUM_RETURN_PACKET_SIZE = 0x37, -1, _generate_ignore 256 | 257 | NULL_PACKET = 0x09, -1, _generate_ignore 258 | BLANKING_PACKET = 0x19, 259 | GENERIC_LONG_WRITE = 0x29, -1, _generate_generic_write 260 | DCS_LONG_WRITE = 0x39, -1, _generate_dcs_write 261 | 262 | PICTURE_PARAMETER_SET = 0x0a, 263 | COMPRESSED_PIXEL_STREAM = 0x0b, 264 | 265 | LOOSELY_PACKED_PIXEL_STREAM_YCBCR20 = 0x0c, 266 | PACKED_PIXEL_STREAM_YCBCR24 = 0x1c, 267 | PACKED_PIXEL_STREAM_YCBCR16 = 0x2c, 268 | 269 | PACKED_PIXEL_STREAM_30 = 0x0d, 270 | PACKED_PIXEL_STREAM_36 = 0x1d, 271 | PACKED_PIXEL_STREAM_YCBCR12 = 0x3d, 272 | 273 | PACKED_PIXEL_STREAM_16 = 0x0e, 274 | PACKED_PIXEL_STREAM_18 = 0x1e, 275 | PIXEL_STREAM_3BYTE_18 = 0x2e, 276 | PACKED_PIXEL_STREAM_24 = 0x3e, 277 | 278 | def __new__(cls, value: int, max_args: int = -1, generate=_generate_fallback) -> Transaction: 279 | obj = object.__new__(cls) 280 | obj._value_ = value 281 | obj.max_args = max_args 282 | obj._generate = generate 283 | return obj 284 | 285 | @property 286 | def identifier(self): 287 | return 'MIPI_DSI_' + self.name 288 | 289 | @property 290 | def is_long(self): 291 | # From mipi_dsi_packet_format_is_long() 292 | return self in [ 293 | Transaction.NULL_PACKET, 294 | Transaction.BLANKING_PACKET, 295 | Transaction.GENERIC_LONG_WRITE, 296 | Transaction.DCS_LONG_WRITE, 297 | Transaction.PICTURE_PARAMETER_SET, 298 | Transaction.COMPRESSED_PIXEL_STREAM, 299 | Transaction.LOOSELY_PACKED_PIXEL_STREAM_YCBCR20, 300 | Transaction.PACKED_PIXEL_STREAM_YCBCR24, 301 | Transaction.PACKED_PIXEL_STREAM_YCBCR16, 302 | Transaction.PACKED_PIXEL_STREAM_30, 303 | Transaction.PACKED_PIXEL_STREAM_36, 304 | Transaction.PACKED_PIXEL_STREAM_YCBCR12, 305 | Transaction.PACKED_PIXEL_STREAM_16, 306 | Transaction.PACKED_PIXEL_STREAM_18, 307 | Transaction.PIXEL_STREAM_3BYTE_18, 308 | Transaction.PACKED_PIXEL_STREAM_24, 309 | ] 310 | 311 | def generate(self, payload: bytes, options: Options) -> str: 312 | return self._generate(self, payload, options) 313 | -------------------------------------------------------------------------------- /panel.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from __future__ import annotations 3 | 4 | import itertools 5 | from dataclasses import dataclass 6 | from enum import Enum, unique 7 | from typing import Iterator, List, Optional 8 | 9 | import libfdt 10 | 11 | import mipi 12 | from fdt2 import Fdt2 13 | 14 | 15 | @unique 16 | class Mode(Enum): 17 | VIDEO_MODE = 'dsi_video_mode', ['MIPI_DSI_MODE_VIDEO'] 18 | CMD_MODE = 'dsi_cmd_mode', [] 19 | 20 | def __new__(cls, value: str, flags: List[str]) -> Mode: 21 | obj = object.__new__(cls) 22 | obj._value_ = value 23 | obj.flags = flags 24 | return obj 25 | 26 | 27 | @unique 28 | class TrafficMode(Enum): 29 | SYNC_PULSE = 'non_burst_sync_pulse', ['MIPI_DSI_MODE_VIDEO_SYNC_PULSE'] 30 | SYNC_EVENT = 'non_burst_sync_event', [] 31 | BURST_MODE = 'burst_mode', ['MIPI_DSI_MODE_VIDEO_BURST'] 32 | 33 | def __new__(cls, value: str, flags: List[str]) -> TrafficMode: 34 | obj = object.__new__(cls) 35 | obj._value_ = value 36 | obj.flags = flags 37 | return obj 38 | 39 | @staticmethod 40 | def parse(prop: libfdt.Property) -> Optional[TrafficMode]: 41 | if prop: 42 | if prop.is_str(): 43 | return TrafficMode(prop.as_str()) 44 | 45 | print(f"WARNING: qcom,mdss-dsi-traffic-mode is not a null terminated string:", prop) 46 | 47 | # Some Samsung panels have the traffic mode as index for some reason 48 | if len(prop) == 4: 49 | i = prop.as_uint32() 50 | traffic_modes = list(TrafficMode.__members__.values()) 51 | if i < len(traffic_modes): 52 | print(f"Interpreting qcom,mdss-dsi-traffic-mode as numeric index: {i} == {traffic_modes[i]}") 53 | return traffic_modes[i] 54 | 55 | # Use the default in mdss_dsi_panel.c 56 | print("Falling back to MIPI_DSI_MODE_VIDEO_SYNC_PULSE") 57 | return TrafficMode.SYNC_PULSE 58 | 59 | 60 | @unique 61 | class LaneMap(Enum): 62 | MAP_0123 = [0, 1, 2, 3] 63 | MAP_3012 = [3, 0, 1, 2] 64 | MAP_2301 = [2, 3, 0, 1] 65 | MAP_1230 = [1, 2, 3, 0] 66 | MAP_0321 = [0, 3, 2, 1] 67 | MAP_1032 = [1, 0, 3, 2] 68 | MAP_2103 = [2, 1, 0, 3] 69 | MAP_3210 = [3, 2, 1, 0] 70 | 71 | def __new__(cls, log2phys: List[int]) -> LaneMap: 72 | obj = object.__new__(cls) 73 | obj._value_ = "lane_map_" + ''.join(map(str, log2phys)) 74 | # Logical lane -> physical lane (used in downstream) 75 | obj.log2phys = log2phys 76 | # Physical lane -> logical lane (used in mainline) 77 | obj.phys2log = [0, 0, 0, 0] 78 | for i, n in enumerate(log2phys): 79 | obj.phys2log[n] = i 80 | return obj 81 | 82 | @staticmethod 83 | def parse(prop: Optional[libfdt.Property]) -> LaneMap: 84 | if not prop: 85 | return LaneMap.MAP_0123 86 | if prop.is_str(): # Null terminated string 87 | return LaneMap(prop.as_str()) 88 | 89 | print(f"WARNING: qcom,mdss-dsi-lane-map is not a null terminated string:", prop) 90 | return LaneMap.MAP_0123 91 | 92 | 93 | @unique 94 | class BacklightControl(Enum): 95 | PWM = 'bl_ctrl_pwm' 96 | DCS = 'bl_ctrl_dcs' 97 | WLED = 'bl_ctrl_wled' 98 | SAMSUNG_PWM = 'bl_ctrl_ss_pwm' 99 | 100 | 101 | @unique 102 | class CompressionMode(Enum): 103 | DSC = "dsc" 104 | 105 | 106 | class Dimension: 107 | @unique 108 | class Type(Enum): 109 | HORIZONTAL = 'h', 'width' 110 | VERTICAL = 'v', 'height' 111 | 112 | def __init__(self, prefix: str, size: str) -> None: 113 | self.prefix = prefix 114 | self.size = size 115 | 116 | def __init__(self, fdt: Fdt2, panel_node: int, mode_node: int, t: Type) -> None: 117 | self.type = type 118 | self.px = fdt.getprop(mode_node, f'qcom,mdss-dsi-panel-{t.size}').as_uint32() 119 | self.fp = fdt.getprop(mode_node, f'qcom,mdss-dsi-{t.prefix}-front-porch').as_uint32() 120 | self.bp = fdt.getprop(mode_node, f'qcom,mdss-dsi-{t.prefix}-back-porch').as_uint32() 121 | self.pw = fdt.getprop(mode_node, f'qcom,mdss-dsi-{t.prefix}-pulse-width').as_uint32() 122 | self.size = fdt.getprop_uint32(panel_node, f'qcom,mdss-pan-physical-{t.size}-dimension') 123 | 124 | 125 | @dataclass 126 | class Command: 127 | type: mipi.Transaction 128 | last: bool 129 | vc: int 130 | ack: bool 131 | wait: int 132 | payload: bytes 133 | generated: str = None 134 | 135 | 136 | class CommandSequence: 137 | generated: str = '' 138 | 139 | @unique 140 | class State(Enum): 141 | LP_MODE = 'dsi_lp_mode' 142 | HS_MODE = 'dsi_hs_mode' 143 | 144 | def __init__(self, fdt: Fdt2, node: int, cmd: str) -> None: 145 | self.state = CommandSequence.State(fdt.getprop(node, f'qcom,mdss-dsi-{cmd}-command-state').as_str()) 146 | self.seq = [] 147 | 148 | prop = fdt.getprop_or_none(node, f'qcom,mdss-dsi-{cmd}-command') 149 | if prop is None: 150 | print(f'Warning: qcom,mdss-dsi-{cmd}-command does not exist') 151 | return # No commands 152 | itr = iter(prop) 153 | 154 | if cmd == 'on': 155 | # WHY SONY/LG, WHY?????? Just put it in on-command... 156 | init = fdt.getprop_or_none(node, 'somc,mdss-dsi-init-command') 157 | if init: 158 | itr = itertools.chain(init, itr) 159 | 160 | on = fdt.getprop_or_none(node, 'lge,display-on-cmds') 161 | if on: 162 | itr = itertools.chain(itr, on) 163 | 164 | for dtype in itr: 165 | last, vc, ack, wait = next(itr), next(itr), next(itr), next(itr) 166 | dlen = next(itr) << 8 | next(itr) 167 | payload = bytes(next(itr) for _ in range(0, dlen)) 168 | 169 | t = mipi.Transaction(dtype) 170 | 171 | # Very often there are too many arguments encoded in the command stream. 172 | # These are redundant, because they would be never sent anyway. 173 | max_dlen = t.max_args + 1 174 | if 0 < max_dlen < dlen: 175 | payload = payload[:max_dlen] 176 | 177 | self.seq.append(Command(t, last, vc, ack, wait, payload)) 178 | 179 | 180 | def _remove_prefixes(text: str, *args: str) -> str: 181 | for prefix in args: 182 | text = text[len(prefix):] if text.startswith(prefix) else text 183 | return text 184 | 185 | 186 | def _replace_all(text: str, *args: str) -> str: 187 | for replace in args: 188 | text = text.replace(replace, '') 189 | return text 190 | 191 | 192 | def _remove_before(text: str, sub: str) -> str: 193 | i = text.find(sub) 194 | return text[i + 1:] if i >= 0 else text 195 | 196 | 197 | def _find_mode_node(fdt: Fdt2, node: int) -> int: 198 | timings_node = fdt.subnode_or_none(node, "qcom,mdss-dsi-display-timings") 199 | if timings_node is None: 200 | return node 201 | 202 | mode_node = None 203 | for timing in fdt.subnodes(timings_node): 204 | if mode_node: 205 | print("WARNING: Multiple display timings are not supported yet, using first!") 206 | break 207 | mode_node = timing 208 | 209 | assert mode_node, "No display timings found" 210 | return mode_node 211 | 212 | 213 | class Panel: 214 | def __init__(self, name: str, fdt: Fdt2, node: int) -> None: 215 | self.name = name 216 | self.node_name = fdt.get_name(node) 217 | self.id = _remove_before(_remove_prefixes(self.node_name, 'qcom,mdss_dsi_', 'ss_dsi_panel_', 'mot_').lower(), ',') 218 | print(f'Parsing: {self.id} ({name})') 219 | self.short_id = _replace_all(self.id, '_panel', '_video', '_vid', '_cmd', 220 | '_fhd', '_hd', '_qhd', '_720p', '_1080p', 221 | '_wvga', '_fwvga', '_qvga', '_xga', '_wxga') 222 | 223 | # Newer SoCs can use panels in different modes (resolution, refresh rate etc). 224 | # We don't support this properly yet but many panels just have a single mode 225 | # ("timing") defined, so let's try to support this here. 226 | mode_node = _find_mode_node(fdt, node) 227 | self.h = Dimension(fdt, node, mode_node, Dimension.Type.HORIZONTAL) 228 | self.v = Dimension(fdt, node, mode_node, Dimension.Type.VERTICAL) 229 | self.framerate = fdt.getprop(mode_node, 'qcom,mdss-dsi-panel-framerate').as_uint32() 230 | self.bpp = fdt.getprop(node, 'qcom,mdss-dsi-bpp').as_uint32() 231 | self.mode = Mode(fdt.getprop(node, 'qcom,mdss-dsi-panel-type').as_str()) 232 | self.traffic_mode = TrafficMode.parse(fdt.getprop_or_none(node, 'qcom,mdss-dsi-traffic-mode')) 233 | 234 | backlight = fdt.getprop_or_none(node, 'qcom,mdss-dsi-bl-pmic-control-type') 235 | self.backlight = BacklightControl(backlight.as_str()) if backlight else None 236 | self.max_brightness = fdt.getprop_uint32(node, 'qcom,mdss-dsi-bl-max-level', None) 237 | if self.backlight == BacklightControl.DCS and self.max_brightness is None: 238 | print("WARNING: DCS backlight without maximum brightness, ignoring...") 239 | self.backlight = None 240 | 241 | self.lanes = 0 242 | while fdt.getprop_or_none(node, f'qcom,mdss-dsi-lane-{self.lanes}-state') is not None: 243 | self.lanes += 1 244 | self.lane_map = LaneMap.parse(fdt.getprop_or_none(node, 'qcom,mdss-dsi-lane-map')) 245 | 246 | self.flags = self.mode.flags + self.traffic_mode.flags 247 | 248 | if fdt.getprop_uint32(node, 'qcom,mdss-dsi-h-sync-pulse') != 0: 249 | self.flags.append('MIPI_DSI_MODE_VIDEO_HSE') 250 | 251 | if fdt.getprop_or_none(node, 'qcom,mdss-dsi-tx-eot-append') is None: 252 | self.flags.append('MIPI_DSI_MODE_NO_EOT_PACKET') 253 | 254 | if fdt.getprop_or_none(node, 'qcom,mdss-dsi-force-clock-lane-hs') is None \ 255 | and fdt.getprop_or_none(node, 'qcom,mdss-dsi-force-clk-lane-hs') is None \ 256 | and fdt.getprop_uint32(node, 'qcom,mdss-force-clk-lane-hs', ignore_empty=True) == 0: 257 | self.flags.append('MIPI_DSI_CLOCK_NON_CONTINUOUS') 258 | 259 | if fdt.getprop_or_none(node, 'qcom,mdss-dsi-hfp-power-mode') is not None: 260 | self.flags.append('MIPI_DSI_MODE_VIDEO_NO_HFP') 261 | if fdt.getprop_or_none(node, 'qcom,mdss-dsi-hbp-power-mode') is not None: 262 | self.flags.append('MIPI_DSI_MODE_VIDEO_NO_HBP') 263 | if fdt.getprop_or_none(node, 'qcom,mdss-dsi-hsa-power-mode') is not None: 264 | self.flags.append('MIPI_DSI_MODE_VIDEO_NO_HSA') 265 | 266 | reset_seq = fdt.getprop_or_none(node, 'qcom,mdss-dsi-reset-sequence') 267 | if reset_seq is not None: 268 | itr = iter(reset_seq.as_uint32_array()) 269 | self.reset_seq = list(zip(itr, itr)) 270 | else: 271 | self.reset_seq = None 272 | 273 | self.cmds = { 274 | 'on': CommandSequence(fdt, mode_node, 'on'), 275 | 'off': CommandSequence(fdt, mode_node, 'off') 276 | } 277 | 278 | # If all commands are sent in LPM, add flag globally 279 | if self.cmds['on'].state == CommandSequence.State.LP_MODE == self.cmds['off'].state: 280 | self.flags.append('MIPI_DSI_MODE_LPM') 281 | 282 | if self.bpp == 24: 283 | self.format = 'MIPI_DSI_FMT_RGB888' 284 | else: 285 | raise ValueError(f'Unsupported bpp: {self.bpp} (TODO)') 286 | 287 | # Sony Panel: 334 | name = fdt.getprop_or_none(node, 'qcom,mdss-dsi-panel-name') 335 | return name and Panel(name.as_str(), fdt, node) 336 | 337 | @staticmethod 338 | def find(fdt: Fdt2) -> Iterator[int]: 339 | for compatible in ['qcom,mdss_mdp', 'qcom,mdss_mdp3', 'qcom,sde-kms']: 340 | for mdp in fdt.find_by_compatible(compatible): 341 | for sub in fdt.subnodes(mdp): 342 | yield sub 343 | 344 | # Newer device trees do not necessarily have panels below MDP, 345 | # search for qcom,dsi-display node instead 346 | panel_phandles = set() 347 | 348 | for display in fdt.find_by_compatible('qcom,dsi-display'): 349 | # On even newer SoCs there is another node with qcom,dsi-display-list 350 | displays = fdt.getprop_or_none(display, 'qcom,dsi-display-list') 351 | if displays is None: 352 | dsi_panel = fdt.getprop_or_none(display, 'qcom,dsi-panel') 353 | if dsi_panel is not None: 354 | panel_phandles.add(dsi_panel.as_uint32()) 355 | else: 356 | for display_phandle in displays.as_uint32_array(): 357 | display = fdt.node_offset_by_phandle(display_phandle) 358 | panel_phandles.add(fdt.getprop(display, 'qcom,dsi-panel').as_uint32()) 359 | 360 | for phandle in panel_phandles: 361 | yield fdt.node_offset_by_phandle(phandle) 362 | -------------------------------------------------------------------------------- /driver.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | from __future__ import annotations 3 | 4 | import datetime 5 | 6 | import mipi 7 | import simple 8 | import wrap 9 | from generator import Options, GpioFlag 10 | from panel import Panel, BacklightControl, CommandSequence, CompressionMode 11 | 12 | 13 | def generate_includes(p: Panel, options: Options) -> str: 14 | includes = { 15 | 'linux': { 16 | 'module.h', 17 | 'mod_devicetable.h', 18 | 'delay.h', 19 | }, 20 | 'video': set(), 21 | 'drm': { 22 | 'drm_mipi_dsi.h', 23 | 'drm_modes.h', 24 | 'drm_panel.h', 25 | 'drm_probe_helper.h', 26 | }, 27 | } 28 | 29 | if p.reset_seq or options.backlight_gpio: 30 | includes['linux'].add('gpio/consumer.h') 31 | if options.regulator: 32 | includes['linux'].add('regulator/consumer.h') 33 | if p.backlight == BacklightControl.DCS or options.backlight_fallback_dcs: 34 | includes['linux'].add('backlight.h') 35 | if p.compression_mode == CompressionMode.DSC: 36 | includes['drm'].add('display/drm_dsc.h') 37 | includes['drm'].add('display/drm_dsc_helper.h') 38 | 39 | for cmd in p.cmds.values(): 40 | if 'MIPI_DCS_' in cmd.generated: 41 | includes['video'].add('mipi_display.h') 42 | break 43 | 44 | lines = [] 45 | for group, headers in includes.items(): 46 | if not headers: 47 | continue 48 | 49 | lines.append('') 50 | for header in sorted(headers): 51 | lines.append(f'#include <{group}/{header}>') 52 | 53 | return '\n'.join(lines) 54 | 55 | 56 | def generate_struct(p: Panel, options: Options) -> str: 57 | variables = [ 58 | 'struct drm_panel panel', 59 | 'struct mipi_dsi_device *dsi', 60 | ] 61 | 62 | if p.compression_mode == CompressionMode.DSC: 63 | variables.append('struct drm_dsc_config dsc') 64 | 65 | if options.regulator: 66 | if len(options.regulator) > 1: 67 | variables.append(f'struct regulator_bulk_data *supplies') 68 | else: 69 | variables.append('struct regulator *supply') 70 | variables += [f'struct gpio_desc *{name}_gpio' for name in options.gpios.keys()] 71 | 72 | s = f'struct {p.short_id} {{' 73 | for v in variables: 74 | s += '\n' 75 | if v: 76 | s += '\t' + v + ';' 77 | s += '\n};' 78 | return s 79 | 80 | 81 | def generate_regulator_bulk(p: Panel, options: Options) -> str: 82 | if not options.regulator or len(options.regulator) == 1: 83 | return '' 84 | 85 | s = '\n\n' 86 | s += f'static const struct regulator_bulk_data {p.short_id}_supplies[] = {{' 87 | for r in options.regulator: 88 | s += f'\n\t{{ .supply = "{r}" }},' 89 | s += '\n};' 90 | return s 91 | 92 | 93 | # msleep(< 20) will possibly sleep up to 20ms 94 | # In this case, usleep_range should be used 95 | def msleep(m: int) -> str: 96 | if m >= 20: 97 | return f"msleep({m})" 98 | else: 99 | # It's hard to say what a good range would be... 100 | # Downstream uses usleep_range(m * 1000, m * 1000) but that doesn't quite sound great 101 | # Sleep for up to 1ms longer for now 102 | u = m * 1000 103 | return f"usleep_range({u}, {u + 1000})" 104 | 105 | 106 | # msleep(< 20) will possibly sleep up to 20ms 107 | # In this case, usleep_range should be used 108 | def dsi_msleep(m: int) -> str: 109 | if m >= 20: 110 | return f"mipi_dsi_msleep(&dsi_ctx, {m})" 111 | else: 112 | # It's hard to say what a good range would be... 113 | # Downstream uses usleep_range(m * 1000, m * 1000) but that doesn't quite sound great 114 | # Sleep for up to 1ms longer for now 115 | u = m * 1000 116 | return f"mipi_dsi_usleep_range(&dsi_ctx, {u}, {u + 1000})" 117 | 118 | 119 | def generate_reset(p: Panel, options: Options) -> str: 120 | if not p.reset_seq: 121 | return '' 122 | 123 | s = f'\nstatic void {p.short_id}_reset(struct {p.short_id} *ctx)\n{{\n' 124 | for state, sleep in p.reset_seq: 125 | # Invert reset sequence if GPIO is active low 126 | if options.gpios["reset"] & GpioFlag.ACTIVE_LOW: 127 | state = int(not bool(state)) 128 | s += f'\tgpiod_set_value_cansleep(ctx->reset_gpio, {state});\n' 129 | if sleep: 130 | s += f'\t{msleep(sleep)};\n' 131 | s += '}\n' 132 | 133 | return s 134 | 135 | 136 | def generate_commands(p: Panel, options: Options, cmd_name: str) -> str: 137 | cmd = p.cmds[cmd_name] 138 | 139 | s = f'''\ 140 | static int {p.short_id}_{cmd_name}(struct {p.short_id} *ctx) 141 | {{ 142 | struct mipi_dsi_multi_context dsi_ctx = {{ .dsi = ctx->dsi }}; 143 | ''' 144 | 145 | if p.cmds['on'].state != p.cmds['off'].state: 146 | if cmd.state == CommandSequence.State.LP_MODE: 147 | s += '\n\tctx->dsi->mode_flags |= MIPI_DSI_MODE_LPM;\n' 148 | elif cmd.state == CommandSequence.State.HS_MODE: 149 | s += '\n\tctx->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;\n' 150 | 151 | block = True 152 | for c in cmd.seq: 153 | if block or '{' in c.generated: 154 | s += '\n' 155 | block = '{' in c.generated 156 | 157 | s += c.generated + '\n' 158 | if c.wait and c.wait > options.ignore_wait: 159 | s += f'\t{dsi_msleep(c.wait)};\n' 160 | 161 | s += ''' 162 | return dsi_ctx.accum_err; 163 | } 164 | ''' 165 | return s 166 | 167 | 168 | def generate_cleanup(p: Panel, options: Options, indent: int = 1) -> str: 169 | cleanup = [] 170 | if p.reset_seq: 171 | cleanup.append('gpiod_set_value_cansleep(ctx->reset_gpio, 1);') 172 | if options.regulator: 173 | if len(options.regulator) > 1: 174 | cleanup.append(f'regulator_bulk_disable(ARRAY_SIZE({p.short_id}_supplies), ctx->supplies);') 175 | else: 176 | cleanup.append('regulator_disable(ctx->supply);') 177 | 178 | if cleanup: 179 | sep = '\n' + '\t' * indent 180 | return sep + sep.join(cleanup) 181 | else: 182 | return '' 183 | 184 | 185 | def generate_prepare(p: Panel, options: Options) -> str: 186 | s = f'''\ 187 | static int {p.short_id}_prepare(struct drm_panel *panel) 188 | {{ 189 | struct {p.short_id} *ctx = to_{p.short_id}(panel); 190 | struct device *dev = &ctx->dsi->dev; 191 | ''' 192 | 193 | if p.compression_mode == CompressionMode.DSC: 194 | s += '''\ 195 | struct drm_dsc_picture_parameter_set pps; 196 | ''' 197 | 198 | s += f'''\ 199 | int ret; 200 | ''' 201 | 202 | if options.regulator: 203 | if len(options.regulator) > 1: 204 | s += f''' 205 | ret = regulator_bulk_enable(ARRAY_SIZE({p.short_id}_supplies), ctx->supplies); 206 | if (ret < 0) {{ 207 | dev_err(dev, "Failed to enable regulators: %d\\n", ret); 208 | return ret; 209 | }} 210 | ''' 211 | else: 212 | s += ''' 213 | ret = regulator_enable(ctx->supply); 214 | if (ret < 0) { 215 | dev_err(dev, "Failed to enable regulator: %d\\n", ret); 216 | return ret; 217 | } 218 | ''' 219 | 220 | if p.reset_seq: 221 | s += f'\n\t{p.short_id}_reset(ctx);\n' 222 | 223 | s += f''' 224 | ret = {p.short_id}_on(ctx); 225 | if (ret < 0) {{ 226 | dev_err(dev, "Failed to initialize panel: %d\\n", ret);{generate_cleanup(p, options, 2)} 227 | return ret; 228 | }} 229 | ''' 230 | 231 | if p.compression_mode == CompressionMode.DSC: 232 | s += ''' 233 | drm_dsc_pps_payload_pack(&pps, &ctx->dsc); 234 | 235 | ret = mipi_dsi_picture_parameter_set(ctx->dsi, &pps); 236 | if (ret < 0) { 237 | dev_err(panel->dev, "failed to transmit PPS: %d\\n", ret); 238 | return ret; 239 | } 240 | 241 | ret = mipi_dsi_compression_mode(ctx->dsi, true); 242 | if (ret < 0) { 243 | dev_err(dev, "failed to enable compression mode: %d\\n", ret); 244 | return ret; 245 | } 246 | 247 | msleep(28); /* TODO: Is this panel-dependent? */ 248 | ''' 249 | 250 | s += ''' 251 | return 0; 252 | } 253 | ''' 254 | return s 255 | 256 | 257 | def generate_unprepare(p: Panel, options: Options) -> str: 258 | return f'''\ 259 | static int {p.short_id}_unprepare(struct drm_panel *panel) 260 | {{ 261 | struct {p.short_id} *ctx = to_{p.short_id}(panel); 262 | struct device *dev = &ctx->dsi->dev; 263 | int ret; 264 | 265 | ret = {p.short_id}_off(ctx); 266 | if (ret < 0) 267 | dev_err(dev, "Failed to un-initialize panel: %d\\n", ret); 268 | {generate_cleanup(p, options)} 269 | 270 | return 0; 271 | }} 272 | ''' 273 | 274 | 275 | def generate_backlight(p: Panel, options: Options) -> str: 276 | if p.backlight != BacklightControl.DCS and not options.backlight_fallback_dcs: 277 | return '' 278 | 279 | brightness_mask = ' & 0xff' 280 | if p.max_brightness > 255: 281 | brightness_mask = '' 282 | 283 | brightness_variant = '' 284 | if p.max_brightness > 255: 285 | brightness_variant = '_large' 286 | 287 | s = f'''\ 288 | static int {p.short_id}_bl_update_status(struct backlight_device *bl) 289 | {{ 290 | struct mipi_dsi_device *dsi = bl_get_data(bl); 291 | ''' 292 | if options.backlight_gpio: 293 | s += f'\tstruct {p.short_id} *ctx = mipi_dsi_get_drvdata(dsi);\n' 294 | 295 | s += '''\ 296 | u16 brightness = backlight_get_brightness(bl); 297 | int ret; 298 | ''' 299 | 300 | if options.backlight_gpio: 301 | s += ''' 302 | gpiod_set_value_cansleep(ctx->backlight_gpio, !!brightness); 303 | ''' 304 | 305 | s += f''' 306 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 307 | 308 | ret = mipi_dsi_dcs_set_display_brightness{brightness_variant}(dsi, brightness); 309 | if (ret < 0) 310 | return ret; 311 | 312 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; 313 | 314 | return 0; 315 | }} 316 | ''' 317 | 318 | if options.dcs_get_brightness: 319 | s += f''' 320 | // TODO: Check if /sys/class/backlight/.../actual_brightness actually returns 321 | // correct values. If not, remove this function. 322 | static int {p.short_id}_bl_get_brightness(struct backlight_device *bl) 323 | {{ 324 | struct mipi_dsi_device *dsi = bl_get_data(bl); 325 | u16 brightness; 326 | int ret; 327 | 328 | dsi->mode_flags &= ~MIPI_DSI_MODE_LPM; 329 | 330 | ret = mipi_dsi_dcs_get_display_brightness{brightness_variant}(dsi, &brightness); 331 | if (ret < 0) 332 | return ret; 333 | 334 | dsi->mode_flags |= MIPI_DSI_MODE_LPM; 335 | 336 | return brightness{brightness_mask}; 337 | }} 338 | ''' 339 | get_brightness = f'\n\t.get_brightness = {p.short_id}_bl_get_brightness,' 340 | else: 341 | get_brightness = '' 342 | 343 | s += f''' 344 | static const struct backlight_ops {p.short_id}_bl_ops = {{ 345 | .update_status = {p.short_id}_bl_update_status,{get_brightness} 346 | }}; 347 | ''' 348 | s += f''' 349 | static struct backlight_device * 350 | {p.short_id}_create_backlight(struct mipi_dsi_device *dsi) 351 | {{ 352 | struct device *dev = &dsi->dev; 353 | const struct backlight_properties props = {{ 354 | .type = BACKLIGHT_RAW, 355 | .brightness = {p.max_brightness or 255}, 356 | .max_brightness = {p.max_brightness or 255}, 357 | }}; 358 | 359 | return devm_backlight_device_register(dev, dev_name(dev), dev, dsi, 360 | &{p.short_id}_bl_ops, &props); 361 | }} 362 | 363 | ''' 364 | return s 365 | 366 | 367 | def generate_probe(p: Panel, options: Options) -> str: 368 | s = f'''\ 369 | static int {p.short_id}_probe(struct mipi_dsi_device *dsi) 370 | {{ 371 | struct device *dev = &dsi->dev; 372 | struct {p.short_id} *ctx; 373 | ''' 374 | 375 | s += f'''\ 376 | int ret; 377 | 378 | ctx = devm_drm_panel_alloc(dev, struct {p.short_id}, panel, 379 | &{p.short_id}_panel_funcs, 380 | DRM_MODE_CONNECTOR_DSI); 381 | if (IS_ERR(ctx)) 382 | return PTR_ERR(ctx); 383 | ''' 384 | 385 | if options.regulator: 386 | if len(options.regulator) > 1: 387 | s += f''' 388 | ret = devm_regulator_bulk_get_const(dev, 389 | ARRAY_SIZE({p.short_id}_supplies), 390 | {p.short_id}_supplies, 391 | &ctx->supplies); 392 | if (ret < 0) 393 | return ret; 394 | ''' 395 | else: 396 | s += f''' 397 | ctx->supply = devm_regulator_get(dev, "{options.regulator[0]}"); 398 | if (IS_ERR(ctx->supply)) 399 | return dev_err_probe(dev, PTR_ERR(ctx->supply), 400 | "Failed to get {options.regulator[0]} regulator\\n"); 401 | ''' 402 | 403 | for name, flags in options.gpios.items(): 404 | # TODO: In the future, we might want to change this to keep panel alive 405 | init = "GPIOD_OUT_LOW" 406 | if name == "reset": 407 | init = "GPIOD_OUT_HIGH" 408 | 409 | s += f''' 410 | ctx->{name}_gpio = devm_gpiod_get(dev, "{name}", {init}); 411 | if (IS_ERR(ctx->{name}_gpio)) 412 | return dev_err_probe(dev, PTR_ERR(ctx->{name}_gpio), 413 | "Failed to get {name}-gpios\\n"); 414 | ''' 415 | 416 | s += f''' 417 | ctx->dsi = dsi; 418 | mipi_dsi_set_drvdata(dsi, ctx); 419 | 420 | dsi->lanes = {p.lanes}; 421 | dsi->format = {p.format}; 422 | {wrap.join(' dsi->mode_flags = ', ' |', ';', p.flags)} 423 | 424 | ctx->panel.prepare_prev_first = true; 425 | ''' 426 | 427 | if options.backlight_fallback_dcs: 428 | s += f''' 429 | ret = drm_panel_of_backlight(&ctx->panel); 430 | if (ret) 431 | return dev_err_probe(dev, ret, "Failed to get backlight\\n"); 432 | 433 | /* Fallback to DCS backlight if no backlight is defined in DT */ 434 | if (!ctx->panel.backlight) {{ 435 | ctx->panel.backlight = {p.short_id}_create_backlight(dsi); 436 | if (IS_ERR(ctx->panel.backlight)) 437 | return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 438 | "Failed to create backlight\\n"); 439 | }} 440 | ''' 441 | elif p.backlight == BacklightControl.DCS: 442 | s += f''' 443 | ctx->panel.backlight = {p.short_id}_create_backlight(dsi); 444 | if (IS_ERR(ctx->panel.backlight)) 445 | return dev_err_probe(dev, PTR_ERR(ctx->panel.backlight), 446 | "Failed to create backlight\\n"); 447 | ''' 448 | elif p.backlight: 449 | s += ''' 450 | ret = drm_panel_of_backlight(&ctx->panel); 451 | if (ret) 452 | return dev_err_probe(dev, ret, "Failed to get backlight\\n"); 453 | ''' 454 | 455 | s += ''' 456 | drm_panel_add(&ctx->panel); 457 | ''' 458 | 459 | if p.compression_mode == CompressionMode.DSC: 460 | s += f''' 461 | /* This panel only supports DSC; unconditionally enable it */ 462 | dsi->dsc = &ctx->dsc; 463 | 464 | ctx->dsc.dsc_version_major = {(p.dsc_version >> 4) & 0xf}; 465 | ctx->dsc.dsc_version_minor = {p.dsc_version & 0xf}; 466 | 467 | /* TODO: Pass slice_per_pkt = {p.dsc_slice_per_pkt} */ 468 | ctx->dsc.slice_height = {p.dsc_slice_height}; 469 | ctx->dsc.slice_width = {p.dsc_slice_width}; 470 | /* 471 | * TODO: hdisplay should be read from the selected mode once 472 | * it is passed back to drm_panel (in prepare?) 473 | */ 474 | WARN_ON({p.h.px} % ctx->dsc.slice_width); 475 | ctx->dsc.slice_count = {p.h.px} / ctx->dsc.slice_width; 476 | ctx->dsc.bits_per_component = {p.dsc_bit_per_component}; 477 | ctx->dsc.bits_per_pixel = {p.dsc_bit_per_pixel} << 4; /* 4 fractional bits */ 478 | ctx->dsc.block_pred_enable = {"true" if p.dsc_block_prediction else "false"}; 479 | ''' 480 | 481 | s += ''' 482 | ret = mipi_dsi_attach(dsi); 483 | if (ret < 0) { 484 | drm_panel_remove(&ctx->panel); 485 | return dev_err_probe(dev, ret, "Failed to attach to DSI host\\n"); 486 | } 487 | ''' 488 | 489 | s += ''' 490 | return 0; 491 | } 492 | ''' 493 | return s 494 | 495 | 496 | def generate_driver(p: Panel, options: Options) -> None: 497 | # Generate command sequences early 498 | for cmd in p.cmds.values(): 499 | for c in cmd.seq: 500 | c.generated = c.type.generate(c.payload, options) 501 | cmd.generated += c.generated 502 | 503 | options.gpios = {} 504 | if p.reset_seq: 505 | # Many panels have active low reset GPIOs. This can be seen if we keep 506 | # reset high after turning the panel on. From a logical perspective this 507 | # does not make sense: We should assert reset to actually do the reset, 508 | # not to disable it. 509 | # 510 | # Therefore we try check the last element from the reset sequence here. 511 | # If it sets the GPIO to 1 (high), we assume that reset is active low. 512 | 513 | flag = GpioFlag.ACTIVE_HIGH 514 | last_val, _ = p.reset_seq[-1] 515 | if last_val == 1: 516 | flag = GpioFlag.ACTIVE_LOW 517 | 518 | options.gpios["reset"] = flag 519 | if options.backlight_gpio: 520 | options.gpios["backlight"] = GpioFlag.ACTIVE_HIGH 521 | 522 | dash_id = p.short_id.replace('_', '-') 523 | compatible = dash_id.split('-', 1) 524 | 525 | # Try to guess if short id starts with vendor name (e.g. booyi) 526 | if compatible[0].isalpha(): 527 | compatible = ','.join(compatible) 528 | else: 529 | # Unknown vendor 530 | compatible = 'mdss,' + '-'.join(compatible) 531 | 532 | options.compatible = compatible 533 | 534 | module = f"panel-{dash_id}" 535 | with open(f'{p.id}/{module}.c', 'w') as f: 536 | f.write(f'''\ 537 | // SPDX-License-Identifier: GPL-2.0-only 538 | // Copyright (c) {datetime.date.today().year} FIXME 539 | // Generated with linux-mdss-dsi-panel-driver-generator from vendor device tree: 540 | // Copyright (c) 2013, The Linux Foundation. All rights reserved. (FIXME) 541 | {generate_includes(p, options)} 542 | 543 | {generate_struct(p, options)}{generate_regulator_bulk(p, options)} 544 | 545 | {wrap.simple([f'static inline', f'struct {p.short_id} *to_{p.short_id}(struct drm_panel *panel)'])} 546 | {{ 547 | return container_of(panel, struct {p.short_id}, panel); 548 | }} 549 | {generate_reset(p, options)} 550 | {generate_commands(p, options, 'on')} 551 | {generate_commands(p, options, 'off')} 552 | {generate_prepare(p, options)} 553 | {generate_unprepare(p, options)} 554 | {simple.generate_mode(p)} 555 | {wrap.join(f'static int {p.short_id}_get_modes(', ',', ')', ['struct drm_panel *panel', 'struct drm_connector *connector'])} 556 | {{ 557 | return drm_connector_helper_get_modes_fixed(connector, &{p.short_id}_mode); 558 | }} 559 | 560 | static const struct drm_panel_funcs {p.short_id}_panel_funcs = {{ 561 | .prepare = {p.short_id}_prepare, 562 | .unprepare = {p.short_id}_unprepare, 563 | .get_modes = {p.short_id}_get_modes, 564 | }}; 565 | 566 | {generate_backlight(p, options)}{generate_probe(p, options)} 567 | static void {p.short_id}_remove(struct mipi_dsi_device *dsi) 568 | {{ 569 | struct {p.short_id} *ctx = mipi_dsi_get_drvdata(dsi); 570 | int ret; 571 | 572 | ret = mipi_dsi_detach(dsi); 573 | if (ret < 0) 574 | dev_err(&dsi->dev, "Failed to detach from DSI host: %d\\n", ret); 575 | 576 | drm_panel_remove(&ctx->panel); 577 | }} 578 | 579 | static const struct of_device_id {p.short_id}_of_match[] = {{ 580 | {{ .compatible = "{compatible}" }}, // FIXME 581 | {{ /* sentinel */ }} 582 | }}; 583 | MODULE_DEVICE_TABLE(of, {p.short_id}_of_match); 584 | 585 | static struct mipi_dsi_driver {p.short_id}_driver = {{ 586 | .probe = {p.short_id}_probe, 587 | .remove = {p.short_id}_remove, 588 | .driver = {{ 589 | .name = "{module}", 590 | .of_match_table = {p.short_id}_of_match, 591 | }}, 592 | }}; 593 | module_mipi_dsi_driver({p.short_id}_driver); 594 | 595 | MODULE_AUTHOR("linux-mdss-dsi-panel-driver-generator "); // FIXME 596 | MODULE_DESCRIPTION("DRM driver for {p.name}"); 597 | MODULE_LICENSE("GPL"); 598 | ''') 599 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------