Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fd015cd23 | ||
|
|
3bb613ee2f | ||
|
|
897cbb89c5 | ||
|
|
4e0541e207 | ||
|
|
fab1110ff3 | ||
|
|
1ae771d9f9 | ||
|
|
c78ba55fd4 | ||
|
|
ac15f4dcbc | ||
|
|
6f79f12858 | ||
|
|
ae757417c8 | ||
|
|
2830892520 | ||
|
|
ed1d014b7b | ||
|
|
f7faca4f11 |
+7
-1984
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
|
||||
/*
|
||||
* U-Boot-only additions for the MT8183 Kukui (krane) boards.
|
||||
*
|
||||
* The upstream mt8183.dtsi models the SSUSB block for the Linux mtu3
|
||||
* dual-role driver: the IPPC register space and the phys are declared
|
||||
* on the parent "mediatek,mtu3" node (ssusb@11201000), and the host
|
||||
* child (usb@11200000) carries only its "mac" register.
|
||||
*
|
||||
* U-Boot has no mtu3 driver; it binds the "mediatek,mtk-xhci" child
|
||||
* directly with drivers/usb/host/xhci-mtk.c, which expects the "mac"
|
||||
* and "ippc" register names and a phys list on the xHCI node itself.
|
||||
* Add the missing ippc address (taken from the ssusb node's "ippc"
|
||||
* reg entry) and the port phys there.
|
||||
*
|
||||
* DM only scans the subnodes of nodes that a driver binds (bus drivers
|
||||
* recurse via dm_scan_fdt_dev). Nothing binds "mediatek,mtu3", so the
|
||||
* xHCI child would never be bound and usb_start() would report "No USB
|
||||
* controllers found". Adding "simple-mfd" makes the generic simple-bus
|
||||
* driver bind the ssusb node and scan its children.
|
||||
*/
|
||||
|
||||
&ssusb {
|
||||
compatible = "mediatek,mt8183-mtu3", "mediatek,mtu3", "simple-mfd";
|
||||
};
|
||||
|
||||
&usb_host {
|
||||
reg = <0x0 0x11200000 0x0 0x1000>,
|
||||
<0x0 0x11203e00 0x0 0x0100>;
|
||||
reg-names = "mac", "ippc";
|
||||
phys = <&u2port0 PHY_TYPE_USB2>,
|
||||
<&u3port0 PHY_TYPE_USB3>;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
stdin=serial,usbkbd
|
||||
stdout=serial,vidconsole
|
||||
stderr=serial,vidconsole
|
||||
@@ -3,10 +3,20 @@
|
||||
* Copyright (C) 2020 BayLibre SAS
|
||||
* Author: Fabien Parent <fparent@baylibre.com>
|
||||
*/
|
||||
|
||||
#include <dm.h>
|
||||
#include <net.h>
|
||||
|
||||
/*
|
||||
* The krane scanout driver maps the coreboot table and the live
|
||||
* framebuffer dynamically after relocation (mmu_map_region), which the
|
||||
* default page-table budget (sized for the static memory map only)
|
||||
* does not account for. Reserve enough for those mappings.
|
||||
*/
|
||||
u64 get_page_table_size(void)
|
||||
{
|
||||
return 0x40000;
|
||||
}
|
||||
|
||||
int board_init(void)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
@@ -2,7 +2,7 @@ CONFIG_ARM=y
|
||||
CONFIG_COUNTER_FREQUENCY=13000000
|
||||
CONFIG_POSITION_INDEPENDENT=y
|
||||
CONFIG_ARCH_MEDIATEK=y
|
||||
CONFIG_TEXT_BASE=0x4c000000
|
||||
CONFIG_TEXT_BASE=0x4C001000
|
||||
CONFIG_SYS_MALLOC_F_LEN=0x4000
|
||||
CONFIG_NR_DRAM_BANKS=1
|
||||
CONFIG_ENV_SIZE=0x1000
|
||||
@@ -55,18 +55,31 @@ CONFIG_CMD_SYSBOOT=y
|
||||
CONFIG_CMD_EXT4=y
|
||||
CONFIG_CMD_FAT=y
|
||||
CONFIG_CMD_FS_GENERIC=y
|
||||
CONFIG_CMD_USB=y
|
||||
# CONFIG_DOS_PARTITION is not set
|
||||
CONFIG_ENV_IS_IN_MMC=y
|
||||
CONFIG_ENV_MMC_EMMC_HW_PARTITION=2
|
||||
CONFIG_ENV_IS_NOWHERE=y
|
||||
CONFIG_ENV_SOURCE_FILE="krane"
|
||||
CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG=y
|
||||
CONFIG_CLK=y
|
||||
CONFIG_VIDEO=y
|
||||
CONFIG_VIDEO_MT8183_SCANOUT=y
|
||||
CONFIG_MT8183_GPIO=y
|
||||
CONFIG_POWER=y
|
||||
CONFIG_DM_REGULATOR=y
|
||||
CONFIG_DM_REGULATOR_FIXED=y
|
||||
CONFIG_VIDEO_MT8183_DISPLAY=y
|
||||
# CONFIG_INPUT is not set
|
||||
# CONFIG_MMC_QUIRKS is not set
|
||||
CONFIG_MMC_MTK=y
|
||||
# CONFIG_POWER is not set
|
||||
CONFIG_BAUDRATE=921600
|
||||
CONFIG_PHY=y
|
||||
CONFIG_PHY_MTK_TPHY=y
|
||||
CONFIG_CONSOLE_ROTATION=y
|
||||
CONFIG_USB=y
|
||||
CONFIG_DM_USB=y
|
||||
CONFIG_USB_XHCI_HCD=y
|
||||
CONFIG_USB_XHCI_MTK=y
|
||||
CONFIG_USB_KEYBOARD=y
|
||||
CONFIG_USB_STORAGE=y
|
||||
CONFIG_BAUDRATE=115200
|
||||
CONFIG_DM_SERIAL=y
|
||||
CONFIG_DEBUG_UART_ANNOUNCE=y
|
||||
CONFIG_MTK_SERIAL=y
|
||||
|
||||
@@ -787,13 +787,133 @@ static const struct mtk_gate infra_clks[] = {
|
||||
GATE_INFRA3(CLK_INFRA_FBIST2FPC, CLK_TOP_MUX_MSDC50_0, 24),
|
||||
};
|
||||
|
||||
/*
|
||||
* The gate arrays follow the register bit order of each clock domain,
|
||||
* while the device tree uses the clock IDs of the legacy clock header;
|
||||
* the two do not agree everywhere. Map each DT clock ID to its array
|
||||
* position; -1 marks IDs that have no gate.
|
||||
*/
|
||||
static const int mt8183_id_infra_offs_map[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
|
||||
50, 52, 53, 55, 56, 57, 58, 59, 60, 61,
|
||||
62, 63, 64, 65, 66, -1, 67, -1, 68, 69,
|
||||
70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
|
||||
80, 81, 82, 83, -1, -1, 84, 85, 86, 87,
|
||||
54, 51, 88, 89, 90, 91, 92, 93, 94, 95,
|
||||
96,
|
||||
};
|
||||
|
||||
static const int mt8183_id_mm_offs_map[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17, 19, 20,
|
||||
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
|
||||
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 18, 42, 43, 44, 45,
|
||||
};
|
||||
|
||||
static const struct mtk_clk_tree mt8183_infracfg_tree = {
|
||||
.ext_clk_rates = ext_clock_rates,
|
||||
.num_ext_clks = ARRAY_SIZE(ext_clock_rates),
|
||||
.id_offs_map = mt8183_id_infra_offs_map,
|
||||
.id_offs_map_size = ARRAY_SIZE(mt8183_id_infra_offs_map),
|
||||
.gates = infra_clks,
|
||||
.num_gates = ARRAY_SIZE(infra_clks),
|
||||
};
|
||||
|
||||
static const struct mtk_gate_regs mm0_cg_regs = {
|
||||
.set_ofs = 0x104,
|
||||
.clr_ofs = 0x108,
|
||||
.sta_ofs = 0x100,
|
||||
};
|
||||
|
||||
static const struct mtk_gate_regs mm1_cg_regs = {
|
||||
.set_ofs = 0x114,
|
||||
.clr_ofs = 0x118,
|
||||
.sta_ofs = 0x110,
|
||||
};
|
||||
|
||||
#define GATE_MM0(_id, _parent, _shift) { \
|
||||
.id = _id, \
|
||||
.parent = _parent, \
|
||||
.regs = &mm0_cg_regs, \
|
||||
.shift = _shift, \
|
||||
.flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \
|
||||
}
|
||||
|
||||
#define GATE_MM1(_id, _parent, _shift) { \
|
||||
.id = _id, \
|
||||
.parent = _parent, \
|
||||
.regs = &mm1_cg_regs, \
|
||||
.shift = _shift, \
|
||||
.flags = CLK_GATE_SETCLR | CLK_PARENT_TOPCKGEN, \
|
||||
}
|
||||
|
||||
/*
|
||||
* MMSYS display gates. Gate bits and their grouping into CG_CON0/CG_CON1
|
||||
* match the Linux kernel driver drivers/clk/mediatek/clk-mt8183-mm.c.
|
||||
*/
|
||||
static const struct mtk_gate mm_clks[] = {
|
||||
/* MM0 */
|
||||
GATE_MM0(CLK_MM_SMI_COMMON, CLK_TOP_MUX_MM, 0),
|
||||
GATE_MM0(CLK_MM_SMI_LARB0, CLK_TOP_MUX_MM, 1),
|
||||
GATE_MM0(CLK_MM_SMI_LARB1, CLK_TOP_MUX_MM, 2),
|
||||
GATE_MM0(CLK_MM_GALS_COMM0, CLK_TOP_MUX_MM, 3),
|
||||
GATE_MM0(CLK_MM_GALS_COMM1, CLK_TOP_MUX_MM, 4),
|
||||
GATE_MM0(CLK_MM_GALS_CCU2MM, CLK_TOP_MUX_MM, 5),
|
||||
GATE_MM0(CLK_MM_GALS_IPU12MM, CLK_TOP_MUX_MM, 6),
|
||||
GATE_MM0(CLK_MM_GALS_IMG2MM, CLK_TOP_MUX_MM, 7),
|
||||
GATE_MM0(CLK_MM_GALS_CAM2MM, CLK_TOP_MUX_MM, 8),
|
||||
GATE_MM0(CLK_MM_GALS_IPU2MM, CLK_TOP_MUX_MM, 9),
|
||||
GATE_MM0(CLK_MM_MDP_DL_TXCK, CLK_TOP_MUX_MM, 10),
|
||||
GATE_MM0(CLK_MM_IPU_DL_TXCK, CLK_TOP_MUX_MM, 11),
|
||||
GATE_MM0(CLK_MM_MDP_RDMA0, CLK_TOP_MUX_MM, 12),
|
||||
GATE_MM0(CLK_MM_MDP_RDMA1, CLK_TOP_MUX_MM, 13),
|
||||
GATE_MM0(CLK_MM_MDP_RSZ0, CLK_TOP_MUX_MM, 14),
|
||||
GATE_MM0(CLK_MM_MDP_RSZ1, CLK_TOP_MUX_MM, 15),
|
||||
GATE_MM0(CLK_MM_MDP_TDSHP, CLK_TOP_MUX_MM, 16),
|
||||
GATE_MM0(CLK_MM_MDP_WROT0, CLK_TOP_MUX_MM, 17),
|
||||
GATE_MM0(CLK_MM_MDP_WDMA0, CLK_TOP_MUX_MM, 18),
|
||||
GATE_MM0(CLK_MM_FAKE_ENG, CLK_TOP_MUX_MM, 19),
|
||||
GATE_MM0(CLK_MM_DISP_OVL0, CLK_TOP_MUX_MM, 20),
|
||||
GATE_MM0(CLK_MM_DISP_OVL0_2L, CLK_TOP_MUX_MM, 21),
|
||||
GATE_MM0(CLK_MM_DISP_OVL1_2L, CLK_TOP_MUX_MM, 22),
|
||||
GATE_MM0(CLK_MM_DISP_RDMA0, CLK_TOP_MUX_MM, 23),
|
||||
GATE_MM0(CLK_MM_DISP_RDMA1, CLK_TOP_MUX_MM, 24),
|
||||
GATE_MM0(CLK_MM_DISP_WDMA0, CLK_TOP_MUX_MM, 25),
|
||||
GATE_MM0(CLK_MM_DISP_COLOR0, CLK_TOP_MUX_MM, 26),
|
||||
GATE_MM0(CLK_MM_DISP_CCORR0, CLK_TOP_MUX_MM, 27),
|
||||
GATE_MM0(CLK_MM_DISP_AAL0, CLK_TOP_MUX_MM, 28),
|
||||
GATE_MM0(CLK_MM_DISP_GAMMA0, CLK_TOP_MUX_MM, 29),
|
||||
GATE_MM0(CLK_MM_DISP_DITHER0, CLK_TOP_MUX_MM, 30),
|
||||
GATE_MM0(CLK_MM_DISP_SPLIT, CLK_TOP_MUX_MM, 31),
|
||||
/* MM1 */
|
||||
GATE_MM1(CLK_MM_DSI0_MM, CLK_TOP_MUX_MM, 0),
|
||||
GATE_MM1(CLK_MM_DSI0_IF, CLK_TOP_MUX_MM, 1),
|
||||
GATE_MM1(CLK_MM_DPI_MM, CLK_TOP_MUX_MM, 2),
|
||||
GATE_MM1(CLK_MM_DPI_IF, CLK_TOP_MUX_DPI0, 3),
|
||||
GATE_MM1(CLK_MM_FAKE_ENG2, CLK_TOP_MUX_MM, 4),
|
||||
GATE_MM1(CLK_MM_MDP_DL_RX, CLK_TOP_MUX_MM, 5),
|
||||
GATE_MM1(CLK_MM_IPU_DL_RX, CLK_TOP_MUX_MM, 6),
|
||||
GATE_MM1(CLK_MM_26M, CLK_TOP_F26M_CK_D2, 7),
|
||||
GATE_MM1(CLK_MM_MMSYS_R2Y, CLK_TOP_MUX_MM, 8),
|
||||
GATE_MM1(CLK_MM_DISP_RSZ, CLK_TOP_MUX_MM, 9),
|
||||
GATE_MM1(CLK_MM_MDP_AAL, CLK_TOP_MUX_MM, 10),
|
||||
GATE_MM1(CLK_MM_MDP_CCORR, CLK_TOP_MUX_MM, 11),
|
||||
GATE_MM1(CLK_MM_DBI_MM, CLK_TOP_MUX_MM, 12),
|
||||
GATE_MM1(CLK_MM_DBI_IF, CLK_TOP_MUX_DPI0, 13),
|
||||
};
|
||||
|
||||
static const struct mtk_clk_tree mt8183_mmsys_tree = {
|
||||
.id_offs_map = mt8183_id_mm_offs_map,
|
||||
.id_offs_map_size = ARRAY_SIZE(mt8183_id_mm_offs_map),
|
||||
.gates = mm_clks,
|
||||
.num_gates = ARRAY_SIZE(mm_clks),
|
||||
};
|
||||
|
||||
static const struct udevice_id mt8183_apmixed_compat[] = {
|
||||
{
|
||||
.compatible = "mediatek,mt8183-apmixedsys",
|
||||
@@ -847,3 +967,21 @@ U_BOOT_DRIVER(mt8183_clk_infracfg) = {
|
||||
.ops = &mtk_clk_topckgen_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
static const struct udevice_id mt8183_mmsys_compat[] = {
|
||||
{
|
||||
.compatible = "mediatek,mt8183-mmsys",
|
||||
.data = (ulong)&mt8183_mmsys_tree,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mt8183_clk_mmsys) = {
|
||||
.name = "mt8183-mmsys",
|
||||
.id = UCLASS_CLK,
|
||||
.of_match = mt8183_mmsys_compat,
|
||||
.probe = mtk_clk_probe,
|
||||
.priv_auto = sizeof(struct mtk_clk_priv),
|
||||
.ops = &mtk_clk_topckgen_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
@@ -772,3 +772,11 @@ config MPFS_GPIO
|
||||
Enable to support the GPIO driver on Polarfire SoC
|
||||
|
||||
endif
|
||||
|
||||
config MT8183_GPIO
|
||||
bool "MediaTek MT8183 GPIO driver"
|
||||
depends on DM_GPIO && ARCH_MEDIATEK
|
||||
help
|
||||
Say yes here to support the MediaTek MT8183 GPIO controller. Only
|
||||
the plain GPIO function is provided: pin mode multiplexing is the
|
||||
responsibility of the boot firmware.
|
||||
|
||||
@@ -83,3 +83,4 @@ obj-$(CONFIG_GPIO_SCMI) += gpio_scmi.o
|
||||
obj-$(CONFIG_$(PHASE_)ADP5585_GPIO) += adp5585_gpio.o
|
||||
obj-$(CONFIG_RZG2L_GPIO) += rzg2l-gpio.o
|
||||
obj-$(CONFIG_MPFS_GPIO) += mpfs_gpio.o
|
||||
obj-$(CONFIG_MT8183_GPIO) += mt8183_gpio.o
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MediaTek MT8183 GPIO driver.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*
|
||||
* Ported from the register layout of the ChromeOS depthcharge driver
|
||||
* src/drivers/gpio/mt8183.h (GpioRegs/GpioValRegs), which is the same
|
||||
* hardware the Linux pinctrl driver drivers/pinctrl/mediatek/
|
||||
* pinctrl-mtk-mt8183.c drives through its common code.
|
||||
*
|
||||
* Only the plain GPIO function is supported: pin direction (DIR), output
|
||||
* value (DOUT) and input value (DIN). The MT8183 multiplexes most pads
|
||||
* between several functions through per-pin mode registers which this
|
||||
* driver does not touch; the boot firmware is expected to have put the
|
||||
* requested pads into GPIO mode already, which holds for every pad this
|
||||
* platform uses as a GPIO at boot time.
|
||||
*
|
||||
* Each of the 192 pins lives in one of six 32-pin groups. Every group
|
||||
* owns a 16-byte GpioValRegs window (value at +0, set at +4, reset at
|
||||
* +8) for each of dir/dout/din, so bits are set or cleared atomically
|
||||
* by writing the target bit to the set/reset register.
|
||||
*/
|
||||
|
||||
#include <dm.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#define GPIO_VAL_BITS 32
|
||||
#define GPIO_NUM_GROUPS 6
|
||||
#define GPIO_VAL_REGS_SIZE 16
|
||||
|
||||
/* Offsets of the per-group GpioValRegs blocks within the controller. */
|
||||
#define GPIO_DIR_OFFSET 0x000
|
||||
#define GPIO_DOUT_OFFSET 0x100
|
||||
#define GPIO_DIN_OFFSET 0x200
|
||||
|
||||
struct mt8183_gpio_priv {
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static void __iomem *mt8183_gpio_val_reg(struct mt8183_gpio_priv *priv,
|
||||
u32 offset, unsigned int pin, int val)
|
||||
{
|
||||
unsigned int group = pin / GPIO_VAL_BITS;
|
||||
|
||||
return priv->base + offset + group * GPIO_VAL_REGS_SIZE + val * 4;
|
||||
}
|
||||
|
||||
static int mt8183_gpio_direction_input(struct udevice *dev, unsigned int pin)
|
||||
{
|
||||
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (pin >= GPIO_VAL_BITS * GPIO_NUM_GROUPS)
|
||||
return -EINVAL;
|
||||
|
||||
/* dir.reset clears the bit: the pad becomes an input. */
|
||||
writel(BIT(pin % GPIO_VAL_BITS),
|
||||
mt8183_gpio_val_reg(priv, GPIO_DIR_OFFSET, pin, 2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt8183_gpio_direction_output(struct udevice *dev, unsigned int pin,
|
||||
int value)
|
||||
{
|
||||
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (pin >= GPIO_VAL_BITS * GPIO_NUM_GROUPS)
|
||||
return -EINVAL;
|
||||
|
||||
/* dir.set sets the bit: the pad becomes an output. */
|
||||
writel(BIT(pin % GPIO_VAL_BITS),
|
||||
mt8183_gpio_val_reg(priv, GPIO_DIR_OFFSET, pin, 1));
|
||||
writel(BIT(pin % GPIO_VAL_BITS),
|
||||
mt8183_gpio_val_reg(priv, GPIO_DOUT_OFFSET, pin,
|
||||
value ? 1 : 2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt8183_gpio_get_value(struct udevice *dev, unsigned int pin)
|
||||
{
|
||||
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
|
||||
|
||||
return !!(readl(mt8183_gpio_val_reg(priv, GPIO_DIN_OFFSET, pin, 0)) &
|
||||
BIT(pin % GPIO_VAL_BITS));
|
||||
}
|
||||
|
||||
static int mt8183_gpio_set_value(struct udevice *dev, unsigned int pin,
|
||||
int value)
|
||||
{
|
||||
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* dout.set (val = 1) or dout.reset (val = 2) drives the pin. */
|
||||
writel(BIT(pin % GPIO_VAL_BITS),
|
||||
mt8183_gpio_val_reg(priv, GPIO_DOUT_OFFSET, pin,
|
||||
value ? 1 : 2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt8183_gpio_get_function(struct udevice *dev, unsigned int pin)
|
||||
{
|
||||
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (pin >= GPIO_VAL_BITS * GPIO_NUM_GROUPS)
|
||||
return GPIOF_UNUSED;
|
||||
|
||||
if (readl(mt8183_gpio_val_reg(priv, GPIO_DIR_OFFSET, pin, 0)) &
|
||||
BIT(pin % GPIO_VAL_BITS))
|
||||
return GPIOF_OUTPUT;
|
||||
|
||||
return GPIOF_INPUT;
|
||||
}
|
||||
|
||||
static const struct dm_gpio_ops mt8183_gpio_ops = {
|
||||
.direction_input = mt8183_gpio_direction_input,
|
||||
.direction_output = mt8183_gpio_direction_output,
|
||||
.get_value = mt8183_gpio_get_value,
|
||||
.set_value = mt8183_gpio_set_value,
|
||||
.get_function = mt8183_gpio_get_function,
|
||||
};
|
||||
|
||||
static int mt8183_gpio_probe(struct udevice *dev)
|
||||
{
|
||||
struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
struct mt8183_gpio_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->base = dev_read_addr_ptr(dev);
|
||||
if (!priv->base)
|
||||
return -EINVAL;
|
||||
|
||||
uc_priv->gpio_count = GPIO_VAL_BITS * GPIO_NUM_GROUPS;
|
||||
uc_priv->bank_name = "pio";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id mt8183_gpio_ids[] = {
|
||||
{ .compatible = "mediatek,mt8183-pinctrl" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mt8183_gpio) = {
|
||||
.name = "mt8183_gpio",
|
||||
.id = UCLASS_GPIO,
|
||||
.of_match = mt8183_gpio_ids,
|
||||
.probe = mt8183_gpio_probe,
|
||||
.ops = &mt8183_gpio_ops,
|
||||
.priv_auto = sizeof(struct mt8183_gpio_priv),
|
||||
};
|
||||
@@ -359,3 +359,10 @@ config PHY_COMMON_PROPS
|
||||
device tree node.
|
||||
|
||||
endmenu
|
||||
|
||||
config PHY_MTK_MIPI_TX
|
||||
bool "MediaTek MT8183 MIPI TX D-PHY driver"
|
||||
depends on PHY && ARCH_MEDIATEK
|
||||
help
|
||||
Enable this to support the MediaTek MT8183 MIPI TX (MIPI D-PHY)
|
||||
used by the MT8183 MIPI DSI host.
|
||||
|
||||
@@ -49,3 +49,4 @@ obj-y += ti/
|
||||
obj-y += qcom/
|
||||
obj-y += renesas/
|
||||
obj-y += starfive/
|
||||
obj-$(CONFIG_PHY_MTK_MIPI_TX) += phy-mtk-mipi-tx.o
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MediaTek MT8183 MIPI TX (MIPI D-PHY) driver.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*
|
||||
* Ported from the Linux kernel driver drivers/phy/mediatek/
|
||||
* phy-mtk-mipi-dsi-mt8183.c (PLL programming and analog lane
|
||||
* configuration). The efuse calibration path of the Linux driver is not
|
||||
* implemented; uncalibrated lanes work (this is what the ChromeOS
|
||||
* coreboot port does on the same hardware, src/soc/mediatek/common/
|
||||
* mtk_mipi_dphy.c in coreboot 4.14).
|
||||
*/
|
||||
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <generic-phy.h>
|
||||
#include <asm/io.h>
|
||||
#include <div64.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
/* Register offsets (MT8183 variant). */
|
||||
#define MIPITX_LANE_CON 0x000c
|
||||
#define RG_DSI_BG_LPF_EN BIT(6)
|
||||
#define RG_DSI_BG_CORE_EN BIT(7)
|
||||
#define RG_DSI_PAD_TIEL_SEL BIT(8)
|
||||
|
||||
#define MIPITX_VOLTAGE_SEL 0x0010
|
||||
#define RG_DSI_HSTX_LDO_REF_SEL GENMASK(9, 6)
|
||||
|
||||
#define MIPITX_PLL_PWR 0x0028
|
||||
#define MIPITX_PLL_CON0 0x002c
|
||||
#define MIPITX_PLL_CON1 0x0030
|
||||
#define MIPITX_PLL_CON4 0x003c
|
||||
#define RG_DSI_PLL_IBIAS GENMASK(11, 10)
|
||||
|
||||
#define MIPITX_D2_SW_CTL_EN 0x0144
|
||||
#define MIPITX_D0_SW_CTL_EN 0x0244
|
||||
#define MIPITX_CK_CKMODE_EN 0x0328
|
||||
#define DSI_CK_CKMODE_EN BIT(0)
|
||||
#define MIPITX_CK_SW_CTL_EN 0x0344
|
||||
#define MIPITX_D1_SW_CTL_EN 0x0444
|
||||
#define MIPITX_D3_SW_CTL_EN 0x0544
|
||||
#define DSI_SW_CTL_EN BIT(0)
|
||||
|
||||
#define AD_DSI_PLL_SDM_PWR_ON BIT(0)
|
||||
#define AD_DSI_PLL_SDM_ISO_EN BIT(1)
|
||||
|
||||
#define RG_DSI_PLL_EN BIT(4)
|
||||
#define RG_DSI_PLL_POSDIV GENMASK(10, 8)
|
||||
|
||||
#define MIPITX_RATE_MIN 125000000
|
||||
#define MIPITX_RATE_MAX 1600000000
|
||||
#define MIPITX_REF_CLOCK 26000000
|
||||
|
||||
struct mtk_mipi_tx {
|
||||
void __iomem *regs;
|
||||
u32 data_rate;
|
||||
};
|
||||
|
||||
static int mtk_mipi_tx_pll_enable(struct mtk_mipi_tx *mipi_tx)
|
||||
{
|
||||
void __iomem *base = mipi_tx->regs;
|
||||
unsigned int txdiv, txdiv0;
|
||||
u64 pcw;
|
||||
|
||||
if (mipi_tx->data_rate >= 2000000000) {
|
||||
txdiv = 1;
|
||||
txdiv0 = 0;
|
||||
} else if (mipi_tx->data_rate >= 1000000000) {
|
||||
txdiv = 2;
|
||||
txdiv0 = 1;
|
||||
} else if (mipi_tx->data_rate >= 500000000) {
|
||||
txdiv = 4;
|
||||
txdiv0 = 2;
|
||||
} else if (mipi_tx->data_rate > 250000000) {
|
||||
txdiv = 8;
|
||||
txdiv0 = 3;
|
||||
} else if (mipi_tx->data_rate >= MIPITX_RATE_MIN) {
|
||||
txdiv = 16;
|
||||
txdiv0 = 4;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* PLL programming, from mtk_mipi_tx_pll_enable() in the Linux
|
||||
* driver. The PLL input is the fixed 26 MHz clock.
|
||||
*/
|
||||
clrsetbits_le32(base + MIPITX_PLL_CON4, RG_DSI_PLL_IBIAS, 0);
|
||||
|
||||
setbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
|
||||
clrbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
|
||||
udelay(1);
|
||||
clrbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
|
||||
|
||||
pcw = ((u64)mipi_tx->data_rate * txdiv) << 24;
|
||||
do_div(pcw, MIPITX_REF_CLOCK);
|
||||
writel(pcw, base + MIPITX_PLL_CON0);
|
||||
clrsetbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_POSDIV,
|
||||
txdiv0 << 8);
|
||||
setbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_init(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
|
||||
|
||||
if (!mipi_tx->data_rate) {
|
||||
dev_err(phy->dev, "data rate not configured\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return mtk_mipi_tx_pll_enable(mipi_tx);
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_power_on(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
|
||||
void __iomem *base = mipi_tx->regs;
|
||||
|
||||
/*
|
||||
* Analog lane bring-up, from mtk_dsi_configure_mipi_tx() in the
|
||||
* coreboot port (proven on this platform): the LANE_CON writes
|
||||
* enable the bandgap and program the default lane impedance
|
||||
* calibration (bits 16-29), which the Linux driver instead
|
||||
* programs through the per-lane RTCODE registers and an HSTX LDO
|
||||
* reference.
|
||||
*/
|
||||
writel(0x3fff0180, base + MIPITX_LANE_CON);
|
||||
udelay(40);
|
||||
writel(0x3fff00c0, base + MIPITX_LANE_CON);
|
||||
|
||||
/* Switch off each lane until the DSI host enables it. */
|
||||
clrbits_le32(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
clrbits_le32(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
clrbits_le32(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
clrbits_le32(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
clrbits_le32(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
|
||||
setbits_le32(base + MIPITX_CK_CKMODE_EN, DSI_CK_CKMODE_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_power_off(struct phy *phy)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
|
||||
void __iomem *base = mipi_tx->regs;
|
||||
|
||||
setbits_le32(base + MIPITX_D0_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
setbits_le32(base + MIPITX_D1_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
setbits_le32(base + MIPITX_D2_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
setbits_le32(base + MIPITX_D3_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
setbits_le32(base + MIPITX_CK_SW_CTL_EN, DSI_SW_CTL_EN);
|
||||
|
||||
writel(RG_DSI_PAD_TIEL_SEL | RG_DSI_BG_CORE_EN, base + MIPITX_LANE_CON);
|
||||
writel(RG_DSI_PAD_TIEL_SEL, base + MIPITX_LANE_CON);
|
||||
|
||||
clrbits_le32(base + MIPITX_PLL_CON1, RG_DSI_PLL_EN);
|
||||
setbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_ISO_EN);
|
||||
clrbits_le32(base + MIPITX_PLL_PWR, AD_DSI_PLL_SDM_PWR_ON);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_mipi_tx_set_mode(struct phy *phy, enum phy_mode mode,
|
||||
int submode)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = dev_get_priv(phy->dev);
|
||||
|
||||
if (mode != PHY_MODE_MIPI_DPHY)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The submode carries the D-PHY data rate in bit/s, the only
|
||||
* parameter the analog PLL needs (the same convention used to
|
||||
* pass the DP link rate to PHY_MODE_DP devices).
|
||||
*/
|
||||
if (submode < MIPITX_RATE_MIN || submode > MIPITX_RATE_MAX) {
|
||||
dev_err(phy->dev, "data rate %d bps out of range\n", submode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mipi_tx->data_rate = submode;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct phy_ops mtk_mipi_tx_ops = {
|
||||
.init = mtk_mipi_tx_init,
|
||||
.set_mode = mtk_mipi_tx_set_mode,
|
||||
.power_on = mtk_mipi_tx_power_on,
|
||||
.power_off = mtk_mipi_tx_power_off,
|
||||
};
|
||||
|
||||
static int mtk_mipi_tx_probe(struct udevice *dev)
|
||||
{
|
||||
struct mtk_mipi_tx *mipi_tx = dev_get_priv(dev);
|
||||
|
||||
mipi_tx->regs = dev_read_addr_ptr(dev);
|
||||
if (!mipi_tx->regs)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id mtk_mipi_tx_ids[] = {
|
||||
{ .compatible = "mediatek,mt8183-mipi-tx" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mtk_mipi_tx) = {
|
||||
.name = "mtk_mipi_tx",
|
||||
.id = UCLASS_PHY,
|
||||
.of_match = mtk_mipi_tx_ids,
|
||||
.probe = mtk_mipi_tx_probe,
|
||||
.ops = &mtk_mipi_tx_ops,
|
||||
.priv_auto = sizeof(struct mtk_mipi_tx),
|
||||
};
|
||||
@@ -171,6 +171,17 @@ set_baud:
|
||||
writel((quot >> 8) & 0xff, &priv->regs->dlm);
|
||||
writel(UART_LCR_WLS_8, &priv->regs->lcr);
|
||||
|
||||
/*
|
||||
* In low speed mode the sample count is fixed to 16 and the
|
||||
* sample point and fraction registers are meaningless. Leave
|
||||
* them at the values the boot firmware programmed (the same
|
||||
* thing the coreboot 8250 driver does) instead of writing a
|
||||
* zero sample count, which breaks the bit rate generator and
|
||||
* garbles the output on boards the firmware already set up.
|
||||
*/
|
||||
if (samplecount <= 1)
|
||||
return;
|
||||
|
||||
/* set highspeed mode sample count & point */
|
||||
writel(samplecount - 1, &priv->regs->sample_count);
|
||||
writel((samplecount >> 1) - 1, &priv->regs->sample_point);
|
||||
|
||||
+48
-1
@@ -1022,9 +1022,16 @@ config VIDEO_MCDE_SIMPLE
|
||||
before u-boot starts, and u-boot will simply render to the pre-
|
||||
allocated frame buffer surface.
|
||||
|
||||
choice
|
||||
prompt "MT8183 display driver"
|
||||
depends on ARCH_MEDIATEK
|
||||
help
|
||||
Selects which driver drives the display pipeline on MT8183
|
||||
boards: revive the pipeline initialized by the boot firmware, or
|
||||
bring it up from scratch.
|
||||
|
||||
config VIDEO_MT8183_SCANOUT
|
||||
bool "Enable MT8183 scanout driver for firmware-initialized displays"
|
||||
depends on ARCH_MEDIATEK
|
||||
help
|
||||
Enables a display driver for the MediaTek MT8183 on boards where
|
||||
the display pipeline has already been initialized by the boot
|
||||
@@ -1035,6 +1042,27 @@ config VIDEO_MT8183_SCANOUT
|
||||
layer address register), and lets the standard vidconsole render
|
||||
into it. No display initialization is performed.
|
||||
|
||||
config VIDEO_MT8183_DISPLAY
|
||||
bool "Enable MT8183 display pipeline bring-up driver"
|
||||
depends on VIDEO_MIPI_DSI && PANEL && PHY
|
||||
select VIDEO_MTK_DSI
|
||||
select PHY_MTK_MIPI_TX
|
||||
select PANEL_BOE_TV101WUM
|
||||
help
|
||||
Enables a display driver for the MediaTek MT8183 that brings the
|
||||
full display pipeline up from scratch: MMSYS display clock gates,
|
||||
MIPI TX D-PHY, MIPI DSI host, panel initialization (BOE
|
||||
TV101WUM-NL6 and friends on the kukui family), overlay scanout of
|
||||
an U-Boot-allocated framebuffer and backlight. The framebuffer is
|
||||
placed outside the DRAM window described by the control device
|
||||
tree and reserved in the FDT and LMB.
|
||||
|
||||
If the pipeline fails to come up, the driver falls back to
|
||||
reviving the pipeline left running by the boot firmware (see
|
||||
VIDEO_MT8183_SCANOUT) and reports which path was taken.
|
||||
|
||||
endchoice
|
||||
|
||||
config OSD
|
||||
bool "Enable OSD support"
|
||||
depends on DM
|
||||
@@ -1439,3 +1467,22 @@ config SPL_HIDE_LOGO_VERSION
|
||||
endif
|
||||
|
||||
endmenu
|
||||
|
||||
config VIDEO_MTK_DSI
|
||||
bool "MediaTek MT8183 MIPI DSI host driver"
|
||||
depends on ARCH_MEDIATEK && VIDEO_MIPI_DSI && PHY
|
||||
select CLK
|
||||
help
|
||||
Enable the MediaTek MT8183 MIPI DSI host (DSI0) driver. The host
|
||||
is driven in command mode to initialize the panel and switches to
|
||||
video mode to stream out pixels. It relies on the MMSYS display
|
||||
gates being available through the clock framework and on the
|
||||
MT8183 MIPI TX D-PHY (PHY_MTK_MIPI_TX).
|
||||
|
||||
config PANEL_BOE_TV101WUM
|
||||
bool "Enable BOE TV101WUM-NL6 DSI panel driver"
|
||||
depends on PANEL && VIDEO_MIPI_DSI
|
||||
help
|
||||
Say Y here if you want to enable support for the BOE
|
||||
TV101WUM-NL6 MIPI DSI panel (1200x1920, 4 data lanes), used in
|
||||
the Lenovo IdeaPad Duet (google,krane) and other devices.
|
||||
|
||||
@@ -80,7 +80,8 @@ obj-$(CONFIG_VIDEO_LCD_SAMSUNG_S6E63M0) += samsung-s6e63m0.o
|
||||
obj-$(CONFIG_VIDEO_MCDE_SIMPLE) += mcde_simple.o
|
||||
obj-${CONFIG_VIDEO_MESON} += meson/
|
||||
obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o
|
||||
obj-$(CONFIG_VIDEO_MT8183_SCANOUT) += mt8183_scanout.o
|
||||
obj-$(CONFIG_VIDEO_MT8183_SCANOUT) += mt8183_scanout.o mt8183_disp.o
|
||||
obj-$(CONFIG_VIDEO_MT8183_DISPLAY) += mt8183_display.o mt8183_disp.o
|
||||
obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o
|
||||
obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_NX) += nexell_display.o videomodes.o nexell/
|
||||
@@ -95,3 +96,5 @@ obj-$(CONFIG_VIDEO_ZYNQMP_DPSUB) += zynqmp/
|
||||
obj-y += bridge/
|
||||
obj-y += sunxi/
|
||||
obj-y += tegra/
|
||||
obj-$(CONFIG_VIDEO_MTK_DSI) += mtk_dsi.o
|
||||
obj-$(CONFIG_PANEL_BOE_TV101WUM) += panel_boe_tv101wum.o
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MediaTek MT8183 display helpers shared by the display drivers.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*
|
||||
* The firmware-handoff model documented here applies to the MT8183 kukui
|
||||
* family of ChromeOS devices (e.g. the Lenovo IdeaPad Duet,
|
||||
* "google,krane"): the display pipeline (MMSYS -> OVL0 -> OVL0_2L ->
|
||||
* RDMA -> COLOR -> DSI -> panel) is fully initialized and running by the
|
||||
* time control is passed to the next boot stage, but depthcharge's
|
||||
* display_cleanup() disables the overlay engines and turns the backlight
|
||||
* off right before jumping to the payload.
|
||||
*/
|
||||
|
||||
#include <dm.h>
|
||||
#include <image.h>
|
||||
#include <video.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include "mt8183_disp.h"
|
||||
|
||||
/*
|
||||
* OVL0 register offsets. Source: Linux drivers/gpu/drm/mediatek/
|
||||
* mtk_disp_ovl.c (DISP_REG_OVL_EN) and device-era depthcharge
|
||||
* src/drivers/video/mtk_ddp.c (ChromeOS R93, the generation shipped on
|
||||
* kukui); both agree on 0x000c for OVL_EN. The 2L sub-engine enable
|
||||
* (DISP_REG_OVL0_2L_EN) lives in the same register block on MT8183.
|
||||
* The layer-0 source address register (DISP_REG_OVL_L0_ADDR) is not reset
|
||||
* by depthcharge's stop() and holds the live scanout address.
|
||||
*/
|
||||
#define DISP_REG_OVL_L0_ADDR 0x0f40
|
||||
#define DISP_REG_OVL_EN 0x000c
|
||||
#define DISP_REG_OVL0_2L_EN 0x100c
|
||||
|
||||
#define LB_TAG_FRAMEBUFFER 0x12
|
||||
|
||||
void mt8183_disp_enable_backlight(void)
|
||||
{
|
||||
writel(MTK_GPIO_DOUT_BIT(PAD_DISP_PWM),
|
||||
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_DISP_PWM));
|
||||
writel(MTK_GPIO_DOUT_BIT(PAD_EN_LCD_BL),
|
||||
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_EN_LCD_BL));
|
||||
}
|
||||
|
||||
int mt8183_disp_find_framebuffer(u64 table,
|
||||
struct mt8183_lb_framebuffer *fb)
|
||||
{
|
||||
void *base = (void *)(uintptr_t)table;
|
||||
u32 header_bytes, entries, i;
|
||||
void *rec;
|
||||
|
||||
if (get_unaligned_le32(base) != 0x4f49424c) /* "LBIO" */
|
||||
return -ENOENT;
|
||||
|
||||
header_bytes = get_unaligned_le32(base + 4);
|
||||
entries = get_unaligned_le32(base + 20);
|
||||
if (header_bytes < 24 || header_bytes > 4096 ||
|
||||
entries == 0 || entries > 4096)
|
||||
return -EINVAL;
|
||||
|
||||
rec = base + header_bytes;
|
||||
for (i = 0; i < entries; i++) {
|
||||
u32 tag = get_unaligned_le32(rec);
|
||||
u32 rsize = get_unaligned_le32(rec + 4);
|
||||
|
||||
if (rsize < 8)
|
||||
return -EINVAL;
|
||||
|
||||
if (tag == LB_TAG_FRAMEBUFFER) {
|
||||
if (rsize < sizeof(*fb))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(fb, rec, sizeof(*fb));
|
||||
fb->physical_address =
|
||||
get_unaligned_le64(rec + 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rec += rsize;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int mt8183_disp_setup_handoff(fdt_addr_t ovl, struct video_uc_plat *plat,
|
||||
struct video_priv *uc_priv)
|
||||
{
|
||||
struct mt8183_lb_framebuffer fb;
|
||||
u64 addr;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Revive the pipeline: undo depthcharge's display_cleanup() by
|
||||
* re-enabling the overlay engines and driving the backlight GPIOs
|
||||
* high. No panel or DSI re-initialization is needed: the panel is
|
||||
* powered and the DSI link stays up through the handoff.
|
||||
*/
|
||||
writel(1, ovl + DISP_REG_OVL_EN);
|
||||
writel(1, ovl + DISP_REG_OVL0_2L_EN);
|
||||
mt8183_disp_enable_backlight();
|
||||
|
||||
/*
|
||||
* The coreboot table sits above the DRAM window described by the
|
||||
* control DTB; map it before parsing.
|
||||
*/
|
||||
mmu_map_region(COREBOOT_TABLE_ADDR, SZ_4K, false);
|
||||
|
||||
ret = mt8183_disp_find_framebuffer(COREBOOT_TABLE_ADDR, &fb);
|
||||
if (ret)
|
||||
return log_msg_ret("lbio", ret);
|
||||
|
||||
if (fb.bits_per_pixel != 32 || fb.red_pos != 16 || fb.red_size != 8 ||
|
||||
fb.green_pos != 8 || fb.green_size != 8 ||
|
||||
fb.blue_pos != 0 || fb.blue_size != 8)
|
||||
return log_msg_ret("fmt", -EOPNOTSUPP);
|
||||
|
||||
/*
|
||||
* Use the address from the coreboot table when it is a plausible
|
||||
* DRAM address (>= 1 GiB), otherwise fall back to the address the
|
||||
* firmware actually programmed into the overlay, which it left in
|
||||
* place across the handoff.
|
||||
*/
|
||||
addr = fb.physical_address;
|
||||
if (addr < SZ_1G)
|
||||
addr = readl(ovl + DISP_REG_OVL_L0_ADDR);
|
||||
|
||||
if (addr < SZ_1G)
|
||||
return log_msg_ret("scanout", -ENODEV);
|
||||
|
||||
plat->base = addr;
|
||||
plat->size = fb.bytes_per_line * fb.y_resolution;
|
||||
|
||||
/*
|
||||
* The scanout buffer was allocated by the boot firmware and is not
|
||||
* tracked by U-Boot's memory management. Without a reservation it
|
||||
* shows up as conventional RAM: once the kernel's page allocator
|
||||
* goes live it reuses the region and the display corrupts. Record a
|
||||
* memory reservation in the control FDT and feed it to LMB, so that
|
||||
* EFI page allocations skip it, the EFI DT fixup marks it reserved
|
||||
* in the memory map, and the kernel honors the /memreserve of the
|
||||
* DTB it receives.
|
||||
*/
|
||||
ret = fdt_add_mem_rsv((void *)gd->fdt_blob, plat->base, plat->size);
|
||||
if (ret)
|
||||
log_warning("FDT reservation for scanout buffer failed: %d\n",
|
||||
ret);
|
||||
else
|
||||
boot_fdt_add_mem_rsv_regions(gd->fdt_blob);
|
||||
|
||||
/* The scanout surface is above the DTB DRAM window: map it. */
|
||||
mmu_map_region(plat->base, ALIGN(plat->size, SZ_4K), false);
|
||||
|
||||
uc_priv->bpix = VIDEO_BPP32;
|
||||
uc_priv->xsize = fb.x_resolution;
|
||||
uc_priv->ysize = fb.y_resolution;
|
||||
uc_priv->line_length = fb.bytes_per_line;
|
||||
|
||||
/*
|
||||
* The BOE TV101WUM-NL6 panel is mounted rotated 270 degrees in
|
||||
* the krane chassis (see the panel node's "rotation" property in
|
||||
* the upstream DT). The keyboard-covered landscape orientation
|
||||
* therefore needs the console rotated; the uclass then binds the
|
||||
* rotated vidconsole3 text driver. rot=1 (90 degrees) was tried
|
||||
* first and read upside down on the device.
|
||||
*/
|
||||
uc_priv->rot = 3;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* MediaTek MT8183 display helpers shared by the display drivers.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _MT8183_DISP_H
|
||||
#define _MT8183_DISP_H
|
||||
|
||||
#include <video.h>
|
||||
|
||||
/*
|
||||
* Backlight GPIOs. The MT8183 GPIO controller is at 0x10005000; the dout
|
||||
* block starts at +0x100 with 16 bytes per 32-pin group and set@+4 (layout
|
||||
* of GpioRegs/GpioValRegs in device-era depthcharge src/drivers/gpio/
|
||||
* mt8183.h). On kukui the backlight is driven by two dedicated GPIOs:
|
||||
* DISP_PWM (pin 43) and EN_LCD_BL (PERIPHERAL_EN13, pin 176); depthcharge's
|
||||
* kukui_backlight_update() drives both high to turn the backlight on.
|
||||
*/
|
||||
#define MTK_GPIO_BASE 0x10005000
|
||||
#define MTK_GPIO_DOUT_SET(pin) (MTK_GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 4)
|
||||
#define MTK_GPIO_DOUT_BIT(pin) BIT((pin) % 32)
|
||||
#define PAD_DISP_PWM 43
|
||||
#define PAD_EN_LCD_BL 176
|
||||
|
||||
/*
|
||||
* The coreboot table sits at a fixed address on kukui: 0xffed9000, size
|
||||
* 0x380 (coreboot memlayout; confirmed on the device through
|
||||
* /sys/firmware/fdt and the coreboot sysfs tags).
|
||||
*/
|
||||
#define COREBOOT_TABLE_ADDR 0xffed9000
|
||||
|
||||
struct mt8183_lb_framebuffer {
|
||||
u32 tag;
|
||||
u32 size;
|
||||
u64 physical_address;
|
||||
u32 x_resolution;
|
||||
u32 y_resolution;
|
||||
u32 bytes_per_line;
|
||||
u8 bits_per_pixel;
|
||||
u8 red_pos;
|
||||
u8 red_size;
|
||||
u8 green_pos;
|
||||
u8 green_size;
|
||||
u8 blue_pos;
|
||||
u8 blue_size;
|
||||
u8 reserved_pos;
|
||||
u8 reserved_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* mt8183_disp_enable_backlight() - drive the two kukui backlight GPIOs on
|
||||
*
|
||||
* Board glue for the krane backlight (DISP_PWM and EN_LCD_BL are plain
|
||||
* GPIOs on this board; the upstream backlight node describes a PWM setup
|
||||
* whose pin muxing the boot firmware already provides).
|
||||
*/
|
||||
void mt8183_disp_enable_backlight(void);
|
||||
|
||||
/**
|
||||
* mt8183_disp_find_framebuffer() - parse the coreboot table LBIO record
|
||||
*
|
||||
* Layout from coreboot src/commonlib/include/commonlib/coreboot_tables.h.
|
||||
* physical_address == 0 is legitimate: upstream coreboot 4.14 (the
|
||||
* generation shipped on kukui) publishes the framebuffer record with
|
||||
* fb_addr=0; the live scanout address then comes from OVL_L0_ADDR.
|
||||
*/
|
||||
int mt8183_disp_find_framebuffer(u64 table,
|
||||
struct mt8183_lb_framebuffer *fb);
|
||||
|
||||
/**
|
||||
* mt8183_disp_setup_handoff() - revive the firmware pipeline and take over
|
||||
* its scanout surface
|
||||
*
|
||||
* Re-enables the overlay engines and the backlight (undoing depthcharge's
|
||||
* display_cleanup()), discovers the geometry and the live scanout address
|
||||
* from the coreboot table framebuffer record (falling back to the OVL
|
||||
* layer-0 address register, which still holds the address the firmware
|
||||
* used), maps the surface and reserves it in the control FDT and LMB, and
|
||||
* fills @plat and @uc_priv with the resulting geometry.
|
||||
*
|
||||
* @ovl: OVL0 base address (already mapped)
|
||||
* Return: 0 if OK, -ve on error
|
||||
*/
|
||||
int mt8183_disp_setup_handoff(fdt_addr_t ovl, struct video_uc_plat *plat,
|
||||
struct video_priv *uc_priv);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,577 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MediaTek MT8183 display driver: full pipeline bring-up.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*
|
||||
* On the MT8183 kukui family of ChromeOS devices (e.g. the Lenovo IdeaPad
|
||||
* Duet, "google,krane") the display pipeline is
|
||||
*
|
||||
* MMSYS -> OVL0 -> OVL0_2L -> RDMA0 -> COLOR0 -> CCORR0 -> AAL0 ->
|
||||
* GAMMA0 -> DITHER0 -> DSI0 -> panel
|
||||
*
|
||||
* This driver brings that pipeline up from scratch, ported from the
|
||||
* ChromeOS coreboot port for this platform (src/soc/mediatek/mt8183/
|
||||
* ddp.c, src/soc/mediatek/mt8183/include/soc/ddp.h and the mainboard
|
||||
* display bring-up in src/mainboard/google/kukui/mainboard.c, all in
|
||||
* coreboot 4.14, the generation shipped on these devices):
|
||||
*
|
||||
* - enable the MMSYS free-run clock gates for the display engines and
|
||||
* take the SMI local arbiter out of the M4U domain (the OVL fetches
|
||||
* the framebuffer directly from DRAM in U-Boot),
|
||||
* - power and initialize the panel over DSI (panel uclass child of the
|
||||
* DSI controller node) and start the DSI video stream,
|
||||
* - route and enable the overlay chain, configure the overlay layer
|
||||
* for the framebuffer surface,
|
||||
* - turn the backlight on.
|
||||
*
|
||||
* If any stage fails (for example when the boot firmware has already
|
||||
* shut the pipeline down in a way this sequence cannot recover), the
|
||||
* driver falls back to reviving the pipeline left running by the boot
|
||||
* firmware instead, and reports which path was taken.
|
||||
*/
|
||||
|
||||
#include <command.h>
|
||||
#include <dm.h>
|
||||
#include <dsi_host.h>
|
||||
#include <efi_loader.h>
|
||||
#include <fdt_support.h>
|
||||
#include <image.h>
|
||||
#include <panel.h>
|
||||
#include <video.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include "mt8183_disp.h"
|
||||
|
||||
/*
|
||||
* Base addresses of the display pipeline components. Source: coreboot
|
||||
* 4.14 src/soc/mediatek/mt8183/include/soc/addressmap.h (IO_PHYS +
|
||||
* offsets, matching the upstream device tree reg properties). MT8183 has
|
||||
* a single MMSYS instance and one display path, so these are fixed for
|
||||
* the SoC. Note the naming difference with coreboot, whose
|
||||
* DISP_OVL1_BASE (0x14009000) is the OVL0_2L engine of the upstream
|
||||
* device tree (node ovl_2l0@14009000).
|
||||
*/
|
||||
#define MMSYS_BASE 0x14000000
|
||||
#define DISP_OVL0_BASE 0x14008000
|
||||
#define DISP_OVL0_2L_BASE 0x14009000
|
||||
#define DISP_RDMA0_BASE 0x1400b000
|
||||
#define DISP_COLOR0_BASE 0x1400e000
|
||||
#define DISP_CCORR0_BASE 0x1400f000
|
||||
#define DISP_AAL0_BASE 0x14010000
|
||||
#define DISP_GAMMA0_BASE 0x14011000
|
||||
#define DISP_DITHER0_BASE 0x14012000
|
||||
#define DISP_MUTEX_BASE 0x14016000
|
||||
#define SMI_LARB0 0x14017000
|
||||
|
||||
/*
|
||||
* MMSYS free-run clock gates (1 = gated). Offsets and bit assignments
|
||||
* from coreboot 4.14 src/soc/mediatek/mt8183/include/soc/ddp.h
|
||||
* (mmsys_cg_con0 at 0x100, mmsys_cg_con1 at 0x110); the same bits are
|
||||
* modeled as CLK_MM_* gate clocks by the clock framework.
|
||||
*/
|
||||
#define MMSYS_CG_CON0 0x100
|
||||
#define MMSYS_CG_CON1 0x110
|
||||
#define CG_CON0_DISP_ALL (BIT(0) | BIT(1) | BIT(3) | BIT(4) | \
|
||||
BIT(20) | BIT(21) | BIT(23) | BIT(26) | \
|
||||
BIT(27) | BIT(28) | BIT(29) | BIT(30))
|
||||
#define CG_CON1_DISP_DSI0 (BIT(0) | BIT(1))
|
||||
|
||||
/* MMSYS routing registers, from the coreboot ddp.h register map. */
|
||||
#define DISP_OVL0_MOUT_EN 0xf00
|
||||
#define DISP_OVL0_2L_MOUT_EN 0xf04
|
||||
#define DISP_DITHER0_MOUT_EN 0xf0c
|
||||
#define DISP_PATH0_SEL_IN 0xf24
|
||||
#define DSI0_SEL_IN 0xf2c
|
||||
#define DISP_RDMA0_SOUT_SEL_IN 0xf50
|
||||
|
||||
#define OVL0_MOUT_EN_OVL0_2L BIT(4)
|
||||
#define OVL0_2L_MOUT_EN_DISP_PATH0 BIT(0)
|
||||
#define DITHER0_MOUT_EN_DISP_DSI0 BIT(0)
|
||||
#define DISP_PATH0_SEL_IN_OVL0_2L 1
|
||||
#define DSI0_SEL_IN_DITHER0_MOUT 0
|
||||
#define RDMA0_SOUT_SEL_IN_COLOR 1
|
||||
|
||||
/* DISP_MUTEX */
|
||||
#define MUTEX_MOD_DISP_RDMA0 BIT(0)
|
||||
#define MUTEX_MOD_DISP_OVL0 BIT(9)
|
||||
#define MUTEX_MOD_DISP_OVL0_2L BIT(10)
|
||||
#define MUTEX_MOD_DISP_COLOR0 BIT(13)
|
||||
#define MUTEX_MOD_DISP_CCORR0 BIT(14)
|
||||
#define MUTEX_MOD_DISP_AAL0 BIT(15)
|
||||
#define MUTEX_MOD_DISP_GAMMA0 BIT(16)
|
||||
#define MUTEX_MOD_DISP_DITHER0 BIT(17)
|
||||
#define MUTEX_MOD_MAIN_PATH (MUTEX_MOD_DISP_OVL0 | \
|
||||
MUTEX_MOD_DISP_OVL0_2L | \
|
||||
MUTEX_MOD_DISP_RDMA0 | \
|
||||
MUTEX_MOD_DISP_COLOR0 | \
|
||||
MUTEX_MOD_DISP_CCORR0 | \
|
||||
MUTEX_MOD_DISP_AAL0 | \
|
||||
MUTEX_MOD_DISP_GAMMA0 | \
|
||||
MUTEX_MOD_DISP_DITHER0)
|
||||
#define MUTEX_SOF_DSI0 1
|
||||
|
||||
/*
|
||||
* OVL registers, from the coreboot ddp_common.h register map (struct
|
||||
* disp_ovl_regs). MT8183 has 4-layer OVL engines; the main path uses
|
||||
* layer 0 of OVL0 for the framebuffer.
|
||||
*/
|
||||
#define DISP_REG_OVL_EN 0x000c
|
||||
#define DISP_REG_OVL_ROI_SIZE 0x0020
|
||||
#define DISP_REG_OVL_DATAPATH_CON 0x0024
|
||||
#define DISP_REG_OVL_ROI_BGCLR 0x0028
|
||||
#define DISP_REG_OVL_SRC_CON 0x002c
|
||||
#define DISP_REG_OVL_L0_CON 0x0030
|
||||
#define DISP_REG_OVL_L0_SRC_SIZE 0x0038
|
||||
#define DISP_REG_OVL_L0_PITCH 0x0044
|
||||
#define DISP_REG_OVL_L0_RDMA_CTRL 0x0070
|
||||
#define DISP_REG_OVL_L0_RDMA_GMC 0x0078
|
||||
#define DISP_REG_OVL_L0_ADDR 0x0f40
|
||||
|
||||
#define OVL_INFMT_RGBA8888 2
|
||||
#define RDMA_MEM_GMC 0x40402020
|
||||
|
||||
/* DISP_RDMA0 registers, from the coreboot ddp_common.h register map. */
|
||||
#define DISP_REG_RDMA_GLOBAL_CON 0x0010
|
||||
#define DISP_REG_RDMA_SIZE_CON_0 0x0014
|
||||
#define DISP_REG_RDMA_SIZE_CON_1 0x0018
|
||||
#define DISP_REG_RDMA_FIFO_CON 0x0040
|
||||
#define RDMA_ENGINE_EN BIT(0)
|
||||
#define RDMA_FIFO_UNDERFLOW_EN BIT(31)
|
||||
#define RDMA_FIFO_PSEUDO_SIZE(bytes) (((bytes) / 16) << 16)
|
||||
#define RDMA_OUTPUT_VALID_FIFO_THRESHOLD(bytes) ((bytes) / 16)
|
||||
|
||||
/* DISP_COLOR0, from the coreboot ddp_common.h register map. */
|
||||
#define DISP_REG_COLOR_CFG_MAIN 0x0400
|
||||
#define DISP_REG_COLOR_START 0x0c00
|
||||
#define DISP_REG_COLOR_WIDTH 0x0c50
|
||||
#define DISP_REG_COLOR_HEIGHT 0x0c54
|
||||
#define COLOR_BYPASS_ALL BIT(7)
|
||||
#define COLOR_SEQ_SEL BIT(13)
|
||||
|
||||
/*
|
||||
* The display "post processing" blocks (CCORR/AAL/GAMMA/DITHER) share
|
||||
* this layout (struct disp_pq_regs of the coreboot ddp.h): enable at
|
||||
* +0x0, config at +0x1c, size at +0x30.
|
||||
*/
|
||||
#define DISP_REG_PQ_EN 0x0000
|
||||
#define DISP_REG_PQ_CFG 0x001c
|
||||
#define DISP_REG_PQ_SIZE 0x0030
|
||||
#define PQ_EN BIT(0)
|
||||
#define PQ_RELAY_MODE BIT(0)
|
||||
|
||||
#define SMI_LARB_NON_SEC_CON 0x380
|
||||
|
||||
/*
|
||||
* The RDMA pseudo FIFO size: the mediatek,rdma-fifo-size property of the
|
||||
* RDMA0 node in the upstream device tree (5 KiB, same value as the
|
||||
* coreboot port passes to rdma_config()).
|
||||
*/
|
||||
#define RDMA_FIFO_SIZE (5 * 1024)
|
||||
|
||||
/*
|
||||
* The framebuffer sits inside the DRAM window the control device tree
|
||||
* describes (2 GiB starting at DRAM_BASE), which both the LMB and EFI
|
||||
* memory maps cover. The region is handed to the OS as reserved: see
|
||||
* the STAGE_PIPELINE comment.
|
||||
*/
|
||||
#define DRAM_BASE 0x40000000
|
||||
#define FB_ADDR 0xbe000000
|
||||
|
||||
enum mt8183_disp_stage {
|
||||
STAGE_CLOCKS,
|
||||
STAGE_PANEL,
|
||||
STAGE_DSI_INIT,
|
||||
STAGE_DSI_ENABLE,
|
||||
STAGE_PIPELINE,
|
||||
STAGE_COUNT,
|
||||
};
|
||||
|
||||
static const char *const stage_names[STAGE_COUNT] = {
|
||||
"clocks", "panel", "dsi-init", "dsi-enable", "pipeline",
|
||||
};
|
||||
|
||||
struct mt8183_display_priv {
|
||||
struct udevice *dsi_host;
|
||||
struct udevice *panel;
|
||||
struct mipi_dsi_device device;
|
||||
struct display_timing timing;
|
||||
};
|
||||
|
||||
static void mt8183_disp_clock_on(void)
|
||||
{
|
||||
void __iomem *mmsys = (void __iomem *)MMSYS_BASE;
|
||||
|
||||
clrbits_le32(mmsys + MMSYS_CG_CON0, CG_CON0_DISP_ALL);
|
||||
clrbits_le32(mmsys + MMSYS_CG_CON1, CG_CON1_DISP_DSI0);
|
||||
|
||||
/*
|
||||
* Turn off the M4U port of SMI LARB0: the OVL reads DRAM
|
||||
* directly in U-Boot, without an IOMMU.
|
||||
*/
|
||||
writel(0, (void __iomem *)(SMI_LARB0 + SMI_LARB_NON_SEC_CON));
|
||||
}
|
||||
|
||||
static void disp_config_main_path_connection(void)
|
||||
{
|
||||
void __iomem *mmsys = (void __iomem *)MMSYS_BASE;
|
||||
|
||||
writel(OVL0_MOUT_EN_OVL0_2L, mmsys + DISP_OVL0_MOUT_EN);
|
||||
writel(OVL0_2L_MOUT_EN_DISP_PATH0, mmsys + DISP_OVL0_2L_MOUT_EN);
|
||||
writel(DISP_PATH0_SEL_IN_OVL0_2L, mmsys + DISP_PATH0_SEL_IN);
|
||||
writel(RDMA0_SOUT_SEL_IN_COLOR, mmsys + DISP_RDMA0_SOUT_SEL_IN);
|
||||
writel(DITHER0_MOUT_EN_DISP_DSI0, mmsys + DISP_DITHER0_MOUT_EN);
|
||||
writel(DSI0_SEL_IN_DITHER0_MOUT, mmsys + DSI0_SEL_IN);
|
||||
}
|
||||
|
||||
static void disp_config_main_path_mutex(void)
|
||||
{
|
||||
void __iomem *mutex = (void __iomem *)DISP_MUTEX_BASE;
|
||||
|
||||
/* mutex[0]: EN at +0x20, CTL at +0x2c, MOD at +0x30. */
|
||||
writel(MUTEX_MOD_MAIN_PATH, mutex + 0x30);
|
||||
writel(MUTEX_SOF_DSI0 | (MUTEX_SOF_DSI0 << 6), mutex + 0x2c);
|
||||
writel(BIT(0), mutex + 0x20);
|
||||
}
|
||||
|
||||
static void enable_pq(void __iomem *regs, u32 width, u32 height,
|
||||
int enable_relay)
|
||||
{
|
||||
writel(height << 16 | width, regs + DISP_REG_PQ_SIZE);
|
||||
if (enable_relay)
|
||||
writel(PQ_RELAY_MODE, regs + DISP_REG_PQ_CFG);
|
||||
writel(PQ_EN, regs + DISP_REG_PQ_EN);
|
||||
}
|
||||
|
||||
static void mt8183_disp_pipeline_setup(u32 width, u32 height,
|
||||
u32 frame_addr)
|
||||
{
|
||||
void __iomem *ovl0 = (void __iomem *)DISP_OVL0_BASE;
|
||||
void __iomem *ovl0_2l = (void __iomem *)DISP_OVL0_2L_BASE;
|
||||
void __iomem *rdma0 = (void __iomem *)DISP_RDMA0_BASE;
|
||||
void __iomem *color0 = (void __iomem *)DISP_COLOR0_BASE;
|
||||
u32 pixel_clk = width * height * 60;
|
||||
u32 threshold;
|
||||
|
||||
/*
|
||||
* ROI of the two overlays of the main path; OVL0 paints its
|
||||
* background opaque (same background color as the coreboot
|
||||
* port), OVL0_2L passes the stream through.
|
||||
*/
|
||||
writel(height << 16 | width, ovl0 + DISP_REG_OVL_ROI_SIZE);
|
||||
writel(0xff0000ff, ovl0 + DISP_REG_OVL_ROI_BGCLR);
|
||||
writel(height << 16 | width, ovl0_2l + DISP_REG_OVL_ROI_SIZE);
|
||||
writel(0, ovl0_2l + DISP_REG_OVL_ROI_BGCLR);
|
||||
|
||||
/* RDMA0: fetch size and FIFO thresholds. */
|
||||
writel(width, rdma0 + DISP_REG_RDMA_SIZE_CON_0);
|
||||
writel(height, rdma0 + DISP_REG_RDMA_SIZE_CON_1);
|
||||
/*
|
||||
* Enable FIFO underflow since DSI can't be blocked. Set the
|
||||
* output threshold to 6 microseconds with 7/6 overhead to
|
||||
* account for blanking, and with a pixel depth of 4 bytes
|
||||
* (from the coreboot rdma_config()).
|
||||
*/
|
||||
threshold = pixel_clk * 4 * 7 / 1000;
|
||||
if (threshold > RDMA_FIFO_SIZE)
|
||||
threshold = RDMA_FIFO_SIZE;
|
||||
writel(RDMA_FIFO_UNDERFLOW_EN |
|
||||
RDMA_FIFO_PSEUDO_SIZE(RDMA_FIFO_SIZE) |
|
||||
RDMA_OUTPUT_VALID_FIFO_THRESHOLD(threshold),
|
||||
rdma0 + DISP_REG_RDMA_FIFO_CON);
|
||||
writel(RDMA_ENGINE_EN, rdma0 + DISP_REG_RDMA_GLOBAL_CON);
|
||||
|
||||
/* COLOR0: bypass all color processing. */
|
||||
writel(width, color0 + DISP_REG_COLOR_WIDTH);
|
||||
writel(height, color0 + DISP_REG_COLOR_HEIGHT);
|
||||
writel(COLOR_BYPASS_ALL | COLOR_SEQ_SEL,
|
||||
color0 + DISP_REG_COLOR_CFG_MAIN);
|
||||
writel(BIT(0), color0 + DISP_REG_COLOR_START);
|
||||
|
||||
/* Post processing blocks: relay the stream through. */
|
||||
enable_pq((void __iomem *)DISP_CCORR0_BASE, width, height, 1);
|
||||
enable_pq((void __iomem *)DISP_AAL0_BASE, width, height, 0);
|
||||
enable_pq((void __iomem *)DISP_GAMMA0_BASE, width, height, 0);
|
||||
enable_pq((void __iomem *)DISP_DITHER0_BASE, width, height, 1);
|
||||
|
||||
/* Routing of the main path and its trigger (DSI vsync). */
|
||||
disp_config_main_path_connection();
|
||||
disp_config_main_path_mutex();
|
||||
|
||||
/*
|
||||
* Overlay layer 0: the U-Boot framebuffer, 32-bit XRGB. The
|
||||
* input format value is the one the coreboot port programs for
|
||||
* the 32-bit framebuffer surface.
|
||||
*/
|
||||
writel(OVL_INFMT_RGBA8888 << 12, ovl0 + DISP_REG_OVL_L0_CON);
|
||||
writel(height << 16 | width, ovl0 + DISP_REG_OVL_L0_SRC_SIZE);
|
||||
writel((width * 4) & 0xffff, ovl0 + DISP_REG_OVL_L0_PITCH);
|
||||
writel(frame_addr, ovl0 + DISP_REG_OVL_L0_ADDR);
|
||||
writel(BIT(0), ovl0 + DISP_REG_OVL_L0_RDMA_CTRL);
|
||||
writel(RDMA_MEM_GMC, ovl0 + DISP_REG_OVL_L0_RDMA_GMC);
|
||||
writel(BIT(0), ovl0 + DISP_REG_OVL_SRC_CON);
|
||||
|
||||
/* Select the background color of OVL0_2L from its input. */
|
||||
setbits_le32(ovl0_2l + DISP_REG_OVL_DATAPATH_CON, BIT(2));
|
||||
|
||||
/*
|
||||
* The pass-through overlay must have no layers of its own: the
|
||||
* boot firmware leaves its layer configuration in place across
|
||||
* the handoff, and a stale OVL0_2L layer would blend leftover
|
||||
* DRAM contents on top of the U-Boot framebuffer.
|
||||
*/
|
||||
writel(0, ovl0_2l + DISP_REG_OVL_SRC_CON);
|
||||
|
||||
/* Enable the overlay engines. */
|
||||
writel(1, ovl0 + DISP_REG_OVL_EN);
|
||||
writel(1, ovl0_2l + DISP_REG_OVL_EN);
|
||||
}
|
||||
|
||||
static int mt8183_display_bringup(struct udevice *dev,
|
||||
struct video_uc_plat *plat,
|
||||
struct video_priv *uc_priv,
|
||||
const char **fail_stage)
|
||||
{
|
||||
struct mt8183_display_priv *priv = dev_get_priv(dev);
|
||||
struct mipi_dsi_panel_plat *mplat;
|
||||
int stage, ret = 0;
|
||||
|
||||
for (stage = STAGE_CLOCKS; stage < STAGE_COUNT && !ret; stage++) {
|
||||
*fail_stage = stage_names[stage];
|
||||
|
||||
switch (stage) {
|
||||
case STAGE_CLOCKS:
|
||||
/*
|
||||
* Power sequencing note: the display MTCMOS power
|
||||
* domain (SCPSYS) is enabled by the boot firmware
|
||||
* and stays on through the handoff; U-Boot has no
|
||||
* SCPSYS driver for MT8183 to (re-)enable it.
|
||||
*/
|
||||
mt8183_disp_clock_on();
|
||||
break;
|
||||
case STAGE_PANEL:
|
||||
ret = uclass_first_device_err(UCLASS_PANEL,
|
||||
&priv->panel);
|
||||
if (ret)
|
||||
break;
|
||||
mplat = dev_get_plat(priv->panel);
|
||||
|
||||
/*
|
||||
* Retrieve the panel data link characteristics and
|
||||
* publish the DSI device the panel driver sends its
|
||||
* commands through.
|
||||
*/
|
||||
priv->device.dev = priv->panel;
|
||||
priv->device.lanes = mplat->lanes;
|
||||
priv->device.format = mplat->format;
|
||||
priv->device.mode_flags = mplat->mode_flags;
|
||||
mplat->device = &priv->device;
|
||||
break;
|
||||
case STAGE_DSI_INIT:
|
||||
ret = uclass_get_device(UCLASS_DSI_HOST, 0,
|
||||
&priv->dsi_host);
|
||||
if (ret)
|
||||
break;
|
||||
ret = panel_get_display_timing(priv->panel,
|
||||
&priv->timing);
|
||||
if (ret)
|
||||
break;
|
||||
ret = dsi_host_init(priv->dsi_host, &priv->device,
|
||||
&priv->timing, 4, NULL);
|
||||
break;
|
||||
case STAGE_DSI_ENABLE:
|
||||
/*
|
||||
* Runs the panel power sequence and the
|
||||
* manufacturer init commands over DSI, in
|
||||
* command mode.
|
||||
*/
|
||||
ret = panel_enable_backlight(priv->panel);
|
||||
if (ret)
|
||||
break;
|
||||
/* Switch the host to video mode and stream. */
|
||||
ret = dsi_host_enable(priv->dsi_host);
|
||||
break;
|
||||
case STAGE_PIPELINE:
|
||||
/*
|
||||
* Allocate the framebuffer surface and make sure
|
||||
* the OS never reuses it. A plain /memreserve/
|
||||
* entry is not an option: libfdt splices entries
|
||||
* by shifting the structure block, which needs
|
||||
* slack the relocated control FDT does not have
|
||||
* (fdt_splice_() fails with -ENOSPC). Instead:
|
||||
*
|
||||
* - allocate the region as reserved through the
|
||||
* EFI allocation API, which marks it in both
|
||||
* LMB and the EFI memory map; the arm64 EFI
|
||||
* stub derives the kernel memory layout from
|
||||
* the latter, and
|
||||
* - shrink the memory bank of the control FDT
|
||||
* for kernels booted without EFI, replacing
|
||||
* the reg property in place, which needs no
|
||||
* slack.
|
||||
*/
|
||||
int mem;
|
||||
|
||||
plat->base = FB_ADDR;
|
||||
plat->size = priv->timing.hactive.typ * 4 *
|
||||
priv->timing.vactive.typ;
|
||||
|
||||
if (CONFIG_IS_ENABLED(EFI_LOADER)) {
|
||||
u64 fb = plat->base;
|
||||
|
||||
efi_allocate_pages(EFI_ALLOCATE_ADDRESS,
|
||||
EFI_RESERVED_MEMORY_TYPE,
|
||||
efi_size_in_pages(plat->size),
|
||||
&fb);
|
||||
}
|
||||
|
||||
mem = fdt_path_offset((void *)gd->fdt_blob,
|
||||
"/memory");
|
||||
if (mem >= 0) {
|
||||
fdt64_t reg[2];
|
||||
|
||||
reg[0] = cpu_to_fdt64(DRAM_BASE);
|
||||
reg[1] = cpu_to_fdt64(FB_ADDR - DRAM_BASE);
|
||||
fdt_setprop((void *)gd->fdt_blob, mem, "reg",
|
||||
reg, sizeof(reg));
|
||||
}
|
||||
mmu_map_region(plat->base,
|
||||
ALIGN(plat->size, SZ_4K), false);
|
||||
|
||||
mt8183_disp_pipeline_setup(priv->timing.hactive.typ,
|
||||
priv->timing.vactive.typ,
|
||||
plat->base);
|
||||
mt8183_disp_enable_backlight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
uc_priv->bpix = VIDEO_BPP32;
|
||||
uc_priv->xsize = priv->timing.hactive.typ;
|
||||
uc_priv->ysize = priv->timing.vactive.typ;
|
||||
uc_priv->line_length = uc_priv->xsize * 4;
|
||||
uc_priv->rot = dev_read_u32_default(priv->panel, "rotation", 0) / 90;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt8183_display_probe(struct udevice *dev)
|
||||
{
|
||||
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
|
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
const char *fail_stage = "start";
|
||||
fdt_addr_t ovl;
|
||||
int ret;
|
||||
|
||||
ovl = dev_read_addr(dev);
|
||||
if (ovl == FDT_ADDR_T_NONE)
|
||||
return log_msg_ret("ovl", -EINVAL);
|
||||
|
||||
printf("[dsi] phase 0: bring-up start\n");
|
||||
ret = mt8183_display_bringup(dev, plat, uc_priv, &fail_stage);
|
||||
if (!ret) {
|
||||
printf("[dsi] phase 9: full bring-up done\n");
|
||||
printf("Video: MT8183 display %dx%d@32bpp at %llx (cold bring-up)\n",
|
||||
uc_priv->xsize, uc_priv->ysize,
|
||||
(unsigned long long)plat->base);
|
||||
|
||||
/*
|
||||
* The framebuffer is ordinary cacheable DRAM that the overlay
|
||||
* reads through DMA: make video_sync() flush the dirty cache
|
||||
* lines, or freshly drawn console output only becomes visible
|
||||
* when the cache happens to evict them.
|
||||
*/
|
||||
video_set_flush_dcache(dev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fall back to reviving the pipeline left running by the boot
|
||||
* firmware.
|
||||
*/
|
||||
printf("[dsi] phase F: bring-up failed at stage %s (%d), falling back to firmware handoff\n",
|
||||
fail_stage, ret);
|
||||
ret = mt8183_disp_setup_handoff(ovl, plat, uc_priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
printf("Video: MT8183 scanout %dx%d@32bpp at %llx (firmware handoff)\n",
|
||||
uc_priv->xsize, uc_priv->ysize,
|
||||
(unsigned long long)plat->base);
|
||||
|
||||
video_set_flush_dcache(dev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id mt8183_display_ids[] = {
|
||||
{ .compatible = "mediatek,mt8183-disp-ovl" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mt8183_display) = {
|
||||
.name = "mt8183_display",
|
||||
.id = UCLASS_VIDEO,
|
||||
.of_match = mt8183_display_ids,
|
||||
.probe = mt8183_display_probe,
|
||||
.priv_auto = sizeof(struct mt8183_display_priv),
|
||||
};
|
||||
|
||||
/*
|
||||
* TEMPORARY DIAGNOSTIC - DO NOT COMMIT.
|
||||
* Prints an ASCII luminance map of the framebuffer (16x16 px blocks)
|
||||
* over the console, in landscape orientation.
|
||||
*/
|
||||
static int do_fbmap(struct cmd_tbl *cmdtp, int flag, int argc,
|
||||
char *const argv[])
|
||||
{
|
||||
struct udevice *vid;
|
||||
struct video_priv *priv;
|
||||
u32 *fb;
|
||||
int fx, fy, i, j, b, maxcol;
|
||||
|
||||
uclass_first_device(UCLASS_VIDEO, &vid);
|
||||
if (!vid)
|
||||
return CMD_RET_FAILURE;
|
||||
priv = dev_get_uclass_priv(vid);
|
||||
fb = (u32 *)priv->fb;
|
||||
|
||||
b = argc > 1 ? simple_strtoul(argv[1], NULL, 10) : 16;
|
||||
maxcol = argc > 2 ? simple_strtoul(argv[2], NULL, 10) : priv->xsize;
|
||||
if (b < 1 || b > 256 || maxcol < b || maxcol > priv->xsize)
|
||||
return CMD_RET_USAGE;
|
||||
|
||||
printf("fb %dx%d ll=%d rot=%d @p %08llx b=%d maxcol=%d\n",
|
||||
priv->xsize, priv->ysize, priv->line_length, priv->rot,
|
||||
(unsigned long long)(uintptr_t)priv->fb, b, maxcol);
|
||||
|
||||
for (j = 0; j < priv->ysize / b; j++) {
|
||||
for (i = 0; i < maxcol / b; i++) {
|
||||
unsigned sum = 0;
|
||||
char c;
|
||||
|
||||
for (fy = 0; fy < b; fy++)
|
||||
for (fx = 0; fx < b; fx++)
|
||||
sum += (fb[(j * b + fy) *
|
||||
priv->line_length / 4 +
|
||||
i * b + fx] >> 8) & 0xff;
|
||||
sum /= b * b;
|
||||
c = sum < 16 ? '.' : sum < 64 ? '-' : sum < 128 ? '+' : '#';
|
||||
putc(c);
|
||||
}
|
||||
putc('\n');
|
||||
}
|
||||
return CMD_RET_SUCCESS;
|
||||
}
|
||||
|
||||
U_BOOT_CMD(fbmap, 3, 1, do_fbmap,
|
||||
"print ASCII luminance map of the framebuffer",
|
||||
"[block_px] [max_col_px]");
|
||||
@@ -16,193 +16,31 @@
|
||||
* the geometry and the live scanout address from the coreboot table
|
||||
* framebuffer record (falling back to the OVL L0 layer address register,
|
||||
* which still holds the address depthcharge used), and hands the surface to
|
||||
* the video uclass so the standard vidconsole can render into it.
|
||||
* the video uclass so the standard vidconsole can render into it. See
|
||||
* mt8183_disp.c for the shared handoff code and mt8183_display.c for a
|
||||
* driver that brings the pipeline up from scratch instead.
|
||||
*/
|
||||
|
||||
#include <dm.h>
|
||||
#include <video.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/system.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/*
|
||||
* OVL0 register offsets. Source: Linux drivers/gpu/drm/mediatek/
|
||||
* mtk_disp_ovl.c (DISP_REG_OVL_EN) and device-era depthcharge
|
||||
* src/drivers/video/mtk_ddp.c (ChromeOS R93, the generation shipped on
|
||||
* kukui); both agree on 0x000c for OVL_EN. The 2L sub-engine enable
|
||||
* (DISP_REG_OVL0_2L_EN) lives in the same register block on MT8183.
|
||||
* The layer-0 source address register (DISP_REG_OVL_L0_ADDR) is not reset
|
||||
* by depthcharge's stop() and holds the live scanout address.
|
||||
*/
|
||||
#define DISP_REG_OVL_L0_ADDR 0x0f40
|
||||
#define DISP_REG_OVL_EN 0x000c
|
||||
#define DISP_REG_OVL0_2L_EN 0x100c
|
||||
|
||||
/*
|
||||
* Backlight GPIOs. The MT8183 GPIO controller is at 0x10005000; the dout
|
||||
* block starts at +0x100 with 16 bytes per 32-pin group and set@+4 (layout
|
||||
* of GpioRegs/GpioValRegs in device-era depthcharge src/drivers/gpio/
|
||||
* mt8183.h). On kukui the backlight is driven by two dedicated GPIOs:
|
||||
* DISP_PWM (pin 43) and EN_LCD_BL (PERIPHERAL_EN13, pin 176); depthcharge's
|
||||
* kukui_backlight_update() drives both high to turn the backlight on.
|
||||
*/
|
||||
#define MTK_GPIO_BASE 0x10005000
|
||||
#define MTK_GPIO_DOUT_SET(pin) (MTK_GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 4)
|
||||
#define MTK_GPIO_DOUT_BIT(pin) BIT((pin) % 32)
|
||||
#define PAD_DISP_PWM 43
|
||||
#define PAD_EN_LCD_BL 176
|
||||
|
||||
/*
|
||||
* The coreboot table sits at a fixed address on kukui: 0xffed9000, size
|
||||
* 0x380 (coreboot memlayout; confirmed on the device through
|
||||
* /sys/firmware/fdt and the coreboot sysfs tags). It is deliberately
|
||||
* hardcoded here rather than read from the handoff DTB: on this platform
|
||||
* the FDT pointer passed at entry cannot be relied upon, and U-Boot boots
|
||||
* with its own embedded control DTB which has no /firmware/coreboot node.
|
||||
*/
|
||||
#define COREBOOT_TABLE_ADDR 0xffed9000
|
||||
|
||||
#define LB_TAG_FRAMEBUFFER 0x12
|
||||
|
||||
/*
|
||||
* Layout from coreboot src/commonlib/include/commonlib/coreboot_tables.h:
|
||||
* struct lb_header: sig[4] "LBIO", header_bytes, header_checksum,
|
||||
* table_bytes, table_checksum, table_entries (LE u32)
|
||||
* struct lb_record: tag, size
|
||||
* struct lb_framebuffer (record payload):
|
||||
* physical_address @8 (4-byte-aligned LE u64), x_resolution @16,
|
||||
* y_resolution @20, bytes_per_line @24, bits_per_pixel @28,
|
||||
* red_pos/size @29/30, green @31/32, blue @33/34,
|
||||
* reserved @35/36, orientation @37, flags @38, pad @39; size 40.
|
||||
*
|
||||
* physical_address == 0 is legitimate: upstream coreboot 4.14 (the
|
||||
* generation shipped on kukui) publishes the framebuffer record with
|
||||
* fb_addr=0. The live scanout address then comes from OVL_L0_ADDR.
|
||||
*/
|
||||
struct lb_framebuffer {
|
||||
u32 tag;
|
||||
u32 size;
|
||||
u64 physical_address;
|
||||
u32 x_resolution;
|
||||
u32 y_resolution;
|
||||
u32 bytes_per_line;
|
||||
u8 bits_per_pixel;
|
||||
u8 red_pos;
|
||||
u8 red_size;
|
||||
u8 green_pos;
|
||||
u8 green_size;
|
||||
u8 blue_pos;
|
||||
u8 blue_size;
|
||||
u8 reserved_pos;
|
||||
u8 reserved_size;
|
||||
};
|
||||
|
||||
static int find_framebuffer(u64 table, struct lb_framebuffer *fb)
|
||||
{
|
||||
void *base = (void *)(uintptr_t)table;
|
||||
u32 header_bytes, entries, i;
|
||||
void *rec;
|
||||
|
||||
if (get_unaligned_le32(base) != 0x4f49424c) /* "LBIO" */
|
||||
return -ENOENT;
|
||||
|
||||
header_bytes = get_unaligned_le32(base + 4);
|
||||
entries = get_unaligned_le32(base + 20);
|
||||
if (header_bytes < 24 || header_bytes > 4096 ||
|
||||
entries == 0 || entries > 4096)
|
||||
return -EINVAL;
|
||||
|
||||
rec = base + header_bytes;
|
||||
for (i = 0; i < entries; i++) {
|
||||
u32 tag = get_unaligned_le32(rec);
|
||||
u32 rsize = get_unaligned_le32(rec + 4);
|
||||
|
||||
if (rsize < 8)
|
||||
return -EINVAL;
|
||||
|
||||
if (tag == LB_TAG_FRAMEBUFFER) {
|
||||
if (rsize < sizeof(*fb))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(fb, rec, sizeof(*fb));
|
||||
fb->physical_address =
|
||||
get_unaligned_le64(rec + 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
rec += rsize;
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
#include "mt8183_disp.h"
|
||||
|
||||
static int mt8183_scanout_probe(struct udevice *dev)
|
||||
{
|
||||
struct video_uc_plat *plat = dev_get_uclass_plat(dev);
|
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
struct lb_framebuffer fb;
|
||||
fdt_addr_t ovl;
|
||||
u64 addr;
|
||||
int ret;
|
||||
|
||||
ovl = dev_read_addr(dev);
|
||||
if (ovl == FDT_ADDR_T_NONE)
|
||||
return log_msg_ret("ovl", -EINVAL);
|
||||
|
||||
/*
|
||||
* Revive the pipeline: undo depthcharge's display_cleanup() by
|
||||
* re-enabling the overlay engines and driving the backlight GPIOs
|
||||
* high. No panel or DSI re-initialization is needed: the panel is
|
||||
* powered and the DSI link stays up through the handoff.
|
||||
*/
|
||||
writel(1, ovl + DISP_REG_OVL_EN);
|
||||
writel(1, ovl + DISP_REG_OVL0_2L_EN);
|
||||
writel(MTK_GPIO_DOUT_BIT(PAD_DISP_PWM),
|
||||
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_DISP_PWM));
|
||||
writel(MTK_GPIO_DOUT_BIT(PAD_EN_LCD_BL),
|
||||
(void __iomem *)MTK_GPIO_DOUT_SET(PAD_EN_LCD_BL));
|
||||
|
||||
/*
|
||||
* The coreboot table sits above the DRAM window described by the
|
||||
* control DTB; map it before parsing.
|
||||
*/
|
||||
mmu_map_region(COREBOOT_TABLE_ADDR, SZ_4K, false);
|
||||
|
||||
ret = find_framebuffer(COREBOOT_TABLE_ADDR, &fb);
|
||||
ret = mt8183_disp_setup_handoff(ovl, plat, uc_priv);
|
||||
if (ret)
|
||||
return log_msg_ret("lbio", ret);
|
||||
|
||||
if (fb.bits_per_pixel != 32 || fb.red_pos != 16 || fb.red_size != 8 ||
|
||||
fb.green_pos != 8 || fb.green_size != 8 ||
|
||||
fb.blue_pos != 0 || fb.blue_size != 8)
|
||||
return log_msg_ret("fmt", -ENOTSUPP);
|
||||
|
||||
/*
|
||||
* Use the address from the coreboot table when it is a plausible
|
||||
* DRAM address (>= 1 GiB), otherwise fall back to the address the
|
||||
* firmware actually programmed into the overlay, which it left in
|
||||
* place across the handoff.
|
||||
*/
|
||||
addr = fb.physical_address;
|
||||
if (addr < SZ_1G)
|
||||
addr = readl(ovl + DISP_REG_OVL_L0_ADDR);
|
||||
|
||||
if (addr < SZ_1G)
|
||||
return log_msg_ret("scanout", -ENODEV);
|
||||
|
||||
plat->base = addr;
|
||||
plat->size = fb.bytes_per_line * fb.y_resolution;
|
||||
|
||||
/* The scanout surface is above the DTB DRAM window: map it. */
|
||||
mmu_map_region(plat->base, ALIGN(plat->size, SZ_4K), false);
|
||||
video_set_flush_dcache(dev, true);
|
||||
|
||||
uc_priv->bpix = VIDEO_BPP32;
|
||||
uc_priv->xsize = fb.x_resolution;
|
||||
uc_priv->ysize = fb.y_resolution;
|
||||
uc_priv->line_length = fb.bytes_per_line;
|
||||
return ret;
|
||||
|
||||
printf("Video: MT8183 scanout %dx%d@32bpp at %llx\n",
|
||||
uc_priv->xsize, uc_priv->ysize, (unsigned long long)plat->base);
|
||||
|
||||
@@ -0,0 +1,666 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MediaTek MT8183 MIPI DSI host driver.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*
|
||||
* Ported from the Linux kernel driver drivers/gpu/drm/mediatek/mtk_dsi.c
|
||||
* (register map and D-PHY timing computation) and from the ChromeOS
|
||||
* coreboot port src/soc/mediatek/common/dsi.c (video mode timing and the
|
||||
* command FIFO programming), the two implementations proven to drive
|
||||
* this host controller on the kukui family of boards.
|
||||
*/
|
||||
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <dsi_host.h>
|
||||
#include <generic-phy.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <div64.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
/* DSI_START */
|
||||
#define DSI_START 0x00
|
||||
|
||||
/* DSI_INTSTA */
|
||||
#define DSI_INTSTA 0x0c
|
||||
#define LPRX_RD_RDY_INT_FLAG BIT(0)
|
||||
#define CMD_DONE_INT_FLAG BIT(1)
|
||||
#define VM_DONE_INT_FLAG BIT(3)
|
||||
#define DSI_BUSY BIT(31)
|
||||
|
||||
/* DSI_CON_CTRL */
|
||||
#define DSI_CON_CTRL 0x10
|
||||
#define DSI_RESET BIT(0)
|
||||
#define DSI_EN BIT(1)
|
||||
#define DPHY_RESET BIT(2)
|
||||
|
||||
/* DSI_MODE_CTRL */
|
||||
#define DSI_MODE_CTRL 0x14
|
||||
#define CMD_MODE 0
|
||||
#define SYNC_PULSE_MODE 1
|
||||
#define SYNC_EVENT_MODE 2
|
||||
#define BURST_MODE 3
|
||||
|
||||
/* DSI_TXRX_CTRL */
|
||||
#define DSI_TXRX_CTRL 0x18
|
||||
#define EOTP_DISABLE BIT(6)
|
||||
#define NON_CONTINUOUS_CLK BIT(16)
|
||||
|
||||
/* DSI_PSCTRL */
|
||||
#define DSI_PSCTRL 0x1c
|
||||
#define DSI_PS_WC 0x3fff
|
||||
#define PACKED_PS_16BIT_RGB565 (0 << 16)
|
||||
#define LOOSELY_PS_18BIT_RGB666 (0x1 << 16)
|
||||
#define PACKED_PS_18BIT_RGB666 (0x2 << 16)
|
||||
#define PACKED_PS_24BIT_RGB888 (0x3 << 16)
|
||||
#define PIXEL_STREAM_CUSTOM_HEADER_SHIFT 26
|
||||
#define PIXEL_STREAM_CUSTOM_HEADER 0xb
|
||||
|
||||
/* DSI_SIZE_CON */
|
||||
#define DSI_SIZE_CON 0x38
|
||||
#define DSI_SIZE_CON_HEIGHT_SHIFT 16
|
||||
#define DSI_SIZE_CON_WIDTH_SHIFT 0
|
||||
|
||||
/* Per-line word counts */
|
||||
#define DSI_VSA_NL 0x20
|
||||
#define DSI_VBP_NL 0x24
|
||||
#define DSI_VFP_NL 0x28
|
||||
#define DSI_VACT_NL 0x2c
|
||||
#define DSI_HSA_WC 0x50
|
||||
#define DSI_HBP_WC 0x54
|
||||
#define DSI_HFP_WC 0x58
|
||||
|
||||
/* DSI_CMDQ_SIZE */
|
||||
#define DSI_CMDQ_SIZE 0x60
|
||||
#define CMDQ_SIZE 0x3f
|
||||
|
||||
/* DSI_PHY_LCCON */
|
||||
#define DSI_PHY_LCCON 0x104
|
||||
#define LC_HS_TX_EN BIT(0)
|
||||
|
||||
/* DSI_PHY_TIMECON0..3 */
|
||||
#define DSI_PHY_TIMECON0 0x110
|
||||
#define DSI_PHY_TIMECON1 0x114
|
||||
#define DSI_PHY_TIMECON2 0x118
|
||||
#define DSI_PHY_TIMECON3 0x11c
|
||||
|
||||
/* DSI_CMDQ0 */
|
||||
#define DSI_CMDQ0 0x200
|
||||
#define CONFIG (0xff << 0)
|
||||
#define SHORT_PACKET 0
|
||||
#define LONG_PACKET 2
|
||||
#define BTA BIT(2)
|
||||
#define HSTX BIT(3)
|
||||
#define DATA_ID (0xff << 8)
|
||||
#define DATA_0 (0xff << 16)
|
||||
#define DATA_1 (0xff << 24)
|
||||
|
||||
/* DSI_RX_DATA0 */
|
||||
#define DSI_RX_DATA0 0x74
|
||||
|
||||
/* DSI_FORCE_COMMIT */
|
||||
#define DSI_FORCE_COMMIT 0x190
|
||||
#define DSI_FORCE_COMMIT_USE_MMSYS BIT(0)
|
||||
#define DSI_FORCE_COMMIT_ALWAYS BIT(1)
|
||||
|
||||
#define MIN_HFP_BYTE 2
|
||||
#define MIN_HBP_BYTE 2
|
||||
|
||||
#define CMD_DONE_TIMEOUT_US 20000
|
||||
|
||||
struct mtk_phy_timing {
|
||||
u32 lpx;
|
||||
u32 da_hs_prepare;
|
||||
u32 da_hs_zero;
|
||||
u32 da_hs_trail;
|
||||
u32 ta_go;
|
||||
u32 ta_sure;
|
||||
u32 ta_get;
|
||||
u32 da_hs_exit;
|
||||
u32 da_hs_sync;
|
||||
u32 clk_hs_prepare;
|
||||
u32 clk_hs_post;
|
||||
u32 clk_hs_trail;
|
||||
u32 clk_hs_zero;
|
||||
u32 clk_hs_exit;
|
||||
};
|
||||
|
||||
struct mtk_dsi {
|
||||
void __iomem *regs;
|
||||
struct mipi_dsi_host host;
|
||||
struct mipi_dsi_device *device;
|
||||
struct display_timing timing;
|
||||
struct clk clk_engine;
|
||||
struct clk clk_digital;
|
||||
struct phy dphy;
|
||||
bool phy_on;
|
||||
bool video_mode;
|
||||
u32 lanes;
|
||||
u32 data_rate;
|
||||
struct mtk_phy_timing phy_timing;
|
||||
};
|
||||
|
||||
static inline struct mtk_dsi *host_to_mtk_dsi(struct mipi_dsi_host *host)
|
||||
{
|
||||
return container_of(host, struct mtk_dsi, host);
|
||||
}
|
||||
|
||||
static u32 mtk_dsi_bits_per_pixel(enum mipi_dsi_pixel_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
return 16;
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
return 18;
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
case MIPI_DSI_FMT_RGB888:
|
||||
default:
|
||||
return 24;
|
||||
}
|
||||
}
|
||||
|
||||
static void mtk_dsi_start(struct mtk_dsi *dsi)
|
||||
{
|
||||
writel(0, dsi->regs + DSI_START);
|
||||
writel(1, dsi->regs + DSI_START);
|
||||
}
|
||||
|
||||
static void mtk_dsi_stop(struct mtk_dsi *dsi)
|
||||
{
|
||||
writel(0, dsi->regs + DSI_START);
|
||||
}
|
||||
|
||||
/*
|
||||
* D-PHY timing computation, from mtk_dsi_phy_timing() in both the Linux
|
||||
* driver and the coreboot port. The values are in units of byte clocks at
|
||||
* the selected data rate.
|
||||
*/
|
||||
static void mtk_dsi_phy_timing(struct mtk_dsi *dsi)
|
||||
{
|
||||
struct mtk_phy_timing *t = &dsi->phy_timing;
|
||||
u32 data_rate_mhz = DIV_ROUND_UP(dsi->data_rate, 1000000);
|
||||
u32 timcon0, timcon1, timcon2, timcon3;
|
||||
|
||||
memset(t, 0, sizeof(*t));
|
||||
|
||||
t->lpx = (60 * data_rate_mhz / (8 * 1000)) + 1;
|
||||
t->da_hs_prepare = (80 * data_rate_mhz + 4 * 1000) / 8000;
|
||||
t->da_hs_zero = (170 * data_rate_mhz + 10 * 1000) / 8000 + 1 -
|
||||
t->da_hs_prepare;
|
||||
t->da_hs_trail = t->da_hs_prepare + 1;
|
||||
|
||||
t->ta_go = 4 * t->lpx - 2;
|
||||
t->ta_sure = t->lpx + 2;
|
||||
t->ta_get = 4 * t->lpx;
|
||||
t->da_hs_exit = 2 * t->lpx + 1;
|
||||
|
||||
t->da_hs_sync = 1;
|
||||
|
||||
t->clk_hs_prepare = 70 * data_rate_mhz / (8 * 1000);
|
||||
t->clk_hs_post = t->clk_hs_prepare + 8;
|
||||
t->clk_hs_trail = t->clk_hs_prepare;
|
||||
t->clk_hs_zero = t->clk_hs_trail * 4;
|
||||
t->clk_hs_exit = 2 * t->clk_hs_trail;
|
||||
|
||||
timcon0 = t->lpx | t->da_hs_prepare << 8 | t->da_hs_zero << 16 |
|
||||
t->da_hs_trail << 24;
|
||||
timcon1 = t->ta_go | t->ta_sure << 8 | t->ta_get << 16 |
|
||||
t->da_hs_exit << 24;
|
||||
timcon2 = t->da_hs_sync << 8 | t->clk_hs_zero << 16 |
|
||||
t->clk_hs_trail << 24;
|
||||
timcon3 = t->clk_hs_prepare | t->clk_hs_post << 8 |
|
||||
t->clk_hs_exit << 16;
|
||||
|
||||
writel(timcon0, dsi->regs + DSI_PHY_TIMECON0);
|
||||
writel(timcon1, dsi->regs + DSI_PHY_TIMECON1);
|
||||
writel(timcon2, dsi->regs + DSI_PHY_TIMECON2);
|
||||
writel(timcon3, dsi->regs + DSI_PHY_TIMECON3);
|
||||
}
|
||||
|
||||
static void mtk_dsi_reset(struct mtk_dsi *dsi)
|
||||
{
|
||||
/*
|
||||
* Enable the DSI core, then pulse the core reset while keeping
|
||||
* the enable bit, like mtk_dsi_enable() + mtk_dsi_reset_engine()
|
||||
* of the Linux driver. Writing CON_CTRL directly would clear
|
||||
* DSI_EN and leave the engine disabled for the panel
|
||||
* initialization commands that follow.
|
||||
*/
|
||||
setbits_le32(dsi->regs + DSI_CON_CTRL, DSI_EN);
|
||||
setbits_le32(dsi->regs + DSI_CON_CTRL, DSI_RESET);
|
||||
clrbits_le32(dsi->regs + DSI_CON_CTRL, DSI_RESET);
|
||||
}
|
||||
|
||||
static void mtk_dsi_reset_dphy(struct mtk_dsi *dsi)
|
||||
{
|
||||
setbits_le32(dsi->regs + DSI_CON_CTRL, DPHY_RESET);
|
||||
clrbits_le32(dsi->regs + DSI_CON_CTRL, DPHY_RESET);
|
||||
}
|
||||
|
||||
static void mtk_dsi_rxtx_control(struct mtk_dsi *dsi)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
switch (dsi->lanes) {
|
||||
case 1:
|
||||
val = 1 << 2;
|
||||
break;
|
||||
case 2:
|
||||
val = 3 << 2;
|
||||
break;
|
||||
case 3:
|
||||
val = 7 << 2;
|
||||
break;
|
||||
case 4:
|
||||
default:
|
||||
val = 0xf << 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (dsi->device->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
|
||||
val |= NON_CONTINUOUS_CLK;
|
||||
|
||||
/*
|
||||
* EOT packets are disabled unless the panel requests them (the
|
||||
* same logic as mtk_dsi_rxtx_control() in the coreboot port).
|
||||
*/
|
||||
if (!(dsi->device->mode_flags & MIPI_DSI_MODE_EOT_PACKET))
|
||||
val |= EOTP_DISABLE;
|
||||
|
||||
writel(val, dsi->regs + DSI_TXRX_CTRL);
|
||||
}
|
||||
|
||||
static void mtk_dsi_clk_hs_mode_enable(struct mtk_dsi *dsi)
|
||||
{
|
||||
setbits_le32(dsi->regs + DSI_PHY_LCCON, LC_HS_TX_EN);
|
||||
}
|
||||
|
||||
static void mtk_dsi_clk_hs_mode_disable(struct mtk_dsi *dsi)
|
||||
{
|
||||
clrbits_le32(dsi->regs + DSI_PHY_LCCON, LC_HS_TX_EN);
|
||||
}
|
||||
|
||||
static void mtk_dsi_set_mode(struct mtk_dsi *dsi, u32 mode_flags)
|
||||
{
|
||||
u32 val = CMD_MODE;
|
||||
|
||||
if (mode_flags & MIPI_DSI_MODE_VIDEO) {
|
||||
if (mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
|
||||
val = BURST_MODE;
|
||||
else if (mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
|
||||
val = SYNC_PULSE_MODE;
|
||||
else
|
||||
val = SYNC_EVENT_MODE;
|
||||
}
|
||||
|
||||
writel(val, dsi->regs + DSI_MODE_CTRL);
|
||||
}
|
||||
|
||||
static void mtk_dsi_config_vdo_timing(struct mtk_dsi *dsi)
|
||||
{
|
||||
struct display_timing *timing = &dsi->timing;
|
||||
u32 bpp = mtk_dsi_bits_per_pixel(dsi->device->format);
|
||||
u32 bytes_per_pixel = DIV_ROUND_UP(bpp, 8);
|
||||
u32 hsync_active_byte, hbp_byte, hfp_byte, data_phy_cycles, d_phy;
|
||||
u32 packet_fmt, hactive;
|
||||
s32 hfp_adjust, hbp_adjust;
|
||||
|
||||
writel(timing->vsync_len.typ, dsi->regs + DSI_VSA_NL);
|
||||
writel(timing->vback_porch.typ, dsi->regs + DSI_VBP_NL);
|
||||
writel(timing->vfront_porch.typ, dsi->regs + DSI_VFP_NL);
|
||||
writel(timing->vactive.typ, dsi->regs + DSI_VACT_NL);
|
||||
|
||||
hsync_active_byte = timing->hsync_len.typ * bytes_per_pixel - 10;
|
||||
|
||||
if (dsi->device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
|
||||
hbp_byte = timing->hback_porch.typ * bytes_per_pixel - 10;
|
||||
else
|
||||
hbp_byte = (timing->hback_porch.typ +
|
||||
timing->hsync_len.typ) * bytes_per_pixel - 10;
|
||||
hfp_byte = timing->hfront_porch.typ * bytes_per_pixel;
|
||||
|
||||
/*
|
||||
* The D-PHY needs some of the horizontal blanking to switch
|
||||
* between LP and HS, from mtk_dsi_config_vdo_timing() in the
|
||||
* coreboot port.
|
||||
*/
|
||||
data_phy_cycles = dsi->phy_timing.lpx + dsi->phy_timing.da_hs_prepare +
|
||||
dsi->phy_timing.da_hs_zero +
|
||||
dsi->phy_timing.da_hs_exit + 3;
|
||||
d_phy = data_phy_cycles * dsi->lanes + 10;
|
||||
|
||||
hfp_adjust = (s32)timing->hfront_porch.typ;
|
||||
hbp_adjust = (s32)timing->hback_porch.typ;
|
||||
if ((hfp_adjust + hbp_adjust) * (s32)bytes_per_pixel > (s32)d_phy) {
|
||||
hfp_byte -= d_phy * hfp_adjust / (hfp_adjust + hbp_adjust);
|
||||
hbp_byte -= d_phy * hbp_adjust / (hfp_adjust + hbp_adjust);
|
||||
} else {
|
||||
dev_warn(dsi->host.dev,
|
||||
"HFP + HBP too small for the D-PHY turnaround, the panel may misbehave\n");
|
||||
}
|
||||
|
||||
if (hfp_byte + hbp_byte < MIN_HFP_BYTE + MIN_HBP_BYTE) {
|
||||
dev_warn(dsi->host.dev,
|
||||
"HFP/HBP too small, the panel may misbehave\n");
|
||||
} else if (hfp_byte < MIN_HFP_BYTE) {
|
||||
hbp_byte -= MIN_HFP_BYTE - hfp_byte;
|
||||
hfp_byte = MIN_HFP_BYTE;
|
||||
} else if (hbp_byte < MIN_HBP_BYTE) {
|
||||
hfp_byte -= MIN_HBP_BYTE - hbp_byte;
|
||||
hbp_byte = MIN_HBP_BYTE;
|
||||
}
|
||||
|
||||
writel(hsync_active_byte, dsi->regs + DSI_HSA_WC);
|
||||
writel(hbp_byte, dsi->regs + DSI_HBP_WC);
|
||||
writel(hfp_byte, dsi->regs + DSI_HFP_WC);
|
||||
|
||||
switch (dsi->device->format) {
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
packet_fmt = PACKED_PS_16BIT_RGB565;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
packet_fmt = LOOSELY_PS_18BIT_RGB666;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
packet_fmt = PACKED_PS_18BIT_RGB666;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB888:
|
||||
default:
|
||||
packet_fmt = PACKED_PS_24BIT_RGB888;
|
||||
break;
|
||||
}
|
||||
|
||||
hactive = timing->hactive.typ;
|
||||
packet_fmt |= (hactive * bytes_per_pixel) & DSI_PS_WC;
|
||||
|
||||
writel(PIXEL_STREAM_CUSTOM_HEADER << PIXEL_STREAM_CUSTOM_HEADER_SHIFT |
|
||||
packet_fmt, dsi->regs + DSI_PSCTRL);
|
||||
|
||||
writel(timing->vactive.typ << DSI_SIZE_CON_HEIGHT_SHIFT |
|
||||
hactive << DSI_SIZE_CON_WIDTH_SHIFT,
|
||||
dsi->regs + DSI_SIZE_CON);
|
||||
}
|
||||
|
||||
static bool mtk_dsi_is_read_command(u32 type)
|
||||
{
|
||||
switch (type) {
|
||||
case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
|
||||
case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
|
||||
case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
|
||||
case MIPI_DSI_DCS_READ:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int mtk_dsi_wait_not_busy(struct mtk_dsi *dsi)
|
||||
{
|
||||
u32 sta;
|
||||
|
||||
return readl_poll_timeout(dsi->regs + DSI_INTSTA, sta,
|
||||
!(sta & DSI_BUSY), CMD_DONE_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static ssize_t mtk_dsi_host_transfer(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
struct mtk_dsi *dsi = host_to_mtk_dsi(host);
|
||||
const u8 *tx_buf = msg->tx_buf;
|
||||
u32 config, reg_val, cmdq_size, sta;
|
||||
bool was_video_mode = dsi->video_mode;
|
||||
ssize_t ret = 0;
|
||||
int i, j;
|
||||
|
||||
if (was_video_mode) {
|
||||
mtk_dsi_stop(dsi);
|
||||
mtk_dsi_set_mode(dsi, 0); /* back to command mode */
|
||||
}
|
||||
|
||||
ret = mtk_dsi_wait_not_busy(dsi);
|
||||
if (ret) {
|
||||
/*
|
||||
* The boot firmware may have left the engine in a state
|
||||
* where polling alone never recovers; re-enable and reset
|
||||
* the core like mtk_dsi_wait_for_idle() of the Linux
|
||||
* driver, then try once more.
|
||||
*/
|
||||
mtk_dsi_reset(dsi);
|
||||
ret = mtk_dsi_wait_not_busy(dsi);
|
||||
}
|
||||
if (ret) {
|
||||
dev_err(host->dev, "DSI busy, cannot send command\n");
|
||||
goto out;
|
||||
}
|
||||
writel(LPRX_RD_RDY_INT_FLAG | CMD_DONE_INT_FLAG | VM_DONE_INT_FLAG,
|
||||
dsi->regs + DSI_INTSTA);
|
||||
|
||||
if (mtk_dsi_is_read_command(msg->type))
|
||||
config = BTA;
|
||||
else
|
||||
config = (msg->tx_len > 2) ? LONG_PACKET : SHORT_PACKET;
|
||||
|
||||
if (!(msg->flags & MIPI_DSI_MSG_USE_LPM))
|
||||
config |= HSTX;
|
||||
|
||||
if (msg->tx_len > 2) {
|
||||
/*
|
||||
* Long packet: the first command queue word carries the
|
||||
* configuration and the packet header, the following
|
||||
* words carry the payload, 4 bytes each.
|
||||
*/
|
||||
reg_val = ((u32)msg->tx_len << 16) | ((u32)msg->type << 8) |
|
||||
config;
|
||||
writel(reg_val, dsi->regs + DSI_CMDQ0);
|
||||
for (i = 0; i < msg->tx_len; i += 4) {
|
||||
u32 val = 0;
|
||||
|
||||
for (j = 0; j < 4 && i + j < msg->tx_len; j++)
|
||||
val |= (u32)tx_buf[i + j] << (j * 8);
|
||||
writel(val, dsi->regs + DSI_CMDQ0 + 4 + (i / 4) * 4);
|
||||
}
|
||||
cmdq_size = 1 + DIV_ROUND_UP(msg->tx_len, 4);
|
||||
} else {
|
||||
/* Short packet: parameters packed in the header word. */
|
||||
reg_val = ((u32)msg->type << 8) | config;
|
||||
for (i = 0; i < msg->tx_len; i++)
|
||||
reg_val |= (u32)tx_buf[i] << ((i + 2) * 8);
|
||||
writel(reg_val, dsi->regs + DSI_CMDQ0);
|
||||
cmdq_size = 1;
|
||||
}
|
||||
|
||||
writel(cmdq_size, dsi->regs + DSI_CMDQ_SIZE);
|
||||
mtk_dsi_start(dsi);
|
||||
|
||||
ret = readl_poll_timeout(dsi->regs + DSI_INTSTA, sta,
|
||||
sta & (CMD_DONE_INT_FLAG | LPRX_RD_RDY_INT_FLAG),
|
||||
CMD_DONE_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(host->dev, "failed to send DSI command\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mtk_dsi_is_read_command(msg->type) && msg->rx_buf) {
|
||||
u8 rx[16];
|
||||
|
||||
for (i = 0; i < 16; i++)
|
||||
rx[i] = readb(dsi->regs + DSI_RX_DATA0 + i);
|
||||
/*
|
||||
* Short responses carry the payload from byte 1, long
|
||||
* ones from byte 4 (after the 2-byte header).
|
||||
*/
|
||||
if (msg->rx_len > 2)
|
||||
memcpy(msg->rx_buf, rx + 4,
|
||||
min_t(size_t, msg->rx_len, 8));
|
||||
else
|
||||
memcpy(msg->rx_buf, rx + 1,
|
||||
min_t(size_t, msg->rx_len, 2));
|
||||
ret = msg->rx_len;
|
||||
}
|
||||
|
||||
out:
|
||||
if (was_video_mode) {
|
||||
mtk_dsi_set_mode(dsi, dsi->device->mode_flags);
|
||||
mtk_dsi_start(dsi);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mtk_dsi_host_attach(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *device)
|
||||
{
|
||||
struct mtk_dsi *dsi = host_to_mtk_dsi(host);
|
||||
|
||||
dsi->lanes = device->lanes;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mipi_dsi_host_ops mtk_dsi_host_ops = {
|
||||
.attach = mtk_dsi_host_attach,
|
||||
.transfer = mtk_dsi_host_transfer,
|
||||
};
|
||||
|
||||
static int mtk_dsi_init(struct udevice *dev,
|
||||
struct mipi_dsi_device *device,
|
||||
struct display_timing *timings,
|
||||
unsigned int max_data_lanes,
|
||||
const struct mipi_dsi_phy_ops *phy_ops)
|
||||
{
|
||||
struct mtk_dsi *dsi = dev_get_priv(dev);
|
||||
int bpp, ret;
|
||||
|
||||
/*
|
||||
* The D-PHY is a separate device on MT8183 (the mipi_tx node);
|
||||
* it is driven through the generic PHY API, so the phy_ops
|
||||
* argument of the uclass is unused here.
|
||||
*/
|
||||
(void)phy_ops;
|
||||
|
||||
dsi->device = device;
|
||||
dsi->lanes = device->lanes;
|
||||
dsi->timing = *timings;
|
||||
|
||||
dsi->host.dev = (struct device *)dev;
|
||||
dsi->host.ops = &mtk_dsi_host_ops;
|
||||
device->host = &dsi->host;
|
||||
|
||||
dsi->regs = dev_read_addr_ptr(dev);
|
||||
if (!dsi->regs)
|
||||
return log_msg_ret("regs", -EINVAL);
|
||||
|
||||
if (dsi->lanes > max_data_lanes) {
|
||||
dev_err(dev, "panel uses %u lanes, host supports %u\n",
|
||||
dsi->lanes, max_data_lanes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(dev, "engine", &dsi->clk_engine);
|
||||
if (ret)
|
||||
return log_msg_ret("clk-engine", ret);
|
||||
ret = clk_get_by_name(dev, "digital", &dsi->clk_digital);
|
||||
if (ret)
|
||||
return log_msg_ret("clk-digital", ret);
|
||||
ret = clk_enable(&dsi->clk_engine);
|
||||
if (ret)
|
||||
return log_msg_ret("clk-engine-en", ret);
|
||||
ret = clk_enable(&dsi->clk_digital);
|
||||
if (ret)
|
||||
return log_msg_ret("clk-digital-en", ret);
|
||||
|
||||
ret = generic_phy_get_by_name(dev, "dphy", &dsi->dphy);
|
||||
if (ret)
|
||||
return log_msg_ret("phy", ret);
|
||||
|
||||
bpp = mipi_dsi_pixel_format_to_bpp(device->format);
|
||||
if (bpp < 0)
|
||||
return log_msg_ret("fmt", bpp);
|
||||
|
||||
dsi->data_rate = DIV_ROUND_UP_ULL((u64)timings->pixelclock.typ * bpp,
|
||||
dsi->lanes);
|
||||
|
||||
/* Program the MIPI TX PLL for the data rate and power it up. */
|
||||
ret = generic_phy_set_mode(&dsi->dphy, PHY_MODE_MIPI_DPHY,
|
||||
(int)dsi->data_rate);
|
||||
if (ret)
|
||||
return log_msg_ret("phy-mode", ret);
|
||||
ret = generic_phy_init(&dsi->dphy);
|
||||
if (ret)
|
||||
return log_msg_ret("phy-init", ret);
|
||||
ret = generic_phy_power_on(&dsi->dphy);
|
||||
if (ret)
|
||||
return log_msg_ret("phy-on", ret);
|
||||
dsi->phy_on = true;
|
||||
|
||||
/*
|
||||
* From here on the sequence follows mtk_dsi_init() of the
|
||||
* coreboot port (proven on this platform), with the final "switch
|
||||
* to video mode and start" split out into the enable() op so that
|
||||
* panel initialization commands can be sent in command mode.
|
||||
*/
|
||||
mtk_dsi_reset(dsi);
|
||||
mtk_dsi_phy_timing(dsi);
|
||||
mtk_dsi_rxtx_control(dsi);
|
||||
mdelay(1);
|
||||
mtk_dsi_reset_dphy(dsi);
|
||||
mtk_dsi_clk_hs_mode_disable(dsi);
|
||||
mtk_dsi_config_vdo_timing(dsi);
|
||||
mtk_dsi_clk_hs_mode_enable(dsi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_dsi_enable(struct udevice *dev)
|
||||
{
|
||||
struct mtk_dsi *dsi = dev_get_priv(dev);
|
||||
|
||||
/* Switch to video mode and start the stream. */
|
||||
mtk_dsi_set_mode(dsi, dsi->device->mode_flags);
|
||||
mtk_dsi_start(dsi);
|
||||
dsi->video_mode = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_dsi_disable(struct udevice *dev)
|
||||
{
|
||||
struct mtk_dsi *dsi = dev_get_priv(dev);
|
||||
|
||||
if (dsi->phy_on)
|
||||
generic_phy_power_off(&dsi->dphy);
|
||||
dsi->phy_on = false;
|
||||
clk_disable(&dsi->clk_digital);
|
||||
clk_disable(&dsi->clk_engine);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mtk_dsi_bind(struct udevice *dev)
|
||||
{
|
||||
/* Bind the panel child node, if any. */
|
||||
return dm_scan_fdt_dev(dev);
|
||||
}
|
||||
|
||||
static const struct udevice_id mtk_dsi_ids[] = {
|
||||
{ .compatible = "mediatek,mt8183-dsi" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct dsi_host_ops mtk_dsi_ops = {
|
||||
.init = mtk_dsi_init,
|
||||
.enable = mtk_dsi_enable,
|
||||
.disable = mtk_dsi_disable,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(mtk_dsi) = {
|
||||
.name = "mtk_dsi",
|
||||
.id = UCLASS_DSI_HOST,
|
||||
.of_match = mtk_dsi_ids,
|
||||
.bind = mtk_dsi_bind,
|
||||
.ops = &mtk_dsi_ops,
|
||||
.priv_auto = sizeof(struct mtk_dsi),
|
||||
};
|
||||
@@ -0,0 +1,544 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* BOE TV101WUM-NL6 MIPI DSI panel driver.
|
||||
*
|
||||
* Copyright (C) 2026 Vincent Haudiquet <vhaudiquet@gmail.com>
|
||||
*
|
||||
* 1200x1920 (native portrait) video-mode panel, 4 data lanes, RGB888,
|
||||
* mounted rotated in the krane (Lenovo IdeaPad Duet) chassis.
|
||||
*
|
||||
* The power sequence and the manufacturer initialization commands are
|
||||
* ported from the ChromeOS coreboot port for this exact panel,
|
||||
* src/mainboard/google/kukui/panel_params/panel-BOE_TV101WUM_NL6.c in
|
||||
* coreboot 4.14, which matches the sequence of the Linux kernel driver
|
||||
* drivers/gpu/drm/panel/panel-boe-tv101wum-nl6.c for the
|
||||
* "boe,tv101wum-nl6" entry (159.425 MHz pixel clock, 100/40/24
|
||||
* horizontal and 10/14/4 vertical timing).
|
||||
*/
|
||||
|
||||
#include <backlight.h>
|
||||
#include <dm.h>
|
||||
#include <mipi_dsi.h>
|
||||
#include <panel.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <power/regulator.h>
|
||||
|
||||
/*
|
||||
* Manufacturer initialization command stream. Packed as
|
||||
* <type> [<len> [<param>...]]
|
||||
* with type one of:
|
||||
* LCM_DELAY_CMD: <type> <milliseconds>
|
||||
* LCM_DCS_CMD: <type> <len> <parameters...>
|
||||
* Ported verbatim from the coreboot panel-BOE_TV101WUM_NL6.c table.
|
||||
*/
|
||||
#define LCM_END_CMD 0
|
||||
#define LCM_DELAY_CMD 1
|
||||
#define LCM_DCS_CMD 3
|
||||
|
||||
#define LCM_DELAY(ms) LCM_DELAY_CMD, (ms)
|
||||
#define LCM_DCS_CMD_ENTRY(seq...) \
|
||||
LCM_DCS_CMD, LCM_NPARAM(seq), seq
|
||||
#define LCM_NPARAM(...) \
|
||||
(sizeof((u8[]){0, ##__VA_ARGS__}) / sizeof(u8) - 1)
|
||||
|
||||
static const u8 boe_tv101wum_nl6_init_seq[] = {
|
||||
LCM_DELAY(24),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x05),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0xE5),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x52),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x88),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x03),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x8B),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x1A),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x0F),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0x0C),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0x0C),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x01),
|
||||
LCM_DCS_CMD_ENTRY(0xE0, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xE1, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xDC, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xDD, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xD2, 0x03),
|
||||
LCM_DCS_CMD_ENTRY(0xD3, 0x03),
|
||||
LCM_DCS_CMD_ENTRY(0xE6, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xE7, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0x09),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0x09),
|
||||
LCM_DCS_CMD_ENTRY(0xD8, 0x0A),
|
||||
LCM_DCS_CMD_ENTRY(0xD9, 0x0A),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0x0B),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0x0B),
|
||||
LCM_DCS_CMD_ENTRY(0xD6, 0x0C),
|
||||
LCM_DCS_CMD_ENTRY(0xD7, 0x0C),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x05),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x05),
|
||||
LCM_DCS_CMD_ENTRY(0xD4, 0x06),
|
||||
LCM_DCS_CMD_ENTRY(0xD5, 0x06),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xDE, 0x08),
|
||||
LCM_DCS_CMD_ENTRY(0xDF, 0x08),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x0D),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0x17),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0x31),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0x1C),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0x2C),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0x33),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0x31),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x37),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x37),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x37),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0x39),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0x2E),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0x2F),
|
||||
LCM_DCS_CMD_ENTRY(0xCF, 0x2F),
|
||||
LCM_DCS_CMD_ENTRY(0xD0, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xD2, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xD3, 0x0D),
|
||||
LCM_DCS_CMD_ENTRY(0xD4, 0x17),
|
||||
LCM_DCS_CMD_ENTRY(0xD5, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xD6, 0x31),
|
||||
LCM_DCS_CMD_ENTRY(0xD7, 0x3F),
|
||||
LCM_DCS_CMD_ENTRY(0xD8, 0x3F),
|
||||
LCM_DCS_CMD_ENTRY(0xD9, 0x3F),
|
||||
LCM_DCS_CMD_ENTRY(0xDA, 0x3F),
|
||||
LCM_DCS_CMD_ENTRY(0xDB, 0x37),
|
||||
LCM_DCS_CMD_ENTRY(0xDC, 0x37),
|
||||
LCM_DCS_CMD_ENTRY(0xDD, 0x37),
|
||||
LCM_DCS_CMD_ENTRY(0xDE, 0x39),
|
||||
LCM_DCS_CMD_ENTRY(0xDF, 0x2E),
|
||||
LCM_DCS_CMD_ENTRY(0xE0, 0x2F),
|
||||
LCM_DCS_CMD_ENTRY(0xE1, 0x2F),
|
||||
LCM_DCS_CMD_ENTRY(0xE2, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x03),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0x0B),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xE7, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0x2A),
|
||||
LCM_DCS_CMD_ENTRY(0xDE, 0x2A),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x43),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xE4, 0xC0),
|
||||
LCM_DCS_CMD_ENTRY(0xE5, 0x0D),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x06),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0xA5),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0xA5),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0x0F),
|
||||
LCM_DCS_CMD_ENTRY(0xD5, 0x32),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x07),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x0F),
|
||||
LCM_DCS_CMD_ENTRY(0xB4, 0x25),
|
||||
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x4E),
|
||||
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x97),
|
||||
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x22),
|
||||
LCM_DCS_CMD_ENTRY(0xBB, 0xA4),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x2B),
|
||||
LCM_DCS_CMD_ENTRY(0xBD, 0x2F),
|
||||
LCM_DCS_CMD_ENTRY(0xBE, 0xA9),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x25),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x61),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x97),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0xB2),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0xCD),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0xD9),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0xE7),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0xF4),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x08),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xB2, 0x05),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x11),
|
||||
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
|
||||
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
|
||||
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x98),
|
||||
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x23),
|
||||
LCM_DCS_CMD_ENTRY(0xBB, 0xA6),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x2C),
|
||||
LCM_DCS_CMD_ENTRY(0xBD, 0x30),
|
||||
LCM_DCS_CMD_ENTRY(0xBE, 0xAA),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x62),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x9B),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0xB5),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0xCF),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0xDB),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0xE8),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0xF5),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x09),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
|
||||
LCM_DCS_CMD_ENTRY(0xB5, 0x3B),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
|
||||
LCM_DCS_CMD_ENTRY(0xB7, 0x73),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x99),
|
||||
LCM_DCS_CMD_ENTRY(0xB9, 0xE0),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xBB, 0xAD),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x36),
|
||||
LCM_DCS_CMD_ENTRY(0xBD, 0x3A),
|
||||
LCM_DCS_CMD_ENTRY(0xBE, 0xAE),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x2A),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x66),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x9E),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0xB8),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0xD1),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0xDD),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0xE9),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0xF6),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x0A),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x0F),
|
||||
LCM_DCS_CMD_ENTRY(0xB4, 0x25),
|
||||
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x4E),
|
||||
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x97),
|
||||
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x22),
|
||||
LCM_DCS_CMD_ENTRY(0xBB, 0xA4),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x2B),
|
||||
LCM_DCS_CMD_ENTRY(0xBD, 0x2F),
|
||||
LCM_DCS_CMD_ENTRY(0xBE, 0xA9),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x25),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x61),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x97),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0xB2),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0xCD),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0xD9),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0xE7),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0xF4),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x0B),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xB2, 0x05),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x11),
|
||||
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
|
||||
LCM_DCS_CMD_ENTRY(0xB5, 0x39),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
|
||||
LCM_DCS_CMD_ENTRY(0xB7, 0x72),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x98),
|
||||
LCM_DCS_CMD_ENTRY(0xB9, 0xDC),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x23),
|
||||
LCM_DCS_CMD_ENTRY(0xBB, 0xA6),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x2C),
|
||||
LCM_DCS_CMD_ENTRY(0xBD, 0x30),
|
||||
LCM_DCS_CMD_ENTRY(0xBE, 0xAA),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x62),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x9B),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0xB5),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0xCF),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0xDB),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0xE8),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0xF5),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x0C),
|
||||
LCM_DCS_CMD_ENTRY(0xB1, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xB2, 0x02),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xB4, 0x24),
|
||||
LCM_DCS_CMD_ENTRY(0xB5, 0x3B),
|
||||
LCM_DCS_CMD_ENTRY(0xB6, 0x4F),
|
||||
LCM_DCS_CMD_ENTRY(0xB7, 0x73),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x99),
|
||||
LCM_DCS_CMD_ENTRY(0xB9, 0xE0),
|
||||
LCM_DCS_CMD_ENTRY(0xBA, 0x26),
|
||||
LCM_DCS_CMD_ENTRY(0xBB, 0xAD),
|
||||
LCM_DCS_CMD_ENTRY(0xBC, 0x36),
|
||||
LCM_DCS_CMD_ENTRY(0xBD, 0x3A),
|
||||
LCM_DCS_CMD_ENTRY(0xBE, 0xAE),
|
||||
LCM_DCS_CMD_ENTRY(0xBF, 0x2A),
|
||||
LCM_DCS_CMD_ENTRY(0xC0, 0x66),
|
||||
LCM_DCS_CMD_ENTRY(0xC1, 0x9E),
|
||||
LCM_DCS_CMD_ENTRY(0xC2, 0xB8),
|
||||
LCM_DCS_CMD_ENTRY(0xC3, 0xD1),
|
||||
LCM_DCS_CMD_ENTRY(0xC4, 0xDD),
|
||||
LCM_DCS_CMD_ENTRY(0xC5, 0xE9),
|
||||
LCM_DCS_CMD_ENTRY(0xC6, 0xF6),
|
||||
LCM_DCS_CMD_ENTRY(0xC7, 0xFA),
|
||||
LCM_DCS_CMD_ENTRY(0xC8, 0xFC),
|
||||
LCM_DCS_CMD_ENTRY(0xC9, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCA, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xCB, 0x16),
|
||||
LCM_DCS_CMD_ENTRY(0xCC, 0xAF),
|
||||
LCM_DCS_CMD_ENTRY(0xCD, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xCE, 0xFF),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x00),
|
||||
LCM_DCS_CMD_ENTRY(0xB3, 0x08),
|
||||
LCM_DCS_CMD_ENTRY(0xB0, 0x04),
|
||||
LCM_DCS_CMD_ENTRY(0xB8, 0x68),
|
||||
LCM_DELAY(150),
|
||||
LCM_END_CMD,
|
||||
};
|
||||
|
||||
/*
|
||||
* Display timing. Totals match the Linux driver's
|
||||
* boe_tv101wum_nl6_default_mode (htotal 1364, vtotal 1948), but the
|
||||
* sync/back-porch split follows the coreboot EDID for this panel
|
||||
* (hso 100 / hspw 24 / hbl-hso-hspw 40; vso 10 / vspw 4 / 14), the
|
||||
* configuration proven to drive this panel on this device.
|
||||
*/
|
||||
static const struct display_timing boe_tv101wum_nl6_timing = {
|
||||
.pixelclock.typ = 159425000,
|
||||
.hactive.typ = 1200,
|
||||
.hfront_porch.typ = 100,
|
||||
.hback_porch.typ = 40,
|
||||
.hsync_len.typ = 24,
|
||||
.vactive.typ = 1920,
|
||||
.vfront_porch.typ = 10,
|
||||
.vback_porch.typ = 14,
|
||||
.vsync_len.typ = 4,
|
||||
};
|
||||
|
||||
struct boe_panel_priv {
|
||||
struct udevice *avdd;
|
||||
struct udevice *avee;
|
||||
struct udevice *pp1800;
|
||||
struct gpio_desc enable;
|
||||
};
|
||||
|
||||
static int boe_panel_send_init_sequence(struct udevice *dev)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
const u8 *p = boe_tv101wum_nl6_init_seq;
|
||||
int ret;
|
||||
|
||||
while (*p != LCM_END_CMD) {
|
||||
u8 type = *p++;
|
||||
u8 len;
|
||||
|
||||
switch (type) {
|
||||
case LCM_DELAY_CMD:
|
||||
mdelay(*p++);
|
||||
continue;
|
||||
case LCM_DCS_CMD:
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "unknown command type %u\n", type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
len = *p++;
|
||||
ret = mipi_dsi_dcs_write_buffer(device, p, len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
p += len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boe_panel_enable_backlight(struct udevice *dev)
|
||||
{
|
||||
struct boe_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Power sequence from boe_panel_prepare() of the Linux driver
|
||||
* (regulators on, reset pulse, manufacturer init commands). On
|
||||
* the kukui family the supplies are already enabled by the boot
|
||||
* firmware; enabling them again is a no-op.
|
||||
*/
|
||||
if (CONFIG_IS_ENABLED(DM_REGULATOR)) {
|
||||
if (priv->avdd) {
|
||||
ret = regulator_set_enable_if_allowed(priv->avdd,
|
||||
true);
|
||||
if (ret && ret != -ENOSYS)
|
||||
return ret;
|
||||
}
|
||||
if (priv->avee) {
|
||||
ret = regulator_set_enable_if_allowed(priv->avee,
|
||||
true);
|
||||
if (ret && ret != -ENOSYS)
|
||||
return ret;
|
||||
}
|
||||
if (priv->pp1800) {
|
||||
ret = regulator_set_enable_if_allowed(priv->pp1800,
|
||||
true);
|
||||
if (ret && ret != -ENOSYS)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
udelay(10000);
|
||||
|
||||
/*
|
||||
* Reset pulse, following boe_panel_prepare() of the Linux driver:
|
||||
* the "enable-gpios" pin is the active-low reset, so the sequence
|
||||
* ends with it de-asserted (logical 1) before the init commands.
|
||||
*/
|
||||
if (dm_gpio_is_valid(&priv->enable)) {
|
||||
dm_gpio_set_value(&priv->enable, 1);
|
||||
udelay(1500);
|
||||
dm_gpio_set_value(&priv->enable, 0);
|
||||
udelay(1500);
|
||||
dm_gpio_set_value(&priv->enable, 1);
|
||||
}
|
||||
udelay(8000);
|
||||
|
||||
ret = boe_panel_send_init_sequence(dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to send init sequence: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable the backlight after the 150 ms delay of the sequence. */
|
||||
if (CONFIG_IS_ENABLED(BACKLIGHT)) {
|
||||
struct udevice *backlight;
|
||||
|
||||
if (!uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
|
||||
"backlight", &backlight))
|
||||
backlight_enable(backlight);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boe_panel_get_display_timing(struct udevice *dev,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
memcpy(timings, &boe_tv101wum_nl6_timing, sizeof(*timings));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boe_panel_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct boe_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
if (CONFIG_IS_ENABLED(DM_REGULATOR)) {
|
||||
ret = device_get_supply_regulator(dev, "avdd-supply",
|
||||
&priv->avdd);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
ret = device_get_supply_regulator(dev, "avee-supply",
|
||||
&priv->avee);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
ret = device_get_supply_regulator(dev, "pp1800-supply",
|
||||
&priv->pp1800);
|
||||
if (ret && ret != -ENOENT)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable,
|
||||
GPIOD_IS_OUT);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "cannot get enable GPIO: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Default to the reset released: a failed bring-up falls back to
|
||||
* the firmware-handoff revival, which needs the panel alive.
|
||||
*/
|
||||
if (dm_gpio_is_valid(&priv->enable))
|
||||
dm_gpio_set_value(&priv->enable, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int boe_panel_probe(struct udevice *dev)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_plat(dev);
|
||||
|
||||
/* Fill the DSI data link characteristics for the host. */
|
||||
plat->lanes = 4;
|
||||
plat->format = MIPI_DSI_FMT_RGB888;
|
||||
plat->mode_flags = MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
|
||||
MIPI_DSI_MODE_LPM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct panel_ops boe_panel_ops = {
|
||||
.enable_backlight = boe_panel_enable_backlight,
|
||||
.get_display_timing = boe_panel_get_display_timing,
|
||||
};
|
||||
|
||||
static const struct udevice_id boe_panel_ids[] = {
|
||||
{ .compatible = "boe,tv101wum-nl6" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(boe_panel) = {
|
||||
.name = "boe_panel",
|
||||
.id = UCLASS_PANEL,
|
||||
.of_match = boe_panel_ids,
|
||||
.ops = &boe_panel_ops,
|
||||
.of_to_plat = boe_panel_of_to_plat,
|
||||
.probe = boe_panel_probe,
|
||||
.plat_auto = sizeof(struct mipi_dsi_panel_plat),
|
||||
.priv_auto = sizeof(struct boe_panel_priv),
|
||||
};
|
||||
@@ -95,7 +95,7 @@ static void vidconsole_newline(struct udevice *dev)
|
||||
|
||||
/* Check if we need to scroll the terminal */
|
||||
if (vid_priv->rot % 2 ?
|
||||
priv->ycur + priv->x_charsize > vid_priv->xsize :
|
||||
priv->ycur + priv->y_charsize > vid_priv->xsize :
|
||||
priv->ycur + priv->y_charsize > vid_priv->ysize) {
|
||||
vidconsole_move_rows(dev, 0, rows, priv->rows - rows);
|
||||
for (i = 0; i < rows; i++)
|
||||
|
||||
@@ -194,6 +194,14 @@ static efi_status_t EFIAPI efi_cout_output_string(
|
||||
}
|
||||
pos = buf;
|
||||
utf16_utf8_strcpy(&pos, string);
|
||||
/*
|
||||
* Output printed by U-Boot itself moves the video console cursor
|
||||
* without updating the position tracked for EFI. Re-sync the console
|
||||
* cursor before printing so text appears where the EFI spec places it.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_VIDEO))
|
||||
printf(ESC "[%d;%dH", (int)con->cursor_row + 1,
|
||||
(int)con->cursor_column + 1);
|
||||
puts(buf);
|
||||
free(buf);
|
||||
|
||||
@@ -344,7 +352,8 @@ static int __maybe_unused query_vidconsole(int *rows, int *cols)
|
||||
struct udevice *dev;
|
||||
struct vidconsole_priv *priv;
|
||||
|
||||
if (!stdout_name || strncmp(stdout_name, "vidconsole", 10))
|
||||
/* stdout may be a comma-separated list, e.g. "serial,vidconsole" */
|
||||
if (!stdout_name || !strstr(stdout_name, "vidconsole"))
|
||||
return -ENODEV;
|
||||
stdout_dev = stdio_get_by_name("vidconsole");
|
||||
if (!stdout_dev)
|
||||
@@ -370,11 +379,14 @@ void efi_setup_console_size(void)
|
||||
|
||||
if (IS_ENABLED(CONFIG_VIDEO))
|
||||
ret = query_vidconsole(&rows, &cols);
|
||||
printf("EFI console: vidquery ret=%d, size %dx%d\n", ret, cols, rows);
|
||||
if (ret) {
|
||||
if (no_ansi)
|
||||
ret = 0;
|
||||
else
|
||||
ret = query_console_serial(&rows, &cols);
|
||||
printf("EFI console: serial fallback ret=%d, size %dx%d\n",
|
||||
ret, cols, rows);
|
||||
}
|
||||
if (ret)
|
||||
return;
|
||||
@@ -399,6 +411,14 @@ void efi_setup_console_size(void)
|
||||
efi_con_mode.max_mode = EFI_MAX_COUT_MODE;
|
||||
efi_con_mode.mode = EFI_COUT_MODE_2;
|
||||
}
|
||||
|
||||
/*
|
||||
* EFI applications expect output to start at the top left of the
|
||||
* screen, while the U-Boot prompt may have left the cursor anywhere.
|
||||
*/
|
||||
printf(ESC "[1;1H");
|
||||
efi_con_mode.cursor_column = 0;
|
||||
efi_con_mode.cursor_row = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -406,6 +406,21 @@ efi_status_t efi_add_memory_map(u64 start, u64 size, int memory_type)
|
||||
* @must_be_allocated: return success if the page is allocated
|
||||
* Return: status code
|
||||
*/
|
||||
/* TEMPORARY DIAGNOSTIC - DO NOT COMMIT */
|
||||
static void efi_mem_dump_diag(void)
|
||||
{
|
||||
struct efi_mem_list *lmem;
|
||||
int i = 0;
|
||||
|
||||
list_for_each_entry(lmem, &efi_mem, link) {
|
||||
log_err("EFI map[%d]: type %d start %08llx pages %llu attr %llx\n",
|
||||
i++, lmem->desc.type,
|
||||
lmem->desc.physical_start,
|
||||
lmem->desc.num_pages,
|
||||
lmem->desc.attribute);
|
||||
}
|
||||
}
|
||||
|
||||
static efi_status_t efi_check_allocated(u64 addr, bool must_be_allocated)
|
||||
{
|
||||
struct efi_mem_list *item;
|
||||
@@ -463,8 +478,12 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type,
|
||||
/* Any page */
|
||||
err = lmb_alloc_mem(LMB_MEM_ALLOC_ANY, EFI_PAGE_SIZE, &addr,
|
||||
len, flags);
|
||||
if (err)
|
||||
if (err) {
|
||||
log_err("EFI alloc %llu pages type %d: lmb_alloc_mem failed (%d)\n",
|
||||
(u64)pages, memory_type, err);
|
||||
efi_mem_dump_diag();
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
break;
|
||||
case EFI_ALLOCATE_MAX_ADDRESS:
|
||||
/* Max address */
|
||||
@@ -493,6 +512,10 @@ efi_status_t efi_allocate_pages(enum efi_allocate_type type,
|
||||
ret = efi_update_memory_map(efi_addr, pages, memory_type, true, false);
|
||||
if (ret != EFI_SUCCESS) {
|
||||
/* Map would overlap, bail out */
|
||||
log_err("EFI alloc %llu pages type %d at %08llx: map update failed (%lu)\n",
|
||||
(u64)pages, memory_type, efi_addr,
|
||||
ret & ~EFI_ERROR_MASK);
|
||||
efi_mem_dump_diag();
|
||||
lmb_free(addr, (u64)pages << EFI_PAGE_SHIFT, flags);
|
||||
unmap_sysmem((void *)(uintptr_t)efi_addr);
|
||||
if (type == EFI_ALLOCATE_ADDRESS)
|
||||
|
||||
Reference in New Issue
Block a user