mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-06-13 15:03:58 +03:00
Merge tag 'u-boot-stm32-20251117' of https://source.denx.de/u-boot/custodians/u-boot-stm
CI: https://source.denx.de/u-boot/custodians/u-boot-stm/-/pipelines/28392 dhelectronics: - Move dh_add_item_number_and_serial_to_env() to common code - Read values from M24256 write-lockable page on STM32MP13xx DHCOR - Add MAC address readout from fuses on DH STM32MP1 DHSOM - Keep the reg11 and reg18 regulators always enabled on STM32MP13xx DHCOR. - Fix boot for stm32mp15xx-dhsom. - Fix build of ST DFU virt code on DH STM32MP1 DHSOM - Introduce DH STM32MP13x target. STM32MP2: - Add support for stm32mp257-dk board. - Fix arm, smc-id value for stm32mp23/25. - Fix stm32mp235f-dk boot (add syscon compatible, add txbyteclk). - Add display support: - Introduce LVDS driver. - Add LTDC support. - Add Ethernet support for stm32mp255. STM32MP13: - Add ADC support. - Add power check for stm32mp135f-dk board.
This commit is contained in:
@@ -734,6 +734,7 @@ F: drivers/spi/stm32_ospi.c
|
||||
F: drivers/spi/stm32_qspi.c
|
||||
F: drivers/spi/stm32_spi.c
|
||||
F: drivers/video/stm32/stm32_ltdc.c
|
||||
F: drivers/video/stm32/stm32_lvds.c
|
||||
F: drivers/watchdog/stm32mp_wdt.c
|
||||
F: include/dt-bindings/clock/stm32fx-clock.h
|
||||
F: include/dt-bindings/clock/stm32mp*
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
u-boot,boot-led = "led-blue";
|
||||
u-boot,error-led = "led-red";
|
||||
u-boot,mmc-env-partition = "u-boot-env";
|
||||
st,adc_usb_pd = <&adc1 6>, <&adc1 12>;
|
||||
};
|
||||
|
||||
gpio-keys {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
/ {
|
||||
aliases {
|
||||
eeprom0 = &eeprom0;
|
||||
eeprom0wl = &eeprom0wl;
|
||||
};
|
||||
|
||||
config {
|
||||
@@ -186,6 +187,14 @@
|
||||
};
|
||||
#endif
|
||||
|
||||
®11 {
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
®18 {
|
||||
regulator-always-on;
|
||||
};
|
||||
|
||||
&sdmmc1 {
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
@@ -51,3 +51,7 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&etzpc {
|
||||
compatible = "simple-bus";
|
||||
};
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
soc@0 {
|
||||
bootph-all;
|
||||
};
|
||||
|
||||
/* temporary until kernel DT update */
|
||||
watchdog {
|
||||
arm,smc-id = <0xbc000000>;
|
||||
};
|
||||
};
|
||||
|
||||
&bsec {
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
u-boot,boot-led = "led-blue";
|
||||
u-boot,mmc-env-partition = "u-boot-env";
|
||||
};
|
||||
|
||||
clocks {
|
||||
txbyteclk {
|
||||
#clock-cells = <0>;
|
||||
compatible = "fixed-clock";
|
||||
clock-frequency = <0>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&usart2 {
|
||||
|
||||
@@ -39,6 +39,11 @@
|
||||
soc@0 {
|
||||
bootph-all;
|
||||
};
|
||||
|
||||
/* temporary until kernel DT update */
|
||||
watchdog {
|
||||
arm,smc-id = <0xbc000000>;
|
||||
};
|
||||
};
|
||||
|
||||
&bsec {
|
||||
|
||||
26
arch/arm/dts/stm32mp257f-dk-u-boot.dtsi
Normal file
26
arch/arm/dts/stm32mp257f-dk-u-boot.dtsi
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) STMicroelectronics 2025 - All Rights Reserved
|
||||
*/
|
||||
|
||||
#include "stm32mp25-u-boot.dtsi"
|
||||
|
||||
/ {
|
||||
config {
|
||||
u-boot,mmc-env-partition = "u-boot-env";
|
||||
};
|
||||
};
|
||||
|
||||
&usart2 {
|
||||
bootph-all;
|
||||
};
|
||||
|
||||
&usart2_pins_a {
|
||||
bootph-all;
|
||||
pins1 {
|
||||
bootph-all;
|
||||
};
|
||||
pins2 {
|
||||
bootph-all;
|
||||
};
|
||||
};
|
||||
@@ -17,6 +17,17 @@ config TARGET_ST_STM32MP13X
|
||||
managed by board/st/stm32mp1.
|
||||
The difference between board are managed with devicetree
|
||||
|
||||
config TARGET_DH_STM32MP13X
|
||||
bool "DH electronics STM32MP13x boards"
|
||||
imply BOOTSTAGE
|
||||
imply CMD_BOOTSTAGE
|
||||
imply CMD_CLS if CMD_BMP
|
||||
imply DISABLE_CONSOLE
|
||||
imply PRE_CONSOLE_BUFFER
|
||||
imply SILENT_CONSOLE
|
||||
help
|
||||
Target the DH DHSBC development kit with STM32MP13x SoM.
|
||||
|
||||
endchoice
|
||||
|
||||
config TEXT_BASE
|
||||
@@ -50,6 +61,7 @@ config DEBUG_UART_CLOCK
|
||||
default 64000000
|
||||
endif
|
||||
|
||||
source "board/dhelectronics/dh_stm32mp1/Kconfig"
|
||||
source "board/st/stm32mp1/Kconfig"
|
||||
|
||||
endif
|
||||
|
||||
@@ -77,6 +77,14 @@ int get_eth_nb(void)
|
||||
case CPU_STM32MP257Axx:
|
||||
nb_eth = 5; /* dual ETH with TSN support */
|
||||
break;
|
||||
case CPU_STM32MP255Fxx:
|
||||
fallthrough;
|
||||
case CPU_STM32MP255Dxx:
|
||||
fallthrough;
|
||||
case CPU_STM32MP255Cxx:
|
||||
fallthrough;
|
||||
case CPU_STM32MP255Axx:
|
||||
fallthrough;
|
||||
case CPU_STM32MP253Fxx:
|
||||
fallthrough;
|
||||
case CPU_STM32MP253Dxx:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
static const struct udevice_id stm32mp_syscon_ids[] = {
|
||||
{ .compatible = "st,stm32mp157-syscfg", .data = STM32MP_SYSCON_SYSCFG },
|
||||
{ .compatible = "st,stm32mp23-syscfg", .data = STM32MP_SYSCON_SYSCFG},
|
||||
{ .compatible = "st,stm32mp25-syscfg", .data = STM32MP_SYSCON_SYSCFG},
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -246,3 +246,40 @@ __weak int dh_setup_mac_address(struct eeprom_id_page *eip)
|
||||
printf("%s: Unable to set mac address!\n", __func__);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
void dh_add_item_number_and_serial_to_env(struct eeprom_id_page *eip)
|
||||
{
|
||||
char *item_number_env;
|
||||
char item_number[8]; /* String with 7 characters + string termination */
|
||||
char *serial_env;
|
||||
char serial[10]; /* String with 9 characters + string termination */
|
||||
int ret;
|
||||
|
||||
ret = dh_get_value_from_eeprom_buffer(DH_ITEM_NUMBER, item_number, sizeof(item_number),
|
||||
eip);
|
||||
if (ret) {
|
||||
printf("%s: Unable to get DHSOM item number from EEPROM ID page! ret = %d\n",
|
||||
__func__, ret);
|
||||
} else {
|
||||
item_number_env = env_get("dh_som_item_number");
|
||||
if (!item_number_env)
|
||||
env_set("dh_som_item_number", item_number);
|
||||
else if (strcmp(item_number_env, item_number))
|
||||
printf("Warning: Environment dh_som_item_number differs from EEPROM ID page value (%s != %s)\n",
|
||||
item_number_env, item_number);
|
||||
}
|
||||
|
||||
ret = dh_get_value_from_eeprom_buffer(DH_SERIAL_NUMBER, serial, sizeof(serial),
|
||||
eip);
|
||||
if (ret) {
|
||||
printf("%s: Unable to get DHSOM serial number from EEPROM ID page! ret = %d\n",
|
||||
__func__, ret);
|
||||
} else {
|
||||
serial_env = env_get("dh_som_serial_number");
|
||||
if (!serial_env)
|
||||
env_set("dh_som_serial_number", serial);
|
||||
else if (strcmp(serial_env, serial))
|
||||
printf("Warning: Environment dh_som_serial_number differs from EEPROM ID page value (%s != %s)\n",
|
||||
serial_env, serial);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,16 @@ int dh_get_value_from_eeprom_buffer(enum eip_request_values request, u8 *data, i
|
||||
|
||||
/*
|
||||
* dh_setup_mac_address - Try to get MAC address from various locations and write it to env
|
||||
* @eip: ID EEPROM buffer
|
||||
*
|
||||
* Return: 0 if OK, other value on error
|
||||
*/
|
||||
int dh_setup_mac_address(struct eeprom_id_page *eip);
|
||||
|
||||
/*
|
||||
* dh_add_item_number_and_serial_to_env - Try to get DH IDs from WLP write them to env
|
||||
* @eip: ID EEPROM buffer
|
||||
*
|
||||
* Return: 0 if OK, other value on error
|
||||
*/
|
||||
void dh_add_item_number_and_serial_to_env(struct eeprom_id_page *eip);
|
||||
|
||||
@@ -116,43 +116,6 @@ int dh_setup_mac_address(struct eeprom_id_page *eip)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void dh_add_item_number_and_serial_to_env(struct eeprom_id_page *eip)
|
||||
{
|
||||
char *item_number_env;
|
||||
char item_number[8]; /* String with 7 characters + string termination */
|
||||
char *serial_env;
|
||||
char serial[10]; /* String with 9 characters + string termination */
|
||||
int ret;
|
||||
|
||||
ret = dh_get_value_from_eeprom_buffer(DH_ITEM_NUMBER, item_number, sizeof(item_number),
|
||||
eip);
|
||||
if (ret) {
|
||||
printf("%s: Unable to get DHSOM item number from EEPROM ID page! ret = %d\n",
|
||||
__func__, ret);
|
||||
} else {
|
||||
item_number_env = env_get("dh_som_item_number");
|
||||
if (!item_number_env)
|
||||
env_set("dh_som_item_number", item_number);
|
||||
else if (strcmp(item_number_env, item_number))
|
||||
printf("Warning: Environment dh_som_item_number differs from EEPROM ID page value (%s != %s)\n",
|
||||
item_number_env, item_number);
|
||||
}
|
||||
|
||||
ret = dh_get_value_from_eeprom_buffer(DH_SERIAL_NUMBER, serial, sizeof(serial),
|
||||
eip);
|
||||
if (ret) {
|
||||
printf("%s: Unable to get DHSOM serial number from EEPROM ID page! ret = %d\n",
|
||||
__func__, ret);
|
||||
} else {
|
||||
serial_env = env_get("dh_som_serial_number");
|
||||
if (!serial_env)
|
||||
env_set("dh_som_serial_number", serial);
|
||||
else if (strcmp(serial_env, serial))
|
||||
printf("Warning: Environment dh_som_serial_number differs from EEPROM ID page value (%s != %s)\n",
|
||||
serial_env, serial);
|
||||
}
|
||||
}
|
||||
|
||||
int board_late_init(void)
|
||||
{
|
||||
u8 eeprom_buffer[DH_EEPROM_ID_PAGE_MAX_SIZE] = { 0 };
|
||||
|
||||
@@ -20,3 +20,26 @@ config ENV_OFFSET_REDUND
|
||||
|
||||
source "board/st/common/Kconfig"
|
||||
endif
|
||||
|
||||
if TARGET_DH_STM32MP13X
|
||||
|
||||
config SYS_BOARD
|
||||
default "dh_stm32mp1"
|
||||
|
||||
config SYS_VENDOR
|
||||
default "dhelectronics"
|
||||
|
||||
config SYS_CONFIG_NAME
|
||||
default "stm32mp13_st_common"
|
||||
|
||||
config ENV_SECT_SIZE
|
||||
default 0x10000 if ENV_IS_IN_SPI_FLASH
|
||||
|
||||
config ENV_OFFSET
|
||||
default 0x3E0000 if ENV_IS_IN_SPI_FLASH
|
||||
|
||||
config ENV_OFFSET_REDUND
|
||||
default 0x3F0000 if ENV_IS_IN_SPI_FLASH
|
||||
|
||||
source "board/st/common/Kconfig"
|
||||
endif
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# Copyright (C) 2018, STMicroelectronics - All Rights Reserved
|
||||
#
|
||||
|
||||
obj-y += ../../st/common/stpmic1.o board.o
|
||||
obj-$(CONFIG_PMIC_STPMIC1) += ../../st/common/stpmic1.o
|
||||
obj-y += board.o
|
||||
|
||||
obj-$(CONFIG_SET_DFU_ALT_INFO) += ../../st/common/stm32mp_dfu.o
|
||||
obj-$(CONFIG_$(PHASE_)DFU_VIRT) += ../../st/common/stm32mp_dfu_virt.o
|
||||
|
||||
@@ -119,7 +119,30 @@ static bool dh_stm32_mac_is_in_ks8851(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int dh_stm32_setup_ethaddr(void)
|
||||
static int dh_stm32_get_mac_from_fuse(unsigned char *enetaddr, int index)
|
||||
{
|
||||
struct udevice *dev;
|
||||
u8 otp[12];
|
||||
int ret;
|
||||
|
||||
ret = uclass_get_device_by_driver(UCLASS_MISC,
|
||||
DM_DRIVER_GET(stm32mp_bsec),
|
||||
&dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = misc_read(dev, STM32_BSEC_SHADOW(BSEC_OTP_MAC), otp, sizeof(otp));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
memcpy(enetaddr, otp + ARP_HLEN * index, ARP_HLEN);
|
||||
if (!is_valid_ethaddr(enetaddr))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dh_stm32_setup_ethaddr(struct eeprom_id_page *eip)
|
||||
{
|
||||
unsigned char enetaddr[6];
|
||||
|
||||
@@ -129,13 +152,22 @@ static int dh_stm32_setup_ethaddr(void)
|
||||
if (dh_get_mac_is_enabled("ethernet0"))
|
||||
return 0;
|
||||
|
||||
if (!dh_stm32_get_mac_from_fuse(enetaddr, 0))
|
||||
goto out;
|
||||
|
||||
if (!dh_get_value_from_eeprom_buffer(DH_MAC0, enetaddr, sizeof(enetaddr), eip))
|
||||
goto out;
|
||||
|
||||
if (!dh_get_mac_from_eeprom(enetaddr, "eeprom0"))
|
||||
return eth_env_set_enetaddr("ethaddr", enetaddr);
|
||||
goto out;
|
||||
|
||||
return -ENXIO;
|
||||
|
||||
out:
|
||||
return eth_env_set_enetaddr("ethaddr", enetaddr);
|
||||
}
|
||||
|
||||
static int dh_stm32_setup_eth1addr(void)
|
||||
static int dh_stm32_setup_eth1addr(struct eeprom_id_page *eip)
|
||||
{
|
||||
unsigned char enetaddr[6];
|
||||
|
||||
@@ -148,20 +180,50 @@ static int dh_stm32_setup_eth1addr(void)
|
||||
if (dh_stm32_mac_is_in_ks8851())
|
||||
return 0;
|
||||
|
||||
if (!dh_get_mac_from_eeprom(enetaddr, "eeprom0")) {
|
||||
enetaddr[5]++;
|
||||
return eth_env_set_enetaddr("eth1addr", enetaddr);
|
||||
}
|
||||
if (!dh_stm32_get_mac_from_fuse(enetaddr, 1))
|
||||
goto out;
|
||||
|
||||
if (!dh_get_value_from_eeprom_buffer(DH_MAC1, enetaddr, sizeof(enetaddr), eip))
|
||||
goto out;
|
||||
|
||||
if (!dh_get_mac_from_eeprom(enetaddr, "eeprom0"))
|
||||
goto increment_out;
|
||||
|
||||
return -ENXIO;
|
||||
|
||||
increment_out:
|
||||
enetaddr[5]++;
|
||||
|
||||
out:
|
||||
return eth_env_set_enetaddr("eth1addr", enetaddr);
|
||||
}
|
||||
|
||||
int setup_mac_address(void)
|
||||
{
|
||||
if (dh_stm32_setup_ethaddr())
|
||||
u8 eeprom_buffer[DH_EEPROM_ID_PAGE_MAX_SIZE] = { 0 };
|
||||
struct eeprom_id_page *eip = (struct eeprom_id_page *)eeprom_buffer;
|
||||
int ret;
|
||||
|
||||
ret = dh_read_eeprom_id_page(eeprom_buffer, "eeprom0wl");
|
||||
if (ret) {
|
||||
/*
|
||||
* The EEPROM ID page is available on SoM rev. 200 and greater.
|
||||
* For SoM rev. 100 the return value will be -ENODEV. Suppress
|
||||
* the error message for that, because the absence cannot be
|
||||
* treated as an error.
|
||||
*/
|
||||
if (ret != -ENODEV)
|
||||
printf("%s: Cannot read valid data from EEPROM ID page! ret = %d\n",
|
||||
__func__, ret);
|
||||
eip = NULL;
|
||||
} else {
|
||||
dh_add_item_number_and_serial_to_env(eip);
|
||||
}
|
||||
|
||||
if (dh_stm32_setup_ethaddr(eip))
|
||||
log_err("%s: Unable to setup ethaddr!\n", __func__);
|
||||
|
||||
if (dh_stm32_setup_eth1addr())
|
||||
if (dh_stm32_setup_eth1addr(eip))
|
||||
log_err("%s: Unable to setup eth1addr!\n", __func__);
|
||||
|
||||
return 0;
|
||||
@@ -692,6 +754,34 @@ void board_quiesce_devices(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TARGET_DH_STM32MP13X
|
||||
enum env_location env_get_location(enum env_operation op, int prio)
|
||||
{
|
||||
u32 bootmode = get_bootmode();
|
||||
|
||||
if (prio)
|
||||
return ENVL_UNKNOWN;
|
||||
|
||||
switch (bootmode & TAMP_BOOT_DEVICE_MASK) {
|
||||
case BOOT_FLASH_SD:
|
||||
case BOOT_FLASH_EMMC:
|
||||
if (CONFIG_IS_ENABLED(ENV_IS_IN_MMC))
|
||||
return ENVL_MMC;
|
||||
else
|
||||
return ENVL_NOWHERE;
|
||||
|
||||
case BOOT_FLASH_NOR:
|
||||
if (CONFIG_IS_ENABLED(ENV_IS_IN_SPI_FLASH))
|
||||
return ENVL_SPI_FLASH;
|
||||
else
|
||||
return ENVL_NOWHERE;
|
||||
|
||||
default:
|
||||
return ENVL_NOWHERE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void dh_stm32_ks8851_fixup(void *blob)
|
||||
{
|
||||
struct gpio_desc ks8851intrn;
|
||||
|
||||
@@ -29,6 +29,7 @@ CONFIG_CMD_NVEDIT_EFI=y
|
||||
CONFIG_CMD_MEMINFO=y
|
||||
CONFIG_CMD_MEMTEST=y
|
||||
CONFIG_CMD_UNZIP=y
|
||||
CONFIG_CMD_ADC=y
|
||||
CONFIG_CMD_CLK=y
|
||||
CONFIG_CMD_FUSE=y
|
||||
CONFIG_CMD_GPIO=y
|
||||
@@ -56,6 +57,7 @@ CONFIG_ENV_REDUNDANT=y
|
||||
CONFIG_ENV_RELOC_GD_ENV_ADDR=y
|
||||
CONFIG_ENV_MMC_DEVICE_INDEX=-1
|
||||
CONFIG_ENV_MMC_USE_DT=y
|
||||
CONFIG_STM32_ADC=y
|
||||
CONFIG_SYS_64BIT_LBA=y
|
||||
CONFIG_BUTTON=y
|
||||
CONFIG_BUTTON_GPIO=y
|
||||
|
||||
@@ -8,7 +8,7 @@ CONFIG_ENV_OFFSET=0x3E0000
|
||||
CONFIG_DEFAULT_DEVICE_TREE="st/stm32mp135f-dhcor-dhsbc"
|
||||
CONFIG_STM32MP13X=y
|
||||
CONFIG_DDR_CACHEABLE_SIZE=0x8000000
|
||||
CONFIG_TARGET_ST_STM32MP13X=y
|
||||
CONFIG_TARGET_DH_STM32MP13X=y
|
||||
CONFIG_ENV_OFFSET_REDUND=0x3F0000
|
||||
CONFIG_STM32MP15_PWR=y
|
||||
CONFIG_ARMV7_NONSEC=y
|
||||
|
||||
@@ -91,6 +91,9 @@ CONFIG_SPI=y
|
||||
CONFIG_DM_SPI=y
|
||||
CONFIG_STM32_OSPI=y
|
||||
# CONFIG_OPTEE_TA_AVB is not set
|
||||
CONFIG_VIDEO=y
|
||||
CONFIG_VIDEO_STM32=y
|
||||
CONFIG_VIDEO_STM32_LVDS=y
|
||||
CONFIG_WDT=y
|
||||
CONFIG_WDT_STM32MP=y
|
||||
CONFIG_WDT_ARM_SMC=y
|
||||
|
||||
@@ -25,6 +25,7 @@ kernel binding directory = Documentation/devicetree/bindings/
|
||||
* display
|
||||
- display/st,stm32-dsi.yaml
|
||||
- display/st,stm32-ltdc.yaml
|
||||
- display/st,stm32mp25-lvds.yaml
|
||||
* gpio
|
||||
- pinctrl/st,stm32-pinctrl.yaml
|
||||
* hwlock
|
||||
|
||||
@@ -200,6 +200,7 @@ err_aclk_disable:
|
||||
static const struct udevice_id stm32_adc_core_ids[] = {
|
||||
{ .compatible = "st,stm32h7-adc-core" },
|
||||
{ .compatible = "st,stm32mp1-adc-core" },
|
||||
{ .compatible = "st,stm32mp13-adc-core" },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -49,29 +49,68 @@
|
||||
/* STM32H7_ADC_SQR1 - bit fields */
|
||||
#define STM32H7_SQ1_SHIFT 6
|
||||
|
||||
/* STM32H7_ADC_DIFSEL - bit fields */
|
||||
#define STM32H7_DIFSEL_SHIFT 0
|
||||
#define STM32H7_DIFSEL_MASK GENMASK(19, 0)
|
||||
|
||||
/* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */
|
||||
#define STM32H7_BOOST_CLKRATE 20000000UL
|
||||
|
||||
/* STM32MP13 - Registers for each ADC instance */
|
||||
#define STM32MP13_ADC_DIFSEL 0xB0
|
||||
|
||||
/* STM32MP13_ADC_CFGR specific bit fields */
|
||||
#define STM32MP13_DMAEN BIT(0)
|
||||
#define STM32MP13_DMACFG BIT(1)
|
||||
|
||||
/* STM32MP13_ADC_DIFSEL - bit fields */
|
||||
#define STM32MP13_DIFSEL_SHIFT 0
|
||||
#define STM32MP13_DIFSEL_MASK GENMASK(18, 0)
|
||||
|
||||
#define STM32_ADC_CH_MAX 20 /* max number of channels */
|
||||
#define STM32_ADC_TIMEOUT_US 100000
|
||||
|
||||
struct stm32_adc_cfg {
|
||||
unsigned int max_channels;
|
||||
unsigned int num_bits;
|
||||
bool has_vregready;
|
||||
};
|
||||
|
||||
struct stm32_adc {
|
||||
void __iomem *regs;
|
||||
int active_channel;
|
||||
const struct stm32_adc_cfg *cfg;
|
||||
};
|
||||
|
||||
struct stm32_adc_regs {
|
||||
int reg;
|
||||
int mask;
|
||||
int shift;
|
||||
};
|
||||
|
||||
struct stm32_adc_regspec {
|
||||
const struct stm32_adc_regs difsel;
|
||||
};
|
||||
|
||||
struct stm32_adc_cfg {
|
||||
const struct stm32_adc_regspec *regs;
|
||||
unsigned int max_channels;
|
||||
unsigned int num_bits;
|
||||
bool has_vregready;
|
||||
bool has_boostmode;
|
||||
bool has_linearcal;
|
||||
bool has_presel;
|
||||
};
|
||||
|
||||
static const struct stm32_adc_regspec stm32h7_adc_regspec = {
|
||||
.difsel = { STM32H7_ADC_DIFSEL, STM32H7_DIFSEL_MASK },
|
||||
};
|
||||
|
||||
static const struct stm32_adc_regspec stm32mp13_adc_regspec = {
|
||||
.difsel = { STM32MP13_ADC_DIFSEL, STM32MP13_DIFSEL_MASK },
|
||||
};
|
||||
|
||||
static void stm32_adc_enter_pwr_down(struct udevice *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
|
||||
if (adc->cfg->has_boostmode)
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
|
||||
|
||||
/* Setting DEEPPWD disables ADC vreg and clears ADVREGEN */
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD);
|
||||
}
|
||||
@@ -90,8 +129,7 @@ static int stm32_adc_exit_pwr_down(struct udevice *dev)
|
||||
/* Exit deep power down, then enable ADC voltage regulator */
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD);
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADVREGEN);
|
||||
|
||||
if (common->rate > STM32H7_BOOST_CLKRATE)
|
||||
if (adc->cfg->has_boostmode && common->rate > STM32H7_BOOST_CLKRATE)
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
|
||||
|
||||
/* Wait for startup time */
|
||||
@@ -134,7 +172,7 @@ static int stm32_adc_start_channel(struct udevice *dev, int channel)
|
||||
return ret;
|
||||
|
||||
/* Only use single ended channels */
|
||||
writel(0, adc->regs + STM32H7_ADC_DIFSEL);
|
||||
clrbits_le32(adc->regs + adc->cfg->regs->difsel.reg, adc->cfg->regs->difsel.mask);
|
||||
|
||||
/* Enable ADC, Poll for ADRDY to be set (after adc startup time) */
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADEN);
|
||||
@@ -147,7 +185,8 @@ static int stm32_adc_start_channel(struct udevice *dev, int channel)
|
||||
}
|
||||
|
||||
/* Preselect channels */
|
||||
writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL);
|
||||
if (adc->cfg->has_presel)
|
||||
writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL);
|
||||
|
||||
/* Set sampling time to max value by default */
|
||||
writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR1);
|
||||
@@ -156,9 +195,11 @@ static int stm32_adc_start_channel(struct udevice *dev, int channel)
|
||||
/* Program regular sequence: chan in SQ1 & len = 0 for one channel */
|
||||
writel(channel << STM32H7_SQ1_SHIFT, adc->regs + STM32H7_ADC_SQR1);
|
||||
|
||||
/* Trigger detection disabled (conversion can be launched in SW) */
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN |
|
||||
STM32H7_DMNGT);
|
||||
/*
|
||||
* Trigger detection disabled (conversion can be launched in SW)
|
||||
* STM32H7_DMNGT is equivalent to STM32MP13_DMAEN & STM32MP13_DMACFG
|
||||
*/
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN | STM32H7_DMNGT);
|
||||
adc->active_channel = channel;
|
||||
|
||||
return 0;
|
||||
@@ -206,7 +247,7 @@ static int stm32_adc_selfcalib(struct udevice *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
int ret;
|
||||
u32 val;
|
||||
u32 val, mask;
|
||||
|
||||
/*
|
||||
* Select calibration mode:
|
||||
@@ -231,7 +272,10 @@ static int stm32_adc_selfcalib(struct udevice *dev)
|
||||
* - Linearity calibration (needs to be done only once for single/diff)
|
||||
* will run simultaneously with offset calibration.
|
||||
*/
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCALDIF | STM32H7_ADCALLIN);
|
||||
mask = STM32H7_ADCALDIF;
|
||||
if (adc->cfg->has_linearcal)
|
||||
mask |= STM32H7_ADCALLIN;
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, mask);
|
||||
|
||||
/* Start calibration, then wait for completion */
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADCAL);
|
||||
@@ -394,14 +438,28 @@ static const struct adc_ops stm32_adc_ops = {
|
||||
};
|
||||
|
||||
static const struct stm32_adc_cfg stm32h7_adc_cfg = {
|
||||
.regs = &stm32h7_adc_regspec,
|
||||
.num_bits = 16,
|
||||
.max_channels = STM32_ADC_CH_MAX,
|
||||
.has_boostmode = true,
|
||||
.has_linearcal = true,
|
||||
.has_presel = true,
|
||||
};
|
||||
|
||||
static const struct stm32_adc_cfg stm32mp1_adc_cfg = {
|
||||
.regs = &stm32h7_adc_regspec,
|
||||
.num_bits = 16,
|
||||
.max_channels = STM32_ADC_CH_MAX,
|
||||
.has_vregready = true,
|
||||
.has_boostmode = true,
|
||||
.has_linearcal = true,
|
||||
.has_presel = true,
|
||||
};
|
||||
|
||||
static const struct stm32_adc_cfg stm32mp13_adc_cfg = {
|
||||
.regs = &stm32mp13_adc_regspec,
|
||||
.num_bits = 12,
|
||||
.max_channels = STM32_ADC_CH_MAX - 1,
|
||||
};
|
||||
|
||||
static const struct udevice_id stm32_adc_ids[] = {
|
||||
@@ -409,6 +467,8 @@ static const struct udevice_id stm32_adc_ids[] = {
|
||||
.data = (ulong)&stm32h7_adc_cfg },
|
||||
{ .compatible = "st,stm32mp1-adc",
|
||||
.data = (ulong)&stm32mp1_adc_cfg },
|
||||
{ .compatible = "st,stm32mp13-adc",
|
||||
.data = (ulong)&stm32mp13_adc_cfg },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -1221,13 +1221,16 @@ int ofnode_decode_display_timing(ofnode parent, int index,
|
||||
int ret = 0;
|
||||
|
||||
timings = ofnode_find_subnode(parent, "display-timings");
|
||||
if (!ofnode_valid(timings))
|
||||
return -EINVAL;
|
||||
|
||||
i = 0;
|
||||
ofnode_for_each_subnode(node, timings) {
|
||||
if (i++ == index)
|
||||
break;
|
||||
if (ofnode_valid(timings)) {
|
||||
i = 0;
|
||||
ofnode_for_each_subnode(node, timings) {
|
||||
if (i++ == index)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (index != 0)
|
||||
return -EINVAL;
|
||||
node = ofnode_find_subnode(parent, "panel-timing");
|
||||
}
|
||||
|
||||
if (!ofnode_valid(node))
|
||||
|
||||
@@ -191,6 +191,7 @@ static const struct mipi_dsi_panel_plat panasonic_vvx10f004b00 = {
|
||||
|
||||
static const struct udevice_id simple_panel_ids[] = {
|
||||
{ .compatible = "simple-panel" },
|
||||
{ .compatible = "panel-lvds" },
|
||||
{ .compatible = "auo,b133xtn01" },
|
||||
{ .compatible = "auo,b116xw03" },
|
||||
{ .compatible = "auo,b133htn01" },
|
||||
|
||||
@@ -23,6 +23,15 @@ config VIDEO_STM32_DSI
|
||||
This option enables support DSI internal bridge which can be used on
|
||||
devices which have DSI devices connected.
|
||||
|
||||
config VIDEO_STM32_LVDS
|
||||
bool "Enable STM32 LVDS video support"
|
||||
depends on VIDEO_STM32
|
||||
select VIDEO_BRIDGE
|
||||
select VIDEO_DW_MIPI_DSI
|
||||
help
|
||||
This enables Low Voltage Differential Signaling (LVDS) display
|
||||
support.
|
||||
|
||||
config VIDEO_STM32_MAX_XRES
|
||||
int "Maximum horizontal resolution (for memory allocation purposes)"
|
||||
depends on VIDEO_STM32
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
|
||||
obj-${CONFIG_VIDEO_STM32} = stm32_ltdc.o
|
||||
obj-${CONFIG_VIDEO_STM32_DSI} += stm32_dsi.o
|
||||
obj-${CONFIG_VIDEO_STM32_LVDS} += stm32_lvds.o
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <video_bridge.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/printk.h>
|
||||
@@ -262,6 +263,7 @@ static const u32 layer_regs_a2[] = {
|
||||
#define HWVER_10300 0x010300
|
||||
#define HWVER_20101 0x020101
|
||||
#define HWVER_40100 0x040100
|
||||
#define HWVER_40101 0x040101
|
||||
|
||||
enum stm32_ltdc_pix_fmt {
|
||||
PF_ARGB8888 = 0, /* ARGB [32 bits] */
|
||||
@@ -494,6 +496,101 @@ static void stm32_ltdc_set_layer1(struct stm32_ltdc_priv *priv, ulong fb_addr)
|
||||
setbits_le32(priv->regs + LTDC_L1CR, LXCR_LEN);
|
||||
}
|
||||
|
||||
static int stm32_ltdc_get_remote_device(struct udevice *dev, ofnode ep_node,
|
||||
enum uclass_id id, struct udevice **remote_dev)
|
||||
{
|
||||
u32 remote_phandle;
|
||||
ofnode remote;
|
||||
int ret = 0;
|
||||
|
||||
ret = ofnode_read_u32(ep_node, "remote-endpoint", &remote_phandle);
|
||||
if (ret) {
|
||||
dev_err(dev, "%s(%s): Could not find remote-endpoint property\n",
|
||||
__func__, dev_read_name(dev));
|
||||
return ret;
|
||||
}
|
||||
|
||||
remote = ofnode_get_by_phandle(remote_phandle);
|
||||
if (!ofnode_valid(remote))
|
||||
return -EINVAL;
|
||||
|
||||
while (ofnode_valid(remote)) {
|
||||
remote = ofnode_get_parent(remote);
|
||||
if (!ofnode_valid(remote)) {
|
||||
dev_dbg(dev, "%s(%s): no uclass_id %d for remote-endpoint\n",
|
||||
__func__, dev_read_name(dev), id);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = uclass_find_device_by_ofnode(id, remote, remote_dev);
|
||||
if (*remote_dev && !ret) {
|
||||
ret = uclass_get_device_by_ofnode(id, remote, remote_dev);
|
||||
if (ret)
|
||||
dev_dbg(dev, "%s(%s): failed to get remote device %s\n",
|
||||
__func__, dev_read_name(dev), dev_read_name(*remote_dev));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_ltdc_get_panel(struct udevice *dev, struct udevice **panel)
|
||||
{
|
||||
ofnode ep_node, node, ports;
|
||||
int ret = 0;
|
||||
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
ports = ofnode_find_subnode(dev_ofnode(dev), "ports");
|
||||
if (!ofnode_valid(ports)) {
|
||||
dev_err(dev, "Remote bridge subnode\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (node = ofnode_first_subnode(ports);
|
||||
ofnode_valid(node);
|
||||
node = dev_read_next_subnode(node)) {
|
||||
ep_node = ofnode_first_subnode(node);
|
||||
if (!ofnode_valid(ep_node))
|
||||
continue;
|
||||
|
||||
ret = stm32_ltdc_get_remote_device(dev, ep_node, UCLASS_PANEL, panel);
|
||||
}
|
||||
|
||||
/* Sanity check, we can get out of the loop without having a clean ofnode */
|
||||
if (!(*panel))
|
||||
ret = -EINVAL;
|
||||
else
|
||||
if (!ofnode_valid(dev_ofnode(*panel)))
|
||||
ret = -EINVAL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_ltdc_display_init(struct udevice *dev, ofnode *ep_node,
|
||||
struct udevice **panel, struct udevice **bridge)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (*panel)
|
||||
return -EINVAL;
|
||||
|
||||
if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
|
||||
ret = stm32_ltdc_get_remote_device(dev, *ep_node, UCLASS_VIDEO_BRIDGE, bridge);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = stm32_ltdc_get_panel(*bridge, panel);
|
||||
} else {
|
||||
/* no bridge, search a panel from display controller node */
|
||||
ret = stm32_ltdc_get_remote_device(dev, *ep_node, UCLASS_PANEL, panel);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_TARGET_STM32F469_DISCOVERY)
|
||||
static int stm32_ltdc_alloc_fb(struct udevice *dev)
|
||||
{
|
||||
@@ -529,8 +626,9 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
struct udevice *bridge = NULL;
|
||||
struct udevice *panel = NULL;
|
||||
struct display_timing timings;
|
||||
struct clk pclk;
|
||||
struct clk pclk, bclk;
|
||||
struct reset_ctl rst;
|
||||
ofnode node, port;
|
||||
ulong rate;
|
||||
int ret;
|
||||
|
||||
@@ -540,7 +638,21 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &pclk);
|
||||
ret = clk_get_by_name(dev, "bus", &bclk);
|
||||
if (ret) {
|
||||
if (ret != -ENODATA) {
|
||||
dev_err(dev, "bus clock get error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
ret = clk_enable(&bclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "bus clock enable error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(dev, "lcd", &pclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "peripheral clock get error %d\n", ret);
|
||||
return ret;
|
||||
@@ -553,7 +665,7 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
}
|
||||
|
||||
priv->hw_version = readl(priv->regs + LTDC_IDR);
|
||||
debug("%s: LTDC hardware 0x%x\n", __func__, priv->hw_version);
|
||||
dev_dbg(dev, "%s: LTDC hardware 0x%x\n", __func__, priv->hw_version);
|
||||
|
||||
switch (priv->hw_version) {
|
||||
case HWVER_10200:
|
||||
@@ -566,6 +678,7 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
priv->pix_fmt_hw = pix_fmt_a1;
|
||||
break;
|
||||
case HWVER_40100:
|
||||
case HWVER_40101:
|
||||
priv->layer_regs = layer_regs_a2;
|
||||
priv->pix_fmt_hw = pix_fmt_a2;
|
||||
break;
|
||||
@@ -573,13 +686,35 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = uclass_first_device_err(UCLASS_PANEL, &panel);
|
||||
if (ret) {
|
||||
if (ret != -ENODEV)
|
||||
dev_err(dev, "panel device error %d\n", ret);
|
||||
return ret;
|
||||
/*
|
||||
* Try all the ports until one working.
|
||||
*
|
||||
* This is done in two times. First is checks for the
|
||||
* UCLASS_VIDEO_BRIDGE available, and then for this bridge
|
||||
* it scans for a UCLASS_PANEL.
|
||||
*/
|
||||
|
||||
port = dev_read_subnode(dev, "port");
|
||||
if (!ofnode_valid(port)) {
|
||||
dev_err(dev, "%s(%s): 'port' subnode not found\n",
|
||||
__func__, dev_read_name(dev));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (node = ofnode_first_subnode(port);
|
||||
ofnode_valid(node);
|
||||
node = dev_read_next_subnode(node)) {
|
||||
ret = stm32_ltdc_display_init(dev, &node, &panel, &bridge);
|
||||
if (ret)
|
||||
dev_dbg(dev, "Device failed ret=%d\n", ret);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/* Sanity check */
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = panel_get_display_timing(panel, &timings);
|
||||
if (ret) {
|
||||
ret = ofnode_decode_display_timing(dev_ofnode(panel),
|
||||
@@ -608,11 +743,6 @@ static int stm32_ltdc_probe(struct udevice *dev)
|
||||
reset_deassert(&rst);
|
||||
|
||||
if (IS_ENABLED(CONFIG_VIDEO_BRIDGE)) {
|
||||
ret = uclass_get_device(UCLASS_VIDEO_BRIDGE, 0, &bridge);
|
||||
if (ret)
|
||||
dev_dbg(dev,
|
||||
"No video bridge, or no backlight on bridge\n");
|
||||
|
||||
if (bridge) {
|
||||
ret = video_bridge_attach(bridge);
|
||||
if (ret) {
|
||||
@@ -688,6 +818,8 @@ static int stm32_ltdc_bind(struct udevice *dev)
|
||||
|
||||
static const struct udevice_id stm32_ltdc_ids[] = {
|
||||
{ .compatible = "st,stm32-ltdc" },
|
||||
{ .compatible = "st,stm32mp251-ltdc" },
|
||||
{ .compatible = "st,stm32mp255-ltdc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
693
drivers/video/stm32/stm32_lvds.c
Normal file
693
drivers/video/stm32/stm32_lvds.c
Normal file
@@ -0,0 +1,693 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later OR BSD-3-Clause
|
||||
/*
|
||||
* Copyright (C) 2025 STMicroelectronics - All Rights Reserved
|
||||
* Author(s): Raphaël Gallais-Pou <raphael.gallais-pou@foss.st.com> for STMicroelectronics.
|
||||
*
|
||||
* This Low Voltage Differential Signal controller driver is based on the Linux Kernel driver from
|
||||
* drivers/gpu/drm/stm/ltdc.c
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_VIDEO_BRIDGE
|
||||
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <log.h>
|
||||
#include <media_bus_format.h>
|
||||
#include <panel.h>
|
||||
#include <reset.h>
|
||||
#include <video.h>
|
||||
#include <video_bridge.h>
|
||||
#include <asm/io.h>
|
||||
#include <dm/device_compat.h>
|
||||
#include <dm/ofnode.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
/* LVDS Host registers */
|
||||
#define LVDS_CR 0x0000 /* configuration register */
|
||||
#define LVDS_DMLCR0 0x0004 /* data mapping lsb configuration register 0 */
|
||||
#define LVDS_DMMCR0 0x0008 /* data mapping msb configuration register 0 */
|
||||
#define LVDS_DMLCR1 0x000C /* data mapping lsb configuration register 1 */
|
||||
#define LVDS_DMMCR1 0x0010 /* data mapping msb configuration register 1 */
|
||||
#define LVDS_DMLCR2 0x0014 /* data mapping lsb configuration register 2 */
|
||||
#define LVDS_DMMCR2 0x0018 /* data mapping msb configuration register 2 */
|
||||
#define LVDS_DMLCR3 0x001C /* data mapping lsb configuration register 3 */
|
||||
#define LVDS_DMMCR3 0x0020 /* data mapping msb configuration register 3 */
|
||||
#define LVDS_DMLCR4 0x0024 /* data mapping lsb configuration register 4 */
|
||||
#define LVDS_DMMCR4 0x0028 /* data mapping msb configuration register 4 */
|
||||
#define LVDS_DMLCR(id) (LVDS_DMLCR0 + 8U * (id))
|
||||
#define LVDS_DMMCR(id) (LVDS_DMMCR0 + 8U * (id))
|
||||
#define LVDS_CDL1CR 0x002C /* channel distrib link 1 configuration register */
|
||||
#define LVDS_CDL2CR 0x0030 /* channel distrib link 2 configuration register */
|
||||
|
||||
#define CDL1CR_DEFAULT 0x4321
|
||||
#define CDL2CR_DEFAULT 0x59876
|
||||
|
||||
/* LVDS Host registers */
|
||||
#define LVDS_PHY_MASTER 0x0
|
||||
#define LVDS_PHY_SLAVE 0x100
|
||||
|
||||
/* phy parameter can only be one of those two above */
|
||||
#define LVDS_PXGCR(phy) ((phy) + 0x1000) /* Global Control Register */
|
||||
#define LVDS_PXCMCR1(phy) ((phy) + 0x100C) /* Current Mode Control Register 1 */
|
||||
#define LVDS_PXCMCR2(phy) ((phy) + 0x1010) /* Current Mode Control Register 2 */
|
||||
#define LVDS_PXSCR(phy) ((phy) + 0x1020) /* Serial Control Register */
|
||||
#define LVDS_PXBCR1(phy) ((phy) + 0x102C) /* Bias Control Register 1 */
|
||||
#define LVDS_PXBCR2(phy) ((phy) + 0x1030) /* Bias Control Register 2 */
|
||||
#define LVDS_PXBCR3(phy) ((phy) + 0x1034) /* Bias Control Register 3 */
|
||||
#define LVDS_PXMPLCR(phy) ((phy) + 0x1064) /* Monitor PLL Lock Control Register */
|
||||
#define LVDS_PXDCR(phy) ((phy) + 0x1084) /* Debug Control Register */
|
||||
#define LVDS_PXSSR1(phy) ((phy) + 0x1088) /* Spare Status Register 1 */
|
||||
#define LVDS_PXCFGCR(phy) ((phy) + 0x10A0) /* Configuration Control Register */
|
||||
#define LVDS_PXPLLCR1(phy) ((phy) + 0x10C0) /* PLL_MODE 1 Control Register */
|
||||
#define LVDS_PXPLLCR2(phy) ((phy) + 0x10C4) /* PLL_MODE 2 Control Register */
|
||||
#define LVDS_PXPLLSR(phy) ((phy) + 0x10C8) /* PLL Status Register */
|
||||
#define LVDS_PXPLLSDCR1(phy) ((phy) + 0x10CC) /* PLL_SD_1 Control Register */
|
||||
#define LVDS_PXPLLSDCR2(phy) ((phy) + 0x10D0) /* PLL_SD_2 Control Register */
|
||||
#define LVDS_PXPLLTWGCR1(phy) ((phy) + 0x10D4) /* PLL_TWG_1 Control Register */
|
||||
#define LVDS_PXPLLTWGCR2(phy) ((phy) + 0x10D8) /* PLL_TWG_2 Control Register */
|
||||
#define LVDS_PXPLLCPCR(phy) ((phy) + 0x10E0) /* PLL_CP Control Register */
|
||||
#define LVDS_PXPLLTESTCR(phy) ((phy) + 0x10E8) /* PLL_TEST Control Register */
|
||||
|
||||
/* LVDS Wrapper registers */
|
||||
#define LVDS_WCLKCR 0x11B0 /* Wrapper clock control register */
|
||||
#define LVDS_HWCFGR 0x1FF0 /* HW configuration register */
|
||||
#define LVDS_VERR 0x1FF4 /* Version register */
|
||||
#define LVDS_IPIDR 0x1FF8 /* Identification register */
|
||||
#define LVDS_SIDR 0x1FFC /* Size Identification register */
|
||||
|
||||
#define CR_LVDSEN BIT(0) /* LVDS PHY Enable */
|
||||
#define CR_HSPOL BIT(1) /* HS Polarity (horizontal sync) */
|
||||
#define CR_VSPOL BIT(2) /* VS Polarity (vertical sync) */
|
||||
#define CR_DEPOL BIT(3) /* DE Polarity (data enable) */
|
||||
#define CR_CI BIT(4) /* Control Internal (software controlled bit) */
|
||||
#define CR_LKMOD BIT(5) /* Link Mode, for both Links */
|
||||
#define CR_LKPHA BIT(6) /* Link Phase, for both Links */
|
||||
#define CR_LK1POL GENMASK(20, 16) /* Link-1 output Polarity */
|
||||
#define CR_LK2POL GENMASK(25, 21) /* Link-2 output Polarity */
|
||||
|
||||
#define DMMCRX_MAP0 GENMASK(4, 0)
|
||||
#define DMMCRX_MAP1 GENMASK(9, 5)
|
||||
#define DMMCRX_MAP2 GENMASK(14, 10)
|
||||
#define DMMCRX_MAP3 GENMASK(19, 15)
|
||||
#define DMLCRX_MAP4 GENMASK(4, 0)
|
||||
#define DMLCRX_MAP5 GENMASK(9, 5)
|
||||
#define DMLCRX_MAP6 GENMASK(14, 10)
|
||||
|
||||
#define CDLCRX_DISTR0 GENMASK(3, 0)
|
||||
#define CDLCRX_DISTR1 GENMASK(7, 4)
|
||||
#define CDLCRX_DISTR2 GENMASK(11, 8)
|
||||
#define CDLCRX_DISTR3 GENMASK(15, 12)
|
||||
#define CDLCRX_DISTR4 GENMASK(19, 16)
|
||||
|
||||
#define FREF_INDEX 0
|
||||
#define NDIV_INDEX 1
|
||||
#define FPFD_INDEX 2
|
||||
#define MDIV_INDEX 3
|
||||
#define FVCO_INDEX 4
|
||||
#define BDIV_INDEX 5
|
||||
#define FBIT_INDEX 6
|
||||
#define FLS_INDEX 7
|
||||
#define FDP_INDEX 8
|
||||
|
||||
#define PXGCR_BIT_CLK_OUT BIT(0)
|
||||
#define PXGCR_LS_CLK_OUT BIT(4)
|
||||
#define PXGCR_DP_CLK_OUT BIT(8)
|
||||
#define PXGCR_RSTZ BIT(24)
|
||||
#define PXGCR_DIV_RSTN BIT(25)
|
||||
|
||||
#define PXCMCR1_CM_EN_DL (BIT(28) | BIT(20) | BIT(12) | BIT(4))
|
||||
#define PXCMCR2_CM_EN_DL4 BIT(4)
|
||||
#define PXSCR_SER_DATA_OK BIT(16)
|
||||
#define PXBCR1_EN_BIAS_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
|
||||
#define PXBCR2_BIAS_EN BIT(28)
|
||||
#define PXBCR3_VM_EN_DL (BIT(16) | BIT(12) | BIT(8) | BIT(4) | BIT(0))
|
||||
#define PXDCR_POWER_OK BIT(12)
|
||||
#define PXCFGCR_EN_DIG_DL GENMASK(4, 0)
|
||||
|
||||
#define PXPLLCR1_PLL_EN BIT(0)
|
||||
#define PxPLLCR1_SD_EN BIT(1)
|
||||
#define PXPLLCR1_TWG_EN BIT(2)
|
||||
#define PXPLLCR1_PLL_DIVIDERS_EN BIT(8)
|
||||
#define PXPLLCR2_NDIV GENMASK(25, 16)
|
||||
#define PXPLLCR2_BDIV GENMASK(9, 0)
|
||||
#define PXPLLSR_PLL_LOCK BIT(0)
|
||||
#define PXPLLSDCR1_MDIV GENMASK(9, 0)
|
||||
#define PXPLLCPCR_CPCTRL_DEFAULT 0x1
|
||||
#define PXPLLTESTCR_PLL_TEST_CLK_EN BIT(0)
|
||||
#define PXPLLTESTCR_PLL_TDIV_EN BIT(8)
|
||||
#define PXPLLTESTCR_TDIV GENMASK(25, 16)
|
||||
#define PXPLLTESTCR_TDIV_VALUE 70
|
||||
|
||||
#define WCLKCR_SLV_CLKPIX_SEL BIT(0)
|
||||
#define WCLKCR_SRCSEL BIT(8)
|
||||
|
||||
/* Sleep & timeout for pll lock/unlock */
|
||||
#define SLEEP_US 1000
|
||||
#define TIMEOUT_US 20000000
|
||||
|
||||
#define PHY_SLV_OFS 0x100
|
||||
|
||||
/* PLL parameters */
|
||||
#define NDIV_MIN 2
|
||||
#define NDIV_MAX 6
|
||||
#define BDIV_MIN 2
|
||||
#define BDIV_MAX 6
|
||||
#define MDIV_MIN 1
|
||||
#define MDIV_MAX 1023
|
||||
|
||||
struct stm32_lvds_plat {
|
||||
void __iomem *base;
|
||||
struct udevice *panel;
|
||||
struct reset_ctl rst;
|
||||
struct clk pclk;
|
||||
struct clk refclk;
|
||||
};
|
||||
|
||||
struct stm32_lvds_priv {
|
||||
struct display_timing timings;
|
||||
u32 refclk_rate;
|
||||
int dual_link;
|
||||
int bus_format;
|
||||
};
|
||||
|
||||
/*
|
||||
* enum lvds_pixels_order - Pixel order of an LVDS connection
|
||||
* @LVDS_DUAL_LINK_EVEN_ODD_PIXELS: Even pixels are expected to be generated
|
||||
* from the first port, odd pixels from the second port
|
||||
* @LVDS_DUAL_LINK_ODD_EVEN_PIXELS: Odd pixels are expected to be generated
|
||||
* from the first port, even pixels from the second port
|
||||
*/
|
||||
enum lvds_pixels_order {
|
||||
LVDS_DUAL_LINK_EVEN_ODD_PIXELS = BIT(0),
|
||||
LVDS_DUAL_LINK_ODD_EVEN_PIXELS = BIT(1),
|
||||
};
|
||||
|
||||
enum lvds_pixel {
|
||||
PIX_R_0 = 0x00,
|
||||
PIX_R_1 = 0x01,
|
||||
PIX_R_2 = 0x02,
|
||||
PIX_R_3 = 0x03,
|
||||
PIX_R_4 = 0x04,
|
||||
PIX_R_5 = 0x05,
|
||||
PIX_R_6 = 0x06,
|
||||
PIX_R_7 = 0x07,
|
||||
PIX_G_0 = 0x08,
|
||||
PIX_G_1 = 0x09,
|
||||
PIX_G_2 = 0x0A,
|
||||
PIX_G_3 = 0x0B,
|
||||
PIX_G_4 = 0x0C,
|
||||
PIX_G_5 = 0x0D,
|
||||
PIX_G_6 = 0x0E,
|
||||
PIX_G_7 = 0x0F,
|
||||
PIX_B_0 = 0x10,
|
||||
PIX_B_1 = 0x11,
|
||||
PIX_B_2 = 0x12,
|
||||
PIX_B_3 = 0x13,
|
||||
PIX_B_4 = 0x14,
|
||||
PIX_B_5 = 0x15,
|
||||
PIX_B_6 = 0x16,
|
||||
PIX_B_7 = 0x17,
|
||||
PIX_H_S = 0x18,
|
||||
PIX_V_S = 0x19,
|
||||
PIX_D_E = 0x1A,
|
||||
PIX_C_E = 0x1B,
|
||||
PIX_C_I = 0x1C,
|
||||
PIX_TOG = 0x1D,
|
||||
PIX_ONE = 0x1E,
|
||||
PIX_ZER = 0x1F,
|
||||
};
|
||||
|
||||
/*
|
||||
* Expected JEIDA-RGB888 data to be sent in LSB format
|
||||
* bit6 ............................bit0
|
||||
*/
|
||||
const enum lvds_pixel lvds_bitmap_jeida_rgb888[5][7] = {
|
||||
{ PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE },
|
||||
{ PIX_G_2, PIX_R_7, PIX_R_6, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2 },
|
||||
{ PIX_B_3, PIX_B_2, PIX_G_7, PIX_G_6, PIX_G_5, PIX_G_4, PIX_G_3 },
|
||||
{ PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_7, PIX_B_6, PIX_B_5, PIX_B_4 },
|
||||
{ PIX_C_E, PIX_B_1, PIX_B_0, PIX_G_1, PIX_G_0, PIX_R_1, PIX_R_0 }
|
||||
};
|
||||
|
||||
/*
|
||||
* Expected VESA-RGB888 data to be sent in LSB format
|
||||
* bit6 ............................bit0
|
||||
*/
|
||||
const enum lvds_pixel lvds_bitmap_vesa_rgb888[5][7] = {
|
||||
{ PIX_ONE, PIX_ONE, PIX_ZER, PIX_ZER, PIX_ZER, PIX_ONE, PIX_ONE },
|
||||
{ PIX_G_0, PIX_R_5, PIX_R_4, PIX_R_3, PIX_R_2, PIX_R_1, PIX_R_0 },
|
||||
{ PIX_B_1, PIX_B_0, PIX_G_5, PIX_G_4, PIX_G_3, PIX_G_2, PIX_G_1 },
|
||||
{ PIX_D_E, PIX_V_S, PIX_H_S, PIX_B_5, PIX_B_4, PIX_B_3, PIX_B_2 },
|
||||
{ PIX_C_E, PIX_B_7, PIX_B_6, PIX_G_7, PIX_G_6, PIX_R_7, PIX_R_6 }
|
||||
};
|
||||
|
||||
static inline void lvds_writel(void __iomem *base, u32 reg, u32 val)
|
||||
{
|
||||
writel(val, base + reg);
|
||||
}
|
||||
|
||||
static inline u32 lvds_readl(void __iomem *base, u32 reg)
|
||||
{
|
||||
return readl(base + reg);
|
||||
}
|
||||
|
||||
static inline void lvds_set(void __iomem *base, u32 reg, u32 mask)
|
||||
{
|
||||
lvds_writel(base, reg, lvds_readl(base, reg) | mask);
|
||||
}
|
||||
|
||||
static inline void lvds_clear(void __iomem *base, u32 reg, u32 mask)
|
||||
{
|
||||
lvds_writel(base, reg, lvds_readl(base, reg) & ~mask);
|
||||
}
|
||||
|
||||
static u32 pll_get_clkout_khz(u32 clkin_khz, u32 bdiv, u32 mdiv, u32 ndiv)
|
||||
{
|
||||
int divisor = ndiv * bdiv;
|
||||
|
||||
/* Prevents from division by 0 */
|
||||
if (!divisor)
|
||||
return 0;
|
||||
|
||||
return clkin_khz * mdiv / divisor;
|
||||
}
|
||||
|
||||
static int lvds_pll_get_params(u32 clkin_khz, u32 clkout_khz,
|
||||
u32 *bdiv, u32 *mdiv, u32 *ndiv)
|
||||
{
|
||||
u32 i, o, n;
|
||||
u32 delta, best_delta; /* all in khz */
|
||||
|
||||
/* Early checks preventing division by 0 & odd results */
|
||||
if (clkin_khz == 0 || clkout_khz == 0)
|
||||
return -EINVAL;
|
||||
|
||||
best_delta = 1000000; /* big started value (1000000khz) */
|
||||
|
||||
for (i = NDIV_MIN; i <= NDIV_MAX; i++) {
|
||||
for (o = BDIV_MIN; o <= BDIV_MAX; o++) {
|
||||
n = DIV_ROUND_CLOSEST(i * o * clkout_khz, clkin_khz);
|
||||
/* Check ndiv according to vco range */
|
||||
if (n < MDIV_MIN || n > MDIV_MAX)
|
||||
continue;
|
||||
/* Check if new delta is better & saves parameters */
|
||||
delta = abs(pll_get_clkout_khz(clkin_khz, i, n, o) - clkout_khz);
|
||||
if (delta < best_delta) {
|
||||
*ndiv = i;
|
||||
*mdiv = n;
|
||||
*bdiv = o;
|
||||
best_delta = delta;
|
||||
}
|
||||
/* fast return in case of "perfect result" */
|
||||
if (!delta)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_lvds_pll_enable(struct udevice *dev,
|
||||
int phy)
|
||||
{
|
||||
struct stm32_lvds_plat *plat = dev_get_plat(dev);
|
||||
struct stm32_lvds_priv *priv = dev_get_priv(dev);
|
||||
struct display_timing timings = priv->timings;
|
||||
u32 pll_in_khz, bdiv = 0, mdiv = 0, ndiv = 0;
|
||||
int ret, val, multiplier;
|
||||
|
||||
/* Release PHY from reset */
|
||||
lvds_set(plat->base, LVDS_PXGCR(phy), PXGCR_DIV_RSTN | PXGCR_RSTZ);
|
||||
|
||||
/* lvds_pll_config */
|
||||
/* Set PLL Slv & Mst configs and timings */
|
||||
pll_in_khz = priv->refclk_rate / 1000;
|
||||
|
||||
if (priv->dual_link)
|
||||
multiplier = 2;
|
||||
else
|
||||
multiplier = 1;
|
||||
|
||||
ret = lvds_pll_get_params(pll_in_khz, timings.pixelclock.typ * 7 / 1000 / multiplier,
|
||||
&bdiv, &mdiv, &ndiv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Set PLL parameters */
|
||||
lvds_writel(plat->base, LVDS_PXPLLCR2(phy), (ndiv << 16) | bdiv);
|
||||
lvds_writel(plat->base, LVDS_PXPLLSDCR1(phy), mdiv);
|
||||
lvds_writel(plat->base, LVDS_PXPLLTESTCR(phy), PXPLLTESTCR_TDIV_VALUE << 16);
|
||||
|
||||
/* Disable TWG and SD: for now, PLL just need to be in integer mode */
|
||||
lvds_clear(plat->base, LVDS_PXPLLCR1(phy), PXPLLCR1_TWG_EN | PxPLLCR1_SD_EN);
|
||||
|
||||
/* Power up bias and PLL dividers */
|
||||
lvds_set(plat->base, LVDS_PXDCR(phy), PXDCR_POWER_OK);
|
||||
|
||||
lvds_set(plat->base, LVDS_PXCMCR1(phy), PXCMCR1_CM_EN_DL);
|
||||
lvds_set(plat->base, LVDS_PXCMCR2(phy), PXCMCR2_CM_EN_DL4);
|
||||
|
||||
lvds_set(plat->base, LVDS_PXPLLCPCR(phy), PXPLLCPCR_CPCTRL_DEFAULT);
|
||||
lvds_set(plat->base, LVDS_PXBCR3(phy), PXBCR3_VM_EN_DL);
|
||||
lvds_set(plat->base, LVDS_PXBCR1(phy), PXBCR1_EN_BIAS_DL);
|
||||
lvds_set(plat->base, LVDS_PXCFGCR(phy), PXCFGCR_EN_DIG_DL);
|
||||
|
||||
/* lvds_pll_enable */
|
||||
/* PLL lock timing control for the monitor unmask after startup (pll_en) */
|
||||
/* Adjust the value so that the masking window is opened at start-up */
|
||||
/* MST_MON_PLL_LOCK_UNMASK_TUNE */
|
||||
lvds_writel(plat->base, LVDS_PXMPLCR(phy), (0x200 - 0x160) << 16);
|
||||
|
||||
lvds_writel(plat->base, LVDS_PXBCR2(phy), PXBCR2_BIAS_EN);
|
||||
|
||||
lvds_set(plat->base, LVDS_PXGCR(phy),
|
||||
PXGCR_DP_CLK_OUT | PXGCR_LS_CLK_OUT | PXGCR_BIT_CLK_OUT);
|
||||
|
||||
lvds_set(plat->base, LVDS_PXPLLTESTCR(phy), PXPLLTESTCR_PLL_TDIV_EN);
|
||||
lvds_set(plat->base, LVDS_PXPLLCR1(phy), PXPLLCR1_PLL_DIVIDERS_EN);
|
||||
lvds_set(plat->base, LVDS_PXSCR(phy), PXSCR_SER_DATA_OK);
|
||||
|
||||
/* Enable the LVDS PLL & wait for its lock */
|
||||
lvds_set(plat->base, LVDS_PXPLLCR1(phy), PXPLLCR1_PLL_EN);
|
||||
ret = readl_poll_sleep_timeout(plat->base + LVDS_PXPLLSR(phy),
|
||||
val, val & PXPLLSR_PLL_LOCK, SLEEP_US, TIMEOUT_US);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Select MST PHY clock as pixel clock for the LDITX instead of FREF */
|
||||
/* WCLKCR_SLV_CLKPIX_SEL is for dual link */
|
||||
lvds_writel(plat->base, LVDS_WCLKCR, WCLKCR_SLV_CLKPIX_SEL);
|
||||
|
||||
lvds_set(plat->base, LVDS_PXPLLTESTCR(phy), PXPLLTESTCR_PLL_TEST_CLK_EN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_lvds_enable(struct udevice *dev)
|
||||
{
|
||||
struct stm32_lvds_plat *plat = dev_get_plat(dev);
|
||||
struct stm32_lvds_priv *priv = dev_get_priv(dev);
|
||||
struct display_timing timings = priv->timings;
|
||||
u32 lvds_cdl1cr = 0;
|
||||
u32 lvds_cdl2cr = 0;
|
||||
u32 lvds_dmlcr = 0;
|
||||
u32 lvds_dmmcr = 0;
|
||||
u32 lvds_cr = 0;
|
||||
int i;
|
||||
|
||||
lvds_clear(plat->base, LVDS_CDL1CR, CDLCRX_DISTR0 | CDLCRX_DISTR1 | CDLCRX_DISTR2
|
||||
| CDLCRX_DISTR3 | CDLCRX_DISTR4);
|
||||
lvds_clear(plat->base, LVDS_CDL2CR, CDLCRX_DISTR0 | CDLCRX_DISTR1 | CDLCRX_DISTR2
|
||||
| CDLCRX_DISTR3 | CDLCRX_DISTR4);
|
||||
|
||||
/* Set channel distribution */
|
||||
lvds_cr &= ~CR_LKMOD;
|
||||
lvds_cdl1cr = CDL1CR_DEFAULT;
|
||||
|
||||
if (priv->dual_link) {
|
||||
lvds_cr |= CR_LKMOD;
|
||||
lvds_cdl2cr = CDL2CR_DEFAULT;
|
||||
}
|
||||
|
||||
/* Set signal polarity */
|
||||
if (timings.flags & DISPLAY_FLAGS_DE_LOW)
|
||||
lvds_cr |= CR_DEPOL;
|
||||
|
||||
if (timings.flags & DISPLAY_FLAGS_HSYNC_LOW)
|
||||
lvds_cr |= CR_HSPOL;
|
||||
|
||||
if (timings.flags & DISPLAY_FLAGS_VSYNC_LOW)
|
||||
lvds_cr |= CR_VSPOL;
|
||||
|
||||
/* Set link phase */
|
||||
switch (priv->dual_link) {
|
||||
case LVDS_DUAL_LINK_EVEN_ODD_PIXELS: /* LKPHA = 0 */
|
||||
lvds_cr &= ~CR_LKPHA;
|
||||
break;
|
||||
case LVDS_DUAL_LINK_ODD_EVEN_PIXELS: /* LKPHA = 1 */
|
||||
lvds_cr |= CR_LKPHA;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "No phase precised, setting default\n");
|
||||
lvds_cr &= ~CR_LKPHA;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set Data Mapping */
|
||||
switch (priv->bus_format) {
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: /* VESA-RGB888 */
|
||||
for (i = 0; i < 5; i++) {
|
||||
lvds_dmlcr = ((lvds_bitmap_vesa_rgb888[i][0])
|
||||
+ (lvds_bitmap_vesa_rgb888[i][1] << 5)
|
||||
+ (lvds_bitmap_vesa_rgb888[i][2] << 10)
|
||||
+ (lvds_bitmap_vesa_rgb888[i][3] << 15));
|
||||
lvds_dmmcr = ((lvds_bitmap_vesa_rgb888[i][4])
|
||||
+ (lvds_bitmap_vesa_rgb888[i][5] << 5)
|
||||
+ (lvds_bitmap_vesa_rgb888[i][6] << 10));
|
||||
|
||||
/* Write registers at the end of computations */
|
||||
lvds_writel(plat->base, LVDS_DMLCR(i), lvds_dmlcr);
|
||||
lvds_writel(plat->base, LVDS_DMMCR(i), lvds_dmmcr);
|
||||
}
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: /* JEIDA-RGB888 */
|
||||
for (i = 0; i < 5; i++) {
|
||||
lvds_dmlcr = ((lvds_bitmap_jeida_rgb888[i][0])
|
||||
+ (lvds_bitmap_jeida_rgb888[i][1] << 5)
|
||||
+ (lvds_bitmap_jeida_rgb888[i][2] << 10)
|
||||
+ (lvds_bitmap_jeida_rgb888[i][3] << 15));
|
||||
lvds_dmmcr = ((lvds_bitmap_jeida_rgb888[i][4])
|
||||
+ (lvds_bitmap_jeida_rgb888[i][5] << 5)
|
||||
+ (lvds_bitmap_jeida_rgb888[i][6] << 10));
|
||||
|
||||
/* Write registers at the end of computations */
|
||||
lvds_writel(plat->base, LVDS_DMLCR(i), lvds_dmlcr);
|
||||
lvds_writel(plat->base, LVDS_DMMCR(i), lvds_dmmcr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "Unsupported LVDS bus format 0x%04x\n", priv->bus_format);
|
||||
}
|
||||
|
||||
/* Turn the output on */
|
||||
lvds_cr |= CR_LVDSEN;
|
||||
|
||||
/* Commit config to registers */
|
||||
lvds_set(plat->base, LVDS_CR, lvds_cr);
|
||||
lvds_writel(plat->base, LVDS_CDL1CR, lvds_cdl1cr);
|
||||
lvds_writel(plat->base, LVDS_CDL2CR, lvds_cdl2cr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_lvds_attach(struct udevice *dev)
|
||||
{
|
||||
struct stm32_lvds_plat *plat = dev_get_plat(dev);
|
||||
struct stm32_lvds_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = panel_get_display_timing(plat->panel, &priv->timings);
|
||||
if (ret) {
|
||||
ret = ofnode_decode_display_timing(dev_ofnode(plat->panel),
|
||||
0, &priv->timings);
|
||||
if (ret) {
|
||||
dev_err(dev, "decode display timing error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = stm32_lvds_enable(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stm32_lvds_set_backlight(struct udevice *dev, int percent)
|
||||
{
|
||||
struct stm32_lvds_plat *plat = dev_get_plat(dev);
|
||||
int ret;
|
||||
|
||||
ret = panel_enable_backlight(plat->panel);
|
||||
if (ret) {
|
||||
dev_err(dev, "panel %s enable backlight error %d\n",
|
||||
plat->panel->name, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lvds_handle_pixel_order(struct stm32_lvds_plat *plat)
|
||||
{
|
||||
ofnode parent, panel_port0, panel_port1;
|
||||
bool even_pixels, odd_pixels;
|
||||
int port0, port1;
|
||||
|
||||
/*
|
||||
* In case we are operating in single link,
|
||||
* there is only one port linked to the LVDS.
|
||||
* Check whether we are in this case and exit if yes.
|
||||
*/
|
||||
parent = ofnode_find_subnode(dev_ofnode(plat->panel), "ports");
|
||||
if (!ofnode_valid(parent))
|
||||
return 0;
|
||||
|
||||
panel_port0 = ofnode_first_subnode(parent);
|
||||
if (!ofnode_valid(panel_port0))
|
||||
return -EPIPE;
|
||||
|
||||
even_pixels = ofnode_read_bool(panel_port0, "dual-lvds-even-pixels");
|
||||
odd_pixels = ofnode_read_bool(panel_port0, "dual-lvds-odd-pixels");
|
||||
if (even_pixels && odd_pixels)
|
||||
return -EINVAL;
|
||||
|
||||
port0 = even_pixels ? LVDS_DUAL_LINK_EVEN_ODD_PIXELS :
|
||||
LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
|
||||
|
||||
panel_port1 = ofnode_next_subnode(panel_port0);
|
||||
if (!ofnode_valid(panel_port1))
|
||||
return -EPIPE;
|
||||
|
||||
even_pixels = ofnode_read_bool(panel_port1, "dual-lvds-even-pixels");
|
||||
odd_pixels = ofnode_read_bool(panel_port1, "dual-lvds-odd-pixels");
|
||||
if (even_pixels && odd_pixels)
|
||||
return -EINVAL;
|
||||
|
||||
port1 = even_pixels ? LVDS_DUAL_LINK_EVEN_ODD_PIXELS :
|
||||
LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
|
||||
|
||||
/*
|
||||
* A valid dual-LVDS bus is found when one port is marked with
|
||||
* "dual-lvds-even-pixels", and the other port is marked with
|
||||
* "dual-lvds-odd-pixels", bail out if the markers are not right.
|
||||
*/
|
||||
if (port0 + port1 != LVDS_DUAL_LINK_EVEN_ODD_PIXELS + LVDS_DUAL_LINK_ODD_EVEN_PIXELS)
|
||||
return -EINVAL;
|
||||
|
||||
return port0;
|
||||
}
|
||||
|
||||
static int stm32_lvds_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct stm32_lvds_plat *plat = dev_get_plat(dev);
|
||||
struct stm32_lvds_priv *priv = dev_get_priv(dev);
|
||||
const char *data_mapping;
|
||||
int ret;
|
||||
|
||||
plat->base = dev_read_addr_ptr(dev);
|
||||
if ((fdt_addr_t)plat->base == FDT_ADDR_T_NONE) {
|
||||
dev_err(dev, "Unable to read LVDS base address\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(dev, "pclk", &plat->pclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to get peripheral clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(dev, "ref", &plat->refclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to get reference clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = reset_get_by_index(dev, 0, &plat->rst);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to get LVDS reset: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = uclass_get_device_by_driver(UCLASS_PANEL,
|
||||
DM_DRIVER_GET(simple_panel), &plat->panel);
|
||||
if (ret) {
|
||||
dev_err(dev, "panel device error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = panel_get_display_timing(plat->panel, &priv->timings);
|
||||
if (ret) {
|
||||
ret = ofnode_decode_display_timing(dev_ofnode(plat->panel),
|
||||
0, &priv->timings);
|
||||
if (ret) {
|
||||
dev_err(dev, "decode display timing error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
data_mapping = ofnode_read_string(dev_ofnode(plat->panel), "data-mapping");
|
||||
if (!strcmp(data_mapping, "vesa-24"))
|
||||
priv->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
|
||||
else if (!strcmp(data_mapping, "jeida-24"))
|
||||
priv->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
|
||||
else
|
||||
priv->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_lvds_probe(struct udevice *dev)
|
||||
{
|
||||
struct stm32_lvds_plat *plat = dev_get_plat(dev);
|
||||
struct stm32_lvds_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = clk_enable(&plat->pclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable peripheral clock: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = clk_enable(&plat->refclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to enable reference clock: %d\n", ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
priv->refclk_rate = (unsigned int)clk_get_rate(&plat->refclk);
|
||||
|
||||
reset_deassert(&plat->rst);
|
||||
|
||||
/* Handle dual link config */
|
||||
priv->dual_link = lvds_handle_pixel_order(plat);
|
||||
if (priv->dual_link < 0)
|
||||
goto err_rst;
|
||||
|
||||
if (priv->dual_link > 0) {
|
||||
ret = stm32_lvds_pll_enable(dev, LVDS_PHY_SLAVE);
|
||||
if (ret)
|
||||
goto err_rst;
|
||||
}
|
||||
|
||||
ret = stm32_lvds_pll_enable(dev, LVDS_PHY_MASTER);
|
||||
if (ret)
|
||||
goto err_rst;
|
||||
|
||||
return 0;
|
||||
|
||||
err_rst:
|
||||
clk_disable(&plat->refclk);
|
||||
err_clk:
|
||||
clk_disable(&plat->pclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct video_bridge_ops stm32_lvds_ops = {
|
||||
.attach = stm32_lvds_attach,
|
||||
.set_backlight = stm32_lvds_set_backlight,
|
||||
};
|
||||
|
||||
static const struct udevice_id stm32_lvds_ids[] = {
|
||||
{.compatible = "st,stm32mp25-lvds"},
|
||||
{}
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(stm32_lvds) = {
|
||||
.name = "stm32-display-lvds",
|
||||
.id = UCLASS_VIDEO_BRIDGE,
|
||||
.of_match = stm32_lvds_ids,
|
||||
.ops = &stm32_lvds_ops,
|
||||
.of_to_plat = stm32_lvds_of_to_plat,
|
||||
.probe = stm32_lvds_probe,
|
||||
.plat_auto = sizeof(struct stm32_lvds_plat),
|
||||
.priv_auto = sizeof(struct stm32_lvds_priv),
|
||||
};
|
||||
Reference in New Issue
Block a user