- panel bridge support in stm32 ltdc
- DSI host uclass - sandbox DSI host uclass test driver and DSI host test - MIPI DSI helpers - Synopsys Designware MIPI DSI host bridge driver - STM32 DSI controller driver - OTM800A and RM68200 panel support - DSI host updates for stm32f769 and stm32mp1 dtsi files - splash screen for stm32f769 and stm32mp1 boards - stm32 defconfig updates for display support -----BEGIN PGP SIGNATURE----- iGwEABECACwWIQSC4hxrSoIUVfFO0kRM6ATMmsalXAUCXaRAgw4cYWd1c3RAZGVu eC5kZQAKCRBM6ATMmsalXH1tAJ44kBh0X+ZD1w3pmqcWYOB1WRRDzwCfeaLy0SfQ H1h5bXYm5KO8Mao9Ts4= =73tE -----END PGP SIGNATURE----- Merge tag 'video-for-2020.01' of https://gitlab.denx.de/u-boot/custodians/u-boot-video - panel bridge support in stm32 ltdc - DSI host uclass - sandbox DSI host uclass test driver and DSI host test - MIPI DSI helpers - Synopsys Designware MIPI DSI host bridge driver - STM32 DSI controller driver - OTM800A and RM68200 panel support - DSI host updates for stm32f769 and stm32mp1 dtsi files - splash screen for stm32f769 and stm32mp1 boards - stm32 defconfig updates for display support
This commit is contained in:
commit
cd5ffc5de5
@ -28,10 +28,72 @@
|
||||
button-gpio = <&gpioa 0 0>;
|
||||
};
|
||||
|
||||
dsi_host: dsi_host {
|
||||
compatible = "synopsys,dw-mipi-dsi";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
led1 {
|
||||
compatible = "st,led1";
|
||||
led-gpio = <&gpioj 5 0>;
|
||||
};
|
||||
|
||||
panel: panel {
|
||||
compatible = "orisetech,otm8009a";
|
||||
reset-gpios = <&gpioj 15 1>;
|
||||
status = "okay";
|
||||
|
||||
port {
|
||||
panel_in: endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
soc {
|
||||
dsi: dsi@40016c00 {
|
||||
compatible = "st,stm32-dsi";
|
||||
reg = <0x40016C00 0x800>;
|
||||
resets = <&rcc STM32F7_APB2_RESET(DSI)>;
|
||||
clocks = <&rcc 0 STM32F7_APB2_CLOCK(DSI)>,
|
||||
<&rcc 0 STM32F7_APB2_CLOCK(LTDC)>,
|
||||
<&clk_hse>;
|
||||
clock-names = "pclk", "px_clk", "ref";
|
||||
u-boot,dm-pre-reloc;
|
||||
status = "okay";
|
||||
|
||||
ports {
|
||||
port@0 {
|
||||
dsi_out: endpoint {
|
||||
remote-endpoint = <&panel_in>;
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
dsi_in: endpoint {
|
||||
remote-endpoint = <&dp_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ltdc: display-controller@40016800 {
|
||||
compatible = "st,stm32-ltdc";
|
||||
reg = <0x40016800 0x200>;
|
||||
resets = <&rcc STM32F7_APB2_RESET(LTDC)>;
|
||||
clocks = <&rcc 0 STM32F7_APB2_CLOCK(LTDC)>;
|
||||
|
||||
status = "okay";
|
||||
u-boot,dm-pre-reloc;
|
||||
|
||||
ports {
|
||||
port@0 {
|
||||
dp_out: endpoint {
|
||||
remote-endpoint = <&dsi_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&fmc {
|
||||
|
@ -5,6 +5,13 @@
|
||||
|
||||
#include "stm32mp157a-dk1-u-boot.dtsi"
|
||||
|
||||
/ {
|
||||
dsi_host: dsi_host {
|
||||
compatible = "synopsys,dw-mipi-dsi";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
&i2c1 {
|
||||
hdmi-transmitter@39 {
|
||||
reset-gpios = <&gpioa 10 GPIO_ACTIVE_LOW>;
|
||||
|
@ -14,6 +14,11 @@
|
||||
spi0 = &qspi;
|
||||
usb0 = &usbotg_hs;
|
||||
};
|
||||
|
||||
dsi_host: dsi_host {
|
||||
compatible = "synopsys,dw-mipi-dsi";
|
||||
status = "okay";
|
||||
};
|
||||
};
|
||||
|
||||
&flash0 {
|
||||
|
@ -25,6 +25,11 @@
|
||||
compatible = "google,cros-ec-sandbox";
|
||||
};
|
||||
|
||||
dsi_host: dsi_host {
|
||||
compatible = "sandbox,dsi-host";
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
ethrawbus {
|
||||
compatible = "sandbox,eth-raw-bus";
|
||||
skip-localhost = <0>;
|
||||
@ -63,7 +68,6 @@
|
||||
compatible = "sandbox,spi";
|
||||
cs-gpios = <0>, <&gpio_a 0>;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#include "sandbox.dtsi"
|
||||
|
@ -76,6 +76,10 @@
|
||||
};
|
||||
};
|
||||
|
||||
dsi_host: dsi_host {
|
||||
compatible = "sandbox,dsi-host";
|
||||
};
|
||||
|
||||
a-test {
|
||||
reg = <0 1>;
|
||||
compatible = "denx,u-boot-fdt-test";
|
||||
|
@ -4,3 +4,4 @@ S: Maintained
|
||||
F: board/st/stm32f746-disco
|
||||
F: include/configs/stm32f746-disco.h
|
||||
F: configs/stm32f746-disco_defconfig
|
||||
F: configs/stm32f769-disco_defconfig
|
||||
|
@ -206,6 +206,7 @@ CONFIG_CONSOLE_ROTATION=y
|
||||
CONFIG_CONSOLE_TRUETYPE=y
|
||||
CONFIG_CONSOLE_TRUETYPE_CANTORAONE=y
|
||||
CONFIG_VIDEO_SANDBOX_SDL=y
|
||||
CONFIG_VIDEO_DSI_HOST_SANDBOX=y
|
||||
CONFIG_OSD=y
|
||||
CONFIG_SANDBOX_OSD=y
|
||||
CONFIG_W1=y
|
||||
|
63
configs/stm32f769-disco_defconfig
Normal file
63
configs/stm32f769-disco_defconfig
Normal file
@ -0,0 +1,63 @@
|
||||
CONFIG_ARM=y
|
||||
CONFIG_STM32=y
|
||||
CONFIG_SYS_TEXT_BASE=0x08008000
|
||||
CONFIG_SYS_MALLOC_F_LEN=0xE00
|
||||
CONFIG_NR_DRAM_BANKS=1
|
||||
CONFIG_STM32F7=y
|
||||
CONFIG_TARGET_STM32F746_DISCO=y
|
||||
CONFIG_DISTRO_DEFAULTS=y
|
||||
CONFIG_BOOTDELAY=3
|
||||
CONFIG_USE_BOOTARGS=y
|
||||
CONFIG_BOOTARGS="console=ttyS0,115200 earlyprintk consoleblank=0 ignore_loglevel"
|
||||
# CONFIG_USE_BOOTCOMMAND is not set
|
||||
# CONFIG_DISPLAY_CPUINFO is not set
|
||||
# CONFIG_DISPLAY_BOARDINFO is not set
|
||||
CONFIG_BOARD_EARLY_INIT_F=y
|
||||
CONFIG_SPL_TEXT_BASE=0x8000000
|
||||
CONFIG_SYS_PROMPT="U-Boot > "
|
||||
CONFIG_AUTOBOOT_KEYED=y
|
||||
CONFIG_AUTOBOOT_PROMPT="Hit SPACE in %d seconds to stop autoboot.\n"
|
||||
CONFIG_AUTOBOOT_STOP_STR=" "
|
||||
CONFIG_CMD_GPT=y
|
||||
# CONFIG_RANDOM_UUID is not set
|
||||
CONFIG_CMD_MMC=y
|
||||
CONFIG_CMD_SF=y
|
||||
# CONFIG_CMD_SETEXPR is not set
|
||||
CONFIG_CMD_SNTP=y
|
||||
CONFIG_CMD_DNS=y
|
||||
CONFIG_CMD_LINK_LOCAL=y
|
||||
CONFIG_CMD_BMP=y
|
||||
CONFIG_CMD_CACHE=y
|
||||
CONFIG_CMD_TIMER=y
|
||||
# CONFIG_SPL_DOS_PARTITION is not set
|
||||
# CONFIG_ISO_PARTITION is not set
|
||||
# CONFIG_SPL_EFI_PARTITION is not set
|
||||
CONFIG_OF_CONTROL=y
|
||||
CONFIG_DEFAULT_DEVICE_TREE="stm32f769-disco"
|
||||
CONFIG_NET_RANDOM_ETHADDR=y
|
||||
CONFIG_NETCONSOLE=y
|
||||
CONFIG_DM_MMC=y
|
||||
# CONFIG_SPL_DM_MMC is not set
|
||||
CONFIG_ARM_PL180_MMCI=y
|
||||
CONFIG_MTD=y
|
||||
CONFIG_MTD_NOR_FLASH=y
|
||||
CONFIG_DM_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH=y
|
||||
CONFIG_SPI_FLASH_MACRONIX=y
|
||||
CONFIG_SPI_FLASH_STMICRO=y
|
||||
CONFIG_DM_ETH=y
|
||||
CONFIG_ETH_DESIGNWARE=y
|
||||
CONFIG_MII=y
|
||||
# CONFIG_PINCTRL_FULL is not set
|
||||
CONFIG_SPI=y
|
||||
CONFIG_DM_SPI=y
|
||||
CONFIG_STM32_QSPI=y
|
||||
CONFIG_DM_VIDEO=y
|
||||
CONFIG_BACKLIGHT_GPIO=y
|
||||
CONFIG_VIDEO_LCD_ORISETECH_OTM8009A=y
|
||||
CONFIG_VIDEO_STM32=y
|
||||
CONFIG_VIDEO_STM32_DSI=y
|
||||
CONFIG_VIDEO_STM32_MAX_XRES=480
|
||||
CONFIG_VIDEO_STM32_MAX_YRES=800
|
||||
CONFIG_OF_LIBFDT_OVERLAY=y
|
||||
# CONFIG_EFI_LOADER is not set
|
@ -124,4 +124,10 @@ CONFIG_USB_GADGET_PRODUCT_NUM=0x5720
|
||||
CONFIG_USB_GADGET_DWC2_OTG=y
|
||||
CONFIG_DM_VIDEO=y
|
||||
CONFIG_BACKLIGHT_GPIO=y
|
||||
CONFIG_VIDEO_LCD_ORISETECH_OTM8009A=y
|
||||
CONFIG_VIDEO_LCD_RAYDIUM_RM68200=y
|
||||
CONFIG_VIDEO_STM32=y
|
||||
CONFIG_VIDEO_STM32_DSI=y
|
||||
CONFIG_VIDEO_STM32_MAX_XRES=1280
|
||||
CONFIG_VIDEO_STM32_MAX_YRES=800
|
||||
CONFIG_FDT_FIXUP_PARTITIONS=y
|
||||
|
@ -109,4 +109,10 @@ CONFIG_USB_GADGET_PRODUCT_NUM=0x5720
|
||||
CONFIG_USB_GADGET_DWC2_OTG=y
|
||||
CONFIG_DM_VIDEO=y
|
||||
CONFIG_BACKLIGHT_GPIO=y
|
||||
CONFIG_VIDEO_LCD_ORISETECH_OTM8009A=y
|
||||
CONFIG_VIDEO_LCD_RAYDIUM_RM68200=y
|
||||
CONFIG_VIDEO_STM32=y
|
||||
CONFIG_VIDEO_STM32_DSI=y
|
||||
CONFIG_VIDEO_STM32_MAX_XRES=1280
|
||||
CONFIG_VIDEO_STM32_MAX_YRES=800
|
||||
CONFIG_FDT_FIXUP_PARTITIONS=y
|
||||
|
@ -108,4 +108,10 @@ CONFIG_USB_GADGET_PRODUCT_NUM=0x5720
|
||||
CONFIG_USB_GADGET_DWC2_OTG=y
|
||||
CONFIG_DM_VIDEO=y
|
||||
CONFIG_BACKLIGHT_GPIO=y
|
||||
CONFIG_VIDEO_LCD_ORISETECH_OTM8009A=y
|
||||
CONFIG_VIDEO_LCD_RAYDIUM_RM68200=y
|
||||
CONFIG_VIDEO_STM32=y
|
||||
CONFIG_VIDEO_STM32_DSI=y
|
||||
CONFIG_VIDEO_STM32_MAX_XRES=1280
|
||||
CONFIG_VIDEO_STM32_MAX_YRES=800
|
||||
CONFIG_FDT_FIXUP_PARTITIONS=y
|
||||
|
@ -73,6 +73,14 @@ config VIDEO_ANSI
|
||||
Enable ANSI escape sequence decoding for a more fully functional
|
||||
console.
|
||||
|
||||
config VIDEO_MIPI_DSI
|
||||
bool "Support MIPI DSI interface"
|
||||
depends on DM_VIDEO
|
||||
help
|
||||
Support MIPI DSI interface for driving a MIPI compatible device.
|
||||
The MIPI Display Serial Interface (MIPI DSI) defines a high-speed
|
||||
serial interface between a host processor and a display module.
|
||||
|
||||
config CONSOLE_NORMAL
|
||||
bool "Support a simple text console"
|
||||
depends on DM_VIDEO
|
||||
@ -320,6 +328,24 @@ config VIDEO_LCD_ANX9804
|
||||
from a parallel LCD interface and translate it on the fy into a DP
|
||||
interface for driving eDP TFT displays. It uses I2C for configuration.
|
||||
|
||||
config VIDEO_LCD_ORISETECH_OTM8009A
|
||||
bool "OTM8009A DSI LCD panel support"
|
||||
depends on DM_VIDEO
|
||||
select VIDEO_MIPI_DSI
|
||||
default n
|
||||
help
|
||||
Say Y here if you want to enable support for Orise Technology
|
||||
otm8009a 480x800 dsi 2dl panel.
|
||||
|
||||
config VIDEO_LCD_RAYDIUM_RM68200
|
||||
bool "RM68200 DSI LCD panel support"
|
||||
depends on DM_VIDEO
|
||||
select VIDEO_MIPI_DSI
|
||||
default n
|
||||
help
|
||||
Say Y here if you want to enable support for Raydium RM68200
|
||||
720x1280 DSI video mode panel.
|
||||
|
||||
config VIDEO_LCD_SSD2828
|
||||
bool "SSD2828 bridge chip"
|
||||
default n
|
||||
@ -678,6 +704,27 @@ config VIDEO_DW_HDMI
|
||||
rather requires a SoC-specific glue driver to call it), it
|
||||
can not be enabled from the configuration menu.
|
||||
|
||||
config VIDEO_DSI_HOST_SANDBOX
|
||||
bool "Enable sandbox for dsi host"
|
||||
depends on SANDBOX
|
||||
select VIDEO_MIPI_DSI
|
||||
help
|
||||
Enable support for sandbox dsi host device used for testing
|
||||
purposes.
|
||||
Display Serial Interface (DSI) defines a serial bus and
|
||||
a communication protocol between the host and the device
|
||||
(panel, bridge).
|
||||
|
||||
config VIDEO_DW_MIPI_DSI
|
||||
bool
|
||||
select VIDEO_MIPI_DSI
|
||||
help
|
||||
Enables the common driver code for the Synopsis Designware
|
||||
MIPI DSI block found in SoCs from various vendors.
|
||||
As this does not provide any functionality by itself (but
|
||||
rather requires a SoC-specific glue driver to call it), it
|
||||
can not be enabled from the configuration menu.
|
||||
|
||||
config VIDEO_SIMPLE
|
||||
bool "Simple display driver for preconfigured display"
|
||||
help
|
||||
|
@ -11,6 +11,7 @@ obj-$(CONFIG_CONSOLE_ROTATION) += console_rotate.o
|
||||
obj-$(CONFIG_CONSOLE_TRUETYPE) += console_truetype.o fonts/
|
||||
obj-$(CONFIG_DISPLAY) += display-uclass.o
|
||||
obj-$(CONFIG_DM_VIDEO) += backlight-uclass.o
|
||||
obj-$(CONFIG_VIDEO_MIPI_DSI) += dsi-host-uclass.o
|
||||
obj-$(CONFIG_DM_VIDEO) += panel-uclass.o simple_panel.o
|
||||
obj-$(CONFIG_DM_VIDEO) += video-uclass.o vidconsole-uclass.o
|
||||
obj-$(CONFIG_DM_VIDEO) += video_bmp.o
|
||||
@ -44,19 +45,24 @@ obj-$(CONFIG_VIDEO_BROADWELL_IGD) += broadwell_igd.o
|
||||
obj-$(CONFIG_VIDEO_COREBOOT) += coreboot.o
|
||||
obj-$(CONFIG_VIDEO_DA8XX) += da8xx-fb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_DW_HDMI) += dw_hdmi.o
|
||||
obj-$(CONFIG_VIDEO_DW_MIPI_DSI) += dw_mipi_dsi.o
|
||||
obj-$(CONFIG_VIDEO_EFI) += efi.o
|
||||
obj-$(CONFIG_VIDEO_FSL_DCU_FB) += fsl_dcu_fb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_IPUV3) += imx/
|
||||
obj-$(CONFIG_VIDEO_IVYBRIDGE_IGD) += ivybridge_igd.o
|
||||
obj-$(CONFIG_VIDEO_LCD_ANX9804) += anx9804.o
|
||||
obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
|
||||
obj-$(CONFIG_VIDEO_LCD_ORISETECH_OTM8009A) += orisetech_otm8009a.o
|
||||
obj-$(CONFIG_VIDEO_LCD_RAYDIUM_RM68200) += raydium-rm68200.o
|
||||
obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
|
||||
obj-$(CONFIG_VIDEO_MB862xx) += mb862xx.o videomodes.o
|
||||
obj-${CONFIG_VIDEO_MESON} += meson/
|
||||
obj-${CONFIG_VIDEO_MIPI_DSI} += mipi_dsi.o
|
||||
obj-$(CONFIG_VIDEO_MVEBU) += mvebu_lcd.o
|
||||
obj-$(CONFIG_VIDEO_MX3) += mx3fb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_MXS) += mxsfb.o videomodes.o
|
||||
obj-$(CONFIG_VIDEO_OMAP3) += omap3_dss.o
|
||||
obj-$(CONFIG_VIDEO_DSI_HOST_SANDBOX) += sandbox_dsi_host.o
|
||||
obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o
|
||||
obj-$(CONFIG_VIDEO_SIMPLE) += simplefb.o
|
||||
obj-$(CONFIG_VIDEO_TEGRA20) += tegra.o
|
||||
|
39
drivers/video/dsi-host-uclass.c
Normal file
39
drivers/video/dsi-host-uclass.c
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <dsi_host.h>
|
||||
|
||||
int dsi_host_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 dsi_host_ops *ops = dsi_host_get_ops(dev);
|
||||
|
||||
if (!ops->init)
|
||||
return -ENOSYS;
|
||||
|
||||
return ops->init(dev, device, timings, max_data_lanes, phy_ops);
|
||||
}
|
||||
|
||||
int dsi_host_enable(struct udevice *dev)
|
||||
{
|
||||
struct dsi_host_ops *ops = dsi_host_get_ops(dev);
|
||||
|
||||
if (!ops->enable)
|
||||
return -ENOSYS;
|
||||
|
||||
return ops->enable(dev);
|
||||
}
|
||||
|
||||
UCLASS_DRIVER(dsi_host) = {
|
||||
.id = UCLASS_DSI_HOST,
|
||||
.name = "dsi_host",
|
||||
};
|
838
drivers/video/dw_mipi_dsi.c
Normal file
838
drivers/video/dw_mipi_dsi.c
Normal file
@ -0,0 +1,838 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2016, Fuzhou Rockchip Electronics Co., Ltd
|
||||
* Copyright (C) 2019, STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
|
||||
* Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
*
|
||||
* This generic Synopsys DesignWare MIPI DSI host driver is inspired from
|
||||
* the Linux Kernel driver drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <dsi_host.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <panel.h>
|
||||
#include <video.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <video_bridge.h>
|
||||
|
||||
#define HWVER_131 0x31333100 /* IP version 1.31 */
|
||||
|
||||
#define DSI_VERSION 0x00
|
||||
#define VERSION GENMASK(31, 8)
|
||||
|
||||
#define DSI_PWR_UP 0x04
|
||||
#define RESET 0
|
||||
#define POWERUP BIT(0)
|
||||
|
||||
#define DSI_CLKMGR_CFG 0x08
|
||||
#define TO_CLK_DIVISION(div) (((div) & 0xff) << 8)
|
||||
#define TX_ESC_CLK_DIVISION(div) ((div) & 0xff)
|
||||
|
||||
#define DSI_DPI_VCID 0x0c
|
||||
#define DPI_VCID(vcid) ((vcid) & 0x3)
|
||||
|
||||
#define DSI_DPI_COLOR_CODING 0x10
|
||||
#define LOOSELY18_EN BIT(8)
|
||||
#define DPI_COLOR_CODING_16BIT_1 0x0
|
||||
#define DPI_COLOR_CODING_16BIT_2 0x1
|
||||
#define DPI_COLOR_CODING_16BIT_3 0x2
|
||||
#define DPI_COLOR_CODING_18BIT_1 0x3
|
||||
#define DPI_COLOR_CODING_18BIT_2 0x4
|
||||
#define DPI_COLOR_CODING_24BIT 0x5
|
||||
|
||||
#define DSI_DPI_CFG_POL 0x14
|
||||
#define COLORM_ACTIVE_LOW BIT(4)
|
||||
#define SHUTD_ACTIVE_LOW BIT(3)
|
||||
#define HSYNC_ACTIVE_LOW BIT(2)
|
||||
#define VSYNC_ACTIVE_LOW BIT(1)
|
||||
#define DATAEN_ACTIVE_LOW BIT(0)
|
||||
|
||||
#define DSI_DPI_LP_CMD_TIM 0x18
|
||||
#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16)
|
||||
#define INVACT_LPCMD_TIME(p) ((p) & 0xff)
|
||||
|
||||
#define DSI_DBI_VCID 0x1c
|
||||
#define DSI_DBI_CFG 0x20
|
||||
#define DSI_DBI_PARTITIONING_EN 0x24
|
||||
#define DSI_DBI_CMDSIZE 0x28
|
||||
|
||||
#define DSI_PCKHDL_CFG 0x2c
|
||||
#define CRC_RX_EN BIT(4)
|
||||
#define ECC_RX_EN BIT(3)
|
||||
#define BTA_EN BIT(2)
|
||||
#define EOTP_RX_EN BIT(1)
|
||||
#define EOTP_TX_EN BIT(0)
|
||||
|
||||
#define DSI_GEN_VCID 0x30
|
||||
|
||||
#define DSI_MODE_CFG 0x34
|
||||
#define ENABLE_VIDEO_MODE 0
|
||||
#define ENABLE_CMD_MODE BIT(0)
|
||||
|
||||
#define DSI_VID_MODE_CFG 0x38
|
||||
#define ENABLE_LOW_POWER (0x3f << 8)
|
||||
#define ENABLE_LOW_POWER_MASK (0x3f << 8)
|
||||
#define VID_MODE_TYPE_NON_BURST_SYNC_PULSES 0x0
|
||||
#define VID_MODE_TYPE_NON_BURST_SYNC_EVENTS 0x1
|
||||
#define VID_MODE_TYPE_BURST 0x2
|
||||
#define VID_MODE_TYPE_MASK 0x3
|
||||
|
||||
#define DSI_VID_PKT_SIZE 0x3c
|
||||
#define VID_PKT_SIZE(p) ((p) & 0x3fff)
|
||||
|
||||
#define DSI_VID_NUM_CHUNKS 0x40
|
||||
#define VID_NUM_CHUNKS(c) ((c) & 0x1fff)
|
||||
|
||||
#define DSI_VID_NULL_SIZE 0x44
|
||||
#define VID_NULL_SIZE(b) ((b) & 0x1fff)
|
||||
|
||||
#define DSI_VID_HSA_TIME 0x48
|
||||
#define DSI_VID_HBP_TIME 0x4c
|
||||
#define DSI_VID_HLINE_TIME 0x50
|
||||
#define DSI_VID_VSA_LINES 0x54
|
||||
#define DSI_VID_VBP_LINES 0x58
|
||||
#define DSI_VID_VFP_LINES 0x5c
|
||||
#define DSI_VID_VACTIVE_LINES 0x60
|
||||
#define DSI_EDPI_CMD_SIZE 0x64
|
||||
|
||||
#define DSI_CMD_MODE_CFG 0x68
|
||||
#define MAX_RD_PKT_SIZE_LP BIT(24)
|
||||
#define DCS_LW_TX_LP BIT(19)
|
||||
#define DCS_SR_0P_TX_LP BIT(18)
|
||||
#define DCS_SW_1P_TX_LP BIT(17)
|
||||
#define DCS_SW_0P_TX_LP BIT(16)
|
||||
#define GEN_LW_TX_LP BIT(14)
|
||||
#define GEN_SR_2P_TX_LP BIT(13)
|
||||
#define GEN_SR_1P_TX_LP BIT(12)
|
||||
#define GEN_SR_0P_TX_LP BIT(11)
|
||||
#define GEN_SW_2P_TX_LP BIT(10)
|
||||
#define GEN_SW_1P_TX_LP BIT(9)
|
||||
#define GEN_SW_0P_TX_LP BIT(8)
|
||||
#define ACK_RQST_EN BIT(1)
|
||||
#define TEAR_FX_EN BIT(0)
|
||||
|
||||
#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \
|
||||
DCS_LW_TX_LP | \
|
||||
DCS_SR_0P_TX_LP | \
|
||||
DCS_SW_1P_TX_LP | \
|
||||
DCS_SW_0P_TX_LP | \
|
||||
GEN_LW_TX_LP | \
|
||||
GEN_SR_2P_TX_LP | \
|
||||
GEN_SR_1P_TX_LP | \
|
||||
GEN_SR_0P_TX_LP | \
|
||||
GEN_SW_2P_TX_LP | \
|
||||
GEN_SW_1P_TX_LP | \
|
||||
GEN_SW_0P_TX_LP)
|
||||
|
||||
#define DSI_GEN_HDR 0x6c
|
||||
#define DSI_GEN_PLD_DATA 0x70
|
||||
|
||||
#define DSI_CMD_PKT_STATUS 0x74
|
||||
#define GEN_RD_CMD_BUSY BIT(6)
|
||||
#define GEN_PLD_R_FULL BIT(5)
|
||||
#define GEN_PLD_R_EMPTY BIT(4)
|
||||
#define GEN_PLD_W_FULL BIT(3)
|
||||
#define GEN_PLD_W_EMPTY BIT(2)
|
||||
#define GEN_CMD_FULL BIT(1)
|
||||
#define GEN_CMD_EMPTY BIT(0)
|
||||
|
||||
#define DSI_TO_CNT_CFG 0x78
|
||||
#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16)
|
||||
#define LPRX_TO_CNT(p) ((p) & 0xffff)
|
||||
|
||||
#define DSI_HS_RD_TO_CNT 0x7c
|
||||
#define DSI_LP_RD_TO_CNT 0x80
|
||||
#define DSI_HS_WR_TO_CNT 0x84
|
||||
#define DSI_LP_WR_TO_CNT 0x88
|
||||
#define DSI_BTA_TO_CNT 0x8c
|
||||
|
||||
#define DSI_LPCLK_CTRL 0x94
|
||||
#define AUTO_CLKLANE_CTRL BIT(1)
|
||||
#define PHY_TXREQUESTCLKHS BIT(0)
|
||||
|
||||
#define DSI_PHY_TMR_LPCLK_CFG 0x98
|
||||
#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16)
|
||||
#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff)
|
||||
|
||||
#define DSI_PHY_TMR_CFG 0x9c
|
||||
#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 24)
|
||||
#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 16)
|
||||
#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff)
|
||||
#define PHY_HS2LP_TIME_V131(lbcc) (((lbcc) & 0x3ff) << 16)
|
||||
#define PHY_LP2HS_TIME_V131(lbcc) ((lbcc) & 0x3ff)
|
||||
|
||||
#define DSI_PHY_RSTZ 0xa0
|
||||
#define PHY_DISFORCEPLL 0
|
||||
#define PHY_ENFORCEPLL BIT(3)
|
||||
#define PHY_DISABLECLK 0
|
||||
#define PHY_ENABLECLK BIT(2)
|
||||
#define PHY_RSTZ 0
|
||||
#define PHY_UNRSTZ BIT(1)
|
||||
#define PHY_SHUTDOWNZ 0
|
||||
#define PHY_UNSHUTDOWNZ BIT(0)
|
||||
|
||||
#define DSI_PHY_IF_CFG 0xa4
|
||||
#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8)
|
||||
#define N_LANES(n) (((n) - 1) & 0x3)
|
||||
|
||||
#define DSI_PHY_ULPS_CTRL 0xa8
|
||||
#define DSI_PHY_TX_TRIGGERS 0xac
|
||||
|
||||
#define DSI_PHY_STATUS 0xb0
|
||||
#define PHY_STOP_STATE_CLK_LANE BIT(2)
|
||||
#define PHY_LOCK BIT(0)
|
||||
|
||||
#define DSI_PHY_TST_CTRL0 0xb4
|
||||
#define PHY_TESTCLK BIT(1)
|
||||
#define PHY_UNTESTCLK 0
|
||||
#define PHY_TESTCLR BIT(0)
|
||||
#define PHY_UNTESTCLR 0
|
||||
|
||||
#define DSI_PHY_TST_CTRL1 0xb8
|
||||
#define PHY_TESTEN BIT(16)
|
||||
#define PHY_UNTESTEN 0
|
||||
#define PHY_TESTDOUT(n) (((n) & 0xff) << 8)
|
||||
#define PHY_TESTDIN(n) ((n) & 0xff)
|
||||
|
||||
#define DSI_INT_ST0 0xbc
|
||||
#define DSI_INT_ST1 0xc0
|
||||
#define DSI_INT_MSK0 0xc4
|
||||
#define DSI_INT_MSK1 0xc8
|
||||
|
||||
#define DSI_PHY_TMR_RD_CFG 0xf4
|
||||
#define MAX_RD_TIME_V131(lbcc) ((lbcc) & 0x7fff)
|
||||
|
||||
#define PHY_STATUS_TIMEOUT_US 10000
|
||||
#define CMD_PKT_STATUS_TIMEOUT_US 20000
|
||||
|
||||
#define MSEC_PER_SEC 1000
|
||||
|
||||
struct dw_mipi_dsi {
|
||||
struct mipi_dsi_host dsi_host;
|
||||
struct mipi_dsi_device *device;
|
||||
void __iomem *base;
|
||||
unsigned int lane_mbps; /* per lane */
|
||||
u32 channel;
|
||||
unsigned int max_data_lanes;
|
||||
const struct mipi_dsi_phy_ops *phy_ops;
|
||||
};
|
||||
|
||||
static int dsi_mode_vrefresh(struct display_timing *timings)
|
||||
{
|
||||
int refresh = 0;
|
||||
unsigned int calc_val;
|
||||
u32 htotal = timings->hactive.typ + timings->hfront_porch.typ +
|
||||
timings->hback_porch.typ + timings->hsync_len.typ;
|
||||
u32 vtotal = timings->vactive.typ + timings->vfront_porch.typ +
|
||||
timings->vback_porch.typ + timings->vsync_len.typ;
|
||||
|
||||
if (htotal > 0 && vtotal > 0) {
|
||||
calc_val = timings->pixelclock.typ;
|
||||
calc_val /= htotal;
|
||||
refresh = (calc_val + vtotal / 2) / vtotal;
|
||||
}
|
||||
|
||||
return refresh;
|
||||
}
|
||||
|
||||
/*
|
||||
* The controller should generate 2 frames before
|
||||
* preparing the peripheral.
|
||||
*/
|
||||
static void dw_mipi_dsi_wait_for_two_frames(struct display_timing *timings)
|
||||
{
|
||||
int refresh, two_frames;
|
||||
|
||||
refresh = dsi_mode_vrefresh(timings);
|
||||
two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2;
|
||||
mdelay(two_frames);
|
||||
}
|
||||
|
||||
static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host)
|
||||
{
|
||||
return container_of(host, struct dw_mipi_dsi, dsi_host);
|
||||
}
|
||||
|
||||
static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val)
|
||||
{
|
||||
writel(val, dsi->base + reg);
|
||||
}
|
||||
|
||||
static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
|
||||
{
|
||||
return readl(dsi->base + reg);
|
||||
}
|
||||
|
||||
static int dw_mipi_dsi_host_attach(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *device)
|
||||
{
|
||||
struct dw_mipi_dsi *dsi = host_to_dsi(host);
|
||||
|
||||
if (device->lanes > dsi->max_data_lanes) {
|
||||
dev_err(device->dev,
|
||||
"the number of data lanes(%u) is too many\n",
|
||||
device->lanes);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dsi->channel = device->channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dw_mipi_message_config(struct dw_mipi_dsi *dsi,
|
||||
const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM;
|
||||
u32 val = 0;
|
||||
|
||||
if (msg->flags & MIPI_DSI_MSG_REQ_ACK)
|
||||
val |= ACK_RQST_EN;
|
||||
if (lpm)
|
||||
val |= CMD_MODE_ALL_LP;
|
||||
|
||||
dsi_write(dsi, DSI_LPCLK_CTRL, lpm ? 0 : PHY_TXREQUESTCLKHS);
|
||||
dsi_write(dsi, DSI_CMD_MODE_CFG, val);
|
||||
}
|
||||
|
||||
static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 hdr_val)
|
||||
{
|
||||
int ret;
|
||||
u32 val, mask;
|
||||
|
||||
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
|
||||
val, !(val & GEN_CMD_FULL),
|
||||
CMD_PKT_STATUS_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dsi->dev, "failed to get available command FIFO\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsi_write(dsi, DSI_GEN_HDR, hdr_val);
|
||||
|
||||
mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
|
||||
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
|
||||
val, (val & mask) == mask,
|
||||
CMD_PKT_STATUS_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dsi->dev, "failed to write command FIFO\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi,
|
||||
const struct mipi_dsi_packet *packet)
|
||||
{
|
||||
const u8 *tx_buf = packet->payload;
|
||||
int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret;
|
||||
__le32 word;
|
||||
u32 val;
|
||||
|
||||
while (len) {
|
||||
if (len < pld_data_bytes) {
|
||||
word = 0;
|
||||
memcpy(&word, tx_buf, len);
|
||||
dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
|
||||
len = 0;
|
||||
} else {
|
||||
memcpy(&word, tx_buf, pld_data_bytes);
|
||||
dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
|
||||
tx_buf += pld_data_bytes;
|
||||
len -= pld_data_bytes;
|
||||
}
|
||||
|
||||
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
|
||||
val, !(val & GEN_PLD_W_FULL),
|
||||
CMD_PKT_STATUS_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dsi->dev,
|
||||
"failed to get available write payload FIFO\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
word = 0;
|
||||
memcpy(&word, packet->header, sizeof(packet->header));
|
||||
return dw_mipi_dsi_gen_pkt_hdr_write(dsi, le32_to_cpu(word));
|
||||
}
|
||||
|
||||
static int dw_mipi_dsi_read(struct dw_mipi_dsi *dsi,
|
||||
const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
int i, j, ret, len = msg->rx_len;
|
||||
u8 *buf = msg->rx_buf;
|
||||
u32 val;
|
||||
|
||||
/* Wait end of the read operation */
|
||||
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
|
||||
val, !(val & GEN_RD_CMD_BUSY),
|
||||
CMD_PKT_STATUS_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dsi->dev, "Timeout during read operation\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i += 4) {
|
||||
/* Read fifo must not be empty before all bytes are read */
|
||||
ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
|
||||
val, !(val & GEN_PLD_R_EMPTY),
|
||||
CMD_PKT_STATUS_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(dsi->dev, "Read payload FIFO is empty\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = dsi_read(dsi, DSI_GEN_PLD_DATA);
|
||||
for (j = 0; j < 4 && j + i < len; j++)
|
||||
buf[i + j] = val >> (8 * j);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
struct dw_mipi_dsi *dsi = host_to_dsi(host);
|
||||
struct mipi_dsi_packet packet;
|
||||
int ret, nb_bytes;
|
||||
|
||||
ret = mipi_dsi_create_packet(&packet, msg);
|
||||
if (ret) {
|
||||
dev_err(dsi->dev, "failed to create packet: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dw_mipi_message_config(dsi, msg);
|
||||
|
||||
ret = dw_mipi_dsi_write(dsi, &packet);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (msg->rx_buf && msg->rx_len) {
|
||||
ret = dw_mipi_dsi_read(dsi, msg);
|
||||
if (ret)
|
||||
return ret;
|
||||
nb_bytes = msg->rx_len;
|
||||
} else {
|
||||
nb_bytes = packet.size;
|
||||
}
|
||||
|
||||
return nb_bytes;
|
||||
}
|
||||
|
||||
static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = {
|
||||
.attach = dw_mipi_dsi_host_attach,
|
||||
.transfer = dw_mipi_dsi_host_transfer,
|
||||
};
|
||||
|
||||
static void dw_mipi_dsi_video_mode_config(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
struct mipi_dsi_device *device = dsi->device;
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* enabling low power is panel-dependent, we should use the
|
||||
* panel configuration here...
|
||||
*/
|
||||
val = ENABLE_LOW_POWER;
|
||||
|
||||
if (device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
|
||||
val |= VID_MODE_TYPE_BURST;
|
||||
else if (device->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
|
||||
val |= VID_MODE_TYPE_NON_BURST_SYNC_PULSES;
|
||||
else
|
||||
val |= VID_MODE_TYPE_NON_BURST_SYNC_EVENTS;
|
||||
|
||||
dsi_write(dsi, DSI_VID_MODE_CFG, val);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi,
|
||||
unsigned long mode_flags)
|
||||
{
|
||||
const struct mipi_dsi_phy_ops *phy_ops = dsi->phy_ops;
|
||||
|
||||
dsi_write(dsi, DSI_PWR_UP, RESET);
|
||||
|
||||
if (mode_flags & MIPI_DSI_MODE_VIDEO) {
|
||||
dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE);
|
||||
dw_mipi_dsi_video_mode_config(dsi);
|
||||
dsi_write(dsi, DSI_LPCLK_CTRL, PHY_TXREQUESTCLKHS);
|
||||
} else {
|
||||
dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
|
||||
}
|
||||
|
||||
if (phy_ops->post_set_mode)
|
||||
phy_ops->post_set_mode(dsi->device, mode_flags);
|
||||
|
||||
dsi_write(dsi, DSI_PWR_UP, POWERUP);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_init_pll(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
/*
|
||||
* The maximum permitted escape clock is 20MHz and it is derived from
|
||||
* lanebyteclk, which is running at "lane_mbps / 8". Thus we want:
|
||||
*
|
||||
* (lane_mbps >> 3) / esc_clk_division < 20
|
||||
* which is:
|
||||
* (lane_mbps >> 3) / 20 > esc_clk_division
|
||||
*/
|
||||
u32 esc_clk_division = (dsi->lane_mbps >> 3) / 20 + 1;
|
||||
|
||||
dsi_write(dsi, DSI_PWR_UP, RESET);
|
||||
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* timeout clock division should be computed with the
|
||||
* high speed transmission counter timeout and byte lane...
|
||||
*/
|
||||
dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(10) |
|
||||
TX_ESC_CLK_DIVISION(esc_clk_division));
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
struct mipi_dsi_device *device = dsi->device;
|
||||
u32 val = 0, color = 0;
|
||||
|
||||
switch (device->format) {
|
||||
case MIPI_DSI_FMT_RGB888:
|
||||
color = DPI_COLOR_CODING_24BIT;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
color = DPI_COLOR_CODING_18BIT_2 | LOOSELY18_EN;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
color = DPI_COLOR_CODING_18BIT_1;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
color = DPI_COLOR_CODING_16BIT_1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (device->mode_flags & DISPLAY_FLAGS_VSYNC_HIGH)
|
||||
val |= VSYNC_ACTIVE_LOW;
|
||||
if (device->mode_flags & DISPLAY_FLAGS_HSYNC_HIGH)
|
||||
val |= HSYNC_ACTIVE_LOW;
|
||||
|
||||
dsi_write(dsi, DSI_DPI_VCID, DPI_VCID(dsi->channel));
|
||||
dsi_write(dsi, DSI_DPI_COLOR_CODING, color);
|
||||
dsi_write(dsi, DSI_DPI_CFG_POL, val);
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* largest packet sizes during hfp or during vsa/vpb/vfp
|
||||
* should be computed according to byte lane, lane number and only
|
||||
* if sending lp cmds in high speed is enable (PHY_TXREQUESTCLKHS)
|
||||
*/
|
||||
dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(4)
|
||||
| INVACT_LPCMD_TIME(4));
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
dsi_write(dsi, DSI_PCKHDL_CFG, CRC_RX_EN | ECC_RX_EN | BTA_EN);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_video_packet_config(struct dw_mipi_dsi *dsi,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* only burst mode is supported here. For non-burst video modes,
|
||||
* we should compute DSI_VID_PKT_SIZE, DSI_VCCR.NUMC &
|
||||
* DSI_VNPCR.NPSIZE... especially because this driver supports
|
||||
* non-burst video modes, see dw_mipi_dsi_video_mode_config()...
|
||||
*/
|
||||
dsi_write(dsi, DSI_VID_PKT_SIZE, VID_PKT_SIZE(timings->hactive.typ));
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
const struct mipi_dsi_phy_ops *phy_ops = dsi->phy_ops;
|
||||
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* compute high speed transmission counter timeout according
|
||||
* to the timeout clock division (TO_CLK_DIVISION) and byte lane...
|
||||
*/
|
||||
dsi_write(dsi, DSI_TO_CNT_CFG, HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000));
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* the Bus-Turn-Around Timeout Counter should be computed
|
||||
* according to byte lane...
|
||||
*/
|
||||
dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00);
|
||||
dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
|
||||
|
||||
if (phy_ops->post_set_mode)
|
||||
phy_ops->post_set_mode(dsi->device, 0);
|
||||
}
|
||||
|
||||
/* Get lane byte clock cycles. */
|
||||
static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi,
|
||||
struct display_timing *timings,
|
||||
u32 hcomponent)
|
||||
{
|
||||
u32 frac, lbcc;
|
||||
|
||||
lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8;
|
||||
|
||||
frac = lbcc % (timings->pixelclock.typ / 1000);
|
||||
lbcc = lbcc / (timings->pixelclock.typ / 1000);
|
||||
if (frac)
|
||||
lbcc++;
|
||||
|
||||
return lbcc;
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_line_timer_config(struct dw_mipi_dsi *dsi,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
u32 htotal, hsa, hbp, lbcc;
|
||||
|
||||
htotal = timings->hactive.typ + timings->hfront_porch.typ +
|
||||
timings->hback_porch.typ + timings->hsync_len.typ;
|
||||
|
||||
hsa = timings->hback_porch.typ;
|
||||
hbp = timings->hsync_len.typ;
|
||||
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* computations below may be improved...
|
||||
*/
|
||||
lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, timings, htotal);
|
||||
dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc);
|
||||
|
||||
lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, timings, hsa);
|
||||
dsi_write(dsi, DSI_VID_HSA_TIME, lbcc);
|
||||
|
||||
lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, timings, hbp);
|
||||
dsi_write(dsi, DSI_VID_HBP_TIME, lbcc);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_vertical_timing_config(struct dw_mipi_dsi *dsi,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
u32 vactive, vsa, vfp, vbp;
|
||||
|
||||
vactive = timings->vactive.typ;
|
||||
vsa = timings->vback_porch.typ;
|
||||
vfp = timings->vfront_porch.typ;
|
||||
vbp = timings->vsync_len.typ;
|
||||
|
||||
dsi_write(dsi, DSI_VID_VACTIVE_LINES, vactive);
|
||||
dsi_write(dsi, DSI_VID_VSA_LINES, vsa);
|
||||
dsi_write(dsi, DSI_VID_VFP_LINES, vfp);
|
||||
dsi_write(dsi, DSI_VID_VBP_LINES, vbp);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
u32 hw_version;
|
||||
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* data & clock lane timers should be computed according to panel
|
||||
* blankings and to the automatic clock lane control mode...
|
||||
* note: DSI_PHY_TMR_CFG.MAX_RD_TIME should be in line with
|
||||
* DSI_CMD_MODE_CFG.MAX_RD_PKT_SIZE_LP (see CMD_MODE_ALL_LP)
|
||||
*/
|
||||
|
||||
hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
|
||||
|
||||
if (hw_version >= HWVER_131) {
|
||||
dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME_V131(0x40) |
|
||||
PHY_LP2HS_TIME_V131(0x40));
|
||||
dsi_write(dsi, DSI_PHY_TMR_RD_CFG, MAX_RD_TIME_V131(10000));
|
||||
} else {
|
||||
dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME(0x40) |
|
||||
PHY_LP2HS_TIME(0x40) | MAX_RD_TIME(10000));
|
||||
}
|
||||
|
||||
dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG, PHY_CLKHS2LP_TIME(0x40)
|
||||
| PHY_CLKLP2HS_TIME(0x40));
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
struct mipi_dsi_device *device = dsi->device;
|
||||
|
||||
/*
|
||||
* TODO dw drv improvements
|
||||
* stop wait time should be the maximum between host dsi
|
||||
* and panel stop wait times
|
||||
*/
|
||||
dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
|
||||
N_LANES(device->lanes));
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_dphy_init(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
/* Clear PHY state */
|
||||
dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK
|
||||
| PHY_RSTZ | PHY_SHUTDOWNZ);
|
||||
dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR);
|
||||
dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_TESTCLR);
|
||||
dsi_write(dsi, DSI_PHY_TST_CTRL0, PHY_UNTESTCLR);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_dphy_enable(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK |
|
||||
PHY_UNRSTZ | PHY_UNSHUTDOWNZ);
|
||||
|
||||
ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, val,
|
||||
val & PHY_LOCK, PHY_STATUS_TIMEOUT_US);
|
||||
if (ret)
|
||||
dev_warn(dsi->dev, "failed to wait phy lock state\n");
|
||||
|
||||
ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS,
|
||||
val, val & PHY_STOP_STATE_CLK_LANE,
|
||||
PHY_STATUS_TIMEOUT_US);
|
||||
if (ret)
|
||||
dev_warn(dsi->dev, "failed to wait phy clk lane stop state\n");
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi)
|
||||
{
|
||||
dsi_read(dsi, DSI_INT_ST0);
|
||||
dsi_read(dsi, DSI_INT_ST1);
|
||||
dsi_write(dsi, DSI_INT_MSK0, 0);
|
||||
dsi_write(dsi, DSI_INT_MSK1, 0);
|
||||
}
|
||||
|
||||
static void dw_mipi_dsi_bridge_set(struct dw_mipi_dsi *dsi,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
const struct mipi_dsi_phy_ops *phy_ops = dsi->phy_ops;
|
||||
struct mipi_dsi_device *device = dsi->device;
|
||||
int ret;
|
||||
|
||||
ret = phy_ops->get_lane_mbps(dsi->device, timings, device->lanes,
|
||||
device->format, &dsi->lane_mbps);
|
||||
if (ret)
|
||||
dev_warn(dsi->dev, "Phy get_lane_mbps() failed\n");
|
||||
|
||||
dw_mipi_dsi_init_pll(dsi);
|
||||
dw_mipi_dsi_dpi_config(dsi, timings);
|
||||
dw_mipi_dsi_packet_handler_config(dsi);
|
||||
dw_mipi_dsi_video_mode_config(dsi);
|
||||
dw_mipi_dsi_video_packet_config(dsi, timings);
|
||||
dw_mipi_dsi_command_mode_config(dsi);
|
||||
dw_mipi_dsi_line_timer_config(dsi, timings);
|
||||
dw_mipi_dsi_vertical_timing_config(dsi, timings);
|
||||
|
||||
dw_mipi_dsi_dphy_init(dsi);
|
||||
dw_mipi_dsi_dphy_timing_config(dsi);
|
||||
dw_mipi_dsi_dphy_interface_config(dsi);
|
||||
|
||||
dw_mipi_dsi_clear_err(dsi);
|
||||
|
||||
ret = phy_ops->init(dsi->device);
|
||||
if (ret)
|
||||
dev_warn(dsi->dev, "Phy init() failed\n");
|
||||
|
||||
dw_mipi_dsi_dphy_enable(dsi);
|
||||
|
||||
dw_mipi_dsi_wait_for_two_frames(timings);
|
||||
|
||||
/* Switch to cmd mode for panel-bridge pre_enable & panel prepare */
|
||||
dw_mipi_dsi_set_mode(dsi, 0);
|
||||
}
|
||||
|
||||
static int dw_mipi_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 dw_mipi_dsi *dsi = dev_get_priv(dev);
|
||||
struct clk clk;
|
||||
int ret;
|
||||
|
||||
if (!phy_ops->init || !phy_ops->get_lane_mbps) {
|
||||
dev_err(device->dev, "Phy not properly configured\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dsi->phy_ops = phy_ops;
|
||||
dsi->max_data_lanes = max_data_lanes;
|
||||
dsi->device = device;
|
||||
dsi->dsi_host.ops = &dw_mipi_dsi_host_ops;
|
||||
device->host = &dsi->dsi_host;
|
||||
|
||||
dsi->base = (void *)dev_read_addr(device->dev);
|
||||
if ((fdt_addr_t)dsi->base == FDT_ADDR_T_NONE) {
|
||||
dev_err(device->dev, "dsi dt register address error\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(device->dev, "px_clk", &clk);
|
||||
if (ret) {
|
||||
dev_err(device->dev, "peripheral clock get error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* get the pixel clock set by the clock framework */
|
||||
timings->pixelclock.typ = clk_get_rate(&clk);
|
||||
|
||||
dw_mipi_dsi_bridge_set(dsi, timings);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_mipi_dsi_enable(struct udevice *dev)
|
||||
{
|
||||
struct dw_mipi_dsi *dsi = dev_get_priv(dev);
|
||||
|
||||
/* Switch to video mode for panel-bridge enable & panel enable */
|
||||
dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dsi_host_ops dw_mipi_dsi_ops = {
|
||||
.init = dw_mipi_dsi_init,
|
||||
.enable = dw_mipi_dsi_enable,
|
||||
};
|
||||
|
||||
static int dw_mipi_dsi_probe(struct udevice *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id dw_mipi_dsi_ids[] = {
|
||||
{ .compatible = "synopsys,dw-mipi-dsi" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(dw_mipi_dsi) = {
|
||||
.name = "dw_mipi_dsi",
|
||||
.id = UCLASS_DSI_HOST,
|
||||
.of_match = dw_mipi_dsi_ids,
|
||||
.probe = dw_mipi_dsi_probe,
|
||||
.ops = &dw_mipi_dsi_ops,
|
||||
.priv_auto_alloc_size = sizeof(struct dw_mipi_dsi),
|
||||
};
|
||||
|
||||
MODULE_AUTHOR("Chris Zhong <zyw@rock-chips.com>");
|
||||
MODULE_AUTHOR("Philippe Cornu <philippe.cornu@st.com>");
|
||||
MODULE_AUTHOR("Yannick Fertré <yannick.fertre@st.com>");
|
||||
MODULE_DESCRIPTION("DW MIPI DSI host controller driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:dw-mipi-dsi");
|
828
drivers/video/mipi_dsi.c
Normal file
828
drivers/video/mipi_dsi.c
Normal file
@ -0,0 +1,828 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* MIPI DSI Bus
|
||||
*
|
||||
* Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Andrzej Hajda <a.hajda@samsung.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sub license, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the
|
||||
* next paragraph) shall be included in all copies or substantial portions
|
||||
* of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Mipi_dsi.c contains a set of dsi helpers.
|
||||
* This file is inspired from the drm helper file drivers/gpu/drm/drm_mipi_dsi.c
|
||||
* (kernel linux).
|
||||
*
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <display.h>
|
||||
#include <dm.h>
|
||||
#include <mipi_display.h>
|
||||
#include <mipi_dsi.h>
|
||||
|
||||
/**
|
||||
* DOC: dsi helpers
|
||||
*
|
||||
* These functions contain some common logic and helpers to deal with MIPI DSI
|
||||
* peripherals.
|
||||
*
|
||||
* Helpers are provided for a number of standard MIPI DSI command as well as a
|
||||
* subset of the MIPI DCS command set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* mipi_dsi_attach - attach a DSI device to its DSI host
|
||||
* @dsi: DSI peripheral
|
||||
*/
|
||||
int mipi_dsi_attach(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
const struct mipi_dsi_host_ops *ops = dsi->host->ops;
|
||||
|
||||
if (!ops || !ops->attach)
|
||||
return -ENOSYS;
|
||||
|
||||
return ops->attach(dsi->host, dsi);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_attach);
|
||||
|
||||
/**
|
||||
* mipi_dsi_detach - detach a DSI device from its DSI host
|
||||
* @dsi: DSI peripheral
|
||||
*/
|
||||
int mipi_dsi_detach(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
const struct mipi_dsi_host_ops *ops = dsi->host->ops;
|
||||
|
||||
if (!ops || !ops->detach)
|
||||
return -ENOSYS;
|
||||
|
||||
return ops->detach(dsi->host, dsi);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_detach);
|
||||
|
||||
/**
|
||||
* mipi_dsi_device_transfer - transfer message to a DSI device
|
||||
* @dsi: DSI peripheral
|
||||
* @msg: message
|
||||
*/
|
||||
static ssize_t mipi_dsi_device_transfer(struct mipi_dsi_device *dsi,
|
||||
struct mipi_dsi_msg *msg)
|
||||
{
|
||||
const struct mipi_dsi_host_ops *ops = dsi->host->ops;
|
||||
|
||||
if (!ops || !ops->transfer)
|
||||
return -ENOSYS;
|
||||
|
||||
if (dsi->mode_flags & MIPI_DSI_MODE_LPM)
|
||||
msg->flags |= MIPI_DSI_MSG_USE_LPM;
|
||||
|
||||
return ops->transfer(dsi->host, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* mipi_dsi_packet_format_is_short - check if a packet is of the short format
|
||||
* @type: MIPI DSI data type of the packet
|
||||
*
|
||||
* Return: true if the packet for the given data type is a short packet, false
|
||||
* otherwise.
|
||||
*/
|
||||
bool mipi_dsi_packet_format_is_short(u8 type)
|
||||
{
|
||||
switch (type) {
|
||||
case MIPI_DSI_V_SYNC_START:
|
||||
case MIPI_DSI_V_SYNC_END:
|
||||
case MIPI_DSI_H_SYNC_START:
|
||||
case MIPI_DSI_H_SYNC_END:
|
||||
case MIPI_DSI_END_OF_TRANSMISSION:
|
||||
case MIPI_DSI_COLOR_MODE_OFF:
|
||||
case MIPI_DSI_COLOR_MODE_ON:
|
||||
case MIPI_DSI_SHUTDOWN_PERIPHERAL:
|
||||
case MIPI_DSI_TURN_ON_PERIPHERAL:
|
||||
case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
|
||||
case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
|
||||
case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
|
||||
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_SHORT_WRITE:
|
||||
case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
|
||||
case MIPI_DSI_DCS_READ:
|
||||
case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_packet_format_is_short);
|
||||
|
||||
/**
|
||||
* mipi_dsi_packet_format_is_long - check if a packet is of the long format
|
||||
* @type: MIPI DSI data type of the packet
|
||||
*
|
||||
* Return: true if the packet for the given data type is a long packet, false
|
||||
* otherwise.
|
||||
*/
|
||||
bool mipi_dsi_packet_format_is_long(u8 type)
|
||||
{
|
||||
switch (type) {
|
||||
case MIPI_DSI_NULL_PACKET:
|
||||
case MIPI_DSI_BLANKING_PACKET:
|
||||
case MIPI_DSI_GENERIC_LONG_WRITE:
|
||||
case MIPI_DSI_DCS_LONG_WRITE:
|
||||
case MIPI_DSI_LOOSELY_PACKED_PIXEL_STREAM_YCBCR20:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR24:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR16:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_30:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_36:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_YCBCR12:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_16:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_18:
|
||||
case MIPI_DSI_PIXEL_STREAM_3BYTE_18:
|
||||
case MIPI_DSI_PACKED_PIXEL_STREAM_24:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_packet_format_is_long);
|
||||
|
||||
/**
|
||||
* mipi_dsi_create_packet - create a packet from a message according to the
|
||||
* DSI protocol
|
||||
* @packet: pointer to a DSI packet structure
|
||||
* @msg: message to translate into a packet
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_create_packet(struct mipi_dsi_packet *packet,
|
||||
const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
if (!packet || !msg)
|
||||
return -EINVAL;
|
||||
|
||||
/* do some minimum sanity checking */
|
||||
if (!mipi_dsi_packet_format_is_short(msg->type) &&
|
||||
!mipi_dsi_packet_format_is_long(msg->type))
|
||||
return -EINVAL;
|
||||
|
||||
if (msg->channel > 3)
|
||||
return -EINVAL;
|
||||
|
||||
memset(packet, 0, sizeof(*packet));
|
||||
packet->header[0] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f);
|
||||
|
||||
/* TODO: compute ECC if hardware support is not available */
|
||||
|
||||
/*
|
||||
* Long write packets contain the word count in header bytes 1 and 2.
|
||||
* The payload follows the header and is word count bytes long.
|
||||
*
|
||||
* Short write packets encode up to two parameters in header bytes 1
|
||||
* and 2.
|
||||
*/
|
||||
if (mipi_dsi_packet_format_is_long(msg->type)) {
|
||||
packet->header[1] = (msg->tx_len >> 0) & 0xff;
|
||||
packet->header[2] = (msg->tx_len >> 8) & 0xff;
|
||||
|
||||
packet->payload_length = msg->tx_len;
|
||||
packet->payload = msg->tx_buf;
|
||||
} else {
|
||||
const u8 *tx = msg->tx_buf;
|
||||
|
||||
packet->header[1] = (msg->tx_len > 0) ? tx[0] : 0;
|
||||
packet->header[2] = (msg->tx_len > 1) ? tx[1] : 0;
|
||||
}
|
||||
|
||||
packet->size = sizeof(packet->header) + packet->payload_length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_create_packet);
|
||||
|
||||
/**
|
||||
* mipi_dsi_shutdown_peripheral() - sends a Shutdown Peripheral command
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_shutdown_peripheral(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.type = MIPI_DSI_SHUTDOWN_PERIPHERAL,
|
||||
.tx_buf = (u8 [2]) { 0, 0 },
|
||||
.tx_len = 2,
|
||||
};
|
||||
int ret = mipi_dsi_device_transfer(dsi, &msg);
|
||||
|
||||
return (ret < 0) ? ret : 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_shutdown_peripheral);
|
||||
|
||||
/**
|
||||
* mipi_dsi_turn_on_peripheral() - sends a Turn On Peripheral command
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.type = MIPI_DSI_TURN_ON_PERIPHERAL,
|
||||
.tx_buf = (u8 [2]) { 0, 0 },
|
||||
.tx_len = 2,
|
||||
};
|
||||
int ret = mipi_dsi_device_transfer(dsi, &msg);
|
||||
|
||||
return (ret < 0) ? ret : 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_turn_on_peripheral);
|
||||
|
||||
/*
|
||||
* mipi_dsi_set_maximum_return_packet_size() - specify the maximum size of the
|
||||
* the payload in a long packet transmitted from the peripheral back to the
|
||||
* host processor
|
||||
* @dsi: DSI peripheral device
|
||||
* @value: the maximum size of the payload
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
|
||||
u16 value)
|
||||
{
|
||||
u8 tx[2] = { value & 0xff, value >> 8 };
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.type = MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
|
||||
.tx_len = sizeof(tx),
|
||||
.tx_buf = tx,
|
||||
};
|
||||
int ret = mipi_dsi_device_transfer(dsi, &msg);
|
||||
|
||||
return (ret < 0) ? ret : 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_set_maximum_return_packet_size);
|
||||
|
||||
/**
|
||||
* mipi_dsi_generic_write() - transmit data using a generic write packet
|
||||
* @dsi: DSI peripheral device
|
||||
* @payload: buffer containing the payload
|
||||
* @size: size of payload buffer
|
||||
*
|
||||
* This function will automatically choose the right data type depending on
|
||||
* the payload length.
|
||||
*
|
||||
* Return: The number of bytes transmitted on success or a negative error code
|
||||
* on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
|
||||
size_t size)
|
||||
{
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.tx_buf = payload,
|
||||
.tx_len = size
|
||||
};
|
||||
|
||||
switch (size) {
|
||||
case 0:
|
||||
msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
msg.type = MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
msg.type = MIPI_DSI_GENERIC_LONG_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
return mipi_dsi_device_transfer(dsi, &msg);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_generic_write);
|
||||
|
||||
/**
|
||||
* mipi_dsi_generic_read() - receive data using a generic read packet
|
||||
* @dsi: DSI peripheral device
|
||||
* @params: buffer containing the request parameters
|
||||
* @num_params: number of request parameters
|
||||
* @data: buffer in which to return the received data
|
||||
* @size: size of receive buffer
|
||||
*
|
||||
* This function will automatically choose the right data type depending on
|
||||
* the number of parameters passed in.
|
||||
*
|
||||
* Return: The number of bytes successfully read or a negative error code on
|
||||
* failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
|
||||
size_t num_params, void *data, size_t size)
|
||||
{
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.tx_len = num_params,
|
||||
.tx_buf = params,
|
||||
.rx_len = size,
|
||||
.rx_buf = data
|
||||
};
|
||||
|
||||
switch (num_params) {
|
||||
case 0:
|
||||
msg.type = MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
msg.type = MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
msg.type = MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return mipi_dsi_device_transfer(dsi, &msg);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_generic_read);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_write_buffer() - transmit a DCS command with payload
|
||||
* @dsi: DSI peripheral device
|
||||
* @data: buffer containing data to be transmitted
|
||||
* @len: size of transmission buffer
|
||||
*
|
||||
* This function will automatically choose the right data type depending on
|
||||
* the command payload length.
|
||||
*
|
||||
* Return: The number of bytes successfully transmitted or a negative error
|
||||
* code on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.tx_buf = data,
|
||||
.tx_len = len
|
||||
};
|
||||
|
||||
switch (len) {
|
||||
case 0:
|
||||
return -EINVAL;
|
||||
|
||||
case 1:
|
||||
msg.type = MIPI_DSI_DCS_SHORT_WRITE;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
msg.type = MIPI_DSI_DCS_SHORT_WRITE_PARAM;
|
||||
break;
|
||||
|
||||
default:
|
||||
msg.type = MIPI_DSI_DCS_LONG_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
return mipi_dsi_device_transfer(dsi, &msg);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_write() - send DCS write command
|
||||
* @dsi: DSI peripheral device
|
||||
* @cmd: DCS command
|
||||
* @data: buffer containing the command payload
|
||||
* @len: command payload length
|
||||
*
|
||||
* This function will automatically choose the right data type depending on
|
||||
* the command payload length.
|
||||
*
|
||||
* Return: The number of bytes successfully transmitted or a negative error
|
||||
* code on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
|
||||
const void *data, size_t len)
|
||||
{
|
||||
ssize_t err;
|
||||
size_t size;
|
||||
u8 *tx;
|
||||
|
||||
if (len > 0) {
|
||||
size = 1 + len;
|
||||
|
||||
tx = kmalloc(size, GFP_KERNEL);
|
||||
if (!tx)
|
||||
return -ENOMEM;
|
||||
|
||||
/* concatenate the DCS command byte and the payload */
|
||||
tx[0] = cmd;
|
||||
memcpy(&tx[1], data, len);
|
||||
} else {
|
||||
tx = &cmd;
|
||||
size = 1;
|
||||
}
|
||||
|
||||
err = mipi_dsi_dcs_write_buffer(dsi, tx, size);
|
||||
|
||||
if (len > 0)
|
||||
kfree(tx);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_write);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_read() - send DCS read request command
|
||||
* @dsi: DSI peripheral device
|
||||
* @cmd: DCS command
|
||||
* @data: buffer in which to receive data
|
||||
* @len: size of receive buffer
|
||||
*
|
||||
* Return: The number of bytes read or a negative error code on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
|
||||
size_t len)
|
||||
{
|
||||
struct mipi_dsi_msg msg = {
|
||||
.channel = dsi->channel,
|
||||
.type = MIPI_DSI_DCS_READ,
|
||||
.tx_buf = &cmd,
|
||||
.tx_len = 1,
|
||||
.rx_buf = data,
|
||||
.rx_len = len
|
||||
};
|
||||
|
||||
return mipi_dsi_device_transfer(dsi, &msg);
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_read);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_nop() - send DCS nop packet
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_nop(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_NOP, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_nop);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_soft_reset() - perform a software reset of the display module
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_soft_reset(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SOFT_RESET, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_soft_reset);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_get_power_mode() - query the display module's current power
|
||||
* mode
|
||||
* @dsi: DSI peripheral device
|
||||
* @mode: return location for the current power mode
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_get_power_mode(struct mipi_dsi_device *dsi, u8 *mode)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_POWER_MODE, mode,
|
||||
sizeof(*mode));
|
||||
if (err <= 0) {
|
||||
if (err == 0)
|
||||
err = -ENODATA;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_get_power_mode);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_get_pixel_format() - gets the pixel format for the RGB image
|
||||
* data used by the interface
|
||||
* @dsi: DSI peripheral device
|
||||
* @format: return location for the pixel format
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_get_pixel_format(struct mipi_dsi_device *dsi, u8 *format)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_PIXEL_FORMAT, format,
|
||||
sizeof(*format));
|
||||
if (err <= 0) {
|
||||
if (err == 0)
|
||||
err = -ENODATA;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_get_pixel_format);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_enter_sleep_mode() - disable all unnecessary blocks inside the
|
||||
* display module except interface communication
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_enter_sleep_mode(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_ENTER_SLEEP_MODE, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_enter_sleep_mode);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_exit_sleep_mode() - enable all blocks inside the display
|
||||
* module
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_exit_sleep_mode);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_display_off() - stop displaying the image data on the
|
||||
* display device
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_display_off(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_display_off);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_display_on() - start displaying the image data on the
|
||||
* display device
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_display_on);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_column_address() - define the column extent of the frame
|
||||
* memory accessed by the host processor
|
||||
* @dsi: DSI peripheral device
|
||||
* @start: first column of frame memory
|
||||
* @end: last column of frame memory
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start,
|
||||
u16 end)
|
||||
{
|
||||
u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_COLUMN_ADDRESS, payload,
|
||||
sizeof(payload));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_column_address);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_page_address() - define the page extent of the frame
|
||||
* memory accessed by the host processor
|
||||
* @dsi: DSI peripheral device
|
||||
* @start: first page of frame memory
|
||||
* @end: last page of frame memory
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start,
|
||||
u16 end)
|
||||
{
|
||||
u8 payload[4] = { start >> 8, start & 0xff, end >> 8, end & 0xff };
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PAGE_ADDRESS, payload,
|
||||
sizeof(payload));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_page_address);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_tear_off() - turn off the display module's Tearing Effect
|
||||
* output signal on the TE signal line
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_tear_off(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_OFF, NULL, 0);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_off);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_tear_on() - turn on the display module's Tearing Effect
|
||||
* output signal on the TE signal line.
|
||||
* @dsi: DSI peripheral device
|
||||
* @mode: the Tearing Effect Output Line mode
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi,
|
||||
enum mipi_dsi_dcs_tear_mode mode)
|
||||
{
|
||||
u8 value = mode;
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_TEAR_ON, &value,
|
||||
sizeof(value));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_on);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_pixel_format() - sets the pixel format for the RGB image
|
||||
* data used by the interface
|
||||
* @dsi: DSI peripheral device
|
||||
* @format: pixel format
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_PIXEL_FORMAT, &format,
|
||||
sizeof(format));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_pixel_format);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_tear_scanline() - set the scanline to use as trigger for
|
||||
* the Tearing Effect output signal of the display module
|
||||
* @dsi: DSI peripheral device
|
||||
* @scanline: scanline to use as trigger
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_tear_scanline(struct mipi_dsi_device *dsi, u16 scanline)
|
||||
{
|
||||
u8 payload[3] = { MIPI_DCS_SET_TEAR_SCANLINE, scanline >> 8,
|
||||
scanline & 0xff };
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_generic_write(dsi, payload, sizeof(payload));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_scanline);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_display_brightness() - sets the brightness value of the
|
||||
* display
|
||||
* @dsi: DSI peripheral device
|
||||
* @brightness: brightness value
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_display_brightness(struct mipi_dsi_device *dsi,
|
||||
u16 brightness)
|
||||
{
|
||||
u8 payload[2] = { brightness & 0xff, brightness >> 8 };
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
|
||||
payload, sizeof(payload));
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_set_display_brightness);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_get_display_brightness() - gets the current brightness value
|
||||
* of the display
|
||||
* @dsi: DSI peripheral device
|
||||
* @brightness: brightness value
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_get_display_brightness(struct mipi_dsi_device *dsi,
|
||||
u16 *brightness)
|
||||
{
|
||||
ssize_t err;
|
||||
|
||||
err = mipi_dsi_dcs_read(dsi, MIPI_DCS_GET_DISPLAY_BRIGHTNESS,
|
||||
brightness, sizeof(*brightness));
|
||||
if (err <= 0) {
|
||||
if (err == 0)
|
||||
err = -ENODATA;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(mipi_dsi_dcs_get_display_brightness);
|
379
drivers/video/orisetech_otm8009a.c
Normal file
379
drivers/video/orisetech_otm8009a.c
Normal file
@ -0,0 +1,379 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
* Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
|
||||
*
|
||||
* This otm8009a panel driver is inspired from the Linux Kernel driver
|
||||
* drivers/gpu/drm/panel/panel-orisetech-otm8009a.c.
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <backlight.h>
|
||||
#include <dm.h>
|
||||
#include <mipi_dsi.h>
|
||||
#include <panel.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <power/regulator.h>
|
||||
|
||||
#define OTM8009A_BACKLIGHT_DEFAULT 240
|
||||
#define OTM8009A_BACKLIGHT_MAX 255
|
||||
|
||||
/* Manufacturer Command Set */
|
||||
#define MCS_ADRSFT 0x0000 /* Address Shift Function */
|
||||
#define MCS_PANSET 0xB3A6 /* Panel Type Setting */
|
||||
#define MCS_SD_CTRL 0xC0A2 /* Source Driver Timing Setting */
|
||||
#define MCS_P_DRV_M 0xC0B4 /* Panel Driving Mode */
|
||||
#define MCS_OSC_ADJ 0xC181 /* Oscillator Adjustment for Idle/Normal mode */
|
||||
#define MCS_RGB_VID_SET 0xC1A1 /* RGB Video Mode Setting */
|
||||
#define MCS_SD_PCH_CTRL 0xC480 /* Source Driver Precharge Control */
|
||||
#define MCS_NO_DOC1 0xC48A /* Command not documented */
|
||||
#define MCS_PWR_CTRL1 0xC580 /* Power Control Setting 1 */
|
||||
#define MCS_PWR_CTRL2 0xC590 /* Power Control Setting 2 for Normal Mode */
|
||||
#define MCS_PWR_CTRL4 0xC5B0 /* Power Control Setting 4 for DC Voltage */
|
||||
#define MCS_PANCTRLSET1 0xCB80 /* Panel Control Setting 1 */
|
||||
#define MCS_PANCTRLSET2 0xCB90 /* Panel Control Setting 2 */
|
||||
#define MCS_PANCTRLSET3 0xCBA0 /* Panel Control Setting 3 */
|
||||
#define MCS_PANCTRLSET4 0xCBB0 /* Panel Control Setting 4 */
|
||||
#define MCS_PANCTRLSET5 0xCBC0 /* Panel Control Setting 5 */
|
||||
#define MCS_PANCTRLSET6 0xCBD0 /* Panel Control Setting 6 */
|
||||
#define MCS_PANCTRLSET7 0xCBE0 /* Panel Control Setting 7 */
|
||||
#define MCS_PANCTRLSET8 0xCBF0 /* Panel Control Setting 8 */
|
||||
#define MCS_PANU2D1 0xCC80 /* Panel U2D Setting 1 */
|
||||
#define MCS_PANU2D2 0xCC90 /* Panel U2D Setting 2 */
|
||||
#define MCS_PANU2D3 0xCCA0 /* Panel U2D Setting 3 */
|
||||
#define MCS_PAND2U1 0xCCB0 /* Panel D2U Setting 1 */
|
||||
#define MCS_PAND2U2 0xCCC0 /* Panel D2U Setting 2 */
|
||||
#define MCS_PAND2U3 0xCCD0 /* Panel D2U Setting 3 */
|
||||
#define MCS_GOAVST 0xCE80 /* GOA VST Setting */
|
||||
#define MCS_GOACLKA1 0xCEA0 /* GOA CLKA1 Setting */
|
||||
#define MCS_GOACLKA3 0xCEB0 /* GOA CLKA3 Setting */
|
||||
#define MCS_GOAECLK 0xCFC0 /* GOA ECLK Setting */
|
||||
#define MCS_NO_DOC2 0xCFD0 /* Command not documented */
|
||||
#define MCS_GVDDSET 0xD800 /* GVDD/NGVDD */
|
||||
#define MCS_VCOMDC 0xD900 /* VCOM Voltage Setting */
|
||||
#define MCS_GMCT2_2P 0xE100 /* Gamma Correction 2.2+ Setting */
|
||||
#define MCS_GMCT2_2N 0xE200 /* Gamma Correction 2.2- Setting */
|
||||
#define MCS_NO_DOC3 0xF5B6 /* Command not documented */
|
||||
#define MCS_CMD2_ENA1 0xFF00 /* Enable Access Command2 "CMD2" */
|
||||
#define MCS_CMD2_ENA2 0xFF80 /* Enable Access Orise Command2 */
|
||||
|
||||
struct otm8009a_panel_priv {
|
||||
struct udevice *reg;
|
||||
struct gpio_desc reset;
|
||||
unsigned int lanes;
|
||||
enum mipi_dsi_pixel_format format;
|
||||
unsigned long mode_flags;
|
||||
};
|
||||
|
||||
static const struct display_timing default_timing = {
|
||||
.pixelclock.typ = 29700000,
|
||||
.hactive.typ = 480,
|
||||
.hfront_porch.typ = 98,
|
||||
.hback_porch.typ = 98,
|
||||
.hsync_len.typ = 32,
|
||||
.vactive.typ = 800,
|
||||
.vfront_porch.typ = 15,
|
||||
.vback_porch.typ = 14,
|
||||
.vsync_len.typ = 10,
|
||||
};
|
||||
|
||||
static void otm8009a_dcs_write_buf(struct udevice *dev, const void *data,
|
||||
size_t len)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
|
||||
if (mipi_dsi_dcs_write_buffer(device, data, len) < 0)
|
||||
dev_err(dev, "mipi dsi dcs write buffer failed\n");
|
||||
}
|
||||
|
||||
static void otm8009a_dcs_write_buf_hs(struct udevice *dev, const void *data,
|
||||
size_t len)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
|
||||
/* data will be sent in dsi hs mode (ie. no lpm) */
|
||||
device->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
|
||||
if (mipi_dsi_dcs_write_buffer(device, data, len) < 0)
|
||||
dev_err(dev, "mipi dsi dcs write buffer failed\n");
|
||||
|
||||
/* restore back the dsi lpm mode */
|
||||
device->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
}
|
||||
|
||||
#define dcs_write_seq(dev, seq...) \
|
||||
({ \
|
||||
static const u8 d[] = { seq }; \
|
||||
otm8009a_dcs_write_buf(dev, d, ARRAY_SIZE(d)); \
|
||||
})
|
||||
|
||||
#define dcs_write_seq_hs(dev, seq...) \
|
||||
({ \
|
||||
static const u8 d[] = { seq }; \
|
||||
otm8009a_dcs_write_buf_hs(dev, d, ARRAY_SIZE(d)); \
|
||||
})
|
||||
|
||||
#define dcs_write_cmd_at(dev, cmd, seq...) \
|
||||
({ \
|
||||
static const u16 c = cmd; \
|
||||
struct udevice *device = dev; \
|
||||
dcs_write_seq(device, MCS_ADRSFT, (c) & 0xFF); \
|
||||
dcs_write_seq(device, (c) >> 8, seq); \
|
||||
})
|
||||
|
||||
static int otm8009a_init_sequence(struct udevice *dev)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
int ret;
|
||||
|
||||
/* Enter CMD2 */
|
||||
dcs_write_cmd_at(dev, MCS_CMD2_ENA1, 0x80, 0x09, 0x01);
|
||||
|
||||
/* Enter Orise Command2 */
|
||||
dcs_write_cmd_at(dev, MCS_CMD2_ENA2, 0x80, 0x09);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_SD_PCH_CTRL, 0x30);
|
||||
mdelay(10);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_NO_DOC1, 0x40);
|
||||
mdelay(10);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL4 + 1, 0xA9);
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 1, 0x34);
|
||||
dcs_write_cmd_at(dev, MCS_P_DRV_M, 0x50);
|
||||
dcs_write_cmd_at(dev, MCS_VCOMDC, 0x4E);
|
||||
dcs_write_cmd_at(dev, MCS_OSC_ADJ, 0x66); /* 65Hz */
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 2, 0x01);
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 5, 0x34);
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL2 + 4, 0x33);
|
||||
dcs_write_cmd_at(dev, MCS_GVDDSET, 0x79, 0x79);
|
||||
dcs_write_cmd_at(dev, MCS_SD_CTRL + 1, 0x1B);
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL1 + 2, 0x83);
|
||||
dcs_write_cmd_at(dev, MCS_SD_PCH_CTRL + 1, 0x83);
|
||||
dcs_write_cmd_at(dev, MCS_RGB_VID_SET, 0x0E);
|
||||
dcs_write_cmd_at(dev, MCS_PANSET, 0x00, 0x01);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_GOAVST, 0x85, 0x01, 0x00, 0x84, 0x01, 0x00);
|
||||
dcs_write_cmd_at(dev, MCS_GOACLKA1, 0x18, 0x04, 0x03, 0x39, 0x00, 0x00,
|
||||
0x00, 0x18, 0x03, 0x03, 0x3A, 0x00, 0x00, 0x00);
|
||||
dcs_write_cmd_at(dev, MCS_GOACLKA3, 0x18, 0x02, 0x03, 0x3B, 0x00, 0x00,
|
||||
0x00, 0x18, 0x01, 0x03, 0x3C, 0x00, 0x00, 0x00);
|
||||
dcs_write_cmd_at(dev, MCS_GOAECLK, 0x01, 0x01, 0x20, 0x20, 0x00, 0x00,
|
||||
0x01, 0x02, 0x00, 0x00);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_NO_DOC2, 0x00);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET5, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4,
|
||||
4, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
dcs_write_cmd_at(dev, MCS_PANCTRLSET8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_PANU2D1, 0x00, 0x26, 0x09, 0x0B, 0x01, 0x25,
|
||||
0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_cmd_at(dev, MCS_PANU2D2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x0A, 0x0C, 0x02);
|
||||
dcs_write_cmd_at(dev, MCS_PANU2D3, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_cmd_at(dev, MCS_PAND2U1, 0x00, 0x25, 0x0C, 0x0A, 0x02, 0x26,
|
||||
0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_cmd_at(dev, MCS_PAND2U2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x0B, 0x09, 0x01);
|
||||
dcs_write_cmd_at(dev, MCS_PAND2U3, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_PWR_CTRL1 + 1, 0x66);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_NO_DOC3, 0x06);
|
||||
|
||||
dcs_write_cmd_at(dev, MCS_GMCT2_2P, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
|
||||
0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
|
||||
0x01);
|
||||
dcs_write_cmd_at(dev, MCS_GMCT2_2N, 0x00, 0x09, 0x0F, 0x0E, 0x07, 0x10,
|
||||
0x0B, 0x0A, 0x04, 0x07, 0x0B, 0x08, 0x0F, 0x10, 0x0A,
|
||||
0x01);
|
||||
|
||||
/* Exit CMD2 */
|
||||
dcs_write_cmd_at(dev, MCS_CMD2_ENA1, 0xFF, 0xFF, 0xFF);
|
||||
|
||||
ret = mipi_dsi_dcs_nop(device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mipi_dsi_dcs_exit_sleep_mode(device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Wait for sleep out exit */
|
||||
mdelay(120);
|
||||
|
||||
/* Default portrait 480x800 rgb24 */
|
||||
dcs_write_seq(dev, MIPI_DCS_SET_ADDRESS_MODE, 0x00);
|
||||
|
||||
ret = mipi_dsi_dcs_set_column_address(device, 0,
|
||||
default_timing.hactive.typ - 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mipi_dsi_dcs_set_page_address(device, 0,
|
||||
default_timing.vactive.typ - 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* See otm8009a driver documentation for pixel format descriptions */
|
||||
ret = mipi_dsi_dcs_set_pixel_format(device, MIPI_DCS_PIXEL_FMT_24BIT |
|
||||
MIPI_DCS_PIXEL_FMT_24BIT << 4);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Disable CABC feature */
|
||||
dcs_write_seq(dev, MIPI_DCS_WRITE_POWER_SAVE, 0x00);
|
||||
|
||||
ret = mipi_dsi_dcs_set_display_on(device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mipi_dsi_dcs_nop(device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Send Command GRAM memory write (no parameters) */
|
||||
dcs_write_seq(dev, MIPI_DCS_WRITE_MEMORY_START);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int otm8009a_panel_enable_backlight(struct udevice *dev)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
int ret;
|
||||
|
||||
ret = mipi_dsi_attach(device);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = otm8009a_init_sequence(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Power on the backlight with the requested brightness
|
||||
* Note We can not use mipi_dsi_dcs_set_display_brightness()
|
||||
* as otm8009a driver support only 8-bit brightness (1 param).
|
||||
*/
|
||||
dcs_write_seq(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
|
||||
OTM8009A_BACKLIGHT_DEFAULT);
|
||||
|
||||
/* Update Brightness Control & Backlight */
|
||||
dcs_write_seq(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24);
|
||||
|
||||
/* Update Brightness Control & Backlight */
|
||||
dcs_write_seq_hs(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY);
|
||||
|
||||
/* Need to wait a few time before sending the first image */
|
||||
mdelay(10);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int otm8009a_panel_get_display_timing(struct udevice *dev,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
struct otm8009a_panel_priv *priv = dev_get_priv(dev);
|
||||
|
||||
memcpy(timings, &default_timing, sizeof(*timings));
|
||||
|
||||
/* fill characteristics of DSI data link */
|
||||
device->lanes = priv->lanes;
|
||||
device->format = priv->format;
|
||||
device->mode_flags = priv->mode_flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int otm8009a_panel_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct otm8009a_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
|
||||
ret = device_get_supply_regulator(dev, "power-supply",
|
||||
&priv->reg);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "Warning: cannot get power supply\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = gpio_request_by_name(dev, "reset-gpios", 0, &priv->reset,
|
||||
GPIOD_IS_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "warning: cannot get reset GPIO\n");
|
||||
if (ret != -ENOENT)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int otm8009a_panel_probe(struct udevice *dev)
|
||||
{
|
||||
struct otm8009a_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) {
|
||||
dev_dbg(dev, "enable regulator '%s'\n", priv->reg->name);
|
||||
ret = regulator_set_enable(priv->reg, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* reset panel */
|
||||
dm_gpio_set_value(&priv->reset, true);
|
||||
mdelay(1); /* >50us */
|
||||
dm_gpio_set_value(&priv->reset, false);
|
||||
mdelay(10); /* >5ms */
|
||||
|
||||
priv->lanes = 2;
|
||||
priv->format = MIPI_DSI_FMT_RGB888;
|
||||
priv->mode_flags = MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_MODE_VIDEO_BURST |
|
||||
MIPI_DSI_MODE_LPM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct panel_ops otm8009a_panel_ops = {
|
||||
.enable_backlight = otm8009a_panel_enable_backlight,
|
||||
.get_display_timing = otm8009a_panel_get_display_timing,
|
||||
};
|
||||
|
||||
static const struct udevice_id otm8009a_panel_ids[] = {
|
||||
{ .compatible = "orisetech,otm8009a" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(otm8009a_panel) = {
|
||||
.name = "otm8009a_panel",
|
||||
.id = UCLASS_PANEL,
|
||||
.of_match = otm8009a_panel_ids,
|
||||
.ops = &otm8009a_panel_ops,
|
||||
.ofdata_to_platdata = otm8009a_panel_ofdata_to_platdata,
|
||||
.probe = otm8009a_panel_probe,
|
||||
.platdata_auto_alloc_size = sizeof(struct mipi_dsi_panel_plat),
|
||||
.priv_auto_alloc_size = sizeof(struct otm8009a_panel_priv),
|
||||
};
|
351
drivers/video/raydium-rm68200.c
Normal file
351
drivers/video/raydium-rm68200.c
Normal file
@ -0,0 +1,351 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
* Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
|
||||
*
|
||||
* This rm68200 panel driver is inspired from the Linux Kernel driver
|
||||
* drivers/gpu/drm/panel/panel-raydium-rm68200.c.
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <backlight.h>
|
||||
#include <dm.h>
|
||||
#include <mipi_dsi.h>
|
||||
#include <panel.h>
|
||||
#include <asm/gpio.h>
|
||||
#include <power/regulator.h>
|
||||
|
||||
/*** Manufacturer Command Set ***/
|
||||
#define MCS_CMD_MODE_SW 0xFE /* CMD Mode Switch */
|
||||
#define MCS_CMD1_UCS 0x00 /* User Command Set (UCS = CMD1) */
|
||||
#define MCS_CMD2_P0 0x01 /* Manufacture Command Set Page0 (CMD2 P0) */
|
||||
#define MCS_CMD2_P1 0x02 /* Manufacture Command Set Page1 (CMD2 P1) */
|
||||
#define MCS_CMD2_P2 0x03 /* Manufacture Command Set Page2 (CMD2 P2) */
|
||||
#define MCS_CMD2_P3 0x04 /* Manufacture Command Set Page3 (CMD2 P3) */
|
||||
|
||||
/* CMD2 P0 commands (Display Options and Power) */
|
||||
#define MCS_STBCTR 0x12 /* TE1 Output Setting Zig-Zag Connection */
|
||||
#define MCS_SGOPCTR 0x16 /* Source Bias Current */
|
||||
#define MCS_SDCTR 0x1A /* Source Output Delay Time */
|
||||
#define MCS_INVCTR 0x1B /* Inversion Type */
|
||||
#define MCS_EXT_PWR_IC 0x24 /* External PWR IC Control */
|
||||
#define MCS_SETAVDD 0x27 /* PFM Control for AVDD Output */
|
||||
#define MCS_SETAVEE 0x29 /* PFM Control for AVEE Output */
|
||||
#define MCS_BT2CTR 0x2B /* DDVDL Charge Pump Control */
|
||||
#define MCS_BT3CTR 0x2F /* VGH Charge Pump Control */
|
||||
#define MCS_BT4CTR 0x34 /* VGL Charge Pump Control */
|
||||
#define MCS_VCMCTR 0x46 /* VCOM Output Level Control */
|
||||
#define MCS_SETVGN 0x52 /* VG M/S N Control */
|
||||
#define MCS_SETVGP 0x54 /* VG M/S P Control */
|
||||
#define MCS_SW_CTRL 0x5F /* Interface Control for PFM and MIPI */
|
||||
|
||||
/* CMD2 P2 commands (GOA Timing Control) - no description in datasheet */
|
||||
#define GOA_VSTV1 0x00
|
||||
#define GOA_VSTV2 0x07
|
||||
#define GOA_VCLK1 0x0E
|
||||
#define GOA_VCLK2 0x17
|
||||
#define GOA_VCLK_OPT1 0x20
|
||||
#define GOA_BICLK1 0x2A
|
||||
#define GOA_BICLK2 0x37
|
||||
#define GOA_BICLK3 0x44
|
||||
#define GOA_BICLK4 0x4F
|
||||
#define GOA_BICLK_OPT1 0x5B
|
||||
#define GOA_BICLK_OPT2 0x60
|
||||
#define MCS_GOA_GPO1 0x6D
|
||||
#define MCS_GOA_GPO2 0x71
|
||||
#define MCS_GOA_EQ 0x74
|
||||
#define MCS_GOA_CLK_GALLON 0x7C
|
||||
#define MCS_GOA_FS_SEL0 0x7E
|
||||
#define MCS_GOA_FS_SEL1 0x87
|
||||
#define MCS_GOA_FS_SEL2 0x91
|
||||
#define MCS_GOA_FS_SEL3 0x9B
|
||||
#define MCS_GOA_BS_SEL0 0xAC
|
||||
#define MCS_GOA_BS_SEL1 0xB5
|
||||
#define MCS_GOA_BS_SEL2 0xBF
|
||||
#define MCS_GOA_BS_SEL3 0xC9
|
||||
#define MCS_GOA_BS_SEL4 0xD3
|
||||
|
||||
/* CMD2 P3 commands (Gamma) */
|
||||
#define MCS_GAMMA_VP 0x60 /* Gamma VP1~VP16 */
|
||||
#define MCS_GAMMA_VN 0x70 /* Gamma VN1~VN16 */
|
||||
|
||||
struct rm68200_panel_priv {
|
||||
struct udevice *reg;
|
||||
struct udevice *backlight;
|
||||
struct gpio_desc reset;
|
||||
unsigned int lanes;
|
||||
enum mipi_dsi_pixel_format format;
|
||||
unsigned long mode_flags;
|
||||
};
|
||||
|
||||
static const struct display_timing default_timing = {
|
||||
.pixelclock.typ = 54000000,
|
||||
.hactive.typ = 720,
|
||||
.hfront_porch.typ = 48,
|
||||
.hback_porch.typ = 48,
|
||||
.hsync_len.typ = 9,
|
||||
.vactive.typ = 1280,
|
||||
.vfront_porch.typ = 12,
|
||||
.vback_porch.typ = 12,
|
||||
.vsync_len.typ = 5,
|
||||
};
|
||||
|
||||
static void rm68200_dcs_write_buf(struct udevice *dev, const void *data,
|
||||
size_t len)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
int err;
|
||||
|
||||
err = mipi_dsi_dcs_write_buffer(device, data, len);
|
||||
if (err < 0)
|
||||
dev_err(dev, "MIPI DSI DCS write buffer failed: %d\n", err);
|
||||
}
|
||||
|
||||
static void rm68200_dcs_write_cmd(struct udevice *dev, u8 cmd, u8 value)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
int err;
|
||||
|
||||
err = mipi_dsi_dcs_write(device, cmd, &value, 1);
|
||||
if (err < 0)
|
||||
dev_err(dev, "MIPI DSI DCS write failed: %d\n", err);
|
||||
}
|
||||
|
||||
#define dcs_write_seq(ctx, seq...) \
|
||||
({ \
|
||||
static const u8 d[] = { seq }; \
|
||||
\
|
||||
rm68200_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
|
||||
})
|
||||
|
||||
/*
|
||||
* This panel is not able to auto-increment all cmd addresses so for some of
|
||||
* them, we need to send them one by one...
|
||||
*/
|
||||
#define dcs_write_cmd_seq(ctx, cmd, seq...) \
|
||||
({ \
|
||||
static const u8 d[] = { seq }; \
|
||||
unsigned int i; \
|
||||
\
|
||||
for (i = 0; i < ARRAY_SIZE(d) ; i++) \
|
||||
rm68200_dcs_write_cmd(ctx, cmd + i, d[i]); \
|
||||
})
|
||||
|
||||
static void rm68200_init_sequence(struct udevice *dev)
|
||||
{
|
||||
/* Enter CMD2 with page 0 */
|
||||
dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD2_P0);
|
||||
dcs_write_cmd_seq(dev, MCS_EXT_PWR_IC, 0xC0, 0x53, 0x00);
|
||||
dcs_write_seq(dev, MCS_BT2CTR, 0xE5);
|
||||
dcs_write_seq(dev, MCS_SETAVDD, 0x0A);
|
||||
dcs_write_seq(dev, MCS_SETAVEE, 0x0A);
|
||||
dcs_write_seq(dev, MCS_SGOPCTR, 0x52);
|
||||
dcs_write_seq(dev, MCS_BT3CTR, 0x53);
|
||||
dcs_write_seq(dev, MCS_BT4CTR, 0x5A);
|
||||
dcs_write_seq(dev, MCS_INVCTR, 0x00);
|
||||
dcs_write_seq(dev, MCS_STBCTR, 0x0A);
|
||||
dcs_write_seq(dev, MCS_SDCTR, 0x06);
|
||||
dcs_write_seq(dev, MCS_VCMCTR, 0x56);
|
||||
dcs_write_seq(dev, MCS_SETVGN, 0xA0, 0x00);
|
||||
dcs_write_seq(dev, MCS_SETVGP, 0xA0, 0x00);
|
||||
dcs_write_seq(dev, MCS_SW_CTRL, 0x11); /* 2 data lanes, see doc */
|
||||
|
||||
dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD2_P2);
|
||||
dcs_write_seq(dev, GOA_VSTV1, 0x05);
|
||||
dcs_write_seq(dev, 0x02, 0x0B);
|
||||
dcs_write_seq(dev, 0x03, 0x0F);
|
||||
dcs_write_seq(dev, 0x04, 0x7D, 0x00, 0x50);
|
||||
dcs_write_cmd_seq(dev, GOA_VSTV2, 0x05, 0x16, 0x0D, 0x11, 0x7D, 0x00,
|
||||
0x50);
|
||||
dcs_write_cmd_seq(dev, GOA_VCLK1, 0x07, 0x08, 0x01, 0x02, 0x00, 0x7D,
|
||||
0x00, 0x85, 0x08);
|
||||
dcs_write_cmd_seq(dev, GOA_VCLK2, 0x03, 0x04, 0x05, 0x06, 0x00, 0x7D,
|
||||
0x00, 0x85, 0x08);
|
||||
dcs_write_seq(dev, GOA_VCLK_OPT1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_cmd_seq(dev, GOA_BICLK1, 0x07, 0x08);
|
||||
dcs_write_seq(dev, 0x2D, 0x01);
|
||||
dcs_write_seq(dev, 0x2F, 0x02, 0x00, 0x40, 0x05, 0x08, 0x54, 0x7D,
|
||||
0x00);
|
||||
dcs_write_cmd_seq(dev, GOA_BICLK2, 0x03, 0x04, 0x05, 0x06, 0x00);
|
||||
dcs_write_seq(dev, 0x3D, 0x40);
|
||||
dcs_write_seq(dev, 0x3F, 0x05, 0x08, 0x54, 0x7D, 0x00);
|
||||
dcs_write_seq(dev, GOA_BICLK3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_seq(dev, GOA_BICLK4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00);
|
||||
dcs_write_seq(dev, 0x58, 0x00, 0x00, 0x00);
|
||||
dcs_write_seq(dev, GOA_BICLK_OPT1, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_seq(dev, GOA_BICLK_OPT2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_seq(dev, MCS_GOA_GPO1, 0x00, 0x00, 0x00, 0x00);
|
||||
dcs_write_seq(dev, MCS_GOA_GPO2, 0x00, 0x20, 0x00);
|
||||
dcs_write_seq(dev, MCS_GOA_EQ, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
|
||||
0x00, 0x00);
|
||||
dcs_write_seq(dev, MCS_GOA_CLK_GALLON, 0x00, 0x00);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL0, 0xBF, 0x02, 0x06, 0x14, 0x10,
|
||||
0x16, 0x12, 0x08, 0x3F);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL1, 0x3F, 0x3F, 0x3F, 0x3F, 0x0C,
|
||||
0x0A, 0x0E, 0x3F, 0x3F, 0x00);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL2, 0x04, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||
0x05, 0x01, 0x3F, 0x3F, 0x0F);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_FS_SEL3, 0x0B, 0x0D, 0x3F, 0x3F, 0x3F,
|
||||
0x3F);
|
||||
dcs_write_cmd_seq(dev, 0xA2, 0x3F, 0x09, 0x13, 0x17, 0x11, 0x15);
|
||||
dcs_write_cmd_seq(dev, 0xA9, 0x07, 0x03, 0x3F);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL0, 0x3F, 0x05, 0x01, 0x17, 0x13,
|
||||
0x15, 0x11, 0x0F, 0x3F);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL1, 0x3F, 0x3F, 0x3F, 0x3F, 0x0B,
|
||||
0x0D, 0x09, 0x3F, 0x3F, 0x07);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL2, 0x03, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||
0x02, 0x06, 0x3F, 0x3F, 0x08);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL3, 0x0C, 0x0A, 0x3F, 0x3F, 0x3F,
|
||||
0x3F, 0x3F, 0x0E, 0x10, 0x14);
|
||||
dcs_write_cmd_seq(dev, MCS_GOA_BS_SEL4, 0x12, 0x16, 0x00, 0x04, 0x3F);
|
||||
dcs_write_seq(dev, 0xDC, 0x02);
|
||||
dcs_write_seq(dev, 0xDE, 0x12);
|
||||
|
||||
dcs_write_seq(dev, MCS_CMD_MODE_SW, 0x0E); /* No documentation */
|
||||
dcs_write_seq(dev, 0x01, 0x75);
|
||||
|
||||
dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD2_P3);
|
||||
dcs_write_cmd_seq(dev, MCS_GAMMA_VP, 0x00, 0x0C, 0x12, 0x0E, 0x06,
|
||||
0x12, 0x0E, 0x0B, 0x15, 0x0B, 0x10, 0x07, 0x0F,
|
||||
0x12, 0x0C, 0x00);
|
||||
dcs_write_cmd_seq(dev, MCS_GAMMA_VN, 0x00, 0x0C, 0x12, 0x0E, 0x06,
|
||||
0x12, 0x0E, 0x0B, 0x15, 0x0B, 0x10, 0x07, 0x0F,
|
||||
0x12, 0x0C, 0x00);
|
||||
|
||||
/* Exit CMD2 */
|
||||
dcs_write_seq(dev, MCS_CMD_MODE_SW, MCS_CMD1_UCS);
|
||||
}
|
||||
|
||||
static int rm68200_panel_enable_backlight(struct udevice *dev)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
struct rm68200_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = mipi_dsi_attach(device);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
rm68200_init_sequence(dev);
|
||||
|
||||
ret = mipi_dsi_dcs_exit_sleep_mode(device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mdelay(125);
|
||||
|
||||
ret = mipi_dsi_dcs_set_display_on(device);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mdelay(20);
|
||||
|
||||
ret = backlight_enable(priv->backlight);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rm68200_panel_get_display_timing(struct udevice *dev,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
struct mipi_dsi_panel_plat *plat = dev_get_platdata(dev);
|
||||
struct mipi_dsi_device *device = plat->device;
|
||||
struct rm68200_panel_priv *priv = dev_get_priv(dev);
|
||||
|
||||
memcpy(timings, &default_timing, sizeof(*timings));
|
||||
|
||||
/* fill characteristics of DSI data link */
|
||||
device->lanes = priv->lanes;
|
||||
device->format = priv->format;
|
||||
device->mode_flags = priv->mode_flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rm68200_panel_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct rm68200_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
|
||||
ret = device_get_supply_regulator(dev, "power-supply",
|
||||
&priv->reg);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "Warning: cannot get power supply\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = gpio_request_by_name(dev, "reset-gpios", 0, &priv->reset,
|
||||
GPIOD_IS_OUT);
|
||||
if (ret) {
|
||||
dev_err(dev, "Warning: cannot get reset GPIO\n");
|
||||
if (ret != -ENOENT)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uclass_get_device_by_phandle(UCLASS_PANEL_BACKLIGHT, dev,
|
||||
"backlight", &priv->backlight);
|
||||
if (ret) {
|
||||
dev_err(dev, "Cannot get backlight: ret=%d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rm68200_panel_probe(struct udevice *dev)
|
||||
{
|
||||
struct rm68200_panel_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
if (IS_ENABLED(CONFIG_DM_REGULATOR) && priv->reg) {
|
||||
ret = regulator_set_enable(priv->reg, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* reset panel */
|
||||
dm_gpio_set_value(&priv->reset, true);
|
||||
mdelay(1);
|
||||
dm_gpio_set_value(&priv->reset, false);
|
||||
mdelay(10);
|
||||
|
||||
priv->lanes = 2;
|
||||
priv->format = MIPI_DSI_FMT_RGB888;
|
||||
priv->mode_flags = MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_MODE_VIDEO_BURST |
|
||||
MIPI_DSI_MODE_LPM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct panel_ops rm68200_panel_ops = {
|
||||
.enable_backlight = rm68200_panel_enable_backlight,
|
||||
.get_display_timing = rm68200_panel_get_display_timing,
|
||||
};
|
||||
|
||||
static const struct udevice_id rm68200_panel_ids[] = {
|
||||
{ .compatible = "raydium,rm68200" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rm68200_panel) = {
|
||||
.name = "rm68200_panel",
|
||||
.id = UCLASS_PANEL,
|
||||
.of_match = rm68200_panel_ids,
|
||||
.ops = &rm68200_panel_ops,
|
||||
.ofdata_to_platdata = rm68200_panel_ofdata_to_platdata,
|
||||
.probe = rm68200_panel_probe,
|
||||
.platdata_auto_alloc_size = sizeof(struct mipi_dsi_panel_plat),
|
||||
.priv_auto_alloc_size = sizeof(struct rm68200_panel_priv),
|
||||
};
|
90
drivers/video/sandbox_dsi_host.c
Normal file
90
drivers/video/sandbox_dsi_host.c
Normal file
@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2019, STMicroelectronics - All Rights Reserved
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <display.h>
|
||||
#include <dm.h>
|
||||
#include <dsi_host.h>
|
||||
|
||||
/**
|
||||
* struct sandbox_dsi_host_priv - private data for driver
|
||||
* @device: DSI peripheral device
|
||||
* @timing: Display timings
|
||||
* @max_data_lanes: maximum number of data lines
|
||||
* @phy_ops: set of function pointers for performing physical operations
|
||||
*/
|
||||
struct sandbox_dsi_host_priv {
|
||||
struct mipi_dsi_device *device;
|
||||
struct display_timing *timings;
|
||||
unsigned int max_data_lanes;
|
||||
const struct mipi_dsi_phy_ops *phy_ops;
|
||||
};
|
||||
|
||||
static int sandbox_dsi_host_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 sandbox_dsi_host_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (!device)
|
||||
return -1;
|
||||
|
||||
if (!timings)
|
||||
return -2;
|
||||
|
||||
if (max_data_lanes == 0)
|
||||
return -3;
|
||||
|
||||
if (!phy_ops)
|
||||
return -4;
|
||||
|
||||
if (!phy_ops->init || !phy_ops->get_lane_mbps ||
|
||||
!phy_ops->post_set_mode)
|
||||
return -5;
|
||||
|
||||
priv->max_data_lanes = max_data_lanes;
|
||||
priv->phy_ops = phy_ops;
|
||||
priv->timings = timings;
|
||||
priv->device = device;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sandbox_dsi_host_enable(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_dsi_host_priv *priv = dev_get_priv(dev);
|
||||
unsigned int lane_mbps;
|
||||
int ret;
|
||||
|
||||
priv->phy_ops->init(priv->device);
|
||||
ret = priv->phy_ops->get_lane_mbps(priv->device, priv->timings, 2,
|
||||
MIPI_DSI_FMT_RGB888, &lane_mbps);
|
||||
if (ret)
|
||||
return -1;
|
||||
|
||||
priv->phy_ops->post_set_mode(priv->device, MIPI_DSI_MODE_VIDEO);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dsi_host_ops sandbox_dsi_host_ops = {
|
||||
.init = sandbox_dsi_host_init,
|
||||
.enable = sandbox_dsi_host_enable,
|
||||
};
|
||||
|
||||
static const struct udevice_id sandbox_dsi_host_ids[] = {
|
||||
{ .compatible = "sandbox,dsi-host"},
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(sandbox_dsi_host) = {
|
||||
.name = "sandbox-dsi-host",
|
||||
.id = UCLASS_DSI_HOST,
|
||||
.of_match = sandbox_dsi_host_ids,
|
||||
.ops = &sandbox_dsi_host_ops,
|
||||
.priv_auto_alloc_size = sizeof(struct sandbox_dsi_host_priv),
|
||||
};
|
@ -13,6 +13,15 @@ menuconfig VIDEO_STM32
|
||||
DSI. This option enables these supports which can be used on
|
||||
devices which have RGB TFT or DSI display connected.
|
||||
|
||||
config VIDEO_STM32_DSI
|
||||
bool "Enable STM32 DSI video support"
|
||||
depends on VIDEO_STM32
|
||||
select VIDEO_BRIDGE
|
||||
select VIDEO_DW_MIPI_DSI
|
||||
help
|
||||
This option enables support DSI internal bridge which can be used on
|
||||
devices which have DSI devices connected.
|
||||
|
||||
config VIDEO_STM32_MAX_XRES
|
||||
int "Maximum horizontal resolution (for memory allocation purposes)"
|
||||
depends on VIDEO_STM32
|
||||
|
@ -6,3 +6,4 @@
|
||||
# Yannick Fertre <yannick.fertre@st.com>
|
||||
|
||||
obj-${CONFIG_VIDEO_STM32} = stm32_ltdc.o
|
||||
obj-${CONFIG_VIDEO_STM32_DSI} += stm32_dsi.o
|
||||
|
490
drivers/video/stm32/stm32_dsi.c
Normal file
490
drivers/video/stm32/stm32_dsi.c
Normal file
@ -0,0 +1,490 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Philippe Cornu <philippe.cornu@st.com> for STMicroelectronics.
|
||||
* Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
*
|
||||
* This MIPI DSI controller driver is based on the Linux Kernel driver from
|
||||
* drivers/gpu/drm/stm/dw_mipi_dsi-stm.c.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <dsi_host.h>
|
||||
#include <mipi_dsi.h>
|
||||
#include <panel.h>
|
||||
#include <reset.h>
|
||||
#include <video.h>
|
||||
#include <video_bridge.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <power/regulator.h>
|
||||
|
||||
#define HWVER_130 0x31333000 /* IP version 1.30 */
|
||||
#define HWVER_131 0x31333100 /* IP version 1.31 */
|
||||
|
||||
/* DSI digital registers & bit definitions */
|
||||
#define DSI_VERSION 0x00
|
||||
#define VERSION GENMASK(31, 8)
|
||||
|
||||
/*
|
||||
* DSI wrapper registers & bit definitions
|
||||
* Note: registers are named as in the Reference Manual
|
||||
*/
|
||||
#define DSI_WCFGR 0x0400 /* Wrapper ConFiGuration Reg */
|
||||
#define WCFGR_DSIM BIT(0) /* DSI Mode */
|
||||
#define WCFGR_COLMUX GENMASK(3, 1) /* COLor MUltipleXing */
|
||||
|
||||
#define DSI_WCR 0x0404 /* Wrapper Control Reg */
|
||||
#define WCR_DSIEN BIT(3) /* DSI ENable */
|
||||
|
||||
#define DSI_WISR 0x040C /* Wrapper Interrupt and Status Reg */
|
||||
#define WISR_PLLLS BIT(8) /* PLL Lock Status */
|
||||
#define WISR_RRS BIT(12) /* Regulator Ready Status */
|
||||
|
||||
#define DSI_WPCR0 0x0418 /* Wrapper Phy Conf Reg 0 */
|
||||
#define WPCR0_UIX4 GENMASK(5, 0) /* Unit Interval X 4 */
|
||||
#define WPCR0_TDDL BIT(16) /* Turn Disable Data Lanes */
|
||||
|
||||
#define DSI_WRPCR 0x0430 /* Wrapper Regulator & Pll Ctrl Reg */
|
||||
#define WRPCR_PLLEN BIT(0) /* PLL ENable */
|
||||
#define WRPCR_NDIV GENMASK(8, 2) /* pll loop DIVision Factor */
|
||||
#define WRPCR_IDF GENMASK(14, 11) /* pll Input Division Factor */
|
||||
#define WRPCR_ODF GENMASK(17, 16) /* pll Output Division Factor */
|
||||
#define WRPCR_REGEN BIT(24) /* REGulator ENable */
|
||||
#define WRPCR_BGREN BIT(28) /* BandGap Reference ENable */
|
||||
#define IDF_MIN 1
|
||||
#define IDF_MAX 7
|
||||
#define NDIV_MIN 10
|
||||
#define NDIV_MAX 125
|
||||
#define ODF_MIN 1
|
||||
#define ODF_MAX 8
|
||||
|
||||
/* dsi color format coding according to the datasheet */
|
||||
enum dsi_color {
|
||||
DSI_RGB565_CONF1,
|
||||
DSI_RGB565_CONF2,
|
||||
DSI_RGB565_CONF3,
|
||||
DSI_RGB666_CONF1,
|
||||
DSI_RGB666_CONF2,
|
||||
DSI_RGB888,
|
||||
};
|
||||
|
||||
#define LANE_MIN_KBPS 31250
|
||||
#define LANE_MAX_KBPS 500000
|
||||
|
||||
/* Timeout for regulator on/off, pll lock/unlock & fifo empty */
|
||||
#define TIMEOUT_US 200000
|
||||
|
||||
struct stm32_dsi_priv {
|
||||
struct mipi_dsi_device device;
|
||||
void __iomem *base;
|
||||
struct udevice *panel;
|
||||
u32 pllref_clk;
|
||||
u32 hw_version;
|
||||
int lane_min_kbps;
|
||||
int lane_max_kbps;
|
||||
struct udevice *vdd_reg;
|
||||
struct udevice *dsi_host;
|
||||
};
|
||||
|
||||
static inline void dsi_write(struct stm32_dsi_priv *dsi, u32 reg, u32 val)
|
||||
{
|
||||
writel(val, dsi->base + reg);
|
||||
}
|
||||
|
||||
static inline u32 dsi_read(struct stm32_dsi_priv *dsi, u32 reg)
|
||||
{
|
||||
return readl(dsi->base + reg);
|
||||
}
|
||||
|
||||
static inline void dsi_set(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
|
||||
{
|
||||
dsi_write(dsi, reg, dsi_read(dsi, reg) | mask);
|
||||
}
|
||||
|
||||
static inline void dsi_clear(struct stm32_dsi_priv *dsi, u32 reg, u32 mask)
|
||||
{
|
||||
dsi_write(dsi, reg, dsi_read(dsi, reg) & ~mask);
|
||||
}
|
||||
|
||||
static inline void dsi_update_bits(struct stm32_dsi_priv *dsi, u32 reg,
|
||||
u32 mask, u32 val)
|
||||
{
|
||||
dsi_write(dsi, reg, (dsi_read(dsi, reg) & ~mask) | val);
|
||||
}
|
||||
|
||||
static enum dsi_color dsi_color_from_mipi(u32 fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
case MIPI_DSI_FMT_RGB888:
|
||||
return DSI_RGB888;
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
return DSI_RGB666_CONF2;
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
return DSI_RGB666_CONF1;
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
return DSI_RGB565_CONF1;
|
||||
default:
|
||||
pr_err("MIPI color invalid, so we use rgb888\n");
|
||||
}
|
||||
return DSI_RGB888;
|
||||
}
|
||||
|
||||
static int dsi_pll_get_clkout_khz(int clkin_khz, int idf, int ndiv, int odf)
|
||||
{
|
||||
int divisor = idf * odf;
|
||||
|
||||
/* prevent from division by 0 */
|
||||
if (!divisor)
|
||||
return 0;
|
||||
|
||||
return DIV_ROUND_CLOSEST(clkin_khz * ndiv, divisor);
|
||||
}
|
||||
|
||||
static int dsi_pll_get_params(struct stm32_dsi_priv *dsi,
|
||||
int clkin_khz, int clkout_khz,
|
||||
int *idf, int *ndiv, int *odf)
|
||||
{
|
||||
int i, o, n, n_min, n_max;
|
||||
int fvco_min, fvco_max, delta, best_delta; /* all in khz */
|
||||
|
||||
/* Early checks preventing division by 0 & odd results */
|
||||
if (clkin_khz <= 0 || clkout_khz <= 0)
|
||||
return -EINVAL;
|
||||
|
||||
fvco_min = dsi->lane_min_kbps * 2 * ODF_MAX;
|
||||
fvco_max = dsi->lane_max_kbps * 2 * ODF_MIN;
|
||||
|
||||
best_delta = 1000000; /* big started value (1000000khz) */
|
||||
|
||||
for (i = IDF_MIN; i <= IDF_MAX; i++) {
|
||||
/* Compute ndiv range according to Fvco */
|
||||
n_min = ((fvco_min * i) / (2 * clkin_khz)) + 1;
|
||||
n_max = (fvco_max * i) / (2 * clkin_khz);
|
||||
|
||||
/* No need to continue idf loop if we reach ndiv max */
|
||||
if (n_min >= NDIV_MAX)
|
||||
break;
|
||||
|
||||
/* Clamp ndiv to valid values */
|
||||
if (n_min < NDIV_MIN)
|
||||
n_min = NDIV_MIN;
|
||||
if (n_max > NDIV_MAX)
|
||||
n_max = NDIV_MAX;
|
||||
|
||||
for (o = ODF_MIN; o <= ODF_MAX; o *= 2) {
|
||||
n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz);
|
||||
/* Check ndiv according to vco range */
|
||||
if (n < n_min || n > n_max)
|
||||
continue;
|
||||
/* Check if new delta is better & saves parameters */
|
||||
delta = dsi_pll_get_clkout_khz(clkin_khz, i, n, o) -
|
||||
clkout_khz;
|
||||
if (delta < 0)
|
||||
delta = -delta;
|
||||
if (delta < best_delta) {
|
||||
*idf = i;
|
||||
*ndiv = n;
|
||||
*odf = o;
|
||||
best_delta = delta;
|
||||
}
|
||||
/* fast return in case of "perfect result" */
|
||||
if (!delta)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_phy_init(void *priv_data)
|
||||
{
|
||||
struct mipi_dsi_device *device = priv_data;
|
||||
struct udevice *dev = device->dev;
|
||||
struct stm32_dsi_priv *dsi = dev_get_priv(dev);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
debug("Initialize DSI physical layer\n");
|
||||
|
||||
/* Enable the regulator */
|
||||
dsi_set(dsi, DSI_WRPCR, WRPCR_REGEN | WRPCR_BGREN);
|
||||
ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_RRS,
|
||||
TIMEOUT_US);
|
||||
if (ret) {
|
||||
debug("!TIMEOUT! waiting REGU\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Enable the DSI PLL & wait for its lock */
|
||||
dsi_set(dsi, DSI_WRPCR, WRPCR_PLLEN);
|
||||
ret = readl_poll_timeout(dsi->base + DSI_WISR, val, val & WISR_PLLLS,
|
||||
TIMEOUT_US);
|
||||
if (ret) {
|
||||
debug("!TIMEOUT! waiting PLL\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_phy_post_set_mode(void *priv_data, unsigned long mode_flags)
|
||||
{
|
||||
struct mipi_dsi_device *device = priv_data;
|
||||
struct udevice *dev = device->dev;
|
||||
struct stm32_dsi_priv *dsi = dev_get_priv(dev);
|
||||
|
||||
debug("Set mode %p enable %ld\n", dsi,
|
||||
mode_flags & MIPI_DSI_MODE_VIDEO);
|
||||
|
||||
if (!dsi)
|
||||
return;
|
||||
|
||||
/*
|
||||
* DSI wrapper must be enabled in video mode & disabled in command mode.
|
||||
* If wrapper is enabled in command mode, the display controller
|
||||
* register access will hang.
|
||||
*/
|
||||
|
||||
if (mode_flags & MIPI_DSI_MODE_VIDEO)
|
||||
dsi_set(dsi, DSI_WCR, WCR_DSIEN);
|
||||
else
|
||||
dsi_clear(dsi, DSI_WCR, WCR_DSIEN);
|
||||
}
|
||||
|
||||
static int dsi_get_lane_mbps(void *priv_data, struct display_timing *timings,
|
||||
u32 lanes, u32 format, unsigned int *lane_mbps)
|
||||
{
|
||||
struct mipi_dsi_device *device = priv_data;
|
||||
struct udevice *dev = device->dev;
|
||||
struct stm32_dsi_priv *dsi = dev_get_priv(dev);
|
||||
int idf, ndiv, odf, pll_in_khz, pll_out_khz;
|
||||
int ret, bpp;
|
||||
u32 val;
|
||||
|
||||
/* Update lane capabilities according to hw version */
|
||||
dsi->hw_version = dsi_read(dsi, DSI_VERSION) & VERSION;
|
||||
dsi->lane_min_kbps = LANE_MIN_KBPS;
|
||||
dsi->lane_max_kbps = LANE_MAX_KBPS;
|
||||
if (dsi->hw_version == HWVER_131) {
|
||||
dsi->lane_min_kbps *= 2;
|
||||
dsi->lane_max_kbps *= 2;
|
||||
}
|
||||
|
||||
pll_in_khz = dsi->pllref_clk / 1000;
|
||||
|
||||
/* Compute requested pll out */
|
||||
bpp = mipi_dsi_pixel_format_to_bpp(format);
|
||||
pll_out_khz = (timings->pixelclock.typ / 1000) * bpp / lanes;
|
||||
/* Add 20% to pll out to be higher than pixel bw (burst mode only) */
|
||||
pll_out_khz = (pll_out_khz * 12) / 10;
|
||||
if (pll_out_khz > dsi->lane_max_kbps) {
|
||||
pll_out_khz = dsi->lane_max_kbps;
|
||||
dev_warn(dev, "Warning max phy mbps is used\n");
|
||||
}
|
||||
if (pll_out_khz < dsi->lane_min_kbps) {
|
||||
pll_out_khz = dsi->lane_min_kbps;
|
||||
dev_warn(dev, "Warning min phy mbps is used\n");
|
||||
}
|
||||
|
||||
/* Compute best pll parameters */
|
||||
idf = 0;
|
||||
ndiv = 0;
|
||||
odf = 0;
|
||||
ret = dsi_pll_get_params(dsi, pll_in_khz, pll_out_khz,
|
||||
&idf, &ndiv, &odf);
|
||||
if (ret) {
|
||||
dev_err(dev, "Warning dsi_pll_get_params(): bad params\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the adjusted pll out value */
|
||||
pll_out_khz = dsi_pll_get_clkout_khz(pll_in_khz, idf, ndiv, odf);
|
||||
|
||||
/* Set the PLL division factors */
|
||||
dsi_update_bits(dsi, DSI_WRPCR, WRPCR_NDIV | WRPCR_IDF | WRPCR_ODF,
|
||||
(ndiv << 2) | (idf << 11) | ((ffs(odf) - 1) << 16));
|
||||
|
||||
/* Compute uix4 & set the bit period in high-speed mode */
|
||||
val = 4000000 / pll_out_khz;
|
||||
dsi_update_bits(dsi, DSI_WPCR0, WPCR0_UIX4, val);
|
||||
|
||||
/* Select video mode by resetting DSIM bit */
|
||||
dsi_clear(dsi, DSI_WCFGR, WCFGR_DSIM);
|
||||
|
||||
/* Select the color coding */
|
||||
dsi_update_bits(dsi, DSI_WCFGR, WCFGR_COLMUX,
|
||||
dsi_color_from_mipi(format) << 1);
|
||||
|
||||
*lane_mbps = pll_out_khz / 1000;
|
||||
|
||||
debug("pll_in %ukHz pll_out %ukHz lane_mbps %uMHz\n",
|
||||
pll_in_khz, pll_out_khz, *lane_mbps);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mipi_dsi_phy_ops dsi_stm_phy_ops = {
|
||||
.init = dsi_phy_init,
|
||||
.get_lane_mbps = dsi_get_lane_mbps,
|
||||
.post_set_mode = dsi_phy_post_set_mode,
|
||||
};
|
||||
|
||||
static int stm32_dsi_attach(struct udevice *dev)
|
||||
{
|
||||
struct stm32_dsi_priv *priv = dev_get_priv(dev);
|
||||
struct mipi_dsi_device *device = &priv->device;
|
||||
struct mipi_dsi_panel_plat *mplat;
|
||||
struct display_timing timings;
|
||||
int ret;
|
||||
|
||||
ret = uclass_first_device(UCLASS_PANEL, &priv->panel);
|
||||
if (ret) {
|
||||
dev_err(dev, "panel device error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mplat = dev_get_platdata(priv->panel);
|
||||
mplat->device = &priv->device;
|
||||
|
||||
ret = panel_get_display_timing(priv->panel, &timings);
|
||||
if (ret) {
|
||||
ret = fdtdec_decode_display_timing(gd->fdt_blob,
|
||||
dev_of_offset(priv->panel),
|
||||
0, &timings);
|
||||
if (ret) {
|
||||
dev_err(dev, "decode display timing error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = uclass_get_device(UCLASS_DSI_HOST, 0, &priv->dsi_host);
|
||||
if (ret) {
|
||||
dev_err(dev, "No video dsi host detected %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dsi_host_init(priv->dsi_host, device, &timings, 2,
|
||||
&dsi_stm_phy_ops);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to initialize mipi dsi host\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_dsi_set_backlight(struct udevice *dev, int percent)
|
||||
{
|
||||
struct stm32_dsi_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = panel_enable_backlight(priv->panel);
|
||||
if (ret) {
|
||||
dev_err(dev, "panel %s enable backlight error %d\n",
|
||||
priv->panel->name, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = dsi_host_enable(priv->dsi_host);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to enable mipi dsi host\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_dsi_probe(struct udevice *dev)
|
||||
{
|
||||
struct stm32_dsi_priv *priv = dev_get_priv(dev);
|
||||
struct mipi_dsi_device *device = &priv->device;
|
||||
struct reset_ctl rst;
|
||||
struct clk clk;
|
||||
int ret;
|
||||
|
||||
device->dev = dev;
|
||||
|
||||
priv->base = (void *)dev_read_addr(dev);
|
||||
if ((fdt_addr_t)priv->base == FDT_ADDR_T_NONE) {
|
||||
dev_err(dev, "dsi dt register address error\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_DM_REGULATOR)) {
|
||||
ret = device_get_supply_regulator(dev, "phy-dsi-supply",
|
||||
&priv->vdd_reg);
|
||||
if (ret && ret != -ENOENT) {
|
||||
dev_err(dev, "Warning: cannot get phy dsi supply\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (ret != -ENOENT) {
|
||||
ret = regulator_set_enable(priv->vdd_reg, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(device->dev, "pclk", &clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "peripheral clock get error %d\n", ret);
|
||||
goto err_reg;
|
||||
}
|
||||
|
||||
ret = clk_enable(&clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "peripheral clock enable error %d\n", ret);
|
||||
goto err_reg;
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(dev, "ref", &clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "pll reference clock get error %d\n", ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
priv->pllref_clk = (unsigned int)clk_get_rate(&clk);
|
||||
|
||||
ret = reset_get_by_index(device->dev, 0, &rst);
|
||||
if (ret) {
|
||||
dev_err(dev, "missing dsi hardware reset\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
/* Reset */
|
||||
reset_deassert(&rst);
|
||||
|
||||
return 0;
|
||||
err_clk:
|
||||
clk_disable(&clk);
|
||||
err_reg:
|
||||
if (IS_ENABLED(CONFIG_DM_REGULATOR))
|
||||
regulator_set_enable(priv->vdd_reg, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct video_bridge_ops stm32_dsi_ops = {
|
||||
.attach = stm32_dsi_attach,
|
||||
.set_backlight = stm32_dsi_set_backlight,
|
||||
};
|
||||
|
||||
static const struct udevice_id stm32_dsi_ids[] = {
|
||||
{ .compatible = "st,stm32-dsi"},
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(stm32_dsi) = {
|
||||
.name = "stm32-display-dsi",
|
||||
.id = UCLASS_VIDEO_BRIDGE,
|
||||
.of_match = stm32_dsi_ids,
|
||||
.bind = dm_scan_fdt_dev,
|
||||
.probe = stm32_dsi_probe,
|
||||
.ops = &stm32_dsi_ops,
|
||||
.priv_auto_alloc_size = sizeof(struct stm32_dsi_priv),
|
||||
};
|
@ -7,19 +7,18 @@
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <display.h>
|
||||
#include <dm.h>
|
||||
#include <panel.h>
|
||||
#include <reset.h>
|
||||
#include <video.h>
|
||||
#include <video_bridge.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/gpio.h>
|
||||
#include <dm/device-internal.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
struct stm32_ltdc_priv {
|
||||
void __iomem *regs;
|
||||
struct display_timing timing;
|
||||
enum video_log2_bpp l2bpp;
|
||||
u32 bg_col_argb;
|
||||
u32 crop_x, crop_y, crop_w, crop_h;
|
||||
@ -174,8 +173,8 @@ static u32 stm32_ltdc_get_pixel_format(enum video_log2_bpp l2bpp)
|
||||
case VIDEO_BPP2:
|
||||
case VIDEO_BPP4:
|
||||
default:
|
||||
debug("%s: warning %dbpp not supported yet, %dbpp instead\n",
|
||||
__func__, VNBITS(l2bpp), VNBITS(VIDEO_BPP16));
|
||||
pr_warn("%s: warning %dbpp not supported yet, %dbpp instead\n",
|
||||
__func__, VNBITS(l2bpp), VNBITS(VIDEO_BPP16));
|
||||
pf = PF_RGB565;
|
||||
break;
|
||||
}
|
||||
@ -209,23 +208,23 @@ static void stm32_ltdc_enable(struct stm32_ltdc_priv *priv)
|
||||
setbits_le32(priv->regs + LTDC_GCR, GCR_LTDCEN);
|
||||
}
|
||||
|
||||
static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv)
|
||||
static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv,
|
||||
struct display_timing *timings)
|
||||
{
|
||||
void __iomem *regs = priv->regs;
|
||||
struct display_timing *timing = &priv->timing;
|
||||
u32 hsync, vsync, acc_hbp, acc_vbp, acc_act_w, acc_act_h;
|
||||
u32 total_w, total_h;
|
||||
u32 val;
|
||||
|
||||
/* Convert video timings to ltdc timings */
|
||||
hsync = timing->hsync_len.typ - 1;
|
||||
vsync = timing->vsync_len.typ - 1;
|
||||
acc_hbp = hsync + timing->hback_porch.typ;
|
||||
acc_vbp = vsync + timing->vback_porch.typ;
|
||||
acc_act_w = acc_hbp + timing->hactive.typ;
|
||||
acc_act_h = acc_vbp + timing->vactive.typ;
|
||||
total_w = acc_act_w + timing->hfront_porch.typ;
|
||||
total_h = acc_act_h + timing->vfront_porch.typ;
|
||||
hsync = timings->hsync_len.typ - 1;
|
||||
vsync = timings->vsync_len.typ - 1;
|
||||
acc_hbp = hsync + timings->hback_porch.typ;
|
||||
acc_vbp = vsync + timings->vback_porch.typ;
|
||||
acc_act_w = acc_hbp + timings->hactive.typ;
|
||||
acc_act_h = acc_vbp + timings->vactive.typ;
|
||||
total_w = acc_act_w + timings->hfront_porch.typ;
|
||||
total_h = acc_act_h + timings->vfront_porch.typ;
|
||||
|
||||
/* Synchronization sizes */
|
||||
val = (hsync << 16) | vsync;
|
||||
@ -247,14 +246,14 @@ static void stm32_ltdc_set_mode(struct stm32_ltdc_priv *priv)
|
||||
|
||||
/* Signal polarities */
|
||||
val = 0;
|
||||
debug("%s: timing->flags 0x%08x\n", __func__, timing->flags);
|
||||
if (timing->flags & DISPLAY_FLAGS_HSYNC_HIGH)
|
||||
debug("%s: timing->flags 0x%08x\n", __func__, timings->flags);
|
||||
if (timings->flags & DISPLAY_FLAGS_HSYNC_HIGH)
|
||||
val |= GCR_HSPOL;
|
||||
if (timing->flags & DISPLAY_FLAGS_VSYNC_HIGH)
|
||||
if (timings->flags & DISPLAY_FLAGS_VSYNC_HIGH)
|
||||
val |= GCR_VSPOL;
|
||||
if (timing->flags & DISPLAY_FLAGS_DE_HIGH)
|
||||
if (timings->flags & DISPLAY_FLAGS_DE_HIGH)
|
||||
val |= GCR_DEPOL;
|
||||
if (timing->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
|
||||
if (timings->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
|
||||
val |= GCR_PCPOL;
|
||||
clrsetbits_le32(regs + LTDC_GCR,
|
||||
GCR_HSPOL | GCR_VSPOL | GCR_DEPOL | GCR_PCPOL, val);
|
||||
@ -330,96 +329,120 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
struct video_uc_platdata *uc_plat = dev_get_uclass_platdata(dev);
|
||||
struct video_priv *uc_priv = dev_get_uclass_priv(dev);
|
||||
struct stm32_ltdc_priv *priv = dev_get_priv(dev);
|
||||
struct udevice *panel;
|
||||
struct udevice *bridge = NULL;
|
||||
struct udevice *panel = NULL;
|
||||
struct display_timing timings;
|
||||
struct clk pclk;
|
||||
struct reset_ctl rst;
|
||||
int rate, ret;
|
||||
int ret;
|
||||
|
||||
priv->regs = (void *)dev_read_addr(dev);
|
||||
if ((fdt_addr_t)priv->regs == FDT_ADDR_T_NONE) {
|
||||
debug("%s: ltdc dt register address error\n", __func__);
|
||||
dev_err(dev, "ltdc dt register address error\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &pclk);
|
||||
if (ret) {
|
||||
debug("%s: peripheral clock get error %d\n", __func__, ret);
|
||||
dev_err(dev, "peripheral clock get error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_enable(&pclk);
|
||||
if (ret) {
|
||||
debug("%s: peripheral clock enable error %d\n",
|
||||
__func__, ret);
|
||||
dev_err(dev, "peripheral clock enable error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uclass_first_device_err(UCLASS_PANEL, &panel);
|
||||
if (ret) {
|
||||
if (ret != -ENODEV)
|
||||
dev_err(dev, "panel device error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = panel_get_display_timing(panel, &timings);
|
||||
if (ret) {
|
||||
ret = fdtdec_decode_display_timing(gd->fdt_blob,
|
||||
dev_of_offset(panel),
|
||||
0, &timings);
|
||||
if (ret) {
|
||||
dev_err(dev, "decode display timing error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_set_rate(&pclk, timings.pixelclock.typ);
|
||||
if (ret)
|
||||
dev_warn(dev, "fail to set pixel clock %d hz\n",
|
||||
timings.pixelclock.typ);
|
||||
|
||||
debug("%s: Set pixel clock req %d hz get %ld hz\n", __func__,
|
||||
timings.pixelclock.typ, clk_get_rate(&pclk));
|
||||
|
||||
ret = reset_get_by_index(dev, 0, &rst);
|
||||
if (ret) {
|
||||
debug("%s: missing ltdc hardware reset\n", __func__);
|
||||
return -ENODEV;
|
||||
dev_err(dev, "missing ltdc hardware reset\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Reset */
|
||||
reset_deassert(&rst);
|
||||
|
||||
ret = uclass_first_device(UCLASS_PANEL, &panel);
|
||||
if (ret) {
|
||||
debug("%s: panel device error %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
|
||||
ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &bridge);
|
||||
if (ret)
|
||||
debug("No video bridge, or no backlight on bridge\n");
|
||||
|
||||
ret = panel_enable_backlight(panel);
|
||||
if (ret) {
|
||||
debug("%s: panel %s enable backlight error %d\n",
|
||||
__func__, panel->name, ret);
|
||||
return ret;
|
||||
if (bridge) {
|
||||
ret = video_bridge_attach(bridge);
|
||||
if (ret) {
|
||||
dev_err(dev, "fail to attach bridge\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = fdtdec_decode_display_timing(gd->fdt_blob,
|
||||
dev_of_offset(dev), 0,
|
||||
&priv->timing);
|
||||
if (ret) {
|
||||
debug("%s: decode display timing error %d\n",
|
||||
__func__, ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rate = clk_set_rate(&pclk, priv->timing.pixelclock.typ);
|
||||
if (rate < 0) {
|
||||
debug("%s: fail to set pixel clock %d hz %d hz\n",
|
||||
__func__, priv->timing.pixelclock.typ, rate);
|
||||
return rate;
|
||||
}
|
||||
|
||||
debug("%s: Set pixel clock req %d hz get %d hz\n", __func__,
|
||||
priv->timing.pixelclock.typ, rate);
|
||||
|
||||
/* TODO Below parameters are hard-coded for the moment... */
|
||||
priv->l2bpp = VIDEO_BPP16;
|
||||
priv->bg_col_argb = 0xFFFFFFFF; /* white no transparency */
|
||||
priv->crop_x = 0;
|
||||
priv->crop_y = 0;
|
||||
priv->crop_w = priv->timing.hactive.typ;
|
||||
priv->crop_h = priv->timing.vactive.typ;
|
||||
priv->crop_w = timings.hactive.typ;
|
||||
priv->crop_h = timings.vactive.typ;
|
||||
priv->alpha = 0xFF;
|
||||
|
||||
debug("%s: %dx%d %dbpp frame buffer at 0x%lx\n", __func__,
|
||||
priv->timing.hactive.typ, priv->timing.vactive.typ,
|
||||
timings.hactive.typ, timings.vactive.typ,
|
||||
VNBITS(priv->l2bpp), uc_plat->base);
|
||||
debug("%s: crop %d,%d %dx%d bg 0x%08x alpha %d\n", __func__,
|
||||
priv->crop_x, priv->crop_y, priv->crop_w, priv->crop_h,
|
||||
priv->bg_col_argb, priv->alpha);
|
||||
|
||||
/* Configure & start LTDC */
|
||||
stm32_ltdc_set_mode(priv);
|
||||
stm32_ltdc_set_mode(priv, &timings);
|
||||
stm32_ltdc_set_layer1(priv, uc_plat->base);
|
||||
stm32_ltdc_enable(priv);
|
||||
|
||||
uc_priv->xsize = priv->timing.hactive.typ;
|
||||
uc_priv->ysize = priv->timing.vactive.typ;
|
||||
uc_priv->xsize = timings.hactive.typ;
|
||||
uc_priv->ysize = timings.vactive.typ;
|
||||
uc_priv->bpix = priv->l2bpp;
|
||||
|
||||
if (!bridge) {
|
||||
ret = panel_enable_backlight(panel);
|
||||
if (ret) {
|
||||
dev_err(dev, "panel %s enable backlight error %d\n",
|
||||
panel->name, ret);
|
||||
return ret;
|
||||
}
|
||||
} else if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
|
||||
ret = video_bridge_set_backlight(bridge, 80);
|
||||
if (ret) {
|
||||
dev_err(dev, "fail to set backlight\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
video_set_flush_dcache(dev, true);
|
||||
|
||||
return 0;
|
||||
|
@ -83,6 +83,13 @@
|
||||
#define CONFIG_SYS_MTDPARTS_RUNTIME
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DM_VIDEO
|
||||
#define CONFIG_VIDEO_BMP_RLE8
|
||||
#define CONFIG_BMP_16BPP
|
||||
#define CONFIG_BMP_24BPP
|
||||
#define CONFIG_BMP_32BPP
|
||||
#endif
|
||||
|
||||
/*****************************************************************************/
|
||||
#ifdef CONFIG_DISTRO_DEFAULTS
|
||||
/*****************************************************************************/
|
||||
|
@ -40,6 +40,7 @@ enum uclass_id {
|
||||
UCLASS_CPU, /* CPU, typically part of an SoC */
|
||||
UCLASS_CROS_EC, /* Chrome OS EC */
|
||||
UCLASS_DISPLAY, /* Display (e.g. DisplayPort, HDMI) */
|
||||
UCLASS_DSI_HOST, /* Display Serial Interface host */
|
||||
UCLASS_DMA, /* Direct Memory Access */
|
||||
UCLASS_EFI, /* EFI managed devices */
|
||||
UCLASS_ETH, /* Ethernet device */
|
||||
|
73
include/dsi_host.h
Normal file
73
include/dsi_host.h
Normal file
@ -0,0 +1,73 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DSI_HOST_H
|
||||
#define _DSI_HOST_H
|
||||
|
||||
#include <mipi_dsi.h>
|
||||
|
||||
struct dsi_host_ops {
|
||||
/**
|
||||
* init() - initialized the dsi_host
|
||||
*
|
||||
* @dev: dsi host device
|
||||
* @device: DSI peripheral device
|
||||
* @timing: Display timings
|
||||
* @max_data_lanes: maximum number of data lines
|
||||
* @phy_ops: set of function pointers for performing physical operations
|
||||
* @return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*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);
|
||||
|
||||
/**
|
||||
* enable() - Enable the dsi_host
|
||||
*
|
||||
* @dev: dsi host device
|
||||
* @return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*enable)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* disable() - Disable the dsi_host
|
||||
*
|
||||
* @dev: dsi host device
|
||||
* @return 0 if OK, -ve on error
|
||||
*/
|
||||
int (*disable)(struct udevice *dev);
|
||||
};
|
||||
|
||||
#define dsi_host_get_ops(dev) ((struct dsi_host_ops *)(dev)->driver->ops)
|
||||
|
||||
/**
|
||||
* dsi_host_init
|
||||
*
|
||||
* @dev: dsi host device
|
||||
* @device: DSI peripheral device
|
||||
* @timing: Display timings
|
||||
* @max_data_lanes: maximum number of data lines
|
||||
* @phy_ops: set of function pointers for performing physical operations
|
||||
* @return 0 if OK, -ve on error
|
||||
*/
|
||||
int dsi_host_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);
|
||||
|
||||
/**
|
||||
* dsi_host_enable
|
||||
*
|
||||
* @dev: dsi host device
|
||||
* @return 0 if OK, -ve on error
|
||||
*/
|
||||
int dsi_host_enable(struct udevice *dev);
|
||||
|
||||
#endif
|
@ -115,6 +115,14 @@ enum {
|
||||
MIPI_DCS_READ_MEMORY_CONTINUE = 0x3E,
|
||||
MIPI_DCS_SET_TEAR_SCANLINE = 0x44,
|
||||
MIPI_DCS_GET_SCANLINE = 0x45,
|
||||
MIPI_DCS_SET_DISPLAY_BRIGHTNESS = 0x51, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_GET_DISPLAY_BRIGHTNESS = 0x52, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_WRITE_CONTROL_DISPLAY = 0x53, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_GET_CONTROL_DISPLAY = 0x54, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_WRITE_POWER_SAVE = 0x55, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_GET_POWER_SAVE = 0x56, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_SET_CABC_MIN_BRIGHTNESS = 0x5E, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_GET_CABC_MIN_BRIGHTNESS = 0x5F, /* MIPI DCS 1.3 */
|
||||
MIPI_DCS_READ_DDB_START = 0xA1,
|
||||
MIPI_DCS_READ_DDB_CONTINUE = 0xA8,
|
||||
};
|
||||
|
466
include/mipi_dsi.h
Normal file
466
include/mipi_dsi.h
Normal file
@ -0,0 +1,466 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* MIPI DSI Bus
|
||||
*
|
||||
* Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd.
|
||||
* Copyright (C) 2018 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Andrzej Hajda <a.hajda@samsung.com>
|
||||
* Yannick Fertre <yannick.fertre@st.com>
|
||||
* Philippe Cornu <philippe.cornu@st.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#ifndef MIPI_DSI_H
|
||||
#define MIPI_DSI_H
|
||||
|
||||
#include <mipi_display.h>
|
||||
|
||||
struct mipi_dsi_host;
|
||||
struct mipi_dsi_device;
|
||||
|
||||
/* request ACK from peripheral */
|
||||
#define MIPI_DSI_MSG_REQ_ACK BIT(0)
|
||||
/* use Low Power Mode to transmit message */
|
||||
#define MIPI_DSI_MSG_USE_LPM BIT(1)
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_msg - read/write DSI buffer
|
||||
* @channel: virtual channel id
|
||||
* @type: payload data type
|
||||
* @flags: flags controlling this message transmission
|
||||
* @tx_len: length of @tx_buf
|
||||
* @tx_buf: data to be written
|
||||
* @rx_len: length of @rx_buf
|
||||
* @rx_buf: data to be read, or NULL
|
||||
*/
|
||||
struct mipi_dsi_msg {
|
||||
u8 channel;
|
||||
u8 type;
|
||||
u16 flags;
|
||||
|
||||
size_t tx_len;
|
||||
const void *tx_buf;
|
||||
|
||||
size_t rx_len;
|
||||
void *rx_buf;
|
||||
};
|
||||
|
||||
bool mipi_dsi_packet_format_is_short(u8 type);
|
||||
bool mipi_dsi_packet_format_is_long(u8 type);
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_packet - represents a MIPI DSI packet in protocol format
|
||||
* @size: size (in bytes) of the packet
|
||||
* @header: the four bytes that make up the header (Data ID, Word Count or
|
||||
* Packet Data, and ECC)
|
||||
* @payload_length: number of bytes in the payload
|
||||
* @payload: a pointer to a buffer containing the payload, if any
|
||||
*/
|
||||
struct mipi_dsi_packet {
|
||||
size_t size;
|
||||
u8 header[4];
|
||||
size_t payload_length;
|
||||
const u8 *payload;
|
||||
};
|
||||
|
||||
int mipi_dsi_create_packet(struct mipi_dsi_packet *packet,
|
||||
const struct mipi_dsi_msg *msg);
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_host_ops - DSI bus operations
|
||||
* @attach: attach DSI device to DSI host
|
||||
* @detach: detach DSI device from DSI host
|
||||
* @transfer: transmit a DSI packet
|
||||
*
|
||||
* DSI packets transmitted by .transfer() are passed in as mipi_dsi_msg
|
||||
* structures. This structure contains information about the type of packet
|
||||
* being transmitted as well as the transmit and receive buffers. When an
|
||||
* error is encountered during transmission, this function will return a
|
||||
* negative error code. On success it shall return the number of bytes
|
||||
* transmitted for write packets or the number of bytes received for read
|
||||
* packets.
|
||||
*
|
||||
* Note that typically DSI packet transmission is atomic, so the .transfer()
|
||||
* function will seldomly return anything other than the number of bytes
|
||||
* contained in the transmit buffer on success.
|
||||
*/
|
||||
struct mipi_dsi_host_ops {
|
||||
int (*attach)(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *dsi);
|
||||
int (*detach)(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *dsi);
|
||||
ssize_t (*transfer)(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_phy_ops - DSI host physical operations
|
||||
* @init: initialized host physical part
|
||||
* @get_lane_mbps: get lane bitrate per lane (mbps)
|
||||
* @post_set_mode: operation that should after set mode
|
||||
*/
|
||||
struct mipi_dsi_phy_ops {
|
||||
int (*init)(void *priv_data);
|
||||
int (*get_lane_mbps)(void *priv_data, struct display_timing *timings,
|
||||
u32 lanes, u32 format, unsigned int *lane_mbps);
|
||||
void (*post_set_mode)(void *priv_data, unsigned long mode_flags);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_host - DSI host device
|
||||
* @dev: driver model device node for this DSI host
|
||||
* @ops: DSI host operations
|
||||
* @list: list management
|
||||
*/
|
||||
struct mipi_dsi_host {
|
||||
struct device *dev;
|
||||
const struct mipi_dsi_host_ops *ops;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/* DSI mode flags */
|
||||
|
||||
/* video mode */
|
||||
#define MIPI_DSI_MODE_VIDEO BIT(0)
|
||||
/* video burst mode */
|
||||
#define MIPI_DSI_MODE_VIDEO_BURST BIT(1)
|
||||
/* video pulse mode */
|
||||
#define MIPI_DSI_MODE_VIDEO_SYNC_PULSE BIT(2)
|
||||
/* enable auto vertical count mode */
|
||||
#define MIPI_DSI_MODE_VIDEO_AUTO_VERT BIT(3)
|
||||
/* enable hsync-end packets in vsync-pulse and v-porch area */
|
||||
#define MIPI_DSI_MODE_VIDEO_HSE BIT(4)
|
||||
/* disable hfront-porch area */
|
||||
#define MIPI_DSI_MODE_VIDEO_HFP BIT(5)
|
||||
/* disable hback-porch area */
|
||||
#define MIPI_DSI_MODE_VIDEO_HBP BIT(6)
|
||||
/* disable hsync-active area */
|
||||
#define MIPI_DSI_MODE_VIDEO_HSA BIT(7)
|
||||
/* flush display FIFO on vsync pulse */
|
||||
#define MIPI_DSI_MODE_VSYNC_FLUSH BIT(8)
|
||||
/* disable EoT packets in HS mode */
|
||||
#define MIPI_DSI_MODE_EOT_PACKET BIT(9)
|
||||
/* device supports non-continuous clock behavior (DSI spec 5.6.1) */
|
||||
#define MIPI_DSI_CLOCK_NON_CONTINUOUS BIT(10)
|
||||
/* transmit data in low power */
|
||||
#define MIPI_DSI_MODE_LPM BIT(11)
|
||||
|
||||
enum mipi_dsi_pixel_format {
|
||||
MIPI_DSI_FMT_RGB888,
|
||||
MIPI_DSI_FMT_RGB666,
|
||||
MIPI_DSI_FMT_RGB666_PACKED,
|
||||
MIPI_DSI_FMT_RGB565,
|
||||
};
|
||||
|
||||
#define DSI_DEV_NAME_SIZE 20
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_device_info - template for creating a mipi_dsi_device
|
||||
* @type: DSI peripheral chip type
|
||||
* @channel: DSI virtual channel assigned to peripheral
|
||||
* @node: pointer to OF device node or NULL
|
||||
*
|
||||
* This is populated and passed to mipi_dsi_device_new to create a new
|
||||
* DSI device
|
||||
*/
|
||||
struct mipi_dsi_device_info {
|
||||
char type[DSI_DEV_NAME_SIZE];
|
||||
u32 channel;
|
||||
struct device_node *node;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_device - DSI peripheral device
|
||||
* @host: DSI host for this peripheral
|
||||
* @dev: driver model device node for this peripheral
|
||||
* @name: DSI peripheral chip type
|
||||
* @channel: virtual channel assigned to the peripheral
|
||||
* @format: pixel format for video mode
|
||||
* @lanes: number of active data lanes
|
||||
* @mode_flags: DSI operation mode related flags
|
||||
*/
|
||||
struct mipi_dsi_device {
|
||||
struct mipi_dsi_host *host;
|
||||
struct udevice *dev;
|
||||
|
||||
char name[DSI_DEV_NAME_SIZE];
|
||||
unsigned int channel;
|
||||
unsigned int lanes;
|
||||
enum mipi_dsi_pixel_format format;
|
||||
unsigned long mode_flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* mipi_dsi_pixel_format_to_bpp - obtain the number of bits per pixel for any
|
||||
* given pixel format defined by the MIPI DSI
|
||||
* specification
|
||||
* @fmt: MIPI DSI pixel format
|
||||
*
|
||||
* Returns: The number of bits per pixel of the given pixel format.
|
||||
*/
|
||||
static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
case MIPI_DSI_FMT_RGB888:
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
return 24;
|
||||
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
return 18;
|
||||
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
return 16;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* struct mipi_dsi_panel_plat - DSI panel platform data
|
||||
* @device: DSI peripheral device
|
||||
*/
|
||||
struct mipi_dsi_panel_plat {
|
||||
struct mipi_dsi_device *device;
|
||||
};
|
||||
|
||||
/**
|
||||
* mipi_dsi_attach - attach a DSI device to its DSI host
|
||||
* @dsi: DSI peripheral
|
||||
*/
|
||||
int mipi_dsi_attach(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_detach - detach a DSI device from its DSI host
|
||||
* @dsi: DSI peripheral
|
||||
*/
|
||||
int mipi_dsi_detach(struct mipi_dsi_device *dsi);
|
||||
int mipi_dsi_shutdown_peripheral(struct mipi_dsi_device *dsi);
|
||||
int mipi_dsi_turn_on_peripheral(struct mipi_dsi_device *dsi);
|
||||
int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi,
|
||||
u16 value);
|
||||
|
||||
ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload,
|
||||
size_t size);
|
||||
ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params,
|
||||
size_t num_params, void *data, size_t size);
|
||||
|
||||
/**
|
||||
* enum mipi_dsi_dcs_tear_mode - Tearing Effect Output Line mode
|
||||
* @MIPI_DSI_DCS_TEAR_MODE_VBLANK: the TE output line consists of V-Blanking
|
||||
* information only
|
||||
* @MIPI_DSI_DCS_TEAR_MODE_VHBLANK : the TE output line consists of both
|
||||
* V-Blanking and H-Blanking information
|
||||
*/
|
||||
enum mipi_dsi_dcs_tear_mode {
|
||||
MIPI_DSI_DCS_TEAR_MODE_VBLANK,
|
||||
MIPI_DSI_DCS_TEAR_MODE_VHBLANK,
|
||||
};
|
||||
|
||||
#define MIPI_DSI_DCS_POWER_MODE_DISPLAY BIT(2)
|
||||
#define MIPI_DSI_DCS_POWER_MODE_NORMAL BIT(3)
|
||||
#define MIPI_DSI_DCS_POWER_MODE_SLEEP BIT(4)
|
||||
#define MIPI_DSI_DCS_POWER_MODE_PARTIAL BIT(5)
|
||||
#define MIPI_DSI_DCS_POWER_MODE_IDLE BIT(6)
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_write_buffer() - transmit a DCS command with payload
|
||||
* @dsi: DSI peripheral device
|
||||
* @data: buffer containing data to be transmitted
|
||||
* @len: size of transmission buffer
|
||||
*
|
||||
* This function will automatically choose the right data type depending on
|
||||
* the command payload length.
|
||||
*
|
||||
* Return: The number of bytes successfully transmitted or a negative error
|
||||
* code on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi,
|
||||
const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_write() - send DCS write command
|
||||
* @dsi: DSI peripheral device
|
||||
* @cmd: DCS command
|
||||
* @data: buffer containing the command payload
|
||||
* @len: command payload length
|
||||
*
|
||||
* This function will automatically choose the right data type depending on
|
||||
* the command payload length.
|
||||
|
||||
* code on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd,
|
||||
const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_read() - send DCS read request command
|
||||
* @dsi: DSI peripheral device
|
||||
* @cmd: DCS command
|
||||
* @data: buffer in which to receive data
|
||||
* @len: size of receive buffer
|
||||
*
|
||||
* Return: The number of bytes read or a negative error code on failure.
|
||||
*/
|
||||
ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data,
|
||||
size_t len);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_nop() - send DCS nop packet
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_nop(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_soft_reset() - perform a software reset of the display module
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_soft_reset(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_get_power_mode() - query the display module's current power
|
||||
* mode
|
||||
* @dsi: DSI peripheral device
|
||||
* @mode: return location for the current power mode
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_get_power_mode(struct mipi_dsi_device *dsi, u8 *mode);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_get_pixel_format() - gets the pixel format for the RGB image
|
||||
* data used by the interface
|
||||
* @dsi: DSI peripheral device
|
||||
* @format: return location for the pixel format
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_get_pixel_format(struct mipi_dsi_device *dsi, u8 *format);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_enter_sleep_mode() - disable all unnecessary blocks inside the
|
||||
* display module except interface communication
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_enter_sleep_mode(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_exit_sleep_mode() - enable all blocks inside the display
|
||||
* module
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_display_off() - stop displaying the image data on the
|
||||
* display device
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_display_off(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_display_on() - start displaying the image data on the
|
||||
* display device
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_column_address() - define the column extent of the frame
|
||||
* memory accessed by the host processor
|
||||
* @dsi: DSI peripheral device
|
||||
* @start: first column of frame memory
|
||||
* @end: last column of frame memory
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start,
|
||||
u16 end);
|
||||
/**
|
||||
* mipi_dsi_dcs_set_page_address() - define the page extent of the frame
|
||||
* memory accessed by the host processor
|
||||
* @dsi: DSI peripheral device
|
||||
* @start: first page of frame memory
|
||||
* @end: last page of frame memory
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start,
|
||||
u16 end);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_tear_off() - turn off the display module's Tearing Effect
|
||||
* output signal on the TE signal line
|
||||
* @dsi: DSI peripheral device
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_tear_off(struct mipi_dsi_device *dsi);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_tear_on() - turn on the display module's Tearing Effect
|
||||
* output signal on the TE signal line.
|
||||
* @dsi: DSI peripheral device
|
||||
* @mode: the Tearing Effect Output Line mode
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi,
|
||||
enum mipi_dsi_dcs_tear_mode mode);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_pixel_format() - sets the pixel format for the RGB image
|
||||
* data used by the interface
|
||||
* @dsi: DSI peripheral device
|
||||
* @format: pixel format
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_tear_scanline() - set the scanline to use as trigger for
|
||||
* the Tearing Effect output signal of the display module
|
||||
* @dsi: DSI peripheral device
|
||||
* @scanline: scanline to use as trigger
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure
|
||||
*/
|
||||
int mipi_dsi_dcs_set_tear_scanline(struct mipi_dsi_device *dsi, u16 scanline);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_set_display_brightness() - sets the brightness value of the
|
||||
* display
|
||||
* @dsi: DSI peripheral device
|
||||
* @brightness: brightness value
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_set_display_brightness(struct mipi_dsi_device *dsi,
|
||||
u16 brightness);
|
||||
|
||||
/**
|
||||
* mipi_dsi_dcs_get_display_brightness() - gets the current brightness value
|
||||
* of the display
|
||||
* @dsi: DSI peripheral device
|
||||
* @brightness: brightness value
|
||||
*
|
||||
* Return: 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int mipi_dsi_dcs_get_display_brightness(struct mipi_dsi_device *dsi,
|
||||
u16 *brightness);
|
||||
|
||||
#endif /* MIPI_DSI_H */
|
@ -18,6 +18,7 @@ obj-$(CONFIG_BLK) += blk.o
|
||||
obj-$(CONFIG_BOARD) += board.o
|
||||
obj-$(CONFIG_DM_BOOTCOUNT) += bootcount.o
|
||||
obj-$(CONFIG_CLK) += clk.o clk_ccf.o
|
||||
obj-$(CONFIG_VIDEO_MIPI_DSI) += dsi_host.o
|
||||
obj-$(CONFIG_DM_ETH) += eth.o
|
||||
obj-$(CONFIG_FIRMWARE) += firmware.o
|
||||
obj-$(CONFIG_DM_GPIO) += gpio.o
|
||||
|
58
test/dm/dsi_host.c
Normal file
58
test/dm/dsi_host.c
Normal file
@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2019 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <dsi_host.h>
|
||||
#include <asm/state.h>
|
||||
#include <asm/test.h>
|
||||
#include <dm/test.h>
|
||||
#include <test/ut.h>
|
||||
|
||||
static int dm_test_dsi_host_phy_init(void *priv_data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dm_test_dsi_host_phy_post_set_mode(void *priv_data,
|
||||
unsigned long mode_flags)
|
||||
{
|
||||
}
|
||||
|
||||
static int dm_test_dsi_host_phy_get_lane_mbps(void *priv_data,
|
||||
struct display_timing *timings,
|
||||
u32 lanes,
|
||||
u32 format,
|
||||
unsigned int *lane_mbps)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mipi_dsi_phy_ops dm_test_dsi_host_phy_ops = {
|
||||
.init = dm_test_dsi_host_phy_init,
|
||||
.get_lane_mbps = dm_test_dsi_host_phy_get_lane_mbps,
|
||||
.post_set_mode = dm_test_dsi_host_phy_post_set_mode,
|
||||
};
|
||||
|
||||
/* Test that dsi_host driver functions are called */
|
||||
static int dm_test_dsi_host(struct unit_test_state *uts)
|
||||
{
|
||||
struct udevice *dev;
|
||||
struct mipi_dsi_device device;
|
||||
struct display_timing timings;
|
||||
unsigned int max_data_lanes = 4;
|
||||
|
||||
ut_assertok(uclass_first_device_err(UCLASS_DSI_HOST, &dev));
|
||||
|
||||
ut_assertok(dsi_host_init(dev, &device, &timings, max_data_lanes,
|
||||
&dm_test_dsi_host_phy_ops));
|
||||
|
||||
ut_assertok(dsi_host_enable(dev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DM_TEST(dm_test_dsi_host, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
|
Loading…
Reference in New Issue
Block a user