Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • clea-os/bsp/qualcomm/linux-seco-qcom
1 result
Show changes
Commits on Source (20)
Showing
with 896 additions and 59 deletions
Device tree bindings for NXP PTN3460 DP/LVDS bridge on Qualcomm SoCs
********************************************************************
Properties
----------
- compatible = "seco,ptn3460-qcom"
- reg = <0x60>: The address on the i2c-bus.
- powerdown-gpios = <phandle>: gpio for power-down
- reset-gpios = <phandle>: gpio for reset
- lvds-enable-gpios = <phandle>: gpio for enableing the panel
- hpd-gpios = <phandle>: gpio for hotplug-detect
- backlight = <phandle>: handle for backlight
- edid-emulation = <int>: edid to emulate, defaults to 0
- width = <int>: panel width
- height = <int>: panel height
- data-mapping = "vesa24" | "jeida-24" | "jeida-18"
- panel-timing: panel timing,
see Documentation/devicetree/bindings/display/panel/display-timings.yaml
- dual-lvds: boolean, use dul-lvds if set
- port@0: input port for the bridge
see https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/graph.yaml
If no panel-timing is defined edid-emulation selects one of the
7 EDIDs preconfigured in the bridge.
Ports are not used by the driver itself. Nevertheless the input port
defines a dependance of devices for the purpose an endpoint can defer
it's probe until the bridge is probed.
Required
--------
- compatible
- reg
- reset-gpios
Examples
--------
&i2c13 {
edp2lvds: ptn3460@60 {
compatible = "seco,ptn3460-qcom";
reg = <0x60>; // DEV_CFG is floating
powerdown-gpios = <&tlmm 46 0>;
reset-gpios = <&tlmm 44 0>;
hpd-gpios = <&stm32 3 (GPIO_PUSH_PULL | GPIO_ACTIVE_HIGH)>;
lvds-enable-gpios = <LCD0_VDD_EN (GPIO_PUSH_PULL | GPIO_ACTIVE_HIGH)>;
edid-emulation = <0>;
status = "disabled";
panel-timing {
clock-frequency = <71100000>;
hactive = <1280>;
hfront-porch = <75>;
hsync-len = <10>;
hback-porch = <75>;
//htotal = <1440>;
vactive = <800>;
vfront-porch = <10>;
vsync-len = <3>;
vback-porch = <10>;
//vtotal = <823>;
vsync-active = <0>;
hsync-active = <0>;
de-active = <1>;
pixelclk-active = <1>;
};
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
edp2lvds_in: endpoint {
remote-endpoint = <&mdss_edp0_out>;
};
};
};
};
};
......@@ -13,7 +13,7 @@ Properties
- pinctrl-0 = <phandle>: Phandle of the pincontrol, only required for the
interrupt-pin.
- interrupt-parent = <phandle>: Where interrupts have to be send to.
- interrupts = <phandle>: Number and flags of the interrupt-pin.
- interrupts = <int int>: Number and flags of the interrupt-pin.
GPIO
- gpio-controller: This is a gpio-controller, all standard-properties of
......
# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs6490-e81-b79.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs6490-e81-b79-edp.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs6490-e81-b79-fn070pgrgul037c.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs6490-e81-b79-g101ean02.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp1-e81-b79.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp1-e81-b79-edp.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp1-e81-b79-fn070pgrgul037c.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp1-e81-b79-g101ean02.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp2-e81-b79.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp2-e81-b79-edp.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp2-e81-b79-fn070pgrgul037c.dtb
dtb-$(CONFIG_ARCH_QCOM) += seco-qcs5430-fp2-e81-b79-g101ean02.dtb
......@@ -13,37 +13,16 @@
#ifndef DISPLAY_E81_EDP_DTSI
#define DISPLAY_E81_EDP_DTSI
#include "qcm6490-sde.dtsi"
#include "e81-pinfunc.h"
&mdss_dp0 {
status = "disabled";
};
&mdss_dsi_phy0 {
status = "disabled";
};
&mdss_dsi0 {
status = "disabled";
};
&mdss_edp0 {
status = "okay";
qcom,display-type = "primary";
qcom,dp-low-power-hw-hpd;
vdda-1p2-supply = <&vreg_l6b_1p2>;
vdda-0p9-supply = <&vreg_l10c_0v9>;
pinctrl-names = "mdss_dp_active", "mdss_dp_sleep", "mdss_dp_hpd_active";
pinctrl-0 = <&edp_hpd_ctrl>;
pinctrl-1 = <&edp_hpd_default>;
pinctrl-2 = <&edp_hpd_default>;
qcom,dp-hpd-gpio = <&tlmm 60 0>;
qcom,dp-gpio-aux-switch;
qcom,edp-pwm-en-gpio = <&pm8350c_gpios 8 GPIO_ACTIVE_HIGH>;
qcom,edp-backlight-en-gpio = <LCD0_BKLT_EN GPIO_ACTIVE_HIGH>;
qcom,dp-ext-hpd;
status = "okay";
};
&mdss_mdp0 {
......@@ -59,34 +38,4 @@ gpio_lcd0_vdd_en {
};
};
// pinctrl for hpd
&tlmm {
edp_hpd_default: hpd_default@60 {
mux {
pins = "gpio60";
function = "gpio";
};
config {
pins = "gpio60";
bias-disable;
input-enable;
drive-strength = <2>;
};
};
edp_hpd_ctrl: hpd_ctrl@60 {
mux {
pins = "gpio60";
function = "edp_hot";
};
config {
pins = "gpio60";
bias-disable;
input-enable;
drive-strength = <2>;
};
};
};
#endif
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device-tree-file for the 7" FANNAL LVDS-display with the bulky name on
* SECO-E81 modules
* see: C3007054491027A_SPEC V1.pdf
*
* Copyright 2025 SECO NE Dietmar Muscholik <dietmar.muscholik@seco.com>
*
*****************************************************************************/
#ifndef DISPLAY_E81_FN070PGRGUL037C_DTSI
#define DISPLAY_E81_FN070PGRGUL037C_DTSI
#include "e81-pinfunc.h"
// bridge
&edp2lvds {
backlight = <&backlight0>;
width-mm = <154>;
height-mm = <86>;
data-mapping = "jeida-24";
status = "okay";
// Note: The 35571428 Hz clock is used on the
// i.MX93 for this display. The 33,3 MHz is
// the calculated clock (1056 H x 525 V x 24 FPS).
panel-timing {
clock-frequency = <33300000>;
//clock-frequency = <35571428>;
hactive = <800>;
hfront-porch = <210>;
hsync-len = <6>;
hback-porch = <40>;
//htotal = <1056>;
vactive = <480>;
vfront-porch = <22>;
vsync-len = <3>;
vback-porch = <20>;
//vtotal = <525>;
vsync-active = <0>;
hsync-active = <0>;
de-active = <1>;
pixelclk-active = <1>;
};
};
// qualcomm edp
&mdss_edp0 {
qcom,display-type = "primary";
status = "okay";
};
// backlight
&backlight0 {
status = "okay";
};
&pm8350c_pwm {
status = "okay";
};
// connect to eDP
&mdss_mdp0 {
connectors = <&smmu_sde_unsec &mdss_edp0>;
};
#endif
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device-tree-file for the 10.1" AOE LVDS-display on SECO-E81 modules
* see: G101EAN02.2.pdf
*
* Copyright 2025 SECO NE Dietmar Muscholik <dietmar.muscholik@seco.com>
*
*****************************************************************************/
#ifndef DISPLAY_E81_G101EAN02_DTSI
#define DISPLAY_E81_G101EAN02_DTSI
#include "e81-pinfunc.h"
// bridge
&edp2lvds {
backlight = <&backlight0>;
width-mm = <228>;
height-mm = <148>;
data-mapping = "vesa-24";
status = "okay";
panel-timing {
clock-frequency = <71100000>;
hactive = <1280>;
hfront-porch = <75>;
hsync-len = <10>;
hback-porch = <75>;
//htotal = <1440>;
vactive = <800>;
vfront-porch = <10>;
vsync-len = <3>;
vback-porch = <10>;
//vtotal = <823>;
vsync-active = <0>;
hsync-active = <0>;
de-active = <1>;
pixelclk-active = <1>;
};
};
// qualcomm edp
&mdss_edp0 {
qcom,display-type = "primary";
status = "okay";
};
// backlight
&backlight0 {
status = "okay";
};
&pm8350c_pwm {
status = "okay";
};
// connect to eDP
&mdss_mdp0 {
connectors = <&smmu_sde_unsec &mdss_edp0>;
};
#endif
......@@ -31,7 +31,9 @@
#include "../../qcom/pm7325.dtsi"
#include "../../qcom/pm8350c.dtsi"
#include "../../qcom/pmk8350.dtsi" // also for pmk7325
#include "../../qcom/qcm6490-addons.dtsi" // firmware, thermal zones, reserved memory ranges, etc.
#include "qcm6490-sde.dtsi"
#include "e81-pinfunc.h"
/ {
......@@ -551,6 +553,42 @@ &lpass_va_macro {
status = "disabled";
};
// display stuff
&mdss_dsi0 {
status = "disabled";
};
&mdss_dsi_phy0 {
status = "disabled";
};
&mdss_dp0 {
status = "disabled";
};
&mdss_edp0 {
pinctrl-names = "mdss_dp_active", "mdss_dp_sleep", "mdss_dp_hpd_active";
pinctrl-0 = <&edp_hpd_ctrl>;
pinctrl-1 = <&edp_hpd_default>;
pinctrl-2 = <&edp_hpd_default>;
vdda-1p2-supply = <&vreg_l6b_1p2>;
vdda-0p9-supply = <&vreg_l10c_0v9>;
qcom,dp-hpd-gpio = <&tlmm 60 0>;
qcom,dp-ext-hpd;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
mdss_edp0_out: endpoint {
remote-endpoint = <&edp2lvds_in>;
};
};
};
};
// PCIe
// PCIe 0 is external on carrier board
&pcie0 {
......@@ -838,6 +876,14 @@ &vreg_l9b_1p2 {
// children of i2c1 (I2C_GP)
&i2c1 {
eeprom_int: mc24aa024@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
vcc-supply = <&vreg_l8c_1p8>;
status = "okay";
};
rtc: rtc@52 {
compatible = "microcrystal,rv3028";
reg = <0x52>;
......@@ -867,6 +913,34 @@ gpio_usb_hub_res {
};
// children of i2c13 (TS_I2C)
&i2c13 {
edp2lvds: ptn3460@60 {
compatible = "seco,ptn3460-qcom";
reg = <0x60>; // DEV_CFG is floating
pinctrl-names = "default";
pinctrl-0 = <&edp2lvds_powerdown_n>, <&edp2lvds_reset_n>;
powerdown-gpios = <&tlmm 46 0>;
reset-gpios = <&tlmm 44 0>;
//hpd-gpios = <&stm32 3 (GPIO_PUSH_PULL | GPIO_ACTIVE_HIGH)>; // temporary
lvds-enable-gpios = <LCD0_VDD_EN (GPIO_PUSH_PULL | GPIO_ACTIVE_HIGH)>;
edid-emulation = <0>;
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
edp2lvds_in: endpoint {
remote-endpoint = <&mdss_edp0_out>;
};
};
};
};
};
// children of pcie1 (QPS615 switch)
&pcie1 {
dummy-supply = <&pcie_switch>;
......@@ -980,7 +1054,35 @@ lcd0_bklt_pwm: lcd0_bklt_pwm {
&tlmm {
// Remove the <48 4> (spi12 pins), which is set in qcm6490.dtsi
gpio-reserved-ranges = <32 2>;
gpio-reserved-ranges = <32 2>;
// pinctrl for hpd
edp_hpd_default: hpd_default@60 {
mux {
pins = "gpio60";
function = "gpio";
};
config {
pins = "gpio60";
bias-disable;
input-enable;
drive-strength = <2>;
};
};
edp_hpd_ctrl: hpd_ctrl@60 {
mux {
pins = "gpio60";
function = "edp_hot";
};
config {
pins = "gpio60";
bias-disable;
input-enable;
drive-strength = <2>;
};
};
pcie1_reset_n: pcie1-reset-n-state {
pins = "gpio2";
......@@ -995,6 +1097,23 @@ qup_spi14_cs1_gpio: qup-spi14-cs1-gpio-state {
function = "gpio";
};
// pinctrl for LVDS bridge
edp2lvds_powerdown_n: edp2lvds-powerdown-n{
pins = "gpio46";
function = "gpio";
drive-strength = <16>;
output-low;
bias-disable;
};
edp2lvds_reset_n: edp2lvds-reset-n {
pins = "gpio44";
function = "gpio";
drive-strength = <16>;
output-low;
bias-disable;
};
qps615_intn_wol {
eth0_intn_wol_sig: eth0_intn_wol_sig {
mux {
......
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device tree file for SECO SMARC Module with QCS5430 FP1 for B79
* and Fannal 7" LVDS display
*
* Copyright 2024-2025 SECO
*
*****************************************************************************/
/dts-v1/;
#include "include/qcs5430-fp1-e81.dtsi"
#include "include/baseboard-e81-b79.dtsi"
#include "include/display-e81-fn070pgrgul037c.dtsi"
/ {
model = "Qualcomm QCS5430 FP1 SECO board (E81) with B79 baseboard and Fannal 7\" LVDS display";
compatible = "qcom,qcs6490-addons-rb3gen2-video-mezz", "qcom,qcm6490";
source = "seco-qcs5430-fp1-e81-b79-fn070pgrgul037c.dts";
};
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device tree file for SECO SMARC Module with QCS5430 FP1 for B79
* and AUO 10.1" LVDS display
*
* Copyright 2024-2025 SECO
*
*****************************************************************************/
/dts-v1/;
#include "include/qcs5430-fp1-e81.dtsi"
#include "include/baseboard-e81-b79.dtsi"
#include "include/display-e81-g101ean02.dtsi"
/ {
model = "Qualcomm QCS5430 FP1 SECO board (E81) with B79 baseboard and AUO 10.1\" LVDS display";
compatible = "qcom,qcs6490-addons-rb3gen2-video-mezz", "qcom,qcm6490";
source = "seco-qcs5430-fp1-e81-b79-g101ean02.dts";
};
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device tree file for SECO SMARC Module with QCS5430 FP2 for B79
* and Fannal 7" LVDS display
*
* Copyright 2024-2025 SECO
*
*****************************************************************************/
/dts-v1/;
#include "include/qcs5430-fp2-e81.dtsi"
#include "include/baseboard-e81-b79.dtsi"
#include "include/display-e81-fn070pgrgul037c.dtsi"
/ {
model = "Qualcomm QCS5430 FP2 SECO board (E81) with B79 baseboard and Fannal 7\" LVDS display";
compatible = "qcom,qcs6490-addons-rb3gen2-video-mezz", "qcom,qcm6490";
source = "seco-qcs5430-fp2-e81-b79-fn070pgrgul037c.dts";
};
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device tree file for SECO SMARC Module with QCS5430 FP1 for B79
* and AUO 10.1" LVDS display
*
* Copyright 2024-2025 SECO
*
*****************************************************************************/
/dts-v1/;
#include "include/qcs5430-fp2-e81.dtsi"
#include "include/baseboard-e81-b79.dtsi"
#include "include/display-e81-g101ean02.dtsi"
/ {
model = "Qualcomm QCS5430 FP2 SECO board (E81) with B79 baseboard and AUO 10.1\" LVDS display";
compatible = "qcom,qcs6490-addons-rb3gen2-video-mezz", "qcom,qcm6490";
source = "seco-qcs5430-fp2-e81-b79-g101ean02.dts";
};
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device tree file for SECO SMARC Module with QCS6490 for B79
* and Fannal 7" LVDS display
*
* Copyright 2024-2025 SECO
*
*****************************************************************************/
/dts-v1/;
#include "include/qcs6490-e81.dtsi"
#include "include/baseboard-e81-b79.dtsi"
#include "include/display-e81-fn070pgrgul037c.dtsi"
/ {
model = "Qualcomm QCS6490 SECO board (E81) with B79 baseboard and Fannal 7\" LVDS display";
compatible = "qcom,qcs6490-addons-rb3gen2-video-mezz", "qcom,qcm6490";
source = "seco-qcs6490-e81-b79-fn070pgrgul037c.dts";
};
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* Device tree file for SECO SMARC Module with QCS6490 for B79
* and AUO 10.1" LVDS display
*
* Copyright 2024-2025 SECO
*
*****************************************************************************/
/dts-v1/;
#include "include/qcs6490-e81.dtsi"
#include "include/baseboard-e81-b79.dtsi"
#include "include/display-e81-g101ean02.dtsi"
/ {
model = "Qualcomm QCS6490 SECO board (E81) with B79 baseboard and AUO 10.1\" LVDS display";
compatible = "qcom,qcs6490-addons-rb3gen2-video-mezz", "qcom,qcm6490";
source = "seco-qcs6490-e81-b79-g101ean02.dts";
};
......@@ -59,12 +59,7 @@ CONFIG_CPU_FREQ_GOV_USERSPACE=y
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_GOV_CONSERVATIVE=m
CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
CONFIG_CPUFREQ_DT=y
CONFIG_ACPI_CPPC_CPUFREQ=m
CONFIG_ARM_SCPI_CPUFREQ=y
CONFIG_ARM_QCOM_CPUFREQ_NVMEM=y
CONFIG_ARM_QCOM_CPUFREQ_HW=y
CONFIG_ARM_SCMI_CPUFREQ=y
CONFIG_ACPI=y
CONFIG_VIRTUALIZATION=y
CONFIG_KVM=y
......@@ -580,6 +575,7 @@ CONFIG_VHOST_NET=y
CONFIG_VHOST_VSOCK=y
CONFIG_STAGING=y
CONFIG_STAGING_MEDIA=y
CONFIG_DRM_SECO_PTN3460_QCOM=m
CONFIG_COMMON_CLK_SCMI=y
CONFIG_COMMON_CLK_SCPI=y
CONFIG_COMMON_CLK_PWM=y
......@@ -900,3 +896,5 @@ CONFIG_SPI_MT65XX=y
CONFIG_SPI_SPIDEV=y
CONFIG_SPI_SLAVE=y
CONFIG_RPMSG=y
CONFIG_MAILBOX=y
......@@ -80,4 +80,6 @@ source "drivers/staging/vme_user/Kconfig"
source "drivers/staging/mfd/Kconfig"
source "drivers/staging/gpu/drm/bridge/Kconfig"
endif # STAGING
......@@ -29,3 +29,4 @@ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/
obj-$(CONFIG_QLGE) += qlge/
obj-$(CONFIG_MFD_SECO_STM32) += mfd/
obj-y += gpu/drm/bridge/
config DRM_SECO_PTN3460_QCOM
tristate "NXP PTN3460 DP/LVDS bridge on Qualcomm SoCs"
depends on OF && DRM && I2C
help
Since Qualcomm (e)DP does not support bridges this driver pretends
to be not a bridge but a native (e)DP display.
If you use a PTN3460 on Qualcomm hardware say Y here.
obj-$(CONFIG_DRM_SECO_PTN3460_QCOM) += seco-ptn3460-qcom.o
/*****************************************************************************/
/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
/*
* NXP PTN3460 DP/LVDS bridge on Qualcomm SoCs
*
* The Qualcomm (e)DP driver does not support bridges.
* Hence this is not really a bridge driver, that means there are no attach-
* or enable-functions.
* Instead the bridge is configured when the device is probed. After this it
* adds itself to the list of panel drivers to be found as a remote-endpoint
* and optionaly pulls a hotplug-detect-gpio to high to fake to the
* (e)DP driver a native (e)DP has been powered on.
*
* Copyright 2025 SECO NE Dietmar Muscholik <dietmar.muscholik@seco.com>
*
*****************************************************************************/
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <linux/backlight.h>
#include <linux/delay.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <drm/drm_edid.h>
#include <video/videomode.h>
#include <drm/drm_panel.h>
#define DRIVER_NAME "seco-ptn3460-qcom"
#define EDIT_DEFAULT 0
// bridge definitions
// see https://www.nxp.com/docs/en/application-note/AN11128.pdf
// section 5
#define PTN3460_EDID_ADDR 0x0
#define PTN3460_EDID_SELECT_ADDR 0x84
#define PTN3460_EDID_SELECT 1
#define PTN3460_EDID_ENABLE 1
#define PTN3460_EDID_ACCESS_ADDR 0x85
#define PTN3460_LVDS_CTRL_ADDR1 0x81
#define PTN3460_LVDS_CTRL_ADDR2 0x82
#define PTN3460_LVDS_CTRL_ADDR3 0x83
#define PTN3460_LVDS_CTRL_DEFAULT 0x0b
#define PTN3460_LVDS_CTRL_VESA24 (0 << 4)
#define PTN3460_LVDS_CTRL_JEIDA24 (1 << 4)
#define PTN3460_LVDS_CTRL_JEIDA18 (2 << 4)
#define PTN3460_LVDS_CTRL_DUAL (1 << 3)
#define PTN3460_LVDS_CTRL_DE_LOW (1 << 2)
static unsigned char edid_init[EDID_LENGTH] = {
// copied from AN11128.pdf
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // header
0x3B, 0x10, 0x60, 0x34, // mfg_id, prod_code
0x00, 0x00, 0x00, 0x00, // serial
0x26, 0x15, 0x01, 0x03, // week, year, version, revision
0x68, 0x1E, 0x16, 0x78, 0xEE,
0x37, 0x25, 0x9E, 0x58, 0x4A, 0x97, 0x26, 0x19, 0x50, 0x54, // color
0xAD, 0xEE, 0x00, // established timings
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // standard timings
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
// preferred timing
0x64, 0x19, // pixel clock
0x00, 0x40, 0x41, 0x00, 0x26, 0x30, 0x18, 0x88,
0x36, 0x00, 0x30, 0xE4, 0x10, 0x00, 0x00, 0x18,
// detailed timing
0x00, 0x00, 0x00, 0xFD, 0x00, 0x32, 0x4C, 0x1E, // range limits
0x3F, 0x08, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x00, 0x00, 0x00, 0xFC, 0x00, 0x41, 0x49, 0x4F, // product name
0x20, 0x50, 0x43, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x00, 0x00, 0x00, 0xFF, 0x00, 0x4E, 0x58, 0x50, // serial number
0x20, 0x50, 0x54, 0x4E, 0x33, 0x34, 0x36, 0x30, 0x20, 0x20,
0x00, 0xAF, // extension blocks, checksum
};
// container for dt stuff
struct ptn3460 {
struct gpio_desc *gpio_powerdown_n;
struct gpio_desc *gpio_reset_n;
struct gpio_desc *gpio_lvds_en;
struct gpio_desc *gpio_hpd;
struct backlight_device *backlight;
u32 edid_emulation;
struct display_timing timing;
const char *mapping;
bool dual_lvds;
struct edid *edid;
struct drm_panel panel;
};
/*
fill the detailed timing of an EDID with display timings read from
device tree
see https://glenwing.github.io/docs/VESA-EEDID-A2.pdf
Table 3.1 and 3.21
*/
static int edid_from_timing(struct edid *edid,
struct display_timing *timing,
unsigned int width,
unsigned int height)
{
struct videomode vm;
struct detailed_timing *dt = edid->detailed_timings;
u32 htotal, vtotal;
unsigned char *p,c;
size_t n;
edid->width_cm = width / 10;
edid->height_cm = height / 10;
// just to save some typing
videomode_from_timing(timing, &vm);
htotal = vm.hactive + vm.hfront_porch + vm.hback_porch + vm.hsync_len;
vtotal = vm.vactive + vm.vfront_porch + vm.vback_porch + vm.vsync_len;
dt->pixel_clock = vm.pixelclock / 10000;
dt->data.pixel_data.hactive_lo = vm.hactive & 0xff;
dt->data.pixel_data.hblank_lo = (htotal - vm.hactive) & 0xff;
dt->data.pixel_data.hactive_hblank_hi = ((vm.hactive >> 4) & 0xf0) +
(((htotal - vm.hactive) >> 8) & 0x0f);
dt->data.pixel_data.vactive_lo = vm.vactive & 0xff;
dt->data.pixel_data.vblank_lo = (vtotal - vm.vactive) & 0xff;
dt->data.pixel_data.vactive_vblank_hi = ((vm.vactive >> 4) & 0xf0) +
(((vtotal - vm.vactive) >> 8) & 0x0f);
dt->data.pixel_data.hsync_offset_lo = vm.hfront_porch & 0xff;
dt->data.pixel_data.hsync_pulse_width_lo = vm.hsync_len & 0xff;
dt->data.pixel_data.vsync_offset_pulse_width_lo =
((vm.vfront_porch & 0x0f) << 4) + (vm.vsync_len & 0x0f);
// write-only-code, hope I never have to change it
dt->data.pixel_data.hsync_vsync_offset_pulse_width_hi =
((vm.hfront_porch & 0x0300) >> 2) +
((vm.hsync_len & 0x0300) >> 4) +
((vm.vfront_porch & 0x0030) >> 2) +
((vm.vsync_len & 0x0030) >> 4);
dt->data.pixel_data.width_mm_lo = width & 0xff;
dt->data.pixel_data.height_mm_lo = height & 0xff;
dt->data.pixel_data.width_height_mm_hi = ((width & 0xff00) >> 4) +
((height & 0xff00) >> 8);
// calc checksum
for(p = (unsigned char *)edid, c = n = 0;
n < sizeof(*edid) - 1;
c += p[n], n++);
p[n] = -c;
return 0;
}
// read the dt
static int ptn3460_parse_dt(struct device *dev, struct ptn3460 *data)
{
int ret = 0;
unsigned int width,height;
data->gpio_reset_n = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if(IS_ERR(data->gpio_reset_n))
{
ret = PTR_ERR(data->gpio_reset_n);
dev_err(dev, "failed to request reset-gpio: %d\n", ret);
return ret;
}
data->gpio_hpd = devm_gpiod_get(dev, "hpd", GPIOD_OUT_LOW);
if(IS_ERR(data->gpio_hpd))
dev_warn(dev, "failed to request hpd-gpio: %ld\n",
PTR_ERR(data->gpio_hpd));
data->gpio_powerdown_n = devm_gpiod_get_optional(dev, "powerdown",
GPIOD_OUT_LOW);
if(IS_ERR(data->gpio_powerdown_n))
dev_warn(dev, "failed to request powerdown-gpio: %ld\n",
PTR_ERR(data->gpio_powerdown_n));
data->gpio_lvds_en = devm_gpiod_get_optional(dev, "lvds-enable",
GPIOD_OUT_LOW);
if(IS_ERR(data->gpio_lvds_en))
dev_warn(dev, "failed to request lvds-enable-gpio: %ld\n",
PTR_ERR(data->gpio_lvds_en));
data->backlight = devm_of_find_backlight(dev);
if(IS_ERR(data->backlight))
dev_warn(dev, "failed to find backlight: %ld\n",
PTR_ERR(data->backlight));
ret = of_property_read_u32(dev->of_node, "edid-emulation",
&data->edid_emulation);
if(ret)
{
dev_warn(dev, "no edit-emulation, using default\n");
data->edid_emulation = EDIT_DEFAULT;
}
ret = of_property_read_u32(dev->of_node, "width-mm", &width);
if(ret)
dev_warn(dev, "failed to read display width: %d\n", ret);
ret = of_property_read_u32(dev->of_node, "height-mm", &height);
if(ret)
dev_warn(dev, "failed to read display height: %d\n", ret);
ret = of_get_display_timing(dev->of_node, "panel-timing", &data->timing);
if(ret == 0)
{
data->edid = devm_kmalloc(dev, sizeof(*data->edid), GFP_KERNEL);
if(!data->edid)
return -ENOMEM;
memcpy(data->edid, edid_init, sizeof(*data->edid));
edid_from_timing(data->edid, &data->timing, width, height);
}
else
dev_warn(dev, "Failed to read display-timings: %d\n", ret);
ret = of_property_read_string(dev->of_node, "data-mapping", &data->mapping);
if(ret)
dev_warn(dev, "Failed to read data-mapping: %d\n", ret);
data->dual_lvds = of_property_read_bool(dev->of_node, "dual-lvds");
return 0;
}
// configure the bridge
static int ptn3460_configure(struct i2c_client *client, struct ptn3460 *data)
{
char msg[EDID_LENGTH + 1];
int ret;
// write edid to device if defined
if(data->edid)
{
msg[0] = PTN3460_EDID_ACCESS_ADDR;
msg[1] = data->edid_emulation;
ret = i2c_master_send(client, msg, 2);
if(ret < 0)
{
dev_err(&client->dev, "Failed to set EDID address: %d\n", ret);
return ret;
}
msg[0] = PTN3460_EDID_ADDR;
memcpy(msg + 1, data->edid, EDID_LENGTH);
ret = i2c_master_send(client, msg, EDID_LENGTH + 1);
if(ret < 0)
{
dev_err(&client->dev, "Failed to send EDID: %d\n", ret);
return ret;
}
}
// select edid
msg[0] = PTN3460_EDID_SELECT_ADDR;
msg[1] = (data->edid_emulation << PTN3460_EDID_SELECT) |
PTN3460_EDID_ENABLE;
ret = i2c_master_send(client, msg, 2);
if(ret < 0)
{
dev_err(&client->dev, "Failed to select EDID: %d\n", ret);
return ret;
}
// configure the LVDS interface
msg[0] = PTN3460_LVDS_CTRL_ADDR1;
msg[1] = PTN3460_LVDS_CTRL_DEFAULT;
if(data->mapping)
{
if(!strcmp(data->mapping, "vesa-24"))
msg[1] += PTN3460_LVDS_CTRL_VESA24;
else if(!strcmp(data->mapping, "jeida-24"))
msg[1] += PTN3460_LVDS_CTRL_JEIDA24;
else if(!strcmp(data->mapping, "jeida-18"))
msg[1] += PTN3460_LVDS_CTRL_JEIDA18;
}
if(data->timing.flags & DISPLAY_FLAGS_DE_LOW)
msg[1] |= PTN3460_LVDS_CTRL_DE_LOW;
else
msg[1] &= ~PTN3460_LVDS_CTRL_DE_LOW;
if(data->dual_lvds)
msg[1] |= PTN3460_LVDS_CTRL_DUAL;
else
msg[1] &= ~PTN3460_LVDS_CTRL_DUAL;
ret = i2c_master_send(client, msg, 2);
if(ret < 0)
{
dev_err(&client->dev, "Failed to configure LVDS: %d\n", ret);
return ret;
}
return 0;
}
// panel functions
// only a dummy because get_modes() is mandantory
int ptn3460_get_modes(struct drm_panel *panel, struct drm_connector *connector)
{
return 0;
}
static struct drm_panel_funcs ptn3460_funcs = {
.get_modes = ptn3460_get_modes,
};
// probe the device
static int ptn3460_probe(struct i2c_client *client)
{
struct device *dev=&client->dev;
struct ptn3460 *data;
int ret = 0;
dev_info(dev, "%s\n", __FUNCTION__);
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if(!data)
return -ENOMEM;
ret = ptn3460_parse_dt(dev, data);
if(ret)
return ret;
// enable the bridge
// the delays are copied from the original driver
// hoping they are correct
gpiod_set_value_cansleep(data->gpio_reset_n, 0);
if(!IS_ERR_OR_NULL(data->gpio_hpd))
gpiod_set_value_cansleep(data->gpio_hpd, 0);
if(!IS_ERR_OR_NULL(data->gpio_powerdown_n))
gpiod_set_value_cansleep(data->gpio_powerdown_n, 1);
usleep_range(10, 20);
gpiod_set_value_cansleep(data->gpio_reset_n, 1);
msleep(90);
// configure the bridge
ret = ptn3460_configure(client, data);
if(ret)
{
gpiod_set_value_cansleep(data->gpio_reset_n, 0);
if(!IS_ERR_OR_NULL(data->gpio_powerdown_n))
gpiod_set_value_cansleep(data->gpio_powerdown_n, 0);
return ret;
}
// enable the panel
msleep(90);
if(!IS_ERR_OR_NULL(data->gpio_hpd))
gpiod_set_value_cansleep(data->gpio_hpd, 1);
if(!IS_ERR_OR_NULL(data->gpio_lvds_en))
gpiod_set_value_cansleep(data->gpio_lvds_en, 1);
if(!IS_ERR_OR_NULL(data->backlight))
backlight_enable(data->backlight);
// add myself to the list of panels
drm_panel_init(&data->panel, dev, &ptn3460_funcs, DRM_MODE_CONNECTOR_eDP);
drm_panel_add(&data->panel);
dev_set_drvdata(dev, data);
dev_info(dev, "%s: OK\n", __FUNCTION__);
return 0;
}
static void ptn3460_remove(struct i2c_client *client)
{
struct ptn3460 *data = (struct ptn3460 *)dev_get_drvdata(&client->dev);
if(!IS_ERR_OR_NULL(data->backlight))
backlight_disable(data->backlight);
if(!IS_ERR_OR_NULL(data->gpio_lvds_en))
gpiod_set_value_cansleep(data->gpio_lvds_en, 0);
if(!IS_ERR_OR_NULL(data->gpio_hpd))
gpiod_set_value_cansleep(data->gpio_hpd, 0);
gpiod_set_value_cansleep(data->gpio_reset_n, 0);
if(!IS_ERR_OR_NULL(data->gpio_powerdown_n))
gpiod_set_value_cansleep(data->gpio_powerdown_n, 0);
drm_panel_remove(&data->panel);
}
static struct of_device_id ptn3460_match_table[] = {
{ .compatible = "seco,ptn3460-qcom",},
};
static struct i2c_driver ptn3460_driver = {
.probe = ptn3460_probe,
.remove = ptn3460_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = ptn3460_match_table,
},
};
module_i2c_driver(ptn3460_driver);
MODULE_AUTHOR("Dietmar Muscholik <dietmar.muscholik@seco.com>");
MODULE_DESCRIPTION("NXP PTN3460 DP/LVDS bridge on Qualcomm SoCs");
MODULE_LICENSE("GPL v2");