adafruit-circuitpython-lt8912b 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: adafruit-circuitpython-lt8912b
3
+ Version: 1.0.0
4
+ Summary: CircuitPython driver for the LT8912B MIPI DSI to HDMI/DVI bridge.
5
+ Author-email: Adafruit Industries <circuitpython@adafruit.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/adafruit/Adafruit_CircuitPython_LT8912B
8
+ Keywords: adafruit,blinka,circuitpython,micropython,lt8912b,lt8912b,mipi,dsi,hdmi,dvi,bridge,display
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Libraries
11
+ Classifier: Topic :: Software Development :: Embedded Systems
12
+ Classifier: Topic :: System :: Hardware
13
+ Classifier: Programming Language :: Python :: 3
14
+ Description-Content-Type: text/x-rst
15
+ License-File: LICENSE
16
+ License-File: NOTICE
17
+ License-File: NOTICE.license
18
+ Requires-Dist: Adafruit-Blinka
19
+ Requires-Dist: adafruit-circuitpython-busdevice
20
+ Provides-Extra: optional
21
+ Dynamic: license-file
22
+
23
+ Introduction
24
+ ============
25
+
26
+
27
+ .. image:: https://readthedocs.org/projects/adafruit-circuitpython-lt8912b/badge/?version=latest
28
+ :target: https://docs.circuitpython.org/projects/lt8912b/en/latest/
29
+ :alt: Documentation Status
30
+
31
+
32
+ .. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg
33
+ :target: https://adafru.it/discord
34
+ :alt: Discord
35
+
36
+
37
+ .. image:: https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/workflows/Build%20CI/badge.svg
38
+ :target: https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/actions
39
+ :alt: Build Status
40
+
41
+
42
+ .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
43
+ :target: https://github.com/astral-sh/ruff
44
+ :alt: Code Style: Ruff
45
+
46
+ CircuitPython driver for the LT8912B MIPI DSI to HDMI/DVI bridge. The bridge is
47
+ configured over I2C and then fed video through a MIPI DSI host, such as the
48
+ ESP32-P4. Pixel data is sent by the ``mipidsi`` module; the LT8912B cannot send
49
+ pixels over I2C.
50
+
51
+
52
+ Dependencies
53
+ =============
54
+ This driver depends on:
55
+
56
+ * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_ with
57
+ ``mipidsi.Display.pixel_clock_frequency`` and ``low_power_blanking``, and
58
+ ``mipidsi.Bus.minimum_frequency``, ``frequency`` and ``num_lanes``
59
+ * `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_
60
+
61
+ Please ensure all dependencies are available on the CircuitPython filesystem.
62
+ This is easily achieved by downloading
63
+ `the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
64
+ or individual libraries can be installed using
65
+ `circup <https://github.com/adafruit/circup>`_.
66
+
67
+
68
+ This driver works with Adafruit's LT8912B MIPI DSI to HDMI/DVI adapter.
69
+
70
+ `Purchase one from the Adafruit shop <https://www.adafruit.com/>`_
71
+
72
+ Installing from PyPI
73
+ =====================
74
+
75
+ On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from
76
+ PyPI <https://pypi.org/project/adafruit-circuitpython-lt8912b/>`_.
77
+ To install for current user:
78
+
79
+ .. code-block:: shell
80
+
81
+ pip3 install adafruit-circuitpython-lt8912b
82
+
83
+ To install system-wide (this may be required in some cases):
84
+
85
+ .. code-block:: shell
86
+
87
+ sudo pip3 install adafruit-circuitpython-lt8912b
88
+
89
+ To install in a virtual environment in your current project:
90
+
91
+ .. code-block:: shell
92
+
93
+ mkdir project-name && cd project-name
94
+ python3 -m venv .venv
95
+ source .env/bin/activate
96
+ pip3 install adafruit-circuitpython-lt8912b
97
+
98
+ Installing to a Connected CircuitPython Device with Circup
99
+ ==========================================================
100
+
101
+ Make sure that you have ``circup`` installed in your Python environment.
102
+ Install it with the following command if necessary:
103
+
104
+ .. code-block:: shell
105
+
106
+ pip3 install circup
107
+
108
+ With ``circup`` installed and your CircuitPython device connected use the
109
+ following command to install:
110
+
111
+ .. code-block:: shell
112
+
113
+ circup install adafruit_lt8912b
114
+
115
+ Or the following command to update an existing version:
116
+
117
+ .. code-block:: shell
118
+
119
+ circup update
120
+
121
+ Usage Example
122
+ =============
123
+
124
+ ``LT8912B`` is a ``mipidsi.Display``. It configures the bridge over I2C and
125
+ then starts RGB888 video on the DSI bus with the same timing. The constructor
126
+ raises ``ValueError`` if the bridge does not respond on I2C. Create the bus
127
+ with a ``minimum_frequency`` of at least 24 times the pixel clock divided by
128
+ the number of lanes. The PHY may run faster than that minimum, so the bridge
129
+ checks the rate the bus actually uses, available as ``bus.frequency``.
130
+
131
+ .. code-block:: python
132
+
133
+ import board
134
+ import busio
135
+ import framebufferio
136
+ import mipidsi
137
+ import adafruit_lt8912b
138
+
139
+ i2c = busio.I2C(board.SCL, board.SDA)
140
+ bus = mipidsi.Bus(minimum_frequency=480_000_000, num_lanes=2)
141
+
142
+ bridge = adafruit_lt8912b.LT8912B(
143
+ bus,
144
+ i2c,
145
+ width=800,
146
+ height=480,
147
+ hsync_pulse_width=48,
148
+ hsync_back_porch=310,
149
+ hsync_front_porch=112,
150
+ vsync_pulse_width=3,
151
+ vsync_back_porch=29,
152
+ vsync_front_porch=13,
153
+ pixel_clock_frequency=40_000_000,
154
+ )
155
+ display = framebufferio.FramebufferDisplay(bridge)
156
+
157
+ See ``examples/lt8912b_simpletest.py`` for a fixed-timing color-bar example,
158
+ ``examples/lt8912b_720p.py`` and ``examples/lt8912b_1080p30.py`` for standard
159
+ CEA-861 modes, ``examples/lt8912b_640x480.py`` for a low-bandwidth mode, and
160
+ ``examples/lt8912b_edid.py`` for one that uses the monitor's preferred EDID
161
+ timing.
162
+
163
+ Bridge Control
164
+ ==============
165
+
166
+ The constructor resets and configures the bridge. It pulses the ``reset`` pin
167
+ when one is given and otherwise sends a software reset.
168
+
169
+ ``hot_plug_detected`` is True when a monitor is connected.
170
+
171
+ EDID Timing
172
+ ===========
173
+
174
+ ``examples/lt8912b_edid.py`` reads the monitor's preferred timing from its
175
+ EDID EEPROM at 0x50 and passes it to ``mipidsi.Display`` and ``LT8912B``, with
176
+ an 800x480 fallback. If the adapter has a DDC switch, turn it on before reading
177
+ EDID. The example also folds the DPI clock rounding into the horizontal back
178
+ porch so the monitor's refresh rate survives the 240/N divider.
179
+
180
+ Documentation
181
+ =============
182
+ API documentation for this library can be found on `Read the Docs <https://docs.circuitpython.org/projects/lt8912b/en/latest/>`_.
183
+
184
+ For information on building library documentation, please check out
185
+ `this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
186
+
187
+ Credits and License
188
+ ===================
189
+
190
+ The register initialization tables are adapted from `Espressif's
191
+ esp_lcd_lt8912b driver <https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_lt8912b>`_
192
+ and Adafruit's `Arduino LT8912B library <https://github.com/adafruit/Adafruit_LT8912B>`_.
193
+ Those portions remain under the Apache License, Version 2.0. See `NOTICE <https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/blob/main/NOTICE>`_ and
194
+ `LICENSES/Apache-2.0.txt <https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/blob/main/LICENSES/Apache-2.0.txt>`_. The Python interface is MIT licensed. Dependencies
195
+ retain their own licenses.
196
+
197
+ Contributing
198
+ ============
199
+
200
+ Contributions are welcome! Please read our `Code of Conduct
201
+ <https://github.com/adafruit/Adafruit_CircuitPython_LT8912B/blob/HEAD/CODE_OF_CONDUCT.md>`_
202
+ before contributing to help this project stay welcoming.
@@ -0,0 +1,8 @@
1
+ adafruit_lt8912b.py,sha256=fDXGEE7U1vFk2kEIcDQhTCH2-qd9kz-xK5maAkLfGkA,13738
2
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/LICENSE,sha256=H3817iFnDqZcaLt_SfLc4AZC_IKA1zeOVqGLv_1k26I,1106
3
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/NOTICE,sha256=sTn3wxq4pRsDgkYWxop72yfsE-gIl57ueakzcPU4P18,1116
4
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/licenses/NOTICE.license,sha256=rNwlROfZy-SwvMkUlgrnuB31BbtjdBA7g409He39PZI,85
5
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/METADATA,sha256=wpGSBzE2MDdF6pqKBmJ2MCSG-8k9x9a_l300-G0Xt44,7313
6
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
7
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/top_level.txt,sha256=cnUI3LgdKwFKVQDHHDZhT5Mdve1Rr_ES6-lXJ04Grfk,17
8
+ adafruit_circuitpython_lt8912b-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Scott Shawcroft for Adafruit Industries
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,26 @@
1
+ Adafruit CircuitPython LT8912B driver
2
+ Copyright (c) 2026 Adafruit Industries
3
+
4
+ This library adapts LT8912B initialization and register programming from
5
+ Espressif's esp_lcd_lt8912b component in esp-bsp:
6
+ https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_lt8912b
7
+
8
+ and from Adafruit's Arduino driver:
9
+ https://github.com/adafruit/Adafruit_LT8912B
10
+
11
+ The source files used carry these notices:
12
+ esp_lcd_lt8912b.c:
13
+ SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
14
+ esp_lcd_lt8912b.h:
15
+ SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
16
+ Adafruit_LT8912B:
17
+ SPDX-FileCopyrightText: 2026 Adafruit Industries
18
+ SPDX-License-Identifier: Apache-2.0
19
+
20
+ The register initialization tables in adafruit_lt8912b.py are derived from
21
+ that code and remain subject to the Apache License, Version 2.0, included in
22
+ LICENSES/Apache-2.0.txt. The Python interface is original Adafruit code under
23
+ the MIT license in LICENSE.
24
+
25
+ This attribution notice was added by Adafruit for this port; it is not a
26
+ NOTICE file supplied by Espressif. Dependencies retain their own licenses.
@@ -0,0 +1,2 @@
1
+ SPDX-FileCopyrightText: 2026 Adafruit Industries
2
+ SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1 @@
1
+ adafruit_lt8912b
adafruit_lt8912b.py ADDED
@@ -0,0 +1,369 @@
1
+ # SPDX-FileCopyrightText: 2026 Scott Shawcroft for Adafruit Industries
2
+ # SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
3
+ # SPDX-FileCopyrightText: 2026 Adafruit Industries
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ # Register initialization tables are adapted from Espressif's
7
+ # esp_lcd_lt8912b and Adafruit's Adafruit_LT8912B under the Apache License,
8
+ # Version 2.0. See NOTICE and LICENSES/Apache-2.0.txt.
9
+ """
10
+ `adafruit_lt8912b`
11
+ ================================================================================
12
+
13
+ CircuitPython driver for the LT8912B MIPI DSI to HDMI/DVI bridge.
14
+
15
+ `LT8912B` is a ``mipidsi.Display`` that also configures the bridge over I2C.
16
+
17
+ * Author(s): Scott Shawcroft
18
+
19
+ Implementation Notes
20
+ --------------------
21
+
22
+ **Hardware:**
23
+
24
+ * Adafruit LT8912B MIPI DSI to HDMI/DVI bridge adapter
25
+ (https://github.com/adafruit/Adafruit_LT8912B)
26
+
27
+ **Software and Dependencies:**
28
+
29
+ * Adafruit CircuitPython firmware for the supported boards, with
30
+ ``mipidsi.Display.pixel_clock_frequency`` and ``low_power_blanking``, and
31
+ ``mipidsi.Bus.minimum_frequency``, ``frequency`` and ``num_lanes``:
32
+ https://circuitpython.org/downloads
33
+
34
+ * Adafruit's Bus Device library:
35
+ https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
36
+ """
37
+
38
+ import time
39
+
40
+ import digitalio
41
+ import mipidsi
42
+ from adafruit_bus_device.i2c_device import I2CDevice
43
+ from micropython import const
44
+
45
+ __version__ = "1.0.0"
46
+ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LT8912B.git"
47
+
48
+ # The bus treats rates within 1 kHz of its minimum as meeting it.
49
+ _BUS_FREQUENCY_TOLERANCE = const(1_000)
50
+
51
+ # I2C addresses of the bridge's three register banks.
52
+ _I2C_MAIN = const(0x48)
53
+ _I2C_DSI = const(0x49)
54
+ _I2C_AVI = const(0x4A)
55
+
56
+ # MAIN bank registers
57
+ _MAIN_MIPI_RESET = const(0x03)
58
+ _MAIN_DDS_RESET = const(0x05)
59
+ _MAIN_TX_CONTROL = const(0x33)
60
+ _MAIN_SYNC_POLARITY = const(0xAB)
61
+ _MAIN_HDMI_MODE = const(0xB2)
62
+ _MAIN_HPD_STATUS = const(0xC1)
63
+
64
+ # DSI bank registers
65
+ _DSI_LANES = const(0x13)
66
+ _DSI_H_SYNC = const(0x18)
67
+ _DSI_V_SYNC = const(0x19)
68
+ _DSI_H_ACTIVE = const(0x1C)
69
+ _DSI_FIFO_BUFFER_LENGTH = const(0x2F)
70
+ _DSI_H_TOTAL = const(0x34)
71
+ _DSI_V_TOTAL = const(0x36)
72
+ _DSI_V_BACK_PORCH = const(0x38)
73
+ _DSI_V_FRONT_PORCH = const(0x3A)
74
+ _DSI_H_BACK_PORCH = const(0x3C)
75
+ _DSI_H_FRONT_PORCH = const(0x3E)
76
+ _DSI_DDS_FREQUENCY = const(0x4E)
77
+ _DSI_DDS_CONTROL = const(0x51)
78
+
79
+ # AVI bank registers
80
+ _AVI_CHECKSUM = const(0x43)
81
+
82
+ # Vendor initialization tables as flat (register, value) pairs.
83
+ _MAIN_INIT = (
84
+ b"\x02\xf7\x08\xff\x09\xff\x0a\xff\x0b\x7c\x0c\xff" # Digital clock enable
85
+ b"\x31\xe1\x32\xe1\x33\x0c\x37\x00\x38\x22\x60\x82" # TX analog
86
+ b"\x39\x45\x3a\x00\x3b\x00" # CBUS analog
87
+ b"\x44\x31\x55\x44\x57\x01\x5a\x02" # HDMI PLL analog
88
+ b"\x3e\xd6\x3f\xd4\x41\x3c" # MIPI analog: no P/N swap, EQ, control
89
+ )
90
+ _DSI_INIT_START = (
91
+ b"\x10\x01" # Termination enable
92
+ b"\x11\x10" # Settle
93
+ )
94
+ _DSI_INIT_END = (
95
+ b"\x14\x00" # Debug mux
96
+ b"\x15\x00" # No lane swap
97
+ b"\x1a\x03" # H shift
98
+ b"\x1b\x03" # V shift
99
+ # DDS: frequency word, full/empty thresholds, timer and trend coefficients
100
+ b"\x4e\x93\x4f\x3e\x50\x29\x51\x80\x1e\x4f\x1f\x5e\x20\x01\x21\x2c"
101
+ b"\x22\x01\x23\xfa\x24\x00\x25\xc8\x26\x00\x27\x5e\x28\x01\x29\x2c"
102
+ b"\x2a\x01\x2b\xfa\x2c\x00\x2d\xc8\x2e\x00\x42\x64\x43\x00\x44\x04"
103
+ b"\x45\x00\x46\x59\x47\x00\x48\xf2\x49\x06\x4a\x00\x4b\x72\x4c\x45"
104
+ b"\x4d\x00\x52\x08\x53\x00\x54\xb2\x55\x00\x56\xe4\x57\x0d\x58\x00"
105
+ b"\x59\xe4\x5a\x8a\x5b\x00\x5c\x34"
106
+ b"\x51\x00" # Release DDS control
107
+ b"\x70\x00" # Disable the internal test pattern
108
+ )
109
+ _MAIN_LVDS_BYPASS = (
110
+ b"\x44\x30" # LVDS power up
111
+ b"\x51\x05"
112
+ b"\x50\x24" # CP = 50 uA
113
+ b"\x51\x2d" # Pixel clock reference, second-order PLL
114
+ b"\x52\x04"
115
+ b"\x69\x0e"
116
+ b"\x69\x8e"
117
+ b"\x6a\x00"
118
+ b"\x6c\xb8"
119
+ b"\x6b\x51"
120
+ b"\x04\xfb" # Core PLL reset
121
+ b"\x04\xff"
122
+ b"\x7f\x00" # Disable scaler
123
+ b"\xa8\x13" # VESA format
124
+ b"\x44\x31" # LVDS output off
125
+ )
126
+ _AVI_AUDIO_OFF = (
127
+ b"\x06\x00" # Stop audio acquisition
128
+ b"\x07\x00" # Disable audio clock regeneration
129
+ b"\x34\x52" # Disable I2S input
130
+ b"\x3c\x40" # Disable audio/null packets
131
+ )
132
+
133
+
134
+ class LT8912B(mipidsi.Display):
135
+ """LT8912B MIPI DSI to HDMI/DVI bridge.
136
+
137
+ This resets the bridge and configures it for the given timing, then starts
138
+ DSI video. Use it with `framebufferio.FramebufferDisplay` like any other
139
+ ``mipidsi.Display``. The framebuffer is always RGB888.
140
+
141
+ :param ~mipidsi.Bus bus: DSI bus connected to the bridge. Create it with a
142
+ ``minimum_frequency`` of at least 24 times the pixel clock divided by
143
+ the number of lanes. The bridge checks the rate the bus actually runs
144
+ at, which can be higher.
145
+ :param ~busio.I2C i2c: I2C bus connected to the bridge.
146
+ :param int width: Active horizontal pixels.
147
+ :param int height: Active vertical lines.
148
+ :param int hsync_pulse_width: Horizontal sync width, 1 to 255 pixels.
149
+ :param int hsync_back_porch: Horizontal back porch in pixels.
150
+ :param int hsync_front_porch: Horizontal front porch in pixels.
151
+ :param int vsync_pulse_width: Vertical sync width, 1 to 255 lines.
152
+ :param int vsync_back_porch: Vertical back porch in lines.
153
+ :param int vsync_front_porch: Vertical front porch in lines.
154
+ :param int pixel_clock_frequency: Pixel clock in Hz.
155
+ :param ~microcontroller.Pin reset: Optional active-low reset pin.
156
+ :param bool hsync_positive: True for positive horizontal sync.
157
+ :param bool vsync_positive: True for positive vertical sync.
158
+ :param int vic: AVI video code, 0 to 127. 0 for custom timing.
159
+ :param int aspect_ratio: AVI aspect ratio: 0 unspecified, 1 4:3, 2 16:9.
160
+ :param kwargs: Other ``mipidsi.Display`` arguments, such as ``rotation``.
161
+ :raises ValueError: If a parameter is out of range, the bus is too slow for
162
+ the pixel clock, or the bridge does not respond on I2C.
163
+ """
164
+
165
+ def __init__( # noqa: PLR0913, PLR0917
166
+ self,
167
+ bus,
168
+ i2c,
169
+ *,
170
+ width,
171
+ height,
172
+ hsync_pulse_width,
173
+ hsync_back_porch,
174
+ hsync_front_porch,
175
+ vsync_pulse_width,
176
+ vsync_back_porch,
177
+ vsync_front_porch,
178
+ pixel_clock_frequency,
179
+ reset=None,
180
+ hsync_positive=False,
181
+ vsync_positive=False,
182
+ vic=0,
183
+ aspect_ratio=0,
184
+ **kwargs,
185
+ ):
186
+ h_total = width + hsync_front_porch + hsync_pulse_width + hsync_back_porch
187
+ v_total = height + vsync_front_porch + vsync_pulse_width + vsync_back_porch
188
+ if h_total > 0xFFFF or v_total > 0xFFFF:
189
+ raise ValueError("horizontal or vertical total does not fit in 16 bits")
190
+ if not 1 <= hsync_pulse_width <= 255:
191
+ raise ValueError("hsync_pulse_width must be between 1 and 255")
192
+ if not 1 <= vsync_pulse_width <= 255:
193
+ raise ValueError("vsync_pulse_width must be between 1 and 255")
194
+ if not 0 <= vic <= 127:
195
+ raise ValueError("vic must be between 0 and 127")
196
+ if not 0 <= aspect_ratio <= 2:
197
+ raise ValueError("aspect_ratio must be 0, 1, or 2")
198
+
199
+ # Configure the bridge before starting DSI video.
200
+ self._main = I2CDevice(i2c, _I2C_MAIN)
201
+ self._dsi = I2CDevice(i2c, _I2C_DSI)
202
+ self._avi = I2CDevice(i2c, _I2C_AVI)
203
+
204
+ self._reset_pin = None
205
+ if reset is not None:
206
+ self._reset_pin = digitalio.DigitalInOut(reset)
207
+ self._reset_pin.switch_to_output(value=True)
208
+
209
+ self._reset()
210
+
211
+ # Stop the transmitter while reconfiguring.
212
+ self._write_bit(self._main, _MAIN_TX_CONTROL, 1, False)
213
+
214
+ self._write(self._main, _MAIN_INIT)
215
+ self._write(self._dsi, _DSI_INIT_START)
216
+ self._write(self._dsi, bytes((_DSI_LANES, bus.num_lanes % 4))) # 0 means 4 lanes
217
+ self._write(self._dsi, _DSI_INIT_END)
218
+ # The vendor table seeds the DDS at 29 MHz; use the pixel clock.
219
+ self._seed_output_clock(pixel_clock_frequency)
220
+
221
+ timing = (
222
+ (_DSI_H_TOTAL, h_total),
223
+ (_DSI_V_TOTAL, v_total),
224
+ (_DSI_V_BACK_PORCH, vsync_back_porch),
225
+ (_DSI_V_FRONT_PORCH, vsync_front_porch),
226
+ (_DSI_H_BACK_PORCH, hsync_back_porch),
227
+ (_DSI_H_FRONT_PORCH, hsync_front_porch),
228
+ )
229
+ sequence = bytearray(
230
+ (
231
+ _DSI_H_SYNC,
232
+ hsync_pulse_width,
233
+ _DSI_V_SYNC,
234
+ vsync_pulse_width,
235
+ _DSI_H_ACTIVE,
236
+ width & 0xFF,
237
+ _DSI_H_ACTIVE + 1,
238
+ width >> 8,
239
+ _DSI_FIFO_BUFFER_LENGTH,
240
+ 0x0C,
241
+ )
242
+ )
243
+ for register, value in timing:
244
+ sequence.extend((register, value & 0xFF, register + 1, value >> 8))
245
+ self._write(self._dsi, sequence)
246
+
247
+ self._write_bit(self._main, _MAIN_SYNC_POLARITY, 1, hsync_positive)
248
+ self._write_bit(self._main, _MAIN_SYNC_POLARITY, 0, vsync_positive)
249
+
250
+ self._write_infoframe(vic, aspect_ratio)
251
+ self._reset_receiver()
252
+ self._write(self._main, _MAIN_LVDS_BYPASS)
253
+ self._write(self._avi, _AVI_AUDIO_OFF)
254
+ self._write_bit(self._main, _MAIN_HDMI_MODE, 0, False) # DVI
255
+ self._write_bit(self._main, _MAIN_TX_CONTROL, 1, True)
256
+
257
+ # Start DSI video only now. The lanes stay in high speed mode, so the
258
+ # bridge's receiver must be ready for the one start of transmission.
259
+ super().__init__(
260
+ bus,
261
+ b"",
262
+ width=width,
263
+ height=height,
264
+ hsync_pulse_width=hsync_pulse_width,
265
+ hsync_back_porch=hsync_back_porch,
266
+ hsync_front_porch=hsync_front_porch,
267
+ vsync_pulse_width=vsync_pulse_width,
268
+ vsync_back_porch=vsync_back_porch,
269
+ vsync_front_porch=vsync_front_porch,
270
+ pixel_clock_frequency=pixel_clock_frequency,
271
+ color_depth=24,
272
+ # The bridge regenerates the pixel clock from the DSI clock and
273
+ # loses lock if the lanes drop to low power during blanking.
274
+ low_power_blanking=False,
275
+ **kwargs,
276
+ )
277
+
278
+ # RGB888 needs 24 bits per pixel spread across the lanes.
279
+ needed = (self.pixel_clock_frequency * 24 + bus.num_lanes - 1) // bus.num_lanes
280
+ if bus.frequency + _BUS_FREQUENCY_TOLERANCE < needed:
281
+ self.deinit()
282
+ raise ValueError(
283
+ f"bus runs at {bus.frequency}, below the {needed} needed for this pixel clock"
284
+ )
285
+
286
+ # Seed again from the real clock now that the DDS has video to track.
287
+ self._seed_output_clock(self.pixel_clock_frequency)
288
+
289
+ def _reset(self):
290
+ """Pulse the reset pin if there is one, otherwise send a software reset."""
291
+ if self._reset_pin is not None:
292
+ self._reset_pin.value = False
293
+ time.sleep(0.01)
294
+ self._reset_pin.value = True
295
+ time.sleep(0.01)
296
+ return
297
+ with self._main:
298
+ self._main.write(bytes((0x01,)))
299
+ time.sleep(0.02)
300
+
301
+ def deinit(self):
302
+ """Stop the display and release the reset pin."""
303
+ if self._reset_pin is not None:
304
+ self._reset_pin.deinit()
305
+ self._reset_pin = None
306
+ super().deinit()
307
+
308
+ @property
309
+ def hot_plug_detected(self):
310
+ """True when the HDMI hot-plug detect line is high."""
311
+ return bool(self._read_byte(self._main, _MAIN_HPD_STATUS) & 0x80)
312
+
313
+ def _seed_output_clock(self, pixel_clock_frequency):
314
+ """Start the output clock generator (DDS) at the pixel clock."""
315
+ dds = int(pixel_clock_frequency / 1_000_000 * 0x16C16)
316
+ self._write(
317
+ self._dsi,
318
+ bytes(
319
+ (
320
+ _DSI_DDS_CONTROL,
321
+ 0x80,
322
+ _DSI_DDS_FREQUENCY,
323
+ dds & 0xFF,
324
+ _DSI_DDS_FREQUENCY + 1,
325
+ (dds >> 8) & 0xFF,
326
+ _DSI_DDS_FREQUENCY + 2,
327
+ (dds >> 16) & 0xFF,
328
+ _DSI_DDS_CONTROL,
329
+ 0x00,
330
+ )
331
+ ),
332
+ )
333
+
334
+ def _write_infoframe(self, vic, aspect_ratio):
335
+ """Write the AVI infoframe: RGB, picture aspect ratio and VIC."""
336
+ # The checksum also covers the 82 02 0D header.
337
+ aspect = aspect_ratio << 4 | 0x08
338
+ payload = ((0x5F - aspect - vic) & 0xFF, 0x10, aspect, 0, vic) + (0,) * 9
339
+ infoframe = bytearray()
340
+ for index, value in enumerate(payload):
341
+ infoframe.extend((_AVI_CHECKSUM + index, value))
342
+ self._write(self._avi, infoframe)
343
+
344
+ def _reset_receiver(self):
345
+ """Reset the MIPI receiver and the DDS."""
346
+ self._write(self._main, bytes((_MAIN_MIPI_RESET, 0x7F)))
347
+ time.sleep(0.01)
348
+ self._write(self._main, bytes((_MAIN_MIPI_RESET, 0xFF, _MAIN_DDS_RESET, 0xFB)))
349
+ time.sleep(0.01)
350
+ self._write(self._main, bytes((_MAIN_DDS_RESET, 0xFF)))
351
+
352
+ @staticmethod
353
+ def _write(device, sequence):
354
+ """Write a flat sequence of (register, value) pairs."""
355
+ with device:
356
+ for index in range(0, len(sequence), 2):
357
+ device.write(sequence, start=index, end=index + 2)
358
+
359
+ @staticmethod
360
+ def _read_byte(device, register):
361
+ buffer = bytearray(1)
362
+ with device:
363
+ device.write_then_readinto(bytes((register,)), buffer)
364
+ return buffer[0]
365
+
366
+ def _write_bit(self, device, register, bit, value):
367
+ mask = 1 << bit
368
+ current = self._read_byte(device, register) & ~mask
369
+ self._write(device, bytes((register, current | (mask if value else 0))))