主题中讨论的其他器件: SN65DSI84
尊敬的先生/女士:
我正在 Linux 环境中使用 NXP IMX8M Mini EVK 板。 我们正在使用 PANASYS LVDS 显示屏作为定制显示屏、为了将 MIPI-DSI 数据线路转换为 LVDS 数据、我们使用 SN65DSI83作为桥接转换器。 我们在该位置添加了带有桥参数的驱动程序(drivers/GPU/DRM/BRIDE/BRIDE/sn65dsi83)、并在位于该位置的 imx8mm-EVK-DTS 文件(arch/arm64/boot/dts/Freescale)中添加了显示参数。 添加网桥驱动程序和 DTS 文件后、我们无法在 EVK 上看到任何显示。 在这里、我将共享引导日志和 DTS 文件以及补丁。
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright 2019 NXP
*/
/dts-v1/;
#include <dt-bindings/usb/pd.h>
#include "imx8mm.dtsi"
/ {
model = "FSL i.MX8MM EVK board";
compatible = "fsl,imx8mm-evk", "fsl,imx8mm";
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
rpmsg_reserved: rpmsg@0xb8000000 {
no-map;
reg = <0 0xb8000000 0 0x400000>;
};
};
chosen {
stdout-path = &uart2;
};
leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_led>;
status {
label = "status";
gpios = <&gpio3 16 GPIO_ACTIVE_HIGH>;
default-state = "on";
};
};
modem_reset: modem-reset {
compatible = "gpio-reset";
reset-gpios = <&gpio2 6 GPIO_ACTIVE_LOW>;
reset-delay-us = <2000>;
reset-post-delay-ms = <40>;
#reset-cells = <0>;
};
ir_recv: ir-receiver {
compatible = "gpio-ir-receiver";
gpios = <&gpio1 13 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ir_recv>;
};
pcie0_refclk: pcie0-refclk {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <100000000>;
};
reg_sd1_vmmc: sd1_regulator {
compatible = "regulator-fixed";
regulator-name = "WLAN_EN";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
gpio = <&gpio2 10 GPIO_ACTIVE_HIGH>;
off-on-delay-us = <20000>;
startup-delay-us = <100>;
enable-active-high;
};
reg_usdhc2_vmmc: regulator-usdhc2 {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_reg_usdhc2_vmmc>;
regulator-name = "VSD_3V3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
gpio = <&gpio2 19 GPIO_ACTIVE_HIGH>;
off-on-delay-us = <20000>;
enable-active-high;
};
reg_audio_board: regulator-audio-board {
compatible = "regulator-fixed";
regulator-name = "EXT_PWREN";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
enable-active-high;
startup-delay-us = <300000>;
gpio = <&pca6416 1 GPIO_ACTIVE_HIGH>;
};
wm8524: audio-codec {
#sound-dai-cells = <0>;
compatible = "wlf,wm8524";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_wlf>;
wlf,mute-gpios = <&gpio5 21 GPIO_ACTIVE_LOW>;
};
bt_sco_codec: bt_sco_codec {
#sound-dai-cells = <0>;
compatible = "linux,bt-sco";
};
sound-bt-sco {
compatible = "simple-audio-card";
simple-audio-card,name = "bt-sco-audio";
simple-audio-card,format = "i2s";
simple-audio-card,bitclock-inversion;
simple-audio-card,frame-master = <&btcpu>;
simple-audio-card,bitclock-master = <&btcpu>;
btcpu: simple-audio-card,cpu {
sound-dai = <&sai2>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <16>;
};
simple-audio-card,codec {
sound-dai = <&bt_sco_codec>;
};
};
sound-wm8524 {
compatible = "simple-audio-card";
simple-audio-card,name = "wm8524-audio";
simple-audio-card,format = "i2s";
simple-audio-card,frame-master = <&cpudai>;
simple-audio-card,bitclock-master = <&cpudai>;
simple-audio-card,widgets =
"Line", "Left Line Out Jack",
"Line", "Right Line Out Jack";
simple-audio-card,routing =
"Left Line Out Jack", "LINEVOUTL",
"Right Line Out Jack", "LINEVOUTR";
cpudai: simple-audio-card,cpu {
sound-dai = <&sai3>;
dai-tdm-slot-num = <2>;
dai-tdm-slot-width = <32>;
};
simple-audio-card,codec {
sound-dai = <&wm8524>;
clocks = <&clk IMX8MM_CLK_SAI3_ROOT>;
};
};
sound-ak4458 {
compatible = "fsl,imx-audio-ak4458";
model = "ak4458-audio";
audio-cpu = <&sai1>;
audio-codec = <&ak4458_1>, <&ak4458_2>;
ak4458,pdn-gpio = <&pca6416 4 GPIO_ACTIVE_HIGH>;
};
sound-ak5558 {
compatible = "fsl,imx-audio-ak5558";
model = "ak5558-audio";
audio-cpu = <&sai5>;
audio-codec = <&ak5558>;
status = "disabled";
};
sound-ak4497 {
compatible = "fsl,imx-audio-ak4497";
model = "ak4497-audio";
audio-cpu = <&sai1>;
audio-codec = <&ak4497>;
status = "disabled";
};
sound-spdif {
compatible = "fsl,imx-audio-spdif";
model = "imx-spdif";
spdif-controller = <&spdif1>;
spdif-out;
spdif-in;
};
sound-micfil {
compatible = "fsl,imx-audio-micfil";
model = "imx-audio-micfil";
cpu-dai = <&micfil>;
};
};
&A53_0 {
cpu-supply = <&buck2_reg>;
};
&csi1_bridge {
fsl,mipi-mode;
status = "okay";
port {
csi1_ep: endpoint {
remote-endpoint = <&csi1_mipi_ep>;
};
};
};
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_fec1>;
phy-mode = "rgmii-id";
phy-handle = <ðphy0>;
fsl,magic-packet;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-ieee802.3-c22";
reg = <0>;
at803x,eee-disabled;
at803x,vddio-1p8v;
};
};
};
&pcie0{
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pcie0>;
disable-gpio = <&gpio1 5 GPIO_ACTIVE_LOW>;
reset-gpio = <&gpio4 21 GPIO_ACTIVE_LOW>;
clocks = <&clk IMX8MM_CLK_PCIE1_ROOT>,
<&clk IMX8MM_CLK_PCIE1_AUX>,
<&clk IMX8MM_CLK_PCIE1_PHY>,
<&pcie0_refclk>;
clock-names = "pcie", "pcie_aux", "pcie_phy", "pcie_bus";
ext_osc = <1>;
reserved-region = <&rpmsg_reserved>;
status = "okay";
};
&pcie0_ep{
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pcie0>;
clocks = <&clk IMX8MM_CLK_PCIE1_ROOT>,
<&clk IMX8MM_CLK_PCIE1_AUX>,
<&clk IMX8MM_CLK_PCIE1_PHY>,
<&pcie0_refclk>;
clock-names = "pcie", "pcie_aux", "pcie_phy", "pcie_bus";
ext_osc = <1>;
status = "disabled";
};
&sai3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai3>;
assigned-clocks = <&clk IMX8MM_CLK_SAI3>;
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
assigned-clock-rates = <24576000>;
status = "okay";
};
&sai2 {
#sound-dai-cells = <0>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai2>;
assigned-clocks = <&clk IMX8MM_CLK_SAI2>;
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
assigned-clock-rates = <24576000>;
status = "okay";
};
&sai1 {
pinctrl-names = "default", "dsd";
pinctrl-0 = <&pinctrl_sai1>;
pinctrl-1 = <&pinctrl_sai1_dsd>;
assigned-clocks = <&clk IMX8MM_CLK_SAI1>;
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
assigned-clock-rates = <49152000>;
clocks = <&clk IMX8MM_CLK_SAI1_IPG>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_SAI1_ROOT>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_DUMMY>, <&clk IMX8MM_AUDIO_PLL1_OUT>,
<&clk IMX8MM_AUDIO_PLL2_OUT>;
clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3", "pll8k", "pll11k";
fsl,sai-multi-lane;
fsl,dataline,dsd = <0 0xff 0xff 2 0xff 0x11>;
dmas = <&sdma2 0 25 0>, <&sdma2 1 25 0>;
status = "okay";
};
&sai5 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai5>;
assigned-clocks = <&clk IMX8MM_CLK_SAI5>;
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
assigned-clock-rates = <49152000>;
clocks = <&clk IMX8MM_CLK_SAI5_IPG>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_SAI5_ROOT>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_DUMMY>, <&clk IMX8MM_AUDIO_PLL1_OUT>,
<&clk IMX8MM_AUDIO_PLL2_OUT>;
clock-names = "bus", "mclk0", "mclk1", "mclk2", "mclk3", "pll8k", "pll11k";
fsl,sai-asynchronous;
status = "disabled";
};
&sai6 {
fsl,sai-monitor-spdif;
fsl,sai-asynchronous;
status = "okay";
};
&spdif1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spdif1>;
assigned-clocks = <&clk IMX8MM_CLK_SPDIF1>;
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
assigned-clock-rates = <24576000>;
clocks = <&clk IMX8MM_CLK_AUDIO_AHB>, <&clk IMX8MM_CLK_24M>,
<&clk IMX8MM_CLK_SPDIF1>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_DUMMY>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_AUDIO_AHB>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_CLK_DUMMY>, <&clk IMX8MM_CLK_DUMMY>,
<&clk IMX8MM_AUDIO_PLL1_OUT>, <&clk IMX8MM_AUDIO_PLL2_OUT>;
clock-names = "core", "rxtx0", "rxtx1", "rxtx2", "rxtx3",
"rxtx4", "rxtx5", "rxtx6", "rxtx7", "spba", "pll8k", "pll11k";
status = "okay";
};
&micfil {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pdm>;
assigned-clocks = <&clk IMX8MM_CLK_PDM>;
assigned-clock-parents = <&clk IMX8MM_AUDIO_PLL1_OUT>;
assigned-clock-rates = <196608000>;
status = "okay";
};
&snvs_pwrkey {
status = "okay";
};
&uart1 { /* BT */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
assigned-clocks = <&clk IMX8MM_CLK_UART1>;
assigned-clock-parents = <&clk IMX8MM_SYS_PLL1_80M>;
fsl,uart-has-rtscts;
resets = <&modem_reset>;
status = "okay";
};
&uart2 { /* console */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart2>;
status = "okay";
};
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
assigned-clocks = <&clk IMX8MM_CLK_UART3>;
assigned-clock-parents = <&clk IMX8MM_SYS_PLL1_80M>;
fsl,uart-has-rtscts;
status = "okay";
};
&usbotg1 {
dr_mode = "otg";
hnp-disable;
srp-disable;
adp-disable;
usb-role-switch;
picophy,pre-emp-curr-control = <3>;
picophy,dc-vol-level-adjust = <7>;
status = "okay";
port {
usb1_drd_sw: endpoint {
remote-endpoint = <&typec1_dr_sw>;
};
};
};
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>, <&pinctrl_usdhc1_gpio>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>, <&pinctrl_usdhc1_gpio>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>, <&pinctrl_usdhc1_gpio>;
bus-width = <4>;
vmmc-supply = <®_sd1_vmmc>;
pm-ignore-notify;
keep-power-in-suspend;
non-removable;
status = "okay";
};
&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2>, <&pinctrl_usdhc2_gpio>;
pinctrl-1 = <&pinctrl_usdhc2_100mhz>, <&pinctrl_usdhc2_gpio>;
pinctrl-2 = <&pinctrl_usdhc2_200mhz>, <&pinctrl_usdhc2_gpio>;
cd-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
bus-width = <4>;
vmmc-supply = <®_usdhc2_vmmc>;
status = "okay";
};
&usdhc3 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc3>;
pinctrl-1 = <&pinctrl_usdhc3_100mhz>;
pinctrl-2 = <&pinctrl_usdhc3_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};
&wdog1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_wdog>;
fsl,ext-reset-output;
status = "okay";
};
&flexspi {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexspi0>;
status = "okay";
flash0: mt25qu256aba@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <1>;
compatible = "jedec,spi-nor";
spi-max-frequency = <80000000>;
spi-tx-bus-width = <4>;
spi-rx-bus-width = <4>;
};
};
&i2c1 {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
pmic@4b {
compatible = "rohm,bd71847";
reg = <0x4b>;
pinctrl-0 = <&pinctrl_pmic>;
interrupt-parent = <&gpio1>;
interrupts = <3 GPIO_ACTIVE_LOW>;
rohm,reset-snvs-powered;
regulators {
buck1_reg: BUCK1 {
regulator-name = "BUCK1";
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <1300000>;
regulator-boot-on;
regulator-always-on;
regulator-ramp-delay = <1250>;
};
buck2_reg: BUCK2 {
regulator-name = "BUCK2";
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <1300000>;
regulator-boot-on;
regulator-always-on;
regulator-ramp-delay = <1250>;
rohm,dvs-run-voltage = <1000000>;
rohm,dvs-idle-voltage = <900000>;
};
buck3_reg: BUCK3 {
// BUCK5 in datasheet
regulator-name = "BUCK3";
regulator-min-microvolt = <700000>;
regulator-max-microvolt = <1350000>;
regulator-boot-on;
regulator-always-on;
};
buck4_reg: BUCK4 {
// BUCK6 in datasheet
regulator-name = "BUCK4";
regulator-min-microvolt = <3000000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
regulator-always-on;
};
buck5_reg: BUCK5 {
// BUCK7 in datasheet
regulator-name = "BUCK5";
regulator-min-microvolt = <1605000>;
regulator-max-microvolt = <1995000>;
regulator-boot-on;
regulator-always-on;
};
buck6_reg: BUCK6 {
// BUCK8 in datasheet
regulator-name = "BUCK6";
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <1400000>;
regulator-boot-on;
regulator-always-on;
};
ldo1_reg: LDO1 {
regulator-name = "LDO1";
regulator-min-microvolt = <1600000>;
regulator-max-microvolt = <1900000>;
regulator-boot-on;
regulator-always-on;
};
ldo2_reg: LDO2 {
regulator-name = "LDO2";
regulator-min-microvolt = <800000>;
regulator-max-microvolt = <900000>;
regulator-boot-on;
regulator-always-on;
};
ldo3_reg: LDO3 {
regulator-name = "LDO3";
regulator-min-microvolt = <1800000>;
regulator-max-microvolt = <3300000>;
regulator-boot-on;
regulator-always-on;
};
ldo4_reg: LDO4 {
regulator-name = "LDO4";
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1800000>;
regulator-boot-on;
regulator-always-on;
};
ldo6_reg: LDO6 {
regulator-name = "LDO6";
regulator-min-microvolt = <900000>;
regulator-max-microvolt = <1800000>;
regulator-boot-on;
regulator-always-on;
};
};
};
};
&i2c2 {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2>;
status = "okay";
dsi_lvds_bridge: sn65dsi84@2c {
compatible = "ti,sn65dsi83";
reg = <0x2c>;
ti,dsi-lanes = <4>;
ti,lvds-format = <1>;
ti,lvds-bpp = <24>;
ti,width-mm = <217>;
ti,height-mm = <136>;
enable-gpios = <&gpio3 0 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio3>;
interrupts = <6 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lvds>;
status = "okay";
display-timings {
native-mode = <&lvds0_g101evn010>;
/* AUO G101EVN01.0 */
lvds0_g101evn010: timing@0 {
clock-frequency = <69000000>;
hactive = <1280>;
vactive = <800>;
hfront-porch = <120>;
hback-porch = <1>;
hsync-len = <8>;
vback-porch = <10>;
vfront-porch = <1>;
vsync-len = <6>;
hsync-active = <1>;
vsync-active = <1>;
de-active = <1>;
pixelclk-active = <0>;
};
/* Fusion 10" F10A-0102 */
lvds0_hsd101pfw2: timing@1 {
clock-frequency = <45000000>;
hactive = <1024>;
vactive = <600>;
hfront-porch = <120>;
hback-porch = <1>;
hsync-len = <8>;
vback-porch = <10>;
vfront-porch = <1>;
vsync-len = <6>;
hsync-active = <1>;
vsync-active = <1>;
de-active = <1>;
pixelclk-active = <0>;
};
};
port {
dsi_lvds_bridge_in: endpoint {
remote-endpoint = <&mipi_dsi_lvds_out>;
};
};
adv_bridge: adv7535@3d {
compatible = "adi,adv7533";
reg = <0x3d>;
adi,addr-cec = <0x3b>;
adi,dsi-lanes = <4>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c2_synaptics_dsx_io>;
interrupt-parent = <&gpio1>;
interrupts = <9 IRQ_TYPE_LEVEL_LOW>;
status = "disable";
port {
adv7535_from_dsim: endpoint {
remote-endpoint = <&dsim_to_adv7535>;
};
};
};
ptn5110: tcpc@50 {
compatible = "nxp,ptn5110";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_typec1>;
reg = <0x50>;
interrupt-parent = <&gpio2>;
interrupts = <11 8>;
status = "okay";
port {
typec1_dr_sw: endpoint {
remote-endpoint = <&usb1_drd_sw>;
};
};
typec1_con: connector {
compatible = "usb-c-connector";
label = "USB-C";
power-role = "dual";
data-role = "dual";
try-power-role = "sink";
source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
PDO_VAR(5000, 20000, 3000)>;
op-sink-microwatt = <15000000>;
self-powered;
};
};
};
&i2c3 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c3>;
status = "okay";
pca6416: gpio@20 {
compatible = "ti,tca6416";
reg = <0x20>;
gpio-controller;
#gpio-cells = <2>;
vcc-supply = <&buck4_reg>;
};
ak4458_1: ak4458@10 {
compatible = "asahi-kasei,ak4458";
reg = <0x10>;
AVDD-supply = <®_audio_board>;
DVDD-supply = <®_audio_board>;
};
ak4458_2: ak4458@12 {
compatible = "asahi-kasei,ak4458";
reg = <0x12>;
AVDD-supply = <®_audio_board>;
DVDD-supply = <®_audio_board>;
};
ak5558: ak5558@13 {
compatible = "asahi-kasei,ak5558";
reg = <0x13>;
ak5558,pdn-gpio = <&pca6416 3 GPIO_ACTIVE_HIGH>;
AVDD-supply = <®_audio_board>;
DVDD-supply = <®_audio_board>;
};
ak4497: ak4497@11 {
compatible = "asahi-kasei,ak4497";
reg = <0x11>;
ak4497,pdn-gpio = <&pca6416 5 GPIO_ACTIVE_HIGH>;
AVDD-supply = <®_audio_board>;
DVDD-supply = <®_audio_board>;
};
ov5640_mipi: ov5640_mipi@3c {
compatible = "ovti,ov5640_mipi";
reg = <0x3c>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_csi_pwn>, <&pinctrl_csi_rst>;
clocks = <&clk IMX8MM_CLK_CLKO1>;
clock-names = "csi_mclk";
assigned-clocks = <&clk IMX8MM_CLK_CLKO1>;
assigned-clock-parents = <&clk IMX8MM_CLK_24M>;
assigned-clock-rates = <24000000>;
csi_id = <0>;
pwn-gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
mclk = <24000000>;
mclk_source = <0>;
port {
ov5640_mipi1_ep: endpoint {
remote-endpoint = <&mipi1_sensor_ep>;
};
};
};
};
&iomuxc {
pinctrl-names = "default";
pinctrl_csi_pwn: csi_pwn_grp {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO07_GPIO1_IO7 0x19
>;
};
pinctrl_csi_rst: csi_rst_grp {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO06_GPIO1_IO6 0x19
MX8MM_IOMUXC_GPIO1_IO14_CCMSRCGPCMIX_CLKO1 0x59
>;
};
pinctrl_ir_recv: ir-recv {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO13_GPIO1_IO13 0x4f
>;
};
pinctrl_fec1: fec1grp {
fsl,pins = <
MX8MM_IOMUXC_ENET_MDC_ENET1_MDC 0x3
MX8MM_IOMUXC_ENET_MDIO_ENET1_MDIO 0x3
MX8MM_IOMUXC_ENET_TD3_ENET1_RGMII_TD3 0x1f
MX8MM_IOMUXC_ENET_TD2_ENET1_RGMII_TD2 0x1f
MX8MM_IOMUXC_ENET_TD1_ENET1_RGMII_TD1 0x1f
MX8MM_IOMUXC_ENET_TD0_ENET1_RGMII_TD0 0x1f
MX8MM_IOMUXC_ENET_RD3_ENET1_RGMII_RD3 0x91
MX8MM_IOMUXC_ENET_RD2_ENET1_RGMII_RD2 0x91
MX8MM_IOMUXC_ENET_RD1_ENET1_RGMII_RD1 0x91
MX8MM_IOMUXC_ENET_RD0_ENET1_RGMII_RD0 0x91
MX8MM_IOMUXC_ENET_TXC_ENET1_RGMII_TXC 0x1f
MX8MM_IOMUXC_ENET_RXC_ENET1_RGMII_RXC 0x91
MX8MM_IOMUXC_ENET_RX_CTL_ENET1_RGMII_RX_CTL 0x91
MX8MM_IOMUXC_ENET_TX_CTL_ENET1_RGMII_TX_CTL 0x1f
MX8MM_IOMUXC_SAI2_RXC_GPIO4_IO22 0x19
>;
};
pinctrl_flexspi0: flexspi0grp {
fsl,pins = <
MX8MM_IOMUXC_NAND_ALE_QSPI_A_SCLK 0x1c2
MX8MM_IOMUXC_NAND_CE0_B_QSPI_A_SS0_B 0x82
MX8MM_IOMUXC_NAND_DATA00_QSPI_A_DATA0 0x82
MX8MM_IOMUXC_NAND_DATA01_QSPI_A_DATA1 0x82
MX8MM_IOMUXC_NAND_DATA02_QSPI_A_DATA2 0x82
MX8MM_IOMUXC_NAND_DATA03_QSPI_A_DATA3 0x82
>;
};
pinctrl_gpio_led: gpioledgrp {
fsl,pins = <
MX8MM_IOMUXC_NAND_READY_B_GPIO3_IO16 0x19
>;
};
pinctrl_lvds: lvdsgrp {
fsl,pins = <
/* SN65DSI83 enable */
MX8MM_IOMUXC_NAND_ALE_GPIO3_IO0 0x19
/* SN65DSI83 interrupt */
MX8MM_IOMUXC_NAND_DATA00_GPIO3_IO6 0x19
>;
};
pinctrl_gpio_wlf: gpiowlfgrp {
fsl,pins = <
MX8MM_IOMUXC_I2C4_SDA_GPIO5_IO21 0xd6
>;
};
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX8MM_IOMUXC_I2C1_SCL_I2C1_SCL 0x400001c3
MX8MM_IOMUXC_I2C1_SDA_I2C1_SDA 0x400001c3
>;
};
pinctrl_i2c2: i2c2grp {
fsl,pins = <
MX8MM_IOMUXC_I2C2_SCL_I2C2_SCL 0x400001c3
MX8MM_IOMUXC_I2C2_SDA_I2C2_SDA 0x400001c3
>;
};
pinctrl_i2c3: i2c3grp {
fsl,pins = <
MX8MM_IOMUXC_I2C3_SCL_I2C3_SCL 0x400001c3
MX8MM_IOMUXC_I2C3_SDA_I2C3_SDA 0x400001c3
>;
};
pinctrl_mipi_dsi_en: mipi_dsi_en {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 0x16
>;
};
pinctrl_i2c2_synaptics_dsx_io: synaptics_dsx_iogrp {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO09_GPIO1_IO9 0x19 /* Touch int */
>;
};
pinctrl_pcie0: pcie0grp {
fsl,pins = <
MX8MM_IOMUXC_I2C4_SCL_PCIE1_CLKREQ_B 0x61 /* open drain, pull up */
MX8MM_IOMUXC_GPIO1_IO05_GPIO1_IO5 0x41
MX8MM_IOMUXC_SAI2_RXFS_GPIO4_IO21 0x41
>;
};
pinctrl_pmic: pmicirq {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO03_GPIO1_IO3 0x41
>;
};
pinctrl_reg_usdhc2_vmmc: regusdhc2vmmc {
fsl,pins = <
MX8MM_IOMUXC_SD2_RESET_B_GPIO2_IO19 0x41
>;
};
pinctrl_sai2: sai2grp {
fsl,pins = <
MX8MM_IOMUXC_SAI2_TXC_SAI2_TX_BCLK 0xd6
MX8MM_IOMUXC_SAI2_TXFS_SAI2_TX_SYNC 0xd6
MX8MM_IOMUXC_SAI2_TXD0_SAI2_TX_DATA0 0xd6
MX8MM_IOMUXC_SAI2_RXD0_SAI2_RX_DATA0 0xd6
>;
};
pinctrl_sai3: sai3grp {
fsl,pins = <
MX8MM_IOMUXC_SAI3_TXFS_SAI3_TX_SYNC 0xd6
MX8MM_IOMUXC_SAI3_TXC_SAI3_TX_BCLK 0xd6
MX8MM_IOMUXC_SAI3_MCLK_SAI3_MCLK 0xd6
MX8MM_IOMUXC_SAI3_TXD_SAI3_TX_DATA0 0xd6
>;
};
pinctrl_sai1: sai1grp {
fsl,pins = <
MX8MM_IOMUXC_SAI1_MCLK_SAI1_MCLK 0xd6
MX8MM_IOMUXC_SAI1_TXFS_SAI1_TX_SYNC 0xd6
MX8MM_IOMUXC_SAI1_RXD7_SAI1_TX_SYNC 0xd6
MX8MM_IOMUXC_SAI1_TXC_SAI1_TX_BCLK 0xd6
MX8MM_IOMUXC_SAI1_TXD0_SAI1_TX_DATA0 0xd6
MX8MM_IOMUXC_SAI1_TXD1_SAI1_TX_DATA1 0xd6
MX8MM_IOMUXC_SAI1_TXD2_SAI1_TX_DATA2 0xd6
MX8MM_IOMUXC_SAI1_TXD3_SAI1_TX_DATA3 0xd6
MX8MM_IOMUXC_SAI1_TXD4_SAI1_TX_DATA4 0xd6
MX8MM_IOMUXC_SAI1_TXD5_SAI1_TX_DATA5 0xd6
MX8MM_IOMUXC_SAI1_TXD6_SAI1_TX_DATA6 0xd6
MX8MM_IOMUXC_SAI1_TXD7_SAI1_TX_DATA7 0xd6
>;
};
pinctrl_sai1_dsd: sai1grp_dsd {
fsl,pins = <
MX8MM_IOMUXC_SAI1_MCLK_SAI1_MCLK 0xd6
MX8MM_IOMUXC_SAI1_TXFS_SAI1_TX_SYNC 0xd6
MX8MM_IOMUXC_SAI1_RXD7_SAI1_TX_DATA4 0xd6
MX8MM_IOMUXC_SAI1_TXC_SAI1_TX_BCLK 0xd6
MX8MM_IOMUXC_SAI1_TXD0_SAI1_TX_DATA0 0xd6
MX8MM_IOMUXC_SAI1_TXD1_SAI1_TX_DATA1 0xd6
MX8MM_IOMUXC_SAI1_TXD2_SAI1_TX_DATA2 0xd6
MX8MM_IOMUXC_SAI1_TXD3_SAI1_TX_DATA3 0xd6
MX8MM_IOMUXC_SAI1_TXD4_SAI1_TX_DATA4 0xd6
MX8MM_IOMUXC_SAI1_TXD5_SAI1_TX_DATA5 0xd6
MX8MM_IOMUXC_SAI1_TXD6_SAI1_TX_DATA6 0xd6
MX8MM_IOMUXC_SAI1_TXD7_SAI1_TX_DATA7 0xd6
>;
};
pinctrl_sai5: sai5grp {
fsl,pins = <
MX8MM_IOMUXC_SAI5_MCLK_SAI5_MCLK 0xd6
MX8MM_IOMUXC_SAI5_RXC_SAI5_RX_BCLK 0xd6
MX8MM_IOMUXC_SAI5_RXFS_SAI5_RX_SYNC 0xd6
MX8MM_IOMUXC_SAI5_RXD0_SAI5_RX_DATA0 0xd6
MX8MM_IOMUXC_SAI5_RXD1_SAI5_RX_DATA1 0xd6
MX8MM_IOMUXC_SAI5_RXD2_SAI5_RX_DATA2 0xd6
MX8MM_IOMUXC_SAI5_RXD3_SAI5_RX_DATA3 0xd6
>;
};
pinctrl_pdm: pdmgrp {
fsl,pins = <
MX8MM_IOMUXC_SAI5_MCLK_SAI5_MCLK 0xd6
MX8MM_IOMUXC_SAI5_RXC_PDM_CLK 0xd6
MX8MM_IOMUXC_SAI5_RXFS_SAI5_RX_SYNC 0xd6
MX8MM_IOMUXC_SAI5_RXD0_PDM_DATA0 0xd6
MX8MM_IOMUXC_SAI5_RXD1_PDM_DATA1 0xd6
MX8MM_IOMUXC_SAI5_RXD2_PDM_DATA2 0xd6
MX8MM_IOMUXC_SAI5_RXD3_PDM_DATA3 0xd6
>;
};
pinctrl_spdif1: spdif1grp {
fsl,pins = <
MX8MM_IOMUXC_SPDIF_TX_SPDIF1_OUT 0xd6
MX8MM_IOMUXC_SPDIF_RX_SPDIF1_IN 0xd6
>;
};
pinctrl_typec1: typec1grp {
fsl,pins = <
MX8MM_IOMUXC_SD1_STROBE_GPIO2_IO11 0x159
>;
};
pinctrl_uart1: uart1grp {
fsl,pins = <
MX8MM_IOMUXC_UART1_RXD_UART1_DCE_RX 0x140
MX8MM_IOMUXC_UART1_TXD_UART1_DCE_TX 0x140
MX8MM_IOMUXC_UART3_RXD_UART1_DCE_CTS_B 0x140
MX8MM_IOMUXC_UART3_TXD_UART1_DCE_RTS_B 0x140
MX8MM_IOMUXC_SD1_DATA4_GPIO2_IO6 0x19
>;
};
pinctrl_uart2: uart2grp {
fsl,pins = <
MX8MM_IOMUXC_UART2_RXD_UART2_DCE_RX 0x140
MX8MM_IOMUXC_UART2_TXD_UART2_DCE_TX 0x140
>;
};
pinctrl_uart3: uart3grp {
fsl,pins = <
MX8MM_IOMUXC_ECSPI1_SCLK_UART3_DCE_RX 0x140
MX8MM_IOMUXC_ECSPI1_MOSI_UART3_DCE_TX 0x140
MX8MM_IOMUXC_ECSPI1_SS0_UART3_DCE_RTS_B 0x140
MX8MM_IOMUXC_ECSPI1_MISO_UART3_DCE_CTS_B 0x140
>;
};
pinctrl_usdhc1_gpio: usdhc1grpgpio {
fsl,pins = <
MX8MM_IOMUXC_SD1_RESET_B_GPIO2_IO10 0x41
>;
};
pinctrl_usdhc1: usdhc1grp {
fsl,pins = <
MX8MM_IOMUXC_SD1_CLK_USDHC1_CLK 0x190
MX8MM_IOMUXC_SD1_CMD_USDHC1_CMD 0x1d0
MX8MM_IOMUXC_SD1_DATA0_USDHC1_DATA0 0x1d0
MX8MM_IOMUXC_SD1_DATA1_USDHC1_DATA1 0x1d0
MX8MM_IOMUXC_SD1_DATA2_USDHC1_DATA2 0x1d0
MX8MM_IOMUXC_SD1_DATA3_USDHC1_DATA3 0x1d0
>;
};
pinctrl_usdhc1_100mhz: usdhc1grp100mhz {
fsl,pins = <
MX8MM_IOMUXC_SD1_CLK_USDHC1_CLK 0x194
MX8MM_IOMUXC_SD1_CMD_USDHC1_CMD 0x1d4
MX8MM_IOMUXC_SD1_DATA0_USDHC1_DATA0 0x1d4
MX8MM_IOMUXC_SD1_DATA1_USDHC1_DATA1 0x1d4
MX8MM_IOMUXC_SD1_DATA2_USDHC1_DATA2 0x1d4
MX8MM_IOMUXC_SD1_DATA3_USDHC1_DATA3 0x1d4
>;
};
pinctrl_usdhc1_200mhz: usdhc1grp200mhz {
fsl,pins = <
MX8MM_IOMUXC_SD1_CLK_USDHC1_CLK 0x196
MX8MM_IOMUXC_SD1_CMD_USDHC1_CMD 0x1d6
MX8MM_IOMUXC_SD1_DATA0_USDHC1_DATA0 0x1d6
MX8MM_IOMUXC_SD1_DATA1_USDHC1_DATA1 0x1d6
MX8MM_IOMUXC_SD1_DATA2_USDHC1_DATA2 0x1d6
MX8MM_IOMUXC_SD1_DATA3_USDHC1_DATA3 0x1d6
>;
};
pinctrl_usdhc2_gpio: usdhc2grpgpio {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO15_GPIO1_IO15 0x1c4
>;
};
pinctrl_usdhc2: usdhc2grp {
fsl,pins = <
MX8MM_IOMUXC_SD2_CLK_USDHC2_CLK 0x190
MX8MM_IOMUXC_SD2_CMD_USDHC2_CMD 0x1d0
MX8MM_IOMUXC_SD2_DATA0_USDHC2_DATA0 0x1d0
MX8MM_IOMUXC_SD2_DATA1_USDHC2_DATA1 0x1d0
MX8MM_IOMUXC_SD2_DATA2_USDHC2_DATA2 0x1d0
MX8MM_IOMUXC_SD2_DATA3_USDHC2_DATA3 0x1d0
MX8MM_IOMUXC_GPIO1_IO04_USDHC2_VSELECT 0x1d0
>;
};
pinctrl_usdhc2_100mhz: usdhc2grp100mhz {
fsl,pins = <
MX8MM_IOMUXC_SD2_CLK_USDHC2_CLK 0x194
MX8MM_IOMUXC_SD2_CMD_USDHC2_CMD 0x1d4
MX8MM_IOMUXC_SD2_DATA0_USDHC2_DATA0 0x1d4
MX8MM_IOMUXC_SD2_DATA1_USDHC2_DATA1 0x1d4
MX8MM_IOMUXC_SD2_DATA2_USDHC2_DATA2 0x1d4
MX8MM_IOMUXC_SD2_DATA3_USDHC2_DATA3 0x1d4
MX8MM_IOMUXC_GPIO1_IO04_USDHC2_VSELECT 0x1d0
>;
};
pinctrl_usdhc2_200mhz: usdhc2grp200mhz {
fsl,pins = <
MX8MM_IOMUXC_SD2_CLK_USDHC2_CLK 0x196
MX8MM_IOMUXC_SD2_CMD_USDHC2_CMD 0x1d6
MX8MM_IOMUXC_SD2_DATA0_USDHC2_DATA0 0x1d6
MX8MM_IOMUXC_SD2_DATA1_USDHC2_DATA1 0x1d6
MX8MM_IOMUXC_SD2_DATA2_USDHC2_DATA2 0x1d6
MX8MM_IOMUXC_SD2_DATA3_USDHC2_DATA3 0x1d6
MX8MM_IOMUXC_GPIO1_IO04_USDHC2_VSELECT 0x1d0
>;
};
pinctrl_usdhc3: usdhc3grp {
fsl,pins = <
MX8MM_IOMUXC_NAND_WE_B_USDHC3_CLK 0x190
MX8MM_IOMUXC_NAND_WP_B_USDHC3_CMD 0x1d0
MX8MM_IOMUXC_NAND_DATA04_USDHC3_DATA0 0x1d0
MX8MM_IOMUXC_NAND_DATA05_USDHC3_DATA1 0x1d0
MX8MM_IOMUXC_NAND_DATA06_USDHC3_DATA2 0x1d0
MX8MM_IOMUXC_NAND_DATA07_USDHC3_DATA3 0x1d0
MX8MM_IOMUXC_NAND_RE_B_USDHC3_DATA4 0x1d0
MX8MM_IOMUXC_NAND_CE2_B_USDHC3_DATA5 0x1d0
MX8MM_IOMUXC_NAND_CE3_B_USDHC3_DATA6 0x1d0
MX8MM_IOMUXC_NAND_CLE_USDHC3_DATA7 0x1d0
MX8MM_IOMUXC_NAND_CE1_B_USDHC3_STROBE 0x190
>;
};
pinctrl_usdhc3_100mhz: usdhc3grp100mhz {
fsl,pins = <
MX8MM_IOMUXC_NAND_WE_B_USDHC3_CLK 0x194
MX8MM_IOMUXC_NAND_WP_B_USDHC3_CMD 0x1d4
MX8MM_IOMUXC_NAND_DATA04_USDHC3_DATA0 0x1d4
MX8MM_IOMUXC_NAND_DATA05_USDHC3_DATA1 0x1d4
MX8MM_IOMUXC_NAND_DATA06_USDHC3_DATA2 0x1d4
MX8MM_IOMUXC_NAND_DATA07_USDHC3_DATA3 0x1d4
MX8MM_IOMUXC_NAND_RE_B_USDHC3_DATA4 0x1d4
MX8MM_IOMUXC_NAND_CE2_B_USDHC3_DATA5 0x1d4
MX8MM_IOMUXC_NAND_CE3_B_USDHC3_DATA6 0x1d4
MX8MM_IOMUXC_NAND_CLE_USDHC3_DATA7 0x1d4
MX8MM_IOMUXC_NAND_CE1_B_USDHC3_STROBE 0x194
>;
};
pinctrl_usdhc3_200mhz: usdhc3grp200mhz {
fsl,pins = <
MX8MM_IOMUXC_NAND_WE_B_USDHC3_CLK 0x196
MX8MM_IOMUXC_NAND_WP_B_USDHC3_CMD 0x1d6
MX8MM_IOMUXC_NAND_DATA04_USDHC3_DATA0 0x1d6
MX8MM_IOMUXC_NAND_DATA05_USDHC3_DATA1 0x1d6
MX8MM_IOMUXC_NAND_DATA06_USDHC3_DATA2 0x1d6
MX8MM_IOMUXC_NAND_DATA07_USDHC3_DATA3 0x1d6
MX8MM_IOMUXC_NAND_RE_B_USDHC3_DATA4 0x1d6
MX8MM_IOMUXC_NAND_CE2_B_USDHC3_DATA5 0x1d6
MX8MM_IOMUXC_NAND_CE3_B_USDHC3_DATA6 0x1d6
MX8MM_IOMUXC_NAND_CLE_USDHC3_DATA7 0x1d6
MX8MM_IOMUXC_NAND_CE1_B_USDHC3_STROBE 0x196
>;
};
lvds_backlight: lvds_backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 100000 0>;
brightness-levels =
< 0 15
23
31
39
47
55
63
71
79
87
95
103
111
119
127
135
143
151
159
167
175
183
191
199
207
215
223
231
239
247
255>;
default-brightness-level = <127>; // 125
};
pinctrl_pwm1: pwmgrp {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO01_PWM1_OUT 0x16
>;
};
pinctrl_wdog: wdoggrp {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO02_WDOG1_WDOG_B 0xc6
>;
};
};
&lcdif {
status = "okay";
};
&mipi_csi_1 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
port {
mipi1_sensor_ep: endpoint@1 {
remote-endpoint = <&ov5640_mipi1_ep>;
data-lanes = <2>;
csis-hs-settle = <13>;
csis-clk-settle = <2>;
csis-wclk;
};
csi1_mipi_ep: endpoint@2 {
remote-endpoint = <&csi1_ep>;
};
};
};
&mipi_dsi {
status = "okay";
port@1 {
dsim_to_adv7535: endpoint {
remote-endpoint = <&adv7535_from_dsim>;
attach-bridge;
};
};
port@2 {
mipi_dsi_lvds_out: endpoint {
remote-endpoint = <&dsi_lvds_bridge_in>;
};
};
&vpu_g1 {
status = "okay";
};
&vpu_g2 {
status = "okay";
};
&vpu_h1 {
status = "okay";
};
&gpu {
status = "okay";
};
From e808b65935978dc0ca28777320c6c2defb8ad569 Mon Sep 17 00:00:00 2001
From: Matteo Lisi <matteo.lisi@engicam.com>
Date: Wed, 6 Feb 2019 15:35:17 0100
Subject: [PATCH 1/3] add sn65dsi83 driver and drm fix
---
drivers/gpu/drm/Makefile | 2 -
drivers/gpu/drm/bridge/Kconfig | 16
drivers/gpu/drm/bridge/Makefile | 3
drivers/gpu/drm/bridge/sec-dsim.c | 4 -
drivers/gpu/drm/bridge/sn65dsi83.c | 666
drivers/gpu/drm/bridge/sn65dsi83/Kconfig | 7
drivers/gpu/drm/bridge/sn65dsi83/Makefile | 2
.../gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c | 400
.../gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h | 55
.../gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c | 433
.../drm/bridge/sn65dsi83/sn65dsi83_timing.h | 33
drivers/gpu/drm/bridge/sn65dsi84-dsi2lvds.c | 210
drivers/gpu/drm/drm_modes.c | 2 -
drivers/gpu/drm/drm_notify.c | 43
include/drm/drmP.h | 4
15 files changed, 1876 insertions( ), 4 deletions(-)
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83.c
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83/Kconfig
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83/Makefile
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c
create mode 100644 drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h
create mode 100644 drivers/gpu/drm/bridge/sn65dsi84-dsi2lvds.c
create mode 100644 drivers/gpu/drm/drm_notify.c
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index c5da7b5a5..02ab6dbd9 100644
--- a/drivers/gpu/drm/Makefile
b/drivers/gpu/drm/Makefile
@@ -18,7 18,7 @@ drm-y := drm_auth.o drm_bufs.o drm_cache.o \
drm_encoder.o drm_mode_object.o drm_property.o \
drm_plane.o drm_color_mgmt.o drm_print.o \
drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
- drm_syncobj.o drm_lease.o
drm_syncobj.o drm_lease.o drm_notify.o
drm-$(CONFIG_DRM_LIB_RANDOM) = lib/drm_random.o
drm-$(CONFIG_DRM_VM) = drm_vm.o
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 593e9a4e9..d711bf187 100644
--- a/drivers/gpu/drm/bridge/Kconfig
b/drivers/gpu/drm/bridge/Kconfig
@@ -134,4 134,20 @@ config DRM_ITE_IT6263
---help---
ITE IT6263 bridge chip driver.
config DRM_TI_SN65DSI84
tristate "SN65DSI84 DSI to LVDS bridge (old)"
select REGMAP_I2C
select DRM_MIPI_DSI
---help---
Support for the Texas Intruments SN65DSI84 bridge.
config DRM_TI_SN65DSI83
tristate "SN65DSI83/4 DSI to LVDS bridge (suggested)"
select REGMAP_I2C
select DRM_MIPI_DSI
---help---
Support for the Texas Intruments SN65DSI84 and SN65DSI83 bridges.
source "drivers/gpu/drm/bridge/sn65dsi83/Kconfig"
endmenu
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index d684dce13..a151c13d1 100644
--- a/drivers/gpu/drm/bridge/Makefile
b/drivers/gpu/drm/bridge/Makefile
@@ -16,3 16,6 @@ obj-$(CONFIG_DRM_ITE_IT6263) = it6263.o
obj-$(CONFIG_DRM_NWL_DSI) = nwl-dsi.o
obj-$(CONFIG_DRM_SEC_MIPI_DSIM) = sec-dsim.o
obj-$(CONFIG_DRM_NXP_SEIKO_43WVFIG) = nxp-seiko-43wvfig.o
obj-$(CONFIG_DRM_TI_SN65DSI84) = sn65dsi84-dsi2lvds.o
obj-$(CONFIG_DRM_TI_SN65DSI83) = sn65dsi83.o
obj-$(CONFIG_DRM_I2C_SN65DSI83) = sn65dsi83/
diff --git a/drivers/gpu/drm/bridge/sec-dsim.c b/drivers/gpu/drm/bridge/sec-dsim.c
index ea4966a5d..dd5dd3a76 100644
--- a/drivers/gpu/drm/bridge/sec-dsim.c
b/drivers/gpu/drm/bridge/sec-dsim.c
@@ -469,14 469,14 @@ static int sec_mipi_dsim_host_attach(struct mipi_dsi_host *host,
if (dsim->channel)
return -EINVAL;
-
/*
if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO) ||
!((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) ||
(dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) {
dev_err(dev, "unsupported dsi mode\n");
return -EINVAL;
}
-
*/
if (dsi->format != MIPI_DSI_FMT_RGB888 &&
dsi->format != MIPI_DSI_FMT_RGB565 &&
dsi->format != MIPI_DSI_FMT_RGB666 &&
diff --git a/drivers/gpu/drm/bridge/sn65dsi83.c b/drivers/gpu/drm/bridge/sn65dsi83.c
new file mode 100644
index 000000000..789ae6404
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83.c
@@ -0,0 1,666 @@
/*
* sn65dsi83.c - DVI output chip
*
* Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <drm/drmP.h>
#include <drm/drm_mode.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/input-polldev.h>
#include <linux/gpio.h>
#include <linux/fb.h>
#include <linux/kthread.h>
#include <video/display_timing.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
/* register definitions according to the sn65dsi83 data sheet */
#define SN_SOFT_RESET 0x09
#define SN_CLK_SRC 0x0a
#define SN_CLK_DIV 0x0b
#define SN_PLL_EN 0x0d
#define SN_DSI_LANES 0x10
#define SN_DSI_EQ 0x11
#define SN_DSI_CLK 0x12
#define SN_FORMAT 0x18
#define SN_LVDS_VOLTAGE 0x19
#define SN_LVDS_TERM 0x1a
#define SN_LVDS_CM_VOLTAGE 0x1b
#define SN_HACTIVE_LOW 0x20
#define SN_HACTIVE_HIGH 0x21
#define SN_VACTIVE_LOW 0x24
#define SN_VACTIVE_HIGH 0x25
#define SN_SYNC_DELAY_LOW 0x28
#define SN_SYNC_DELAY_HIGH 0x29
#define SN_HSYNC_LOW 0x2c
#define SN_HSYNC_HIGH 0x2d
#define SN_VSYNC_LOW 0x30
#define SN_VSYNC_HIGH 0x31
#define SN_HBP 0x34
#define SN_VBP 0x36
#define SN_HFP 0x38
#define SN_VFP 0x3a
#define SN_TEST_PATTERN 0x3c
#define SN_IRQ_EN 0xe0
#define SN_IRQ_MASK 0xe1
#define SN_IRQ_STAT 0xe5
static const char *client_name = "sn65dsi83";
static const unsigned char registers_to_show[] = {
SN_SOFT_RESET, SN_CLK_SRC, SN_CLK_DIV, SN_PLL_EN,
SN_DSI_LANES, SN_DSI_EQ, SN_DSI_CLK, SN_FORMAT,
SN_LVDS_VOLTAGE, SN_LVDS_TERM, SN_LVDS_CM_VOLTAGE,
0, SN_HACTIVE_LOW,
0, SN_VACTIVE_LOW,
0, SN_SYNC_DELAY_LOW,
0, SN_HSYNC_LOW,
0, SN_VSYNC_LOW,
SN_HBP, SN_VBP,
SN_HFP, SN_VFP,
SN_TEST_PATTERN,
SN_IRQ_EN, SN_IRQ_MASK, SN_IRQ_STAT,
};
struct sn65dsi83_priv
{
struct i2c_client *client;
struct device_node *disp_node;
struct device_node *disp_dsi;
struct gpio_desc *gp_en;
struct clk *mipi_clk;
struct notifier_block fbnb;
struct notifier_block drmnb;
u32 int_cnt;
u32 pixelclock;
u8 chip_enabled;
u8 show_reg;
u8 dsi_lanes;
u8 spwg; /* lvds lane 3 has MSBs of color */
u8 jeida; /* lvds lane 3 has LSBs of color */
u8 dsi_bpp;
u16 sync_delay;
u8 dsi_clk_divider;
u8 mipi_clk_index;
};
/**
* sn_i2c_read_reg - read data from a register of the i2c slave device.
*
* @client: i2c device.
* @reg: the register to read from.
* @buf: raw write data buffer.
* @len: length of the buffer to write
*/
static int sn_i2c_read_byte(struct sn65dsi83_priv *sn, u8 reg)
{
struct i2c_client *client = sn->client;
int ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
dev_err(&client->dev, "%s failed(%i)\n", __func__, ret);
return ret;
}
/**
* sn_i2c_write - write data to a register of the i2c slave device.
*
* @client: i2c device.
* @reg: the register to write to.
* @buf: raw data buffer to write.
* @len: length of the buffer to write
*/
static int sn_i2c_write_byte(struct sn65dsi83_priv *sn, u8 reg, u8 val)
{
struct i2c_client *client = sn->client;
int ret = i2c_smbus_write_byte_data(sn->client, reg, val);
if (ret < 0)
dev_err(&client->dev, "%s failed(%i)\n", __func__, ret);
return ret;
}
static void sn_disable(struct sn65dsi83_priv *sn)
{
if (sn->chip_enabled) {
disable_irq(sn->client->irq);
sn->chip_enabled = 0;
}
gpiod_set_value(sn->gp_en, 0);
}
static void sn_enable_gp(struct gpio_desc *gp_en)
{
msleep(15); /* disabled for at least 10 ms */
gpiod_set_value(gp_en, 1);
msleep(1);
}
static void sn_enable_irq(struct sn65dsi83_priv *sn)
{
sn_i2c_write_byte(sn, SN_IRQ_STAT, 0xff);
sn_i2c_write_byte(sn, SN_IRQ_MASK, 0x7f);
sn_i2c_write_byte(sn, SN_IRQ_EN, 1);
}
static int sn_get_dsi_clk_divider(struct sn65dsi83_priv *sn)
{
u32 dsi_clk_divider = 25;
u32 mipi_clk_rate;
u8 mipi_clk_index;
int ret;
u32 pixelclock = sn->pixelclock;
mipi_clk_rate = clk_get_rate(sn->mipi_clk);
if (!mipi_clk_rate) {
pr_err("mipi clock is off\n");
/* Divided by 2 because mipi output clock is DDR */
mipi_clk_rate = pixelclock * sn->dsi_bpp / (sn->dsi_lanes * 2);
}
if (mipi_clk_rate > 500000000) {
pr_err("mipi clock(%d) is too high\n", mipi_clk_rate);
mipi_clk_rate = 500000000;
}
if (pixelclock)
dsi_clk_divider = mipi_clk_rate / pixelclock;
if (dsi_clk_divider > 25)
dsi_clk_divider = 25;
else if (!dsi_clk_divider)
dsi_clk_divider = 1;
mipi_clk_index = mipi_clk_rate / 5000000;
if (mipi_clk_index < 8)
mipi_clk_index = 8;
ret = (sn->dsi_clk_divider == dsi_clk_divider) &&
(sn->mipi_clk_index == mipi_clk_index);
if (!ret)
pr_info("dsi_clk_divider = %d, mipi_clk_index=%d, mipi_clk_rate=%d\n",
dsi_clk_divider, mipi_clk_index, mipi_clk_rate);
sn->dsi_clk_divider = dsi_clk_divider;
sn->mipi_clk_index = mipi_clk_index;
return ret;
}
static int sn_setup_regs(struct sn65dsi83_priv *sn)
{
unsigned i = 5;
int format = 0x10;
u32 pixelclock;
struct videomode vm;
int ret;
ret = of_get_videomode(sn->disp_dsi, &vm, 0);
if (ret < 0)
return ret;
pixelclock = vm.pixelclock;
if (pixelclock) {
if (pixelclock > 37500000) {
i = (pixelclock - 12500000) / 25000000;
if (i > 5)
i = 5;
}
}
sn->pixelclock = pixelclock;
pr_info("pixelclock=%d %dx%d, margins=%d,%d %d,%d syncs=%d %d\n",
pixelclock, vm.hactive, vm.vactive,
vm.hback_porch, vm.hfront_porch,
vm.vback_porch, vm.vfront_porch,
vm.hsync_len, vm.vsync_len);
sn_i2c_write_byte(sn, SN_CLK_SRC, (i << 1) | 1);
sn_get_dsi_clk_divider(sn);
sn_i2c_write_byte(sn, SN_CLK_DIV, (sn->dsi_clk_divider - 1) << 3);
sn_i2c_write_byte(sn, SN_DSI_LANES, ((4 - sn->dsi_lanes) << 3) | 0x20);
sn_i2c_write_byte(sn, SN_DSI_EQ, 0);
sn_i2c_write_byte(sn, SN_DSI_CLK, sn->mipi_clk_index);
if (vm.flags & DISPLAY_FLAGS_DE_LOW)
format |= BIT(7);
if (!(vm.flags & DISPLAY_FLAGS_HSYNC_HIGH))
format |= BIT(6);
if (!(vm.flags & DISPLAY_FLAGS_VSYNC_HIGH))
format |= BIT(5);
if (sn->dsi_bpp == 24) {
if (sn->spwg) {
/* lvds lane 3 has MSBs of color */
format |= BIT(3);
} else if (sn->jeida) {
/* lvds lane 3 has LSBs of color */
format |= BIT(3) | BIT(1);
} else {
/* unused lvds lane 3 has LSBs of color */
format |= BIT(1);
}
}
sn_i2c_write_byte(sn, SN_FORMAT, format);
sn_i2c_write_byte(sn, SN_LVDS_VOLTAGE, 5);
sn_i2c_write_byte(sn, SN_LVDS_TERM, 3);
sn_i2c_write_byte(sn, SN_LVDS_CM_VOLTAGE, 0);
sn_i2c_write_byte(sn, SN_HACTIVE_LOW, (u8)vm.hactive);
sn_i2c_write_byte(sn, SN_HACTIVE_HIGH, (u8)(vm.hactive >> 8));
sn_i2c_write_byte(sn, SN_VACTIVE_LOW, (u8)vm.vactive);
sn_i2c_write_byte(sn, SN_VACTIVE_HIGH, (u8)(vm.vactive >> 8));
sn_i2c_write_byte(sn, SN_SYNC_DELAY_LOW, (u8)sn->sync_delay);
sn_i2c_write_byte(sn, SN_SYNC_DELAY_HIGH, (u8)(sn->sync_delay >> 8));
sn_i2c_write_byte(sn, SN_HSYNC_LOW, (u8)vm.hsync_len);
sn_i2c_write_byte(sn, SN_HSYNC_HIGH, (u8)(vm.hsync_len >> 8));
sn_i2c_write_byte(sn, SN_VSYNC_LOW, (u8)vm.vsync_len);
sn_i2c_write_byte(sn, SN_VSYNC_HIGH, (u8)(vm.vsync_len >> 8));
sn_i2c_write_byte(sn, SN_HBP, (u8)vm.hback_porch);
sn_i2c_write_byte(sn, SN_VBP, (u8)vm.vback_porch);
sn_i2c_write_byte(sn, SN_HFP, (u8)vm.hfront_porch);
sn_i2c_write_byte(sn, SN_VFP, (u8)vm.vfront_porch);
sn_i2c_write_byte(sn, SN_TEST_PATTERN, 0);
return 0;
}
static void sn_enable_pll(struct sn65dsi83_priv *sn)
{
if (!sn_get_dsi_clk_divider(sn)) {
sn_i2c_write_byte(sn, SN_CLK_DIV, (sn->dsi_clk_divider - 1) << 3);
sn_i2c_write_byte(sn, SN_DSI_CLK, sn->mipi_clk_index);
}
sn_i2c_write_byte(sn, SN_PLL_EN, 1);
msleep(5);
sn_i2c_write_byte(sn, SN_SOFT_RESET, 1);
sn_enable_irq(sn);
}
static void sn_disable_pll(struct sn65dsi83_priv *sn)
{
if (sn->chip_enabled)
sn_i2c_write_byte(sn, SN_PLL_EN, 0);
}
static void sn_init(struct sn65dsi83_priv *sn)
{
sn_i2c_write_byte(sn, SN_SOFT_RESET, 1);
sn_i2c_write_byte(sn, SN_PLL_EN, 0);
sn_i2c_write_byte(sn, SN_IRQ_MASK, 0x0);
sn_i2c_write_byte(sn, SN_IRQ_EN, 1);
}
static void sn_prepare(struct sn65dsi83_priv *sn)
{
sn_enable_gp(sn->gp_en);
sn_init(sn);
if (!sn->chip_enabled) {
sn->chip_enabled = 1;
enable_irq(sn->client->irq);
}
sn_setup_regs(sn);
}
static int sn_drm_event(struct notifier_block *nb, unsigned long event, void *data)
{
struct drm_device *drm_dev = data;
struct device_node *node = drm_dev->dev->of_node;
struct sn65dsi83_priv *sn = container_of(nb, struct sn65dsi83_priv, drmnb);
struct device *dev = &sn->client->dev;
dev_dbg(dev, "%s: event %lx\n", __func__, event);
if (node != sn->disp_node)
return 0;
switch (event) {
case DRM_MODE_DPMS_ON:
sn_enable_pll(sn);
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
sn_disable_pll(sn);
break;
default:
dev_info(dev, "%s: unknown event %lx\n", __func__, event);
}
return 0;
}
static int sn_fb_event(struct notifier_block *nb, unsigned long event, void *data)
{
struct fb_event *evdata = data;
struct fb_info *info = evdata->info;
struct device_node *node = info->device->of_node;
struct sn65dsi83_priv *sn = container_of(nb, struct sn65dsi83_priv, fbnb);
struct device *dev;
int blank_type;
dev = &sn->client->dev;
dev_dbg(dev, "%s: event %lx\n", __func__, event);
if (node != sn->disp_node)
return 0;
switch (event) {
case FB_R_EARLY_EVENT_BLANK:
blank_type = *((int *)evdata->data);
if (blank_type == FB_BLANK_UNBLANK) {
sn_disable(sn);
} else {
sn_enable_pll(sn);
}
break;
case FB_EARLY_EVENT_BLANK:
blank_type = *((int *)evdata->data);
if (blank_type == FB_BLANK_UNBLANK) {
sn_prepare(sn);
} else {
sn_disable_pll(sn);
}
break;
case FB_EVENT_BLANK: {
blank_type = *((int *)evdata->data);
if (blank_type == FB_BLANK_UNBLANK) {
sn_enable_pll(sn);
} else {
sn_disable(sn);
}
dev_info(dev, "%s: blank type 0x%x\n", __func__, blank_type );
break;
}
case FB_EVENT_SUSPEND : {
dev_info(dev, "%s: suspend\n", __func__ );
sn_disable(sn);
break;
}
case FB_EVENT_RESUME : {
dev_info(dev, "%s: resume\n", __func__ );
break;
}
case FB_EVENT_FB_REGISTERED : {
if (clk_get_rate(sn->mipi_clk)) {
sn_prepare(sn);
sn_enable_pll(sn);
}
break;
}
default:
dev_info(dev, "%s: unknown event %lx\n", __func__, event);
}
return 0;
}
/*
* We only report errors in this handler
*/
static irqreturn_t sn_irq_handler(int irq, void *id)
{
struct sn65dsi83_priv *sn = id;
int status = sn_i2c_read_byte(sn, SN_IRQ_STAT);
if (status > 0) {
sn_i2c_write_byte(sn, SN_IRQ_STAT, status);
dev_info(&sn->client->dev, "%s: status %x %x %x\n", __func__,
status, sn_i2c_read_byte(sn, SN_CLK_SRC),
sn_i2c_read_byte(sn, SN_IRQ_MASK));
// if (status & 1)
// sn_i2c_write_byte(sn, SN_SOFT_RESET, 1);
if (sn->int_cnt > 10) {
disable_irq_nosync(sn->client->irq);
} else {
msleep(100);
}
return IRQ_HANDLED;
} else {
dev_err(&sn->client->dev, "%s: read error %d\n", __func__, status);
}
return IRQ_NONE;
}
static ssize_t sn65dsi83_reg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sn65dsi83_priv *sn = dev_get_drvdata(dev);
int val;
const unsigned char *p = registers_to_show;
int i = 0;
int total = 0;
int reg;
int cnt;
if (!sn->chip_enabled)
return -EBUSY;
if (sn->show_reg != 0) {
val = sn_i2c_read_byte(sn, sn->show_reg);
return sprintf(buf, "%02x: %02x\n", sn->show_reg, val);
}
while (i < ARRAY_SIZE(registers_to_show)) {
reg = *p ;
i ;
if (!reg) {
reg = *p ;
i ;
val = sn_i2c_read_byte(sn, reg);
val |= sn_i2c_read_byte(sn, reg 1) << 8;
cnt = sprintf(&buf[total], "%02x: %04x (%d)\n", reg, val, val);
} else {
val = sn_i2c_read_byte(sn, reg);
cnt = sprintf(&buf[total], "%02x: %02x (%d)\n", reg, val, val);
}
if (cnt <= 0)
break;
total = cnt;
}
return total;
}
static ssize_t sn65dsi83_reg_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned val;
int ret;
struct sn65dsi83_priv *sn = dev_get_drvdata(dev);
char *endp;
unsigned reg = simple_strtol(buf, &endp, 16);
if (!sn->chip_enabled)
return -EBUSY;
if (reg > 0xe5)
return count;
sn->show_reg = reg;
if (!endp)
return count;
if (*endp == 0x20)
endp ;
if (!*endp || *endp == 0x0a)
return count;
val = simple_strtol(endp, &endp, 16);
if (val >= 0x100)
return count;
dev_err(dev, "%s:reg=0x%x, val=0x%x\n", __func__, reg, val);
ret = sn_i2c_write_byte(sn, reg, val);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR(sn65dsi83_reg, 0644, sn65dsi83_reg_show, sn65dsi83_reg_store);
/*
* I2C init/probing/exit functions
*/
static int sn65dsi83_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
struct sn65dsi83_priv *sn;
struct i2c_adapter *adapter;
struct device_node *np = client->dev.of_node;
struct gpio_desc *gp_en;
const char *df;
u32 sync_delay;
u32 dsi_lanes;
dev_info(&client->dev, "sn65dsi83_probe 1\n");
adapter = to_i2c_adapter(client->dev.parent);
ret = i2c_check_functionality(adapter,
I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA);
if (!ret) {
dev_err(&client->dev, "i2c_check_functionality failed\n");
return -ENODEV;
}
gp_en = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(gp_en)) {
if (PTR_ERR(gp_en) != -EPROBE_DEFER)
dev_err(&client->dev, "Failed to get enable gpio: %ld\n",
PTR_ERR(gp_en));
return PTR_ERR(gp_en);
}
if (gp_en) {
sn_enable_gp(gp_en);
} else {
dev_warn(&client->dev, "no enable pin available");
}
ret = i2c_smbus_read_byte_data(client, SN_CLK_SRC);
if (ret < 0) {
/* enable might be used for something else, change to input */
gpiod_direction_input(gp_en);
dev_info(&client->dev, "i2c read failed\n");
return -ENODEV;
}
// gpiod_set_value(gp_en, 0);
sn = devm_kzalloc(&client->dev, sizeof(*sn), GFP_KERNEL);
if (!sn)
return -ENOMEM;
sn->client = client;
sn->gp_en = gp_en;
sn_init(sn);
sn->disp_dsi = of_parse_phandle(np, "display-dsi", 0);
if (!sn->disp_dsi)
return -ENODEV;
sn->disp_node = of_parse_phandle(np, "display", 0);
if (!sn->disp_node)
return -ENODEV;
sn->sync_delay = 0x120;
if (!of_property_read_u32(np, "sync-delay", &sync_delay)) {
if (sync_delay > 0xfff)
return -EINVAL;
sn->sync_delay = sync_delay;
}
if (of_property_read_u32(sn->disp_dsi, "dsi-lanes", &dsi_lanes) < 0)
return -EINVAL;
if (dsi_lanes < 1 || dsi_lanes > 4)
return -EINVAL;
sn->dsi_lanes = dsi_lanes;
sn->spwg = of_property_read_bool(sn->disp_dsi, "spwg");
sn->jeida = of_property_read_bool(sn->disp_dsi, "jeida");
ret = of_property_read_string(sn->disp_dsi, "dsi-format", &df);
if (ret) {
dev_err(&client->dev, "dsi-format missing in display node%d\n", ret);
return ret;
}
sn->dsi_bpp = !strcmp(df, "rgb666") ? 18 : 24;
sn->mipi_clk = devm_clk_get(&client->dev, "mipi_clk");
if (IS_ERR(sn->mipi_clk))
return PTR_ERR(sn->mipi_clk);
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, sn_irq_handler,
IRQF_ONESHOT, client->name, sn);
if (ret)
pr_info("%s: request_irq failed, irq:%i\n", client_name, client->irq);
disable_irq(client->irq);
i2c_set_clientdata(client, sn);
sn->drmnb.notifier_call = sn_drm_event;
ret = drm_register_client(&sn->drmnb);
if (ret < 0) {
dev_err(&client->dev, "drm_register_client failed(%d)\n", ret);
return ret;
}
sn->fbnb.notifier_call = sn_fb_event;
ret = fb_register_client(&sn->fbnb);
if (ret < 0) {
dev_err(&client->dev, "fb_register_client failed(%d)\n", ret);
return ret;
}
ret = device_create_file(&client->dev, &dev_attr_sn65dsi83_reg);
if (ret < 0)
pr_warn("failed to add sn65dsi83 sysfs files\n");
sn_prepare(sn);
dev_info(&client->dev, "succeeded\n");
return 0;
}
static int sn65dsi83_remove(struct i2c_client *client)
{
struct sn65dsi83_priv *sn = i2c_get_clientdata(client);
device_remove_file(&client->dev, &dev_attr_sn65dsi83_reg);
fb_unregister_client(&sn->drmnb);
fb_unregister_client(&sn->fbnb);
sn_disable(sn);
return 0;
}
static const struct i2c_device_id sn65dsi83_id[] = {
{"sn65dsi83", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, sn65dsi83_id);
static struct i2c_driver sn65dsi83_driver = {
.driver = {
.name = "sn65dsi83",
.owner = THIS_MODULE,
},
.probe = sn65dsi83_probe,
.remove = sn65dsi83_remove,
.id_table = sn65dsi83_id,
};
module_i2c_driver(sn65dsi83_driver);
MODULE_AUTHOR("Boundary Devices, Inc.");
MODULE_DESCRIPTION("sn65dsi83 mipi to lvds bridge");
MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/Kconfig b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig
new file mode 100644
index 000000000..1d8f37f68
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83/Kconfig
@@ -0,0 1,7 @@
config DRM_I2C_SN65DSI83
bool "SN65DSI83 mipi dsi to lvds bridge"
depends on OF
select DRM_MIPI_DSI
default y
help
Support for the sn65dsi83 MIPI DSI to LVDS bridge
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/Makefile b/drivers/gpu/drm/bridge/sn65dsi83/Makefile
new file mode 100644
index 000000000..dee7f493b
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83/Makefile
@@ -0,0 1,2 @@
sn65dsi83-objs := sn65dsi83_drv.o sn65dsi83_brg.o
obj-$(CONFIG_DRM_I2C_SN65DSI83) := sn65dsi83.o
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c
new file mode 100644
index 000000000..20bbf2a54
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.c
@@ -0,0 1,400 @@
/*
* Copyright (C) 2018 CopuLab Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/i2c.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/slab.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_connector.h>
#include <video/mipi_display.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include "sn65dsi83_brg.h"
/* Register addresses */
#define SN65DSI83_SOFT_RESET 0x09
#define SN65DSI83_CORE_PLL 0x0A
#define LVDS_CLK_RANGE_SHIFT 1
#define HS_CLK_SRC_SHIFT 0
#define SN65DSI83_PLL_DIV 0x0B
#define DSI_CLK_DIV_SHIFT 3
#define SN65DSI83_PLL_EN 0x0D
#define SN65DSI83_DSI_CFG 0x10
#define CHA_DSI_LANES_SHIFT 3
#define SN65DSI83_DSI_EQ 0x11
#define SN65DSI83_CHA_DSI_CLK_RNG 0x12
#define SN65DSI83_CHB_DSI_CLK_RNG 0x13
#define SN65DSI83_LVDS_MODE 0x18
#define DE_NEG_POLARITY_SHIFT 7
#define HS_NEG_POLARITY_SHIFT 6
#define VS_NEG_POLARITY_SHIFT 5
#define LVDS_LINK_CFG_SHIFT 4
#define CHA_24BPP_MODE_SHIFT 3
#define CHA_24BPP_FMT1_SHIFT 1
#define SN65DSI83_LVDS_SIGN 0x19
#define SN65DSI83_LVDS_TERM 0x1A
#define SN65DSI83_LVDS_CM_ADJ 0x1B
#define SN65DSI83_CHA_LINE_LEN_LO 0x20
#define SN65DSI83_CHA_LINE_LEN_HI 0x21
#define SN65DSI83_CHB_LINE_LEN_LO 0x22
#define SN65DSI83_CHB_LINE_LEN_HI 0x23
#define SN65DSI83_CHA_VERT_LINES_LO 0x24
#define SN65DSI83_CHA_VERT_LINES_HI 0x25
#define SN65DSI83_CHB_VERT_LINES_LO 0x26
#define SN65DSI83_CHB_VERT_LINES_HI 0x27
#define SN65DSI83_CHA_SYNC_DELAY_LO 0x28
#define SN65DSI83_CHA_SYNC_DELAY_HI 0x29
#define SN65DSI83_CHB_SYNC_DELAY_LO 0x2A
#define SN65DSI83_CHB_SYNC_DELAY_HI 0x2B
#define SN65DSI83_CHA_HSYNC_WIDTH_LO 0x2C
#define SN65DSI83_CHA_HSYNC_WIDTH_HI 0x2D
#define SN65DSI83_CHB_HSYNC_WIDTH_LO 0x2E
#define SN65DSI83_CHB_HSYNC_WIDTH_HI 0x2F
#define SN65DSI83_CHA_VSYNC_WIDTH_LO 0x30
#define SN65DSI83_CHA_VSYNC_WIDTH_HI 0x31
#define SN65DSI83_CHB_VSYNC_WIDTH_LO 0x32
#define SN65DSI83_CHB_VSYNC_WIDTH_HI 0x33
#define SN65DSI83_CHA_HORZ_BACKPORCH 0x34
#define SN65DSI83_CHB_HORZ_BACKPORCH 0x35
#define SN65DSI83_CHA_VERT_BACKPORCH 0x36
#define SN65DSI83_CHB_VERT_BACKPORCH 0x37
#define SN65DSI83_CHA_HORZ_FRONTPORCH 0x38
#define SN65DSI83_CHB_HORZ_FRONTPORCH 0x39
#define SN65DSI83_CHA_VERT_FRONTPORCH 0x3A
#define SN65DSI83_CHB_VERT_FRONTPORCH 0x3B
#define SN65DSI83_CHA_ERR 0xE5
#define SN65DSI83_TEST_PATTERN 0x3C
#define SN65DSI83_REG_3D 0x3D
#define SN65DSI83_REG_3E 0x3E
static int sn65dsi83_brg_power_on(struct sn65dsi83_brg *brg)
{
dev_info(&brg->client->dev,"%s\n",__func__);
gpiod_set_value_cansleep(brg->gpio_enable, 1);
/* Wait for 1ms for the internal voltage regulator to stabilize */
msleep(1);
return 0;
}
static void sn65dsi83_brg_power_off(struct sn65dsi83_brg *brg)
{
dev_info(&brg->client->dev,"%s\n",__func__);
gpiod_set_value_cansleep(brg->gpio_enable, 0);
/*
* The EN pin must be held low for at least 10 ms
* before being asserted high
*/
msleep(10);
}
static int sn65dsi83_write(struct i2c_client *client, u8 reg, u8 val)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, val);
if (ret)
dev_err(&client->dev, "failed to write at 0x%02x", reg);
dev_dbg(&client->dev, "%s: write reg 0x%02x data 0x%02x", __func__, reg, val);
return ret;
}
#define SN65DSI83_WRITE(reg,val) sn65dsi83_write(client, (reg) , (val))
static int sn65dsi83_read(struct i2c_client *client, u8 reg)
{
int ret;
dev_info(&client->dev, "client 0x%p", client);
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "failed reading at 0x%02x", reg);
return ret;
}
dev_dbg(&client->dev, "%s: read reg 0x%02x data 0x%02x", __func__, reg, ret);
return ret;
}
#define SN65DSI83_READ(reg) sn65dsi83_read(client, (reg))
static int sn65dsi83_brg_start_stream(struct sn65dsi83_brg *brg)
{
int regval;
struct i2c_client *client = I2C_CLIENT(brg);
dev_info(&client->dev,"%s\n",__func__);
/* Set the PLL_EN bit (CSR 0x0D.0) */
SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x1);
/* Wait for the PLL_LOCK bit to be set (CSR 0x0A.7) */
msleep(200);
/* Perform SW reset to apply changes */
SN65DSI83_WRITE(SN65DSI83_SOFT_RESET, 0x01);
/* Read CHA Error register */
regval = SN65DSI83_READ(SN65DSI83_CHA_ERR);
dev_info(&client->dev, "CHA (0x%02x) = 0x%02x",
SN65DSI83_CHA_ERR, regval);
SN65DSI83_WRITE(SN65DSI83_CHA_ERR, 0xff);
regval = SN65DSI83_READ(SN65DSI83_CHA_ERR);
dev_info(&client->dev, "CHA (0x%02x) = 0x%02x",
SN65DSI83_CHA_ERR, regval);
msleep(10);
regval = SN65DSI83_READ(SN65DSI83_CHA_ERR);
dev_info(&client->dev, "CHA (0x%02x) = 0x%02x",
SN65DSI83_CHA_ERR, regval);
return 0;
}
static void sn65dsi83_brg_stop_stream(struct sn65dsi83_brg *brg)
{
struct i2c_client *client = I2C_CLIENT(brg);
dev_info(&client->dev,"%s\n",__func__);
/* Clear the PLL_EN bit (CSR 0x0D.0) */
SN65DSI83_WRITE(SN65DSI83_PLL_EN, 0x00);
}
static int sn65dsi83_calk_clk_range(int min_regval, int max_regval,
unsigned long min_clk, unsigned long inc,
unsigned long target_clk)
{
int regval = min_regval;
unsigned long clk = min_clk;
while (regval <= max_regval) {
if ((clk <= target_clk) && (target_clk < (clk inc)))
return regval;
regval ;
clk = inc;
}
return -1;
}
#define ABS(X) ((X) < 0 ? (-1 * (X)) : (X))
static int sn65dsi83_calk_div(int min_regval, int max_regval, int min_div,
int inc, unsigned long source_clk,
unsigned long target_clk)
{
int regval = min_regval;
int div = min_div;
unsigned long curr_delta;
unsigned long prev_delta = ABS(DIV_ROUND_UP(source_clk, div) -
target_clk);
while (regval <= max_regval) {
curr_delta = ABS(DIV_ROUND_UP(source_clk, div) - target_clk);
if (curr_delta > prev_delta)
return --regval;
regval ;
div = inc;
}
return -1;
}
static int sn65dsi83_brg_configure(struct sn65dsi83_brg *brg)
{
int regval = 0;
struct i2c_client *client = I2C_CLIENT(brg);
struct videomode *vm = VM(brg);
u32 dsi_clk = (((PIXCLK * BPP(brg)) / DSI_LANES(brg)) >> 1);
dev_info(&client->dev, "DSI clock [ %u ] Hz\n",dsi_clk);
dev_info(&client->dev, "GeoMetry [ %d x %d ] Hz\n",HACTIVE,VACTIVE);
/* Reset PLL_EN and SOFT_RESET registers */
SN65DSI83_WRITE(SN65DSI83_SOFT_RESET,0x00);
SN65DSI83_WRITE(SN65DSI83_PLL_EN,0x00);
/* LVDS clock setup */
if ((25000000 <= PIXCLK) && (PIXCLK < 37500000))
regval = 0;
else
regval = sn65dsi83_calk_clk_range(0x01, 0x05, 37500000, 25000000,
PIXCLK);
if (regval < 0) {
dev_err(&client->dev, "failed to configure LVDS clock");
return -EINVAL;
}
regval = (regval << LVDS_CLK_RANGE_SHIFT);
regval |= (1 << HS_CLK_SRC_SHIFT); /* Use DSI clock */
SN65DSI83_WRITE(SN65DSI83_CORE_PLL,regval);
/* DSI clock range */
regval = sn65dsi83_calk_clk_range(0x08, 0x64, 40000000, 5000000, dsi_clk);
if (regval < 0) {
dev_err(&client->dev, "failed to configure DSI clock range\n");
return -EINVAL;
}
SN65DSI83_WRITE(SN65DSI83_CHA_DSI_CLK_RNG,regval);
/* DSI clock divider */
regval = sn65dsi83_calk_div(0x0, 0x18, 1, 1, dsi_clk, PIXCLK);
if (regval < 0) {
dev_err(&client->dev, "failed to calculate DSI clock divider");
return -EINVAL;
}
regval = regval << DSI_CLK_DIV_SHIFT;
SN65DSI83_WRITE(SN65DSI83_PLL_DIV,regval);
/* Configure DSI_LANES */
regval = SN65DSI83_READ(SN65DSI83_DSI_CFG);
regval &= ~(3 << CHA_DSI_LANES_SHIFT);
regval |= ((4 - DSI_LANES(brg)) << CHA_DSI_LANES_SHIFT);
printk("DSI lanes = %d.... val = 0x%x\n", (int)DSI_LANES(brg), regval);
SN65DSI83_WRITE(SN65DSI83_DSI_CFG,regval);
/* CHA_DSI_DATA_EQ - No Equalization */
/* CHA_DSI_CLK_EQ - No Equalization */
SN65DSI83_WRITE(SN65DSI83_DSI_EQ,0x00);
/* Video formats */
regval = 0;
if (FLAGS & DISPLAY_FLAGS_HSYNC_LOW)
regval |= (1 << HS_NEG_POLARITY_SHIFT);
if (FLAGS & DISPLAY_FLAGS_VSYNC_LOW)
regval |= (1 << VS_NEG_POLARITY_SHIFT);
if (FLAGS & DISPLAY_FLAGS_DE_LOW)
regval |= (1 << DE_NEG_POLARITY_SHIFT);
if (BPP(brg) == 24)
regval |= (1 << CHA_24BPP_MODE_SHIFT);
if (FORMAT(brg) == 1)
regval |= (1 << CHA_24BPP_FMT1_SHIFT);
regval |= (1 << LVDS_LINK_CFG_SHIFT);
printk("SN65DSI83_LVDS_MODE = 0x%x\n", regval);
SN65DSI83_WRITE(SN65DSI83_LVDS_MODE,regval);
/* Voltage and pins */
SN65DSI83_WRITE(SN65DSI83_LVDS_SIGN,0x00);
SN65DSI83_WRITE(SN65DSI83_LVDS_TERM,0x03);
SN65DSI83_WRITE(SN65DSI83_LVDS_CM_ADJ,0x00);
/* Configure sync delay to minimal allowed value */
SN65DSI83_WRITE(SN65DSI83_CHA_SYNC_DELAY_LO,0x21);
SN65DSI83_WRITE(SN65DSI83_CHA_SYNC_DELAY_HI,0x00);
/* Geometry */
SN65DSI83_WRITE(SN65DSI83_CHA_LINE_LEN_LO,LOW(HACTIVE));
SN65DSI83_WRITE(SN65DSI83_CHA_LINE_LEN_HI,HIGH(HACTIVE));
SN65DSI83_WRITE(SN65DSI83_CHA_VERT_LINES_LO,LOW(VACTIVE));
SN65DSI83_WRITE(SN65DSI83_CHA_VERT_LINES_HI,HIGH(VACTIVE));
SN65DSI83_WRITE(SN65DSI83_CHA_HSYNC_WIDTH_LO,LOW(HPW));
SN65DSI83_WRITE(SN65DSI83_CHA_HSYNC_WIDTH_HI,HIGH(HPW));
SN65DSI83_WRITE(SN65DSI83_CHA_VSYNC_WIDTH_LO,LOW(VPW));
SN65DSI83_WRITE(SN65DSI83_CHA_VSYNC_WIDTH_HI,HIGH(VPW));
SN65DSI83_WRITE(SN65DSI83_CHA_HORZ_BACKPORCH,LOW(HBP));
SN65DSI83_WRITE(SN65DSI83_CHA_VERT_BACKPORCH,LOW(VBP));
SN65DSI83_WRITE(SN65DSI83_CHA_HORZ_FRONTPORCH,LOW(HFP));
SN65DSI83_WRITE(SN65DSI83_CHA_VERT_FRONTPORCH,LOW(VFP));
SN65DSI83_WRITE(SN65DSI83_TEST_PATTERN,0x00);
SN65DSI83_WRITE(SN65DSI83_REG_3D,0x00);
SN65DSI83_WRITE(SN65DSI83_REG_3E,0x00);
/* mute channel B */
SN65DSI83_WRITE(SN65DSI83_CHB_DSI_CLK_RNG, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_LINE_LEN_LO, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_LINE_LEN_HI, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_VERT_LINES_LO, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_VERT_LINES_HI, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_SYNC_DELAY_LO, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_SYNC_DELAY_HI, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_HSYNC_WIDTH_LO, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_HSYNC_WIDTH_HI, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_VSYNC_WIDTH_LO, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_VSYNC_WIDTH_HI, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_HORZ_BACKPORCH, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_VERT_BACKPORCH, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_HORZ_FRONTPORCH, 0x00);
SN65DSI83_WRITE(SN65DSI83_CHB_VERT_FRONTPORCH, 0x00);
return 0;
}
static int sn65dsi83_brg_setup(struct sn65dsi83_brg *brg)
{
struct i2c_client *client = I2C_CLIENT(brg);
dev_info(&client->dev,"%s\n",__func__);
sn65dsi83_brg_power_on(brg);
return sn65dsi83_brg_configure(brg);
}
static int sn65dsi83_brg_reset(struct sn65dsi83_brg *brg)
{
/* Soft Reset reg value at power on should be 0x00 */
struct i2c_client *client = I2C_CLIENT(brg);
int ret = SN65DSI83_READ(SN65DSI83_SOFT_RESET);
dev_info(&client->dev,"%s\n",__func__);
if (ret != 0x00) {
dev_err(&client->dev,"Failed to reset the device");
return -ENODEV;
}
return 0;
}
static struct sn65dsi83_brg_funcs brg_func = {
.power_on = sn65dsi83_brg_power_on,
.power_off = sn65dsi83_brg_power_off,
.setup = sn65dsi83_brg_setup,
.reset = sn65dsi83_brg_reset,
.start_stream = sn65dsi83_brg_start_stream,
.stop_stream = sn65dsi83_brg_stop_stream,
};
static struct sn65dsi83_brg brg = {
.funcs = &brg_func,
};
struct sn65dsi83_brg *sn65dsi83_brg_get(void) {
return &brg;
}
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h
new file mode 100644
index 000000000..9f23df8af
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_brg.h
@@ -0,0 1,55 @@
#ifndef _SN65DSI83_BRG_H__
#define _SN65DSI83_BRG_H__
#include <linux/i2c.h>
#include <linux/gpio/consumer.h>
#include <video/videomode.h>
struct sn65dsi83_brg;
struct sn65dsi83_brg_funcs {
int (*power_on)(struct sn65dsi83_brg *sn65dsi8383_brg);
void (*power_off)(struct sn65dsi83_brg *sn65dsi8383_brg);
int (*reset)(struct sn65dsi83_brg *sn65dsi8383_brg);
int (*setup)(struct sn65dsi83_brg *sn65dsi8383_brg);
int (*start_stream)(struct sn65dsi83_brg *sn65dsi8383_brg);
void (*stop_stream)(struct sn65dsi83_brg *sn65dsi8383_brg);
};
struct sn65dsi83_brg {
struct i2c_client *client;
struct gpio_desc *gpio_enable;
/* Bridge Panel Parameters */
struct videomode vm;
u32 width_mm;
u32 height_mm;
u32 format;
u32 bpp;
u8 num_dsi_lanes;
struct sn65dsi83_brg_funcs *funcs;
};
struct sn65dsi83_brg *sn65dsi83_brg_get(void);
#define I2C_DEVICE(A) &(A)->client->dev
#define I2C_CLIENT(A) (A)->client
#define VM(A) &(A)->vm
#define BPP(A) (A)->bpp
#define FORMAT(A) (A)->format
#define DSI_LANES(A) (A)->num_dsi_lanes
/* The caller has to have a vm structure defined */
#define PIXCLK vm->pixelclock
#define HACTIVE vm->hactive
#define HFP vm->hfront_porch
#define HBP vm->hback_porch
#define HPW vm->hsync_len
#define VACTIVE vm->vactive
#define VFP vm->vfront_porch
#define VBP vm->vback_porch
#define VPW vm->vsync_len
#define FLAGS vm->flags
#define HIGH(A) (((A) >> 8) & 0xFF)
#define LOW(A) ((A) & 0xFF)
#endif /* _SN65DSI83_BRG_H__ */
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c
new file mode 100644
index 000000000..b0adb20de
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_drv.c
@@ -0,0 1,433 @@
/*
* Licensed under the GPL-2.
*/
#define DEBUG
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_graph.h>
#include <linux/slab.h>
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc_helper.h>
#include <video/mipi_display.h>
#include <video/of_videomode.h>
#include <video/videomode.h>
#include "sn65dsi83_timing.h"
#include "sn65dsi83_brg.h"
struct sn65dsi83 {
u8 channel_id;
enum drm_connector_status status;
bool powered;
struct drm_display_mode curr_mode;
struct drm_bridge bridge;
struct drm_connector connector;
struct device_node *host_node;
struct mipi_dsi_device *dsi;
struct sn65dsi83_brg *brg;
};
static int sn65dsi83_attach_dsi(struct sn65dsi83 *sn65dsi83);
#define DRM_DEVICE(A) A->dev->dev
/* Connector funcs */
static struct sn65dsi83 *connector_to_sn65dsi83(struct drm_connector *connector)
{
return container_of(connector, struct sn65dsi83, connector);
}
static int sn65dsi83_connector_get_modes(struct drm_connector *connector)
{
struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector);
struct sn65dsi83_brg *brg = sn65dsi83->brg;
struct device *dev = connector->dev->dev;
struct drm_display_mode *mode;
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
u32 *bus_flags = &connector->display_info.bus_flags;
int ret;
dev_info(dev, "%s\n",__func__);
mode = drm_mode_create(connector->dev);
if (!mode) {
DRM_DEV_ERROR(dev, "Failed to create display mode!\n");
return 0;
}
drm_display_mode_from_videomode(&brg->vm, mode);
mode->width_mm = brg->width_mm;
mode->height_mm = brg->height_mm;
mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
drm_mode_connector_list_update(connector);
connector->display_info.width_mm = mode->width_mm;
connector->display_info.height_mm = mode->height_mm;
if (brg->vm.flags & DISPLAY_FLAGS_DE_HIGH)
*bus_flags |= DRM_BUS_FLAG_DE_HIGH;
if (brg->vm.flags & DISPLAY_FLAGS_DE_LOW)
*bus_flags |= DRM_BUS_FLAG_DE_LOW;
if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
*bus_flags |= DRM_BUS_FLAG_PIXDATA_NEGEDGE;
if (brg->vm.flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
*bus_flags |= DRM_BUS_FLAG_PIXDATA_POSEDGE;
ret = drm_display_info_set_bus_formats(&connector->display_info,
&bus_format, 1);
if (ret)
return ret;
return 1;
}
static enum drm_mode_status
sn65dsi83_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector);
struct device *dev = connector->dev->dev;
if (mode->clock > ( sn65dsi83->brg->vm.pixelclock / 1000 ))
return MODE_CLOCK_HIGH;
dev_info(dev, "%s: mode: %d*%d@%d is valid\n",__func__,
mode->hdisplay,mode->vdisplay,mode->clock);
//drm_kms_helper_hotplug_event(connector->dev);
return MODE_OK;
}
static struct drm_connector_helper_funcs sn65dsi83_connector_helper_funcs = {
.get_modes = sn65dsi83_connector_get_modes,
.mode_valid = sn65dsi83_connector_mode_valid,
};
static enum drm_connector_status
sn65dsi83_connector_detect(struct drm_connector *connector, bool force)
{
struct sn65dsi83 *sn65dsi83 = connector_to_sn65dsi83(connector);
struct device *dev = connector->dev->dev;
enum drm_connector_status status;
dev_info(dev, "%s\n",__func__);
status = connector_status_connected;
sn65dsi83->status = status;
return status;
}
static int sn65dsi83_drm_helper_connector_dpms(struct drm_connector *connector, int mode)
{
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_OFF);
drm_helper_connector_dpms(connector, DRM_MODE_DPMS_ON);
return 0;
}
static struct drm_connector_funcs sn65dsi83_connector_funcs = {
// .dpms = drm_atomic_helper_connector_dpms,
.dpms = sn65dsi83_drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = sn65dsi83_connector_detect,
.destroy = drm_connector_cleanup,
.reset = drm_atomic_helper_connector_reset,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
/* Bridge funcs */
static struct sn65dsi83 *bridge_to_sn65dsi83(struct drm_bridge *bridge)
{
return container_of(bridge, struct sn65dsi83, bridge);
}
static void sn65dsi83_bridge_enable(struct drm_bridge *bridge)
{
struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
dev_info(DRM_DEVICE(bridge),"%s\n",__func__);
sn65dsi83->brg->funcs->setup(sn65dsi83->brg);
sn65dsi83->brg->funcs->start_stream(sn65dsi83->brg);
}
static void sn65dsi83_bridge_disable(struct drm_bridge *bridge)
{
struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
dev_info(DRM_DEVICE(bridge),"%s\n",__func__);
sn65dsi83->brg->funcs->stop_stream(sn65dsi83->brg);
sn65dsi83->brg->funcs->power_off(sn65dsi83->brg);
}
static void sn65dsi83_bridge_mode_set(struct drm_bridge *bridge,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode)
{
struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
dev_info(DRM_DEVICE(bridge), "%s: mode: %d*%d@%d\n",__func__,
mode->hdisplay,mode->vdisplay,mode->clock);
drm_mode_copy(&sn65dsi83->curr_mode, adj_mode);
}
static int sn65dsi83_bridge_attach(struct drm_bridge *bridge)
{
struct sn65dsi83 *sn65dsi83 = bridge_to_sn65dsi83(bridge);
int ret;
dev_info(DRM_DEVICE(bridge),"%s\n",__func__);
if (!bridge->encoder) {
DRM_ERROR("Parent encoder object not found");
return -ENODEV;
}
sn65dsi83->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
ret = drm_connector_init(bridge->dev, &sn65dsi83->connector,
&sn65dsi83_connector_funcs,
DRM_MODE_CONNECTOR_DSI);
if (ret) {
DRM_ERROR("Failed to initialize connector with drm\n");
return ret;
}
drm_connector_helper_add(&sn65dsi83->connector,
&sn65dsi83_connector_helper_funcs);
drm_mode_connector_attach_encoder(&sn65dsi83->connector, bridge->encoder);
ret = sn65dsi83_attach_dsi(sn65dsi83);
// MM
//drm_bridge_enable(bridge);
return ret;
}
static struct drm_bridge_funcs sn65dsi83_bridge_funcs = {
.enable = sn65dsi83_bridge_enable,
.disable = sn65dsi83_bridge_disable,
.mode_set = sn65dsi83_bridge_mode_set,
.attach = sn65dsi83_bridge_attach,
};
static int sn65dsi83_parse_dt(struct device_node *np,
struct sn65dsi83 *sn65dsi83)
{
struct device *dev = &sn65dsi83->brg->client->dev;
u32 num_lanes = 4, bpp = 24, format = 2, width = 149, height = 93;
struct device_node *endpoint;
dev_dbg(dev, "sn65dsi83_parse_dt\n");
endpoint = of_graph_get_next_endpoint(np, NULL);
if (!endpoint)
return -ENODEV;
sn65dsi83->host_node = of_graph_get_remote_port_parent(endpoint);
if (!sn65dsi83->host_node) {
of_node_put(endpoint);
return -ENODEV;
}
dev_dbg(dev, "sn65dsi83_parse_dt... parsing dt\n");
of_property_read_u32(np, "ti,dsi-lanes", &num_lanes);
of_property_read_u32(np, "ti,lvds-format", &format);
of_property_read_u32(np, "ti,lvds-bpp", &bpp);
of_property_read_u32(np, "ti,width-mm", &width);
of_property_read_u32(np, "ti,height-mm", &height);
if (num_lanes < 1 || num_lanes > 4) {
dev_err(dev, "Invalid dsi-lanes: %d\n", num_lanes);
return -EINVAL;
}
sn65dsi83->brg->num_dsi_lanes = num_lanes;
sn65dsi83->brg->gpio_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(sn65dsi83->brg->gpio_enable)) {
dev_err(dev, "failed to parse enable gpio");
return PTR_ERR(sn65dsi83->brg->gpio_enable);
}
sn65dsi83->brg->format = format;
sn65dsi83->brg->bpp = bpp;
sn65dsi83->brg->width_mm = width;
sn65dsi83->brg->height_mm = height;
/* Read default timing if there is not device tree node for */
if ((of_get_videomode(np, &sn65dsi83->brg->vm, 0)) < 0)
{
dev_dbg(dev, "******** panel_default_timing\n");
videomode_from_timing(&panel_default_timing, &sn65dsi83->brg->vm);
}
of_node_put(endpoint);
of_node_put(sn65dsi83->host_node);
return 0;
}
static int sn65dsi83_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct sn65dsi83 *sn65dsi83;
struct device *dev = &i2c->dev;
int ret;
dev_info(dev,"%s\n",__func__);
if (!dev->of_node)
return -EINVAL;
sn65dsi83 = devm_kzalloc(dev, sizeof(*sn65dsi83), GFP_KERNEL);
if (!sn65dsi83)
return -ENOMEM;
/* Initialize it before DT parser */
sn65dsi83->brg = sn65dsi83_brg_get();
sn65dsi83->brg->client = i2c;
sn65dsi83->powered = false;
sn65dsi83->status = connector_status_disconnected;
i2c_set_clientdata(i2c, sn65dsi83);
ret = sn65dsi83_parse_dt(dev->of_node, sn65dsi83);
if (ret)
return ret;
sn65dsi83->brg->funcs->power_off(sn65dsi83->brg);
sn65dsi83->brg->funcs->power_on(sn65dsi83->brg);
ret = sn65dsi83->brg->funcs->reset(sn65dsi83->brg);
if (ret != 0x00) {
dev_err(dev, "Failed to reset the device");
return -ENODEV;
}
sn65dsi83->brg->funcs->power_off(sn65dsi83->brg);
sn65dsi83->bridge.funcs = &sn65dsi83_bridge_funcs;
sn65dsi83->bridge.of_node = dev->of_node;
ret = drm_bridge_add(&sn65dsi83->bridge);
if (ret) {
dev_err(dev, "failed to add sn65dsi83 bridge\n");
}
dev_info(dev,"%s done\n",__func__);
return ret;
}
static int sn65dsi83_attach_dsi(struct sn65dsi83 *sn65dsi83)
{
struct device *dev = &sn65dsi83->brg->client->dev;
struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi;
int ret = 0;
const struct mipi_dsi_device_info info = { .type = "sn65dsi83",
.channel = 0,
.node = NULL,
};
dev_info(dev, "%s\n",__func__);
host = of_find_mipi_dsi_host_by_node(sn65dsi83->host_node);
if (!host) {
dev_err(dev, "failed to find dsi host\n");
return -EPROBE_DEFER;
}
dsi = mipi_dsi_device_register_full(host, &info);
if (IS_ERR(dsi)) {
dev_err(dev, "failed to create dsi device\n");
ret = PTR_ERR(dsi);
return -ENODEV;
}
sn65dsi83->dsi = dsi;
dsi->lanes = sn65dsi83->brg->num_dsi_lanes;
dsi->format = MIPI_DSI_FMT_RGB888;
// dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_HSE |
// MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_VIDEO_BURST;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_HSE;
// dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
// MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
ret = mipi_dsi_attach(dsi);
if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n");
mipi_dsi_device_unregister(dsi);
}
return ret;
}
static void sn65dsi83_detach_dsi(struct sn65dsi83 *sn65dsi83)
{
struct device *dev = &sn65dsi83->brg->client->dev;
dev_info(dev, "%s\n",__func__);
mipi_dsi_detach(sn65dsi83->dsi);
mipi_dsi_device_unregister(sn65dsi83->dsi);
}
static int sn65dsi83_remove(struct i2c_client *i2c)
{
struct sn65dsi83 *sn65dsi83 = i2c_get_clientdata(i2c);
struct device *dev = &sn65dsi83->brg->client->dev;
dev_info(dev, "%s\n",__func__);
sn65dsi83_detach_dsi(sn65dsi83);
drm_bridge_remove(&sn65dsi83->bridge);
return 0;
}
static const struct i2c_device_id sn65dsi83_i2c_ids[] = {
{ "sn65dsi83", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sn65dsi83_i2c_ids);
static const struct of_device_id sn65dsi83_of_ids[] = {
{ .compatible = "ti,sn65dsi83" },
{ }
};
MODULE_DEVICE_TABLE(of, sn65dsi83_of_ids);
static struct mipi_dsi_driver sn65dsi83_dsi_driver = {
.driver.name = "sn65dsi83",
};
static struct i2c_driver sn65dsi83_driver = {
.driver = {
.name = "sn65dsi83",
.of_match_table = sn65dsi83_of_ids,
},
.id_table = sn65dsi83_i2c_ids,
.probe = sn65dsi83_probe,
.remove = sn65dsi83_remove,
};
static int __init sn65dsi83_init(void)
{
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
mipi_dsi_driver_register(&sn65dsi83_dsi_driver);
return i2c_add_driver(&sn65dsi83_driver);
}
module_init(sn65dsi83_init);
static void __exit sn65dsi83_exit(void)
{
i2c_del_driver(&sn65dsi83_driver);
if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
mipi_dsi_driver_unregister(&sn65dsi83_dsi_driver);
}
module_exit(sn65dsi83_exit);
MODULE_AUTHOR("CompuLab <compulab@compula.co.il>");
MODULE_DESCRIPTION("SN65DSI bridge driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h
new file mode 100644
index 000000000..e9bb6633c
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi83/sn65dsi83_timing.h
@@ -0,0 1,33 @@
#ifndef __SN65DSI83_TIMING_H__
#define __SN65DSI83_TIMING_H__
/* Default Video Parameters */
#define PIXCLK_INIT 62500000
#define HACTIVE_INIT 1280
#define HPW_INIT 2
#define HBP_INIT 6
#define HFP_INIT 5
#define VACTIVE_INIT 800
#define VPW_INIT 1
#define VBP_INIT 2
#define VFP_INIT 3
static const struct display_timing panel_default_timing = {
.pixelclock = { PIXCLK_INIT, PIXCLK_INIT, PIXCLK_INIT },
.hactive = { HACTIVE_INIT, HACTIVE_INIT, HACTIVE_INIT },
.hfront_porch = { HFP_INIT, HFP_INIT, HFP_INIT },
.hsync_len = { HPW_INIT, HPW_INIT, HPW_INIT },
.hback_porch = { HBP_INIT, HBP_INIT, HBP_INIT },
.vactive = { VACTIVE_INIT, VACTIVE_INIT, VACTIVE_INIT },
.vfront_porch = { VFP_INIT, VFP_INIT, VFP_INIT },
.vsync_len = { VPW_INIT, VPW_INIT, VPW_INIT },
.vback_porch = { VBP_INIT, VBP_INIT, VBP_INIT },
.flags = DISPLAY_FLAGS_HSYNC_LOW |
DISPLAY_FLAGS_VSYNC_LOW |
DISPLAY_FLAGS_DE_LOW |
DISPLAY_FLAGS_PIXDATA_NEGEDGE,
};
#endif /* __SN65DSI83_TIMING_H__ */
diff --git a/drivers/gpu/drm/bridge/sn65dsi84-dsi2lvds.c b/drivers/gpu/drm/bridge/sn65dsi84-dsi2lvds.c
new file mode 100644
index 000000000..2d0858505
--- /dev/null
b/drivers/gpu/drm/bridge/sn65dsi84-dsi2lvds.c
@@ -0,0 1,210 @@
/*
* Texas Instruments sn65dsi84 DSI to LVDS bridge driver.
*
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
static int sn65dsi84_i2c_read(struct i2c_client *client, char *writebuf,
int writelen, char *readbuf, int readlen)
{
int ret;
if (writelen > 0) {
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = writelen,
.buf = writebuf,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = readlen,
.buf = readbuf,
},
};
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
dev_err(&client->dev, "%s: i2c read error.\n", __func__);
} else {
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = readlen,
.buf = readbuf,
},
};
ret = i2c_transfer(client->adapter, msgs, 1);
if (ret < 0)
dev_err(&client->dev, "%s:i2c read error.\n", __func__);
}
return ret;
}
static int sn65dsi84_i2c_write(struct i2c_client *client, char *writebuf,
int writelen)
{
int ret;
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = writelen,
.buf = writebuf,
},
};
ret = i2c_transfer(client->adapter, msgs, 1);
if (ret < 0)
dev_err(&client->dev, "%s: i2c write error.\n", __func__);
return ret;
}
static int sn65dsi84_write_reg(struct i2c_client *client, u8 addr, const u8 val)
{
u8 buf[2] = {0};
buf[0] = addr;
buf[1] = val;
return sn65dsi84_i2c_write(client, buf, sizeof(buf));
}
static int sn65dsi84_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct property *prop;
int err;
int i, size;
struct device_node *np = client->dev.of_node;
int addresses[100];
int values[100];
int chipid[] = {0x35, 0x38, 0x49, 0x53, 0x44, 0x20, 0x20, 0x20, 0x01};
char address, value;
struct gpio_desc *enable_gpio;
enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH);
if (enable_gpio)
gpiod_set_value_cansleep(enable_gpio, 1);
for (i = 0; i < sizeof(chipid) / sizeof(int); i ) {
address = (char)i;
err = sn65dsi84_i2c_read(client, &address, 1, &value, 1);
if (err < 0) {
dev_err(&client->dev, "failed to read chip id\n");
return err;
}
if (value != chipid[i]) {
dev_err(&client->dev, "chip id is not correct\n");
return err;
}
}
prop = of_find_property(np, "sn65dsi84,addresses", NULL);
if (!prop)
return -EINVAL;
if (!prop->value)
return -ENODATA;
size = prop->length / sizeof(int);
err = of_property_read_u32_array(np, "sn65dsi84,addresses", addresses, size);
if (err && (err != -EINVAL)) {
dev_err(&client->dev, "Unable to read 'sn65dsi84,addresses'\n");
return err;
}
prop = of_find_property(np, "sn65dsi84,values", NULL);
if (!prop)
return -EINVAL;
if (!prop->value)
return -ENODATA;
i = prop->length / sizeof(u32);
if (i != size) {
dev_err(&client->dev, "invalid 'sn65dsi84,values' length should be same as addresses\n");
return -EINVAL;
}
err = of_property_read_u32_array(np, "sn65dsi84,values", values, i);
if (err && (err != -EINVAL)) {
dev_err(&client->dev, "Unable to read 'sn65dsi84,values'\n");
return err;
}
for (i = 0; i < size; i ) {
sn65dsi84_write_reg(client, addresses[i], values[i]);
if (err < 0) {
dev_err(&client->dev, "failed to write data to the chip\n");
return err;
}
}
return 0;
}
static int sn65dsi84_remove(struct i2c_client *client)
{
struct gpio_desc *enable_gpio;
enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW);
if (enable_gpio)
gpiod_set_value_cansleep(enable_gpio, 0);
return 0;
}
static const struct i2c_device_id sn65dsi84_id[] = {
{"sn65dsi84", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, sn65dsi84_id);
static struct of_device_id sn65dsi84_match_table[] = {
{ .compatible = "ti,sn65dsi84",},
{ },
};
static struct i2c_driver sn65dsi84_i2c_driver = {
.probe = sn65dsi84_probe,
.remove = sn65dsi84_remove,
.driver = {
.name = "sn65dsi84",
.owner = THIS_MODULE,
.of_match_table = sn65dsi84_match_table,
},
.id_table = sn65dsi84_id,
};
static int __init sn65dsi84_init(void)
{
return i2c_add_driver(&sn65dsi84_i2c_driver);
}
static void __exit sn65dsi84_exit(void)
{
i2c_del_driver(&sn65dsi84_i2c_driver);
}
module_init(sn65dsi84_init);
module_exit(sn65dsi84_exit);
MODULE_DESCRIPTION("TI SN65DSI84 DSI to LVDS bridge driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
index 4a3f68a33..6d6ed9d2c 100644
--- a/drivers/gpu/drm/drm_modes.c
b/drivers/gpu/drm/drm_modes.c
@@ -1340,7 1340,7 @@ bool drm_mode_parse_command_line_for_connector(const char *mode_option,
bool yres_specified = false, cvt = false, rb = false;
bool interlace = false, margins = false, was_digit = false;
int i;
- enum drm_connector_force force = DRM_FORCE_UNSPECIFIED;
enum drm_connector_force force = DRM_FORCE_ON;
#ifdef CONFIG_FB
if (!mode_option)
diff --git a/drivers/gpu/drm/drm_notify.c b/drivers/gpu/drm/drm_notify.c
new file mode 100644
index 000000000..f5d46f375
--- /dev/null
b/drivers/gpu/drm/drm_notify.c
@@ -0,0 1,43 @@
// SPDX-License-Identifier: (GPL-2.0 )
/*
* linux/drivers/gpu/drm/drm_notify.c
*
* Based on:
* linux/drivers/video/fb_notify.c
*
* Copyright (C) 2018 Boundary Devices LLC
*/
#include <linux/notifier.h>
#include <linux/export.h>
static BLOCKING_NOTIFIER_HEAD(drm_notifier_list);
/**
* drm_register_client - register a client notifier
* @nb: notifier block to callback on events
*/
int drm_register_client(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&drm_notifier_list, nb);
}
EXPORT_SYMBOL(drm_register_client);
/**
* drm_unregister_client - unregister a client notifier
* @nb: notifier block to callback on events
*/
int drm_unregister_client(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&drm_notifier_list, nb);
}
EXPORT_SYMBOL(drm_unregister_client);
/**
* drm_notifier_call_chain - notify clients of events
*
*/
int drm_notifier_call_chain(unsigned long val, void *v)
{
return blocking_notifier_call_chain(&drm_notifier_list, val, v);
}
EXPORT_SYMBOL_GPL(drm_notifier_call_chain);
diff --git a/include/drm/drmP.h b/include/drm/drmP.h
index 59be1232d..8f1e7839f 100644
--- a/include/drm/drmP.h
b/include/drm/drmP.h
@@ -348,4 348,8 @@ static __inline__ bool drm_can_sleep(void)
/* helper for handling conditionals in various for_each macros */
#define for_each_if(condition) if (!(condition)) {} else
extern int drm_register_client(struct notifier_block *nb);
extern int drm_unregister_client(struct notifier_block *nb);
extern int drm_notifier_call_chain(unsigned long val, void *v);
#endif