From 53bdcf5f026c565e605ff4ced9178f85d48f69c5 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Fri, 17 Jul 2015 12:06:11 +0200 Subject: drm: sti: fix sub-components bind Fix misunderstanding in how use component framework. drm_platform_init() is now call only when all the sub-components are register themselves instead of the previous broken two stages mechanism. Update bindings documentation. Signed-off-by: Benjamin Gaignard --- .../devicetree/bindings/gpu/st,stih4xx.txt | 72 +++++++++++----------- 1 file changed, 35 insertions(+), 37 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/gpu/st,stih4xx.txt b/Documentation/devicetree/bindings/gpu/st,stih4xx.txt index 6b1d75f1a529..a36dfce0032e 100644 --- a/Documentation/devicetree/bindings/gpu/st,stih4xx.txt +++ b/Documentation/devicetree/bindings/gpu/st,stih4xx.txt @@ -52,10 +52,9 @@ STMicroelectronics stih4xx platforms See ../reset/reset.txt for details. - reset-names: names of the resets listed in resets property in the same order. - - ranges: to allow probing of subdevices - sti-hdmi: hdmi output block - must be a child of sti-tvout + must be a child of sti-display-subsystem Required properties: - compatible: "st,stih-hdmi"; - reg: Physical base address of the IP registers and length of memory mapped region. @@ -72,7 +71,7 @@ STMicroelectronics stih4xx platforms sti-hda: Required properties: - must be a child of sti-tvout + must be a child of sti-display-subsystem - compatible: "st,stih-hda" - reg: Physical base address of the IP registers and length of memory mapped region. - reg-names: names of the mapped memory regions listed in regs property in @@ -85,7 +84,7 @@ sti-hda: sti-dvo: Required properties: - must be a child of sti-tvout + must be a child of sti-display-subsystem - compatible: "st,stih-dvo" - reg: Physical base address of the IP registers and length of memory mapped region. - reg-names: names of the mapped memory regions listed in regs property in @@ -195,38 +194,37 @@ Example: reg-names = "tvout-reg", "hda-reg", "syscfg"; reset-names = "tvout"; resets = <&softreset STIH416_HDTVOUT_SOFTRESET>; - ranges; - - sti-hdmi@fe85c000 { - compatible = "st,stih416-hdmi"; - reg = <0xfe85c000 0x1000>, <0xfe830000 0x10000>; - reg-names = "hdmi-reg", "syscfg"; - interrupts = ; - interrupt-names = "irq"; - clock-names = "pix", "tmds", "phy", "audio"; - clocks = <&clockgen_c_vcc CLK_S_PIX_HDMI>, <&clockgen_c_vcc CLK_S_TMDS_HDMI>, <&clockgen_c_vcc CLK_S_HDMI_REJECT_PLL>, <&clockgen_b1 CLK_S_PCM_0>; - }; - - sti-hda@fe85a000 { - compatible = "st,stih416-hda"; - reg = <0xfe85a000 0x400>, <0xfe83085c 0x4>; - reg-names = "hda-reg", "video-dacs-ctrl"; - clock-names = "pix", "hddac"; - clocks = <&clockgen_c_vcc CLK_S_PIX_HD>, <&clockgen_c_vcc CLK_S_HDDAC>; - }; - - sti-dvo@8d00400 { - compatible = "st,stih407-dvo"; - reg = <0x8d00400 0x200>; - reg-names = "dvo-reg"; - clock-names = "dvo_pix", "dvo", - "main_parent", "aux_parent"; - clocks = <&clk_s_d2_flexgen CLK_PIX_DVO>, <&clk_s_d2_flexgen CLK_DVO>, - <&clk_s_d2_quadfs 0>, <&clk_s_d2_quadfs 1>; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_dvo>; - sti,panel = <&panel_dvo>; - }; + }; + + sti-hdmi@fe85c000 { + compatible = "st,stih416-hdmi"; + reg = <0xfe85c000 0x1000>, <0xfe830000 0x10000>; + reg-names = "hdmi-reg", "syscfg"; + interrupts = ; + interrupt-names = "irq"; + clock-names = "pix", "tmds", "phy", "audio"; + clocks = <&clockgen_c_vcc CLK_S_PIX_HDMI>, <&clockgen_c_vcc CLK_S_TMDS_HDMI>, <&clockgen_c_vcc CLK_S_HDMI_REJECT_PLL>, <&clockgen_b1 CLK_S_PCM_0>; + }; + + sti-hda@fe85a000 { + compatible = "st,stih416-hda"; + reg = <0xfe85a000 0x400>, <0xfe83085c 0x4>; + reg-names = "hda-reg", "video-dacs-ctrl"; + clock-names = "pix", "hddac"; + clocks = <&clockgen_c_vcc CLK_S_PIX_HD>, <&clockgen_c_vcc CLK_S_HDDAC>; + }; + + sti-dvo@8d00400 { + compatible = "st,stih407-dvo"; + reg = <0x8d00400 0x200>; + reg-names = "dvo-reg"; + clock-names = "dvo_pix", "dvo", + "main_parent", "aux_parent"; + clocks = <&clk_s_d2_flexgen CLK_PIX_DVO>, <&clk_s_d2_flexgen CLK_DVO>, + <&clk_s_d2_quadfs 0>, <&clk_s_d2_quadfs 1>; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_dvo>; + sti,panel = <&panel_dvo>; }; sti-hqvdp@9c000000 { @@ -237,7 +235,7 @@ Example: reset-names = "hqvdp"; resets = <&softreset STIH407_HDQVDP_SOFTRESET>; st,vtg = <&vtg_main>; - }; + }; }; ... }; -- cgit v1.2.3 From 3309ac836229d8bc3db7618e04a51334bef13b0a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 30 Jul 2015 10:32:46 +0200 Subject: drm/tegra: sor: Add Tegra210 eDP support The SOR found on Tegra210 is very similar to the version found on Tegra124, except that it no longer supports LVDS. Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt | 7 ++++--- drivers/gpu/drm/tegra/drm.c | 1 + drivers/gpu/drm/tegra/sor.c | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt index 009f4bfa1590..626115911282 100644 --- a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt +++ b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt @@ -197,9 +197,10 @@ of the following host1x client modules: - sor: serial output resource Required properties: - - compatible: For Tegra124, must contain "nvidia,tegra124-sor". Otherwise, - must contain '"nvidia,-sor", "nvidia,tegra124-sor"', where - is tegra132. + - compatible: Should be: + - "nvidia,tegra124-sor": for Tegra124 and Tegra132 + - "nvidia,tegra132-sor": for Tegra132 + - "nvidia,tegra210-sor": for Tegra210 - reg: Physical base address and length of the controller's registers. - interrupts: The interrupt outputs from the controller. - clocks: Must contain an entry for each entry in clock-names. diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 1993ab90226a..dfbbd88b040f 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1060,6 +1060,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra132-dsi", }, { .compatible = "nvidia,tegra210-dc", }, { .compatible = "nvidia,tegra210-dsi", }, + { .compatible = "nvidia,tegra210-sor", }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index a7214e99bb36..8495478d1e15 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1600,6 +1600,7 @@ static int tegra_sor_remove(struct platform_device *pdev) static const struct of_device_id tegra_sor_of_match[] = { { .compatible = "nvidia,tegra124-sor", }, + { .compatible = "nvidia,tegra210-sor", }, { }, }; MODULE_DEVICE_TABLE(of, tegra_sor_of_match); -- cgit v1.2.3 From 459cc2c6800b545a482e428a631d99bca8da7790 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 30 Jul 2015 10:34:24 +0200 Subject: drm/tegra: sor: Add HDMI support The SOR1 introduced on Tegra210 supports HDMI 2.0 and DisplayPort. Add HDMI support and name the debugfs node after the type of SOR. The SOR introduced with Tegra124 is known simply as "sor", whereas the additional SOR found on Tegra210 is known as "sor1". Signed-off-by: Thierry Reding --- .../bindings/gpu/nvidia,tegra20-host1x.txt | 1 + drivers/gpu/drm/tegra/dc.h | 4 + drivers/gpu/drm/tegra/drm.c | 1 + drivers/gpu/drm/tegra/sor.c | 994 ++++++++++++++++++++- drivers/gpu/drm/tegra/sor.h | 86 ++ 5 files changed, 1052 insertions(+), 34 deletions(-) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt index 626115911282..e685610d38e2 100644 --- a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt +++ b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt @@ -201,6 +201,7 @@ of the following host1x client modules: - "nvidia,tegra124-sor": for Tegra124 and Tegra132 - "nvidia,tegra132-sor": for Tegra132 - "nvidia,tegra210-sor": for Tegra210 + - "nvidia,tegra210-sor1": for Tegra210 - reg: Physical base address and length of the controller's registers. - interrupts: The interrupt outputs from the controller. - clocks: Must contain an entry for each entry in clock-names. diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index 203056a378f0..4a268635749b 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -128,6 +128,8 @@ #define DC_DISP_DISP_WIN_OPTIONS 0x402 #define HDMI_ENABLE (1 << 30) #define DSI_ENABLE (1 << 29) +#define SOR1_TIMING_CYA (1 << 27) +#define SOR1_ENABLE (1 << 26) #define SOR_ENABLE (1 << 25) #define CURSOR_ENABLE (1 << 16) @@ -247,9 +249,11 @@ #define BASE_COLOR_SIZE565 (6 << 0) #define BASE_COLOR_SIZE332 (7 << 0) #define BASE_COLOR_SIZE888 (8 << 0) +#define DITHER_CONTROL_MASK (3 << 8) #define DITHER_CONTROL_DISABLE (0 << 8) #define DITHER_CONTROL_ORDERED (2 << 8) #define DITHER_CONTROL_ERRDIFF (3 << 8) +#define BASE_COLOR_SIZE_MASK (0xf << 0) #define BASE_COLOR_SIZE_666 (0 << 0) #define BASE_COLOR_SIZE_111 (1 << 0) #define BASE_COLOR_SIZE_222 (2 << 0) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index dfbbd88b040f..6d88cf1fcd1c 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -1061,6 +1061,7 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra210-dc", }, { .compatible = "nvidia,tegra210-dsi", }, { .compatible = "nvidia,tegra210-sor", }, + { .compatible = "nvidia,tegra210-sor1", }, { /* sentinel */ } }; diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 8495478d1e15..da1715ebdd71 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -10,7 +10,9 @@ #include #include #include +#include #include +#include #include #include @@ -23,11 +25,146 @@ #include "drm.h" #include "sor.h" +#define SOR_REKEY 0x38 + +struct tegra_sor_hdmi_settings { + unsigned long frequency; + + u8 vcocap; + u8 ichpmp; + u8 loadadj; + u8 termadj; + u8 tx_pu; + u8 bg_vref; + + u8 drive_current[4]; + u8 preemphasis[4]; +}; + +#if 1 +static const struct tegra_sor_hdmi_settings tegra210_sor_hdmi_defaults[] = { + { + .frequency = 54000000, + .vcocap = 0x0, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x10, + .bg_vref = 0x8, + .drive_current = { 0x33, 0x3a, 0x3a, 0x3a }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 75000000, + .vcocap = 0x3, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x40, + .bg_vref = 0x8, + .drive_current = { 0x33, 0x3a, 0x3a, 0x3a }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 150000000, + .vcocap = 0x3, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x66, + .bg_vref = 0x8, + .drive_current = { 0x33, 0x3a, 0x3a, 0x3a }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 300000000, + .vcocap = 0x3, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x66, + .bg_vref = 0xa, + .drive_current = { 0x33, 0x3f, 0x3f, 0x3f }, + .preemphasis = { 0x00, 0x17, 0x17, 0x17 }, + }, { + .frequency = 600000000, + .vcocap = 0x3, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x66, + .bg_vref = 0x8, + .drive_current = { 0x33, 0x3f, 0x3f, 0x3f }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, +}; +#else +static const struct tegra_sor_hdmi_settings tegra210_sor_hdmi_defaults[] = { + { + .frequency = 75000000, + .vcocap = 0x3, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x40, + .bg_vref = 0x8, + .drive_current = { 0x29, 0x29, 0x29, 0x29 }, + .preemphasis = { 0x00, 0x00, 0x00, 0x00 }, + }, { + .frequency = 150000000, + .vcocap = 0x3, + .ichpmp = 0x1, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x66, + .bg_vref = 0x8, + .drive_current = { 0x30, 0x37, 0x37, 0x37 }, + .preemphasis = { 0x01, 0x02, 0x02, 0x02 }, + }, { + .frequency = 300000000, + .vcocap = 0x3, + .ichpmp = 0x6, + .loadadj = 0x3, + .termadj = 0x9, + .tx_pu = 0x66, + .bg_vref = 0xf, + .drive_current = { 0x30, 0x37, 0x37, 0x37 }, + .preemphasis = { 0x10, 0x3e, 0x3e, 0x3e }, + }, { + .frequency = 600000000, + .vcocap = 0x3, + .ichpmp = 0xa, + .loadadj = 0x3, + .termadj = 0xb, + .tx_pu = 0x66, + .bg_vref = 0xe, + .drive_current = { 0x35, 0x3e, 0x3e, 0x3e }, + .preemphasis = { 0x02, 0x3f, 0x3f, 0x3f }, + }, +}; +#endif + +struct tegra_sor_soc { + bool supports_edp; + bool supports_lvds; + bool supports_hdmi; + bool supports_dp; + + const struct tegra_sor_hdmi_settings *settings; + unsigned int num_settings; +}; + +struct tegra_sor; + +struct tegra_sor_ops { + const char *name; + int (*probe)(struct tegra_sor *sor); + int (*remove)(struct tegra_sor *sor); +}; + struct tegra_sor { struct host1x_client client; struct tegra_output output; struct device *dev; + const struct tegra_sor_soc *soc; void __iomem *regs; struct reset_control *rst; @@ -41,6 +178,16 @@ struct tegra_sor { struct drm_info_list *debugfs_files; struct drm_minor *minor; struct dentry *debugfs; + + const struct tegra_sor_ops *ops; + + /* for HDMI 2.0 */ + struct tegra_sor_hdmi_settings *settings; + unsigned int num_settings; + + struct regulator *avdd_io_supply; + struct regulator *vdd_pll_supply; + struct regulator *hdmi_supply; }; struct tegra_sor_config { @@ -184,6 +331,47 @@ static int tegra_sor_dp_train_fast(struct tegra_sor *sor, return 0; } +static void tegra_sor_dp_term_calibrate(struct tegra_sor *sor) +{ + u32 mask = 0x08, adj = 0, value; + + /* enable pad calibration logic */ + value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value &= ~SOR_DP_PADCTL_PAD_CAL_PD; + tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + + value = tegra_sor_readl(sor, SOR_PLL1); + value |= SOR_PLL1_TMDS_TERM; + tegra_sor_writel(sor, value, SOR_PLL1); + + while (mask) { + adj |= mask; + + value = tegra_sor_readl(sor, SOR_PLL1); + value &= ~SOR_PLL1_TMDS_TERMADJ_MASK; + value |= SOR_PLL1_TMDS_TERMADJ(adj); + tegra_sor_writel(sor, value, SOR_PLL1); + + usleep_range(100, 200); + + value = tegra_sor_readl(sor, SOR_PLL1); + if (value & SOR_PLL1_TERM_COMPOUT) + adj &= ~mask; + + mask >>= 1; + } + + value = tegra_sor_readl(sor, SOR_PLL1); + value &= ~SOR_PLL1_TMDS_TERMADJ_MASK; + value |= SOR_PLL1_TMDS_TERMADJ(adj); + tegra_sor_writel(sor, value, SOR_PLL1); + + /* disable pad calibration logic */ + value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value |= SOR_DP_PADCTL_PAD_CAL_PD; + tegra_sor_writel(sor, value, SOR_DP_PADCTL0); +} + static void tegra_sor_super_update(struct tegra_sor *sor) { tegra_sor_writel(sor, 0, SOR_SUPER_STATE0); @@ -800,10 +988,11 @@ static const struct drm_info_list debugfs_files[] = { static int tegra_sor_debugfs_init(struct tegra_sor *sor, struct drm_minor *minor) { + const char *name = sor->soc->supports_dp ? "sor1" : "sor"; unsigned int i; int err; - sor->debugfs = debugfs_create_dir("sor", minor->debugfs_root); + sor->debugfs = debugfs_create_dir(name, minor->debugfs_root); if (!sor->debugfs) return -ENOMEM; @@ -858,7 +1047,7 @@ tegra_sor_connector_detect(struct drm_connector *connector, bool force) if (sor->dpaux) return tegra_dpaux_detect(sor->dpaux); - return connector_status_unknown; + return tegra_output_connector_detect(connector, force); } static const struct drm_connector_funcs tegra_sor_connector_funcs = { @@ -956,6 +1145,48 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder) clk_disable_unprepare(sor->clk); } +#if 0 +static int calc_h_ref_to_sync(const struct drm_display_mode *mode, + unsigned int *value) +{ + unsigned int hfp, hsw, hbp, a = 0, b; + + hfp = mode->hsync_start - mode->hdisplay; + hsw = mode->hsync_end - mode->hsync_start; + hbp = mode->htotal - mode->hsync_end; + + pr_info("hfp: %u, hsw: %u, hbp: %u\n", hfp, hsw, hbp); + + b = hfp - 1; + + pr_info("a: %u, b: %u\n", a, b); + pr_info("a + hsw + hbp = %u\n", a + hsw + hbp); + + if (a + hsw + hbp <= 11) { + a = 1 + 11 - hsw - hbp; + pr_info("a: %u\n", a); + } + + if (a > b) + return -EINVAL; + + if (hsw < 1) + return -EINVAL; + + if (mode->hdisplay < 16) + return -EINVAL; + + if (value) { + if (b > a && a % 2) + *value = a + 1; + else + *value = a; + } + + return 0; +} +#endif + static void tegra_sor_edp_enable(struct drm_encoder *encoder) { struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; @@ -1377,34 +1608,578 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder, return 0; } -static const struct drm_encoder_helper_funcs tegra_sor_edp_helper_funcs = { +static const struct drm_encoder_helper_funcs tegra_sor_edp_helpers = { .disable = tegra_sor_edp_disable, .enable = tegra_sor_edp_enable, .atomic_check = tegra_sor_encoder_atomic_check, }; +static inline u32 tegra_sor_hdmi_subpack(const u8 *ptr, size_t size) +{ + u32 value = 0; + size_t i; + + for (i = size; i > 0; i--) + value = (value << 8) | ptr[i - 1]; + + return value; +} + +static void tegra_sor_hdmi_write_infopack(struct tegra_sor *sor, + const void *data, size_t size) +{ + const u8 *ptr = data; + unsigned long offset; + size_t i, j; + u32 value; + + switch (ptr[0]) { + case HDMI_INFOFRAME_TYPE_AVI: + offset = SOR_HDMI_AVI_INFOFRAME_HEADER; + break; + + case HDMI_INFOFRAME_TYPE_AUDIO: + offset = SOR_HDMI_AUDIO_INFOFRAME_HEADER; + break; + + case HDMI_INFOFRAME_TYPE_VENDOR: + offset = SOR_HDMI_VSI_INFOFRAME_HEADER; + break; + + default: + dev_err(sor->dev, "unsupported infoframe type: %02x\n", + ptr[0]); + return; + } + + value = INFOFRAME_HEADER_TYPE(ptr[0]) | + INFOFRAME_HEADER_VERSION(ptr[1]) | + INFOFRAME_HEADER_LEN(ptr[2]); + tegra_sor_writel(sor, value, offset); + offset++; + + /* + * Each subpack contains 7 bytes, divided into: + * - subpack_low: bytes 0 - 3 + * - subpack_high: bytes 4 - 6 (with byte 7 padded to 0x00) + */ + for (i = 3, j = 0; i < size; i += 7, j += 8) { + size_t rem = size - i, num = min_t(size_t, rem, 4); + + value = tegra_sor_hdmi_subpack(&ptr[i], num); + tegra_sor_writel(sor, value, offset++); + + num = min_t(size_t, rem - num, 3); + + value = tegra_sor_hdmi_subpack(&ptr[i + 4], num); + tegra_sor_writel(sor, value, offset++); + } +} + +static int +tegra_sor_hdmi_setup_avi_infoframe(struct tegra_sor *sor, + const struct drm_display_mode *mode) +{ + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + struct hdmi_avi_infoframe frame; + u32 value; + int err; + + /* disable AVI infoframe */ + value = tegra_sor_readl(sor, SOR_HDMI_AVI_INFOFRAME_CTRL); + value &= ~INFOFRAME_CTRL_SINGLE; + value &= ~INFOFRAME_CTRL_OTHER; + value &= ~INFOFRAME_CTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_AVI_INFOFRAME_CTRL); + + err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); + if (err < 0) { + dev_err(sor->dev, "failed to setup AVI infoframe: %d\n", err); + return err; + } + + err = hdmi_avi_infoframe_pack(&frame, buffer, sizeof(buffer)); + if (err < 0) { + dev_err(sor->dev, "failed to pack AVI infoframe: %d\n", err); + return err; + } + + tegra_sor_hdmi_write_infopack(sor, buffer, err); + + /* enable AVI infoframe */ + value = tegra_sor_readl(sor, SOR_HDMI_AVI_INFOFRAME_CTRL); + value |= INFOFRAME_CTRL_CHECKSUM_ENABLE; + value |= INFOFRAME_CTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_AVI_INFOFRAME_CTRL); + + return 0; +} + +static void tegra_sor_hdmi_disable_audio_infoframe(struct tegra_sor *sor) +{ + u32 value; + + value = tegra_sor_readl(sor, SOR_HDMI_AUDIO_INFOFRAME_CTRL); + value &= ~INFOFRAME_CTRL_ENABLE; + tegra_sor_writel(sor, value, SOR_HDMI_AUDIO_INFOFRAME_CTRL); +} + +static struct tegra_sor_hdmi_settings * +tegra_sor_hdmi_find_settings(struct tegra_sor *sor, unsigned long frequency) +{ + unsigned int i; + + for (i = 0; i < sor->num_settings; i++) + if (frequency <= sor->settings[i].frequency) + return &sor->settings[i]; + + return NULL; +} + +static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) +{ + struct tegra_output *output = encoder_to_output(encoder); + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct tegra_sor *sor = to_sor(output); + u32 value; + int err; + + err = tegra_sor_detach(sor); + if (err < 0) + dev_err(sor->dev, "failed to detach SOR: %d\n", err); + + tegra_sor_writel(sor, 0, SOR_STATE1); + tegra_sor_update(sor); + + /* disable display to SOR clock */ + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~SOR1_TIMING_CYA; + value &= ~SOR1_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_commit(dc); + + err = tegra_sor_power_down(sor); + if (err < 0) + dev_err(sor->dev, "failed to power down SOR: %d\n", err); + + err = tegra_io_rail_power_off(TEGRA_IO_RAIL_HDMI); + if (err < 0) + dev_err(sor->dev, "failed to power off HDMI rail: %d\n", err); + + reset_control_assert(sor->rst); + usleep_range(1000, 2000); + clk_disable_unprepare(sor->clk); +} + +static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) +{ + struct tegra_output *output = encoder_to_output(encoder); + unsigned int h_ref_to_sync = 1, pulse_start, max_ac; + struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + unsigned int vbe, vse, hbe, hse, vbs, hbs, div; + struct tegra_sor_hdmi_settings *settings; + struct tegra_sor *sor = to_sor(output); + struct drm_display_mode *mode; + struct drm_display_info *info; + u32 value; + int err; + + mode = &encoder->crtc->state->adjusted_mode; + info = &output->connector.display_info; + + err = clk_prepare_enable(sor->clk); + if (err < 0) + dev_err(sor->dev, "failed to enable clock: %d\n", err); + + usleep_range(1000, 2000); + + reset_control_deassert(sor->rst); + + err = clk_set_parent(sor->clk, sor->clk_safe); + if (err < 0) + dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); + + div = clk_get_rate(sor->clk) / 1000000 * 4; + + err = tegra_io_rail_power_on(TEGRA_IO_RAIL_HDMI); + if (err < 0) + dev_err(sor->dev, "failed to power on HDMI rail: %d\n", err); + + usleep_range(20, 100); + + value = tegra_sor_readl(sor, SOR_PLL2); + value &= ~SOR_PLL2_BANDGAP_POWERDOWN; + tegra_sor_writel(sor, value, SOR_PLL2); + + usleep_range(20, 100); + + value = tegra_sor_readl(sor, SOR_PLL3); + value &= ~SOR_PLL3_PLL_VDD_MODE_3V3; + tegra_sor_writel(sor, value, SOR_PLL3); + + value = tegra_sor_readl(sor, SOR_PLL0); + value &= ~SOR_PLL0_VCOPD; + value &= ~SOR_PLL0_PWR; + tegra_sor_writel(sor, value, SOR_PLL0); + + value = tegra_sor_readl(sor, SOR_PLL2); + value &= ~SOR_PLL2_SEQ_PLLCAPPD_ENFORCE; + tegra_sor_writel(sor, value, SOR_PLL2); + + usleep_range(200, 400); + + value = tegra_sor_readl(sor, SOR_PLL2); + value &= ~SOR_PLL2_POWERDOWN_OVERRIDE; + value &= ~SOR_PLL2_PORT_POWERDOWN; + tegra_sor_writel(sor, value, SOR_PLL2); + + usleep_range(20, 100); + + value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value |= SOR_DP_PADCTL_PD_TXD_3 | SOR_DP_PADCTL_PD_TXD_0 | + SOR_DP_PADCTL_PD_TXD_1 | SOR_DP_PADCTL_PD_TXD_2; + tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + + while (true) { + value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); + if ((value & SOR_LANE_SEQ_CTL_STATE_BUSY) == 0) + break; + + usleep_range(250, 1000); + } + + value = SOR_LANE_SEQ_CTL_TRIGGER | SOR_LANE_SEQ_CTL_SEQUENCE_DOWN | + SOR_LANE_SEQ_CTL_POWER_STATE_UP | SOR_LANE_SEQ_CTL_DELAY(5); + tegra_sor_writel(sor, value, SOR_LANE_SEQ_CTL); + + while (true) { + value = tegra_sor_readl(sor, SOR_LANE_SEQ_CTL); + if ((value & SOR_LANE_SEQ_CTL_TRIGGER) == 0) + break; + + usleep_range(250, 1000); + } + + value = tegra_sor_readl(sor, SOR_CLK_CNTRL); + value &= ~SOR_CLK_CNTRL_DP_LINK_SPEED_MASK; + value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK; + + if (mode->clock < 340000) + value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G2_70; + else + value |= SOR_CLK_CNTRL_DP_LINK_SPEED_G5_40; + + value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK; + tegra_sor_writel(sor, value, SOR_CLK_CNTRL); + + value = tegra_sor_readl(sor, SOR_DP_SPARE0); + value |= SOR_DP_SPARE_DISP_VIDEO_PREAMBLE; + value &= ~SOR_DP_SPARE_PANEL_INTERNAL; + value |= SOR_DP_SPARE_SEQ_ENABLE; + tegra_sor_writel(sor, value, SOR_DP_SPARE0); + + value = SOR_SEQ_CTL_PU_PC(0) | SOR_SEQ_CTL_PU_PC_ALT(0) | + SOR_SEQ_CTL_PD_PC(8) | SOR_SEQ_CTL_PD_PC_ALT(8); + tegra_sor_writel(sor, value, SOR_SEQ_CTL); + + value = SOR_SEQ_INST_DRIVE_PWM_OUT_LO | SOR_SEQ_INST_HALT | + SOR_SEQ_INST_WAIT_VSYNC | SOR_SEQ_INST_WAIT(1); + tegra_sor_writel(sor, value, SOR_SEQ_INST(0)); + tegra_sor_writel(sor, value, SOR_SEQ_INST(8)); + + /* program the reference clock */ + value = SOR_REFCLK_DIV_INT(div) | SOR_REFCLK_DIV_FRAC(div); + tegra_sor_writel(sor, value, SOR_REFCLK); + + /* XXX don't hardcode */ + value = SOR_XBAR_CTRL_LINK1_XSEL(4, 4) | + SOR_XBAR_CTRL_LINK1_XSEL(3, 3) | + SOR_XBAR_CTRL_LINK1_XSEL(2, 2) | + SOR_XBAR_CTRL_LINK1_XSEL(1, 1) | + SOR_XBAR_CTRL_LINK1_XSEL(0, 0) | + SOR_XBAR_CTRL_LINK0_XSEL(4, 4) | + SOR_XBAR_CTRL_LINK0_XSEL(3, 3) | + SOR_XBAR_CTRL_LINK0_XSEL(2, 0) | + SOR_XBAR_CTRL_LINK0_XSEL(1, 1) | + SOR_XBAR_CTRL_LINK0_XSEL(0, 2); + tegra_sor_writel(sor, value, SOR_XBAR_CTRL); + + tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL); + + err = clk_set_parent(sor->clk, sor->clk_parent); + if (err < 0) + dev_err(sor->dev, "failed to set parent clock: %d\n", err); + + value = SOR_INPUT_CONTROL_HDMI_SRC_SELECT(dc->pipe); + + /* XXX is this the proper check? */ + if (mode->clock < 75000) + value |= SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED; + + tegra_sor_writel(sor, value, SOR_INPUT_CONTROL); + + max_ac = ((mode->htotal - mode->hdisplay) - SOR_REKEY - 18) / 32; + + value = SOR_HDMI_CTRL_ENABLE | SOR_HDMI_CTRL_MAX_AC_PACKET(max_ac) | + SOR_HDMI_CTRL_AUDIO_LAYOUT | SOR_HDMI_CTRL_REKEY(SOR_REKEY); + tegra_sor_writel(sor, value, SOR_HDMI_CTRL); + + /* H_PULSE2 setup */ + pulse_start = h_ref_to_sync + (mode->hsync_end - mode->hsync_start) + + (mode->htotal - mode->hsync_end) - 10; + + value = PULSE_LAST_END_A | PULSE_QUAL_VACTIVE | + PULSE_POLARITY_HIGH | PULSE_MODE_NORMAL; + tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_CONTROL); + + value = PULSE_END(pulse_start + 8) | PULSE_START(pulse_start); + tegra_dc_writel(dc, value, DC_DISP_H_PULSE2_POSITION_A); + + value = tegra_dc_readl(dc, DC_DISP_DISP_SIGNAL_OPTIONS0); + value |= H_PULSE2_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_SIGNAL_OPTIONS0); + + /* infoframe setup */ + err = tegra_sor_hdmi_setup_avi_infoframe(sor, mode); + if (err < 0) + dev_err(sor->dev, "failed to setup AVI infoframe: %d\n", err); + + /* XXX HDMI audio support not implemented yet */ + tegra_sor_hdmi_disable_audio_infoframe(sor); + + /* use single TMDS protocol */ + value = tegra_sor_readl(sor, SOR_STATE1); + value &= ~SOR_STATE_ASY_PROTOCOL_MASK; + value |= SOR_STATE_ASY_PROTOCOL_SINGLE_TMDS_A; + tegra_sor_writel(sor, value, SOR_STATE1); + + /* power up pad calibration */ + value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value &= ~SOR_DP_PADCTL_PAD_CAL_PD; + tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + + /* production settings */ + settings = tegra_sor_hdmi_find_settings(sor, mode->clock * 1000); + if (IS_ERR(settings)) { + dev_err(sor->dev, "no settings for pixel clock %d Hz: %ld\n", + mode->clock * 1000, PTR_ERR(settings)); + return; + } + + value = tegra_sor_readl(sor, SOR_PLL0); + value &= ~SOR_PLL0_ICHPMP_MASK; + value &= ~SOR_PLL0_VCOCAP_MASK; + value |= SOR_PLL0_ICHPMP(settings->ichpmp); + value |= SOR_PLL0_VCOCAP(settings->vcocap); + tegra_sor_writel(sor, value, SOR_PLL0); + + tegra_sor_dp_term_calibrate(sor); + + value = tegra_sor_readl(sor, SOR_PLL1); + value &= ~SOR_PLL1_LOADADJ_MASK; + value |= SOR_PLL1_LOADADJ(settings->loadadj); + tegra_sor_writel(sor, value, SOR_PLL1); + + value = tegra_sor_readl(sor, SOR_PLL3); + value &= ~SOR_PLL3_BG_VREF_LEVEL_MASK; + value |= SOR_PLL3_BG_VREF_LEVEL(settings->bg_vref); + tegra_sor_writel(sor, value, SOR_PLL3); + + value = settings->drive_current[0] << 24 | + settings->drive_current[1] << 16 | + settings->drive_current[2] << 8 | + settings->drive_current[3] << 0; + tegra_sor_writel(sor, value, SOR_LANE_DRIVE_CURRENT0); + + value = settings->preemphasis[0] << 24 | + settings->preemphasis[1] << 16 | + settings->preemphasis[2] << 8 | + settings->preemphasis[3] << 0; + tegra_sor_writel(sor, value, SOR_LANE_PREEMPHASIS0); + + value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value &= ~SOR_DP_PADCTL_TX_PU_MASK; + value |= SOR_DP_PADCTL_TX_PU_ENABLE; + value |= SOR_DP_PADCTL_TX_PU(settings->tx_pu); + tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + + /* power down pad calibration */ + value = tegra_sor_readl(sor, SOR_DP_PADCTL0); + value |= SOR_DP_PADCTL_PAD_CAL_PD; + tegra_sor_writel(sor, value, SOR_DP_PADCTL0); + + /* miscellaneous display controller settings */ + value = VSYNC_H_POSITION(1); + tegra_dc_writel(dc, value, DC_DISP_DISP_TIMING_OPTIONS); + + value = tegra_dc_readl(dc, DC_DISP_DISP_COLOR_CONTROL); + value &= ~DITHER_CONTROL_MASK; + value &= ~BASE_COLOR_SIZE_MASK; + + switch (info->bpc) { + case 6: + value |= BASE_COLOR_SIZE_666; + break; + + case 8: + value |= BASE_COLOR_SIZE_888; + break; + + default: + WARN(1, "%u bits-per-color not supported\n", info->bpc); + break; + } + + tegra_dc_writel(dc, value, DC_DISP_DISP_COLOR_CONTROL); + + err = tegra_sor_power_up(sor, 250); + if (err < 0) + dev_err(sor->dev, "failed to power up SOR: %d\n", err); + + /* configure mode */ + value = tegra_sor_readl(sor, SOR_STATE1); + value &= ~SOR_STATE_ASY_PIXELDEPTH_MASK; + value &= ~SOR_STATE_ASY_CRC_MODE_MASK; + value &= ~SOR_STATE_ASY_OWNER_MASK; + + value |= SOR_STATE_ASY_CRC_MODE_COMPLETE | + SOR_STATE_ASY_OWNER(dc->pipe + 1); + + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + value &= ~SOR_STATE_ASY_HSYNCPOL; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + value |= SOR_STATE_ASY_HSYNCPOL; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + value &= ~SOR_STATE_ASY_VSYNCPOL; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + value |= SOR_STATE_ASY_VSYNCPOL; + + switch (info->bpc) { + case 8: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; + break; + + case 6: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444; + break; + + default: + BUG(); + break; + } + + tegra_sor_writel(sor, value, SOR_STATE1); + + value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe)); + value &= ~SOR_HEAD_STATE_RANGECOMPRESS_MASK; + value &= ~SOR_HEAD_STATE_DYNRANGE_MASK; + tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); + + value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe)); + value &= ~SOR_HEAD_STATE_COLORSPACE_MASK; + value |= SOR_HEAD_STATE_COLORSPACE_RGB; + tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); + + /* + * TODO: The video timing programming below doesn't seem to match the + * register definitions. + */ + + value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe)); + + /* sync end = sync width - 1 */ + vse = mode->vsync_end - mode->vsync_start - 1; + hse = mode->hsync_end - mode->hsync_start - 1; + + value = ((vse & 0x7fff) << 16) | (hse & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe)); + + /* blank end = sync end + back porch */ + vbe = vse + (mode->vtotal - mode->vsync_end); + hbe = hse + (mode->htotal - mode->hsync_end); + + value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe)); + + /* blank start = blank end + active */ + vbs = vbe + mode->vdisplay; + hbs = hbe + mode->hdisplay; + + value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe)); + + tegra_sor_writel(sor, 0x1, SOR_HEAD_STATE5(dc->pipe)); + + tegra_sor_update(sor); + + err = tegra_sor_attach(sor); + if (err < 0) + dev_err(sor->dev, "failed to attach SOR: %d\n", err); + + /* enable display to SOR clock and generate HDMI preamble */ + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= SOR1_ENABLE | SOR1_TIMING_CYA; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_commit(dc); + + err = tegra_sor_wakeup(sor); + if (err < 0) + dev_err(sor->dev, "failed to wakeup SOR: %d\n", err); +} + +static const struct drm_encoder_helper_funcs tegra_sor_hdmi_helpers = { + .disable = tegra_sor_hdmi_disable, + .enable = tegra_sor_hdmi_enable, + .atomic_check = tegra_sor_encoder_atomic_check, +}; + static int tegra_sor_init(struct host1x_client *client) { struct drm_device *drm = dev_get_drvdata(client->parent); + const struct drm_encoder_helper_funcs *helpers = NULL; struct tegra_sor *sor = host1x_client_to_sor(client); + int connector = DRM_MODE_CONNECTOR_Unknown; + int encoder = DRM_MODE_ENCODER_NONE; int err; - if (!sor->dpaux) - return -ENODEV; + if (!sor->dpaux) { + if (sor->soc->supports_hdmi) { + connector = DRM_MODE_CONNECTOR_HDMIA; + encoder = DRM_MODE_ENCODER_TMDS; + helpers = &tegra_sor_hdmi_helpers; + } else if (sor->soc->supports_lvds) { + connector = DRM_MODE_CONNECTOR_LVDS; + encoder = DRM_MODE_ENCODER_LVDS; + } + } else { + if (sor->soc->supports_edp) { + connector = DRM_MODE_CONNECTOR_eDP; + encoder = DRM_MODE_ENCODER_TMDS; + helpers = &tegra_sor_edp_helpers; + } else if (sor->soc->supports_dp) { + connector = DRM_MODE_CONNECTOR_DisplayPort; + encoder = DRM_MODE_ENCODER_TMDS; + } + } sor->output.dev = sor->dev; drm_connector_init(drm, &sor->output.connector, &tegra_sor_connector_funcs, - DRM_MODE_CONNECTOR_eDP); + connector); drm_connector_helper_add(&sor->output.connector, &tegra_sor_connector_helper_funcs); sor->output.connector.dpms = DRM_MODE_DPMS_OFF; drm_encoder_init(drm, &sor->output.encoder, &tegra_sor_encoder_funcs, - DRM_MODE_ENCODER_TMDS); - drm_encoder_helper_add(&sor->output.encoder, - &tegra_sor_edp_helper_funcs); + encoder); + drm_encoder_helper_add(&sor->output.encoder, helpers); drm_mode_connector_attach_encoder(&sor->output.connector, &sor->output.encoder); @@ -1497,18 +2272,130 @@ static const struct host1x_client_ops sor_client_ops = { .exit = tegra_sor_exit, }; +static const struct tegra_sor_ops tegra_sor_edp_ops = { + .name = "eDP", +}; + +static int tegra_sor_hdmi_probe(struct tegra_sor *sor) +{ + int err; + + sor->avdd_io_supply = devm_regulator_get(sor->dev, "avdd-io"); + if (IS_ERR(sor->avdd_io_supply)) { + dev_err(sor->dev, "cannot get AVDD I/O supply: %ld\n", + PTR_ERR(sor->avdd_io_supply)); + return PTR_ERR(sor->avdd_io_supply); + } + + err = regulator_enable(sor->avdd_io_supply); + if (err < 0) { + dev_err(sor->dev, "failed to enable AVDD I/O supply: %d\n", + err); + return err; + } + + sor->vdd_pll_supply = devm_regulator_get(sor->dev, "vdd-pll"); + if (IS_ERR(sor->vdd_pll_supply)) { + dev_err(sor->dev, "cannot get VDD PLL supply: %ld\n", + PTR_ERR(sor->vdd_pll_supply)); + return PTR_ERR(sor->vdd_pll_supply); + } + + err = regulator_enable(sor->vdd_pll_supply); + if (err < 0) { + dev_err(sor->dev, "failed to enable VDD PLL supply: %d\n", + err); + return err; + } + + sor->hdmi_supply = devm_regulator_get(sor->dev, "hdmi"); + if (IS_ERR(sor->hdmi_supply)) { + dev_err(sor->dev, "cannot get HDMI supply: %ld\n", + PTR_ERR(sor->hdmi_supply)); + return PTR_ERR(sor->hdmi_supply); + } + + err = regulator_enable(sor->hdmi_supply); + if (err < 0) { + dev_err(sor->dev, "failed to enable HDMI supply: %d\n", err); + return err; + } + + return 0; +} + +static int tegra_sor_hdmi_remove(struct tegra_sor *sor) +{ + regulator_disable(sor->hdmi_supply); + regulator_disable(sor->vdd_pll_supply); + regulator_disable(sor->avdd_io_supply); + + return 0; +} + +static const struct tegra_sor_ops tegra_sor_hdmi_ops = { + .name = "HDMI", + .probe = tegra_sor_hdmi_probe, + .remove = tegra_sor_hdmi_remove, +}; + +static const struct tegra_sor_soc tegra124_sor = { + .supports_edp = true, + .supports_lvds = true, + .supports_hdmi = false, + .supports_dp = false, +}; + +static const struct tegra_sor_soc tegra210_sor = { + .supports_edp = true, + .supports_lvds = false, + .supports_hdmi = false, + .supports_dp = false, +}; + +static const struct tegra_sor_soc tegra210_sor1 = { + .supports_edp = false, + .supports_lvds = false, + .supports_hdmi = true, + .supports_dp = true, + + .num_settings = ARRAY_SIZE(tegra210_sor_hdmi_defaults), + .settings = tegra210_sor_hdmi_defaults, +}; + +static const struct of_device_id tegra_sor_of_match[] = { + { .compatible = "nvidia,tegra210-sor1", .data = &tegra210_sor1 }, + { .compatible = "nvidia,tegra210-sor", .data = &tegra210_sor }, + { .compatible = "nvidia,tegra124-sor", .data = &tegra124_sor }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_sor_of_match); + static int tegra_sor_probe(struct platform_device *pdev) { + const struct of_device_id *match; struct device_node *np; struct tegra_sor *sor; struct resource *regs; int err; + match = of_match_device(tegra_sor_of_match, &pdev->dev); + sor = devm_kzalloc(&pdev->dev, sizeof(*sor), GFP_KERNEL); if (!sor) return -ENOMEM; sor->output.dev = sor->dev = &pdev->dev; + sor->soc = match->data; + + sor->settings = devm_kmemdup(&pdev->dev, sor->soc->settings, + sor->soc->num_settings * + sizeof(*sor->settings), + GFP_KERNEL); + if (!sor->settings) + return -ENOMEM; + + sor->num_settings = sor->soc->num_settings; np = of_parse_phandle(pdev->dev.of_node, "nvidia,dpaux", 0); if (np) { @@ -1519,50 +2406,83 @@ static int tegra_sor_probe(struct platform_device *pdev) return -EPROBE_DEFER; } + if (!sor->dpaux) { + if (sor->soc->supports_hdmi) { + sor->ops = &tegra_sor_hdmi_ops; + } else if (sor->soc->supports_lvds) { + dev_err(&pdev->dev, "LVDS not supported yet\n"); + return -ENODEV; + } else { + dev_err(&pdev->dev, "unknown (non-DP) support\n"); + return -ENODEV; + } + } else { + if (sor->soc->supports_edp) { + sor->ops = &tegra_sor_edp_ops; + } else if (sor->soc->supports_dp) { + dev_err(&pdev->dev, "DisplayPort not supported yet\n"); + return -ENODEV; + } else { + dev_err(&pdev->dev, "unknown (DP) support\n"); + return -ENODEV; + } + } + err = tegra_output_probe(&sor->output); if (err < 0) { dev_err(&pdev->dev, "failed to probe output: %d\n", err); return err; } + if (sor->ops && sor->ops->probe) { + err = sor->ops->probe(sor); + if (err < 0) { + dev_err(&pdev->dev, "failed to probe %s: %d\n", + sor->ops->name, err); + goto output; + } + } + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); sor->regs = devm_ioremap_resource(&pdev->dev, regs); - if (IS_ERR(sor->regs)) - return PTR_ERR(sor->regs); + if (IS_ERR(sor->regs)) { + err = PTR_ERR(sor->regs); + goto remove; + } sor->rst = devm_reset_control_get(&pdev->dev, "sor"); if (IS_ERR(sor->rst)) { - dev_err(&pdev->dev, "failed to get reset control: %ld\n", - PTR_ERR(sor->rst)); - return PTR_ERR(sor->rst); + err = PTR_ERR(sor->rst); + dev_err(&pdev->dev, "failed to get reset control: %d\n", err); + goto remove; } sor->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(sor->clk)) { - dev_err(&pdev->dev, "failed to get module clock: %ld\n", - PTR_ERR(sor->clk)); - return PTR_ERR(sor->clk); + err = PTR_ERR(sor->clk); + dev_err(&pdev->dev, "failed to get module clock: %d\n", err); + goto remove; } sor->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(sor->clk_parent)) { - dev_err(&pdev->dev, "failed to get parent clock: %ld\n", - PTR_ERR(sor->clk_parent)); - return PTR_ERR(sor->clk_parent); + err = PTR_ERR(sor->clk_parent); + dev_err(&pdev->dev, "failed to get parent clock: %d\n", err); + goto remove; } sor->clk_safe = devm_clk_get(&pdev->dev, "safe"); if (IS_ERR(sor->clk_safe)) { - dev_err(&pdev->dev, "failed to get safe clock: %ld\n", - PTR_ERR(sor->clk_safe)); - return PTR_ERR(sor->clk_safe); + err = PTR_ERR(sor->clk_safe); + dev_err(&pdev->dev, "failed to get safe clock: %d\n", err); + goto remove; } sor->clk_dp = devm_clk_get(&pdev->dev, "dp"); if (IS_ERR(sor->clk_dp)) { - dev_err(&pdev->dev, "failed to get DP clock: %ld\n", - PTR_ERR(sor->clk_dp)); - return PTR_ERR(sor->clk_dp); + err = PTR_ERR(sor->clk_dp); + dev_err(&pdev->dev, "failed to get DP clock: %d\n", err); + goto remove; } INIT_LIST_HEAD(&sor->client.list); @@ -1573,12 +2493,19 @@ static int tegra_sor_probe(struct platform_device *pdev) if (err < 0) { dev_err(&pdev->dev, "failed to register host1x client: %d\n", err); - return err; + goto remove; } platform_set_drvdata(pdev, sor); return 0; + +remove: + if (sor->ops && sor->ops->remove) + sor->ops->remove(sor); +output: + tegra_output_remove(&sor->output); + return err; } static int tegra_sor_remove(struct platform_device *pdev) @@ -1593,18 +2520,17 @@ static int tegra_sor_remove(struct platform_device *pdev) return err; } + if (sor->ops && sor->ops->remove) { + err = sor->ops->remove(sor); + if (err < 0) + dev_err(&pdev->dev, "failed to remove SOR: %d\n", err); + } + tegra_output_remove(&sor->output); return 0; } -static const struct of_device_id tegra_sor_of_match[] = { - { .compatible = "nvidia,tegra124-sor", }, - { .compatible = "nvidia,tegra210-sor", }, - { }, -}; -MODULE_DEVICE_TABLE(of, tegra_sor_of_match); - struct platform_driver tegra_sor_driver = { .driver = { .name = "tegra-sor", diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index 561b03ba969d..2d31d027e3f6 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -43,6 +43,12 @@ #define SOR_STATE_ASY_OWNER(x) (((x) & 0xf) << 0) #define SOR_HEAD_STATE0(x) (0x05 + (x)) +#define SOR_HEAD_STATE_RANGECOMPRESS_MASK (0x1 << 3) +#define SOR_HEAD_STATE_DYNRANGE_MASK (0x1 << 2) +#define SOR_HEAD_STATE_DYNRANGE_VESA (0 << 2) +#define SOR_HEAD_STATE_DYNRANGE_CEA (1 << 2) +#define SOR_HEAD_STATE_COLORSPACE_MASK (0x3 << 0) +#define SOR_HEAD_STATE_COLORSPACE_RGB (0 << 0) #define SOR_HEAD_STATE1(x) (0x07 + (x)) #define SOR_HEAD_STATE2(x) (0x09 + (x)) #define SOR_HEAD_STATE3(x) (0x0b + (x)) @@ -96,7 +102,11 @@ #define SOR_PLL1 0x18 /* XXX: read-only bit? */ +#define SOR_PLL1_LOADADJ_MASK (0xf << 20) +#define SOR_PLL1_LOADADJ(x) (((x) & 0xf) << 20) #define SOR_PLL1_TERM_COMPOUT (1 << 15) +#define SOR_PLL1_TMDS_TERMADJ_MASK (0xf << 9) +#define SOR_PLL1_TMDS_TERMADJ(x) (((x) & 0xf) << 9) #define SOR_PLL1_TMDS_TERM (1 << 8) #define SOR_PLL2 0x19 @@ -106,12 +116,17 @@ #define SOR_PLL2_BANDGAP_POWERDOWN (1 << 22) #define SOR_PLL2_POWERDOWN_OVERRIDE (1 << 18) #define SOR_PLL2_SEQ_PLLCAPPD (1 << 17) +#define SOR_PLL2_SEQ_PLL_PULLDOWN (1 << 16) #define SOR_PLL3 0x1a +#define SOR_PLL3_BG_VREF_LEVEL_MASK (0xf << 24) +#define SOR_PLL3_BG_VREF_LEVEL(x) (((x) & 0xf) << 24) #define SOR_PLL3_PLL_VDD_MODE_1V8 (0 << 13) #define SOR_PLL3_PLL_VDD_MODE_3V3 (1 << 13) #define SOR_CSTM 0x1b +#define SOR_CSTM_ROTCLK_MASK (0xf << 24) +#define SOR_CSTM_ROTCLK(x) (((x) & 0xf) << 24) #define SOR_CSTM_LVDS (1 << 16) #define SOR_CSTM_LINK_ACT_B (1 << 15) #define SOR_CSTM_LINK_ACT_A (1 << 14) @@ -124,15 +139,45 @@ #define SOR_CRCB 0x1e #define SOR_BLANK 0x1f #define SOR_SEQ_CTL 0x20 +#define SOR_SEQ_CTL_PD_PC_ALT(x) (((x) & 0xf) << 12) +#define SOR_SEQ_CTL_PD_PC(x) (((x) & 0xf) << 8) +#define SOR_SEQ_CTL_PU_PC_ALT(x) (((x) & 0xf) << 4) +#define SOR_SEQ_CTL_PU_PC(x) (((x) & 0xf) << 0) #define SOR_LANE_SEQ_CTL 0x21 #define SOR_LANE_SEQ_CTL_TRIGGER (1 << 31) +#define SOR_LANE_SEQ_CTL_STATE_BUSY (1 << 28) #define SOR_LANE_SEQ_CTL_SEQUENCE_UP (0 << 20) #define SOR_LANE_SEQ_CTL_SEQUENCE_DOWN (1 << 20) #define SOR_LANE_SEQ_CTL_POWER_STATE_UP (0 << 16) #define SOR_LANE_SEQ_CTL_POWER_STATE_DOWN (1 << 16) +#define SOR_LANE_SEQ_CTL_DELAY(x) (((x) & 0xf) << 12) #define SOR_SEQ_INST(x) (0x22 + (x)) +#define SOR_SEQ_INST_PLL_PULLDOWN (1 << 31) +#define SOR_SEQ_INST_POWERDOWN_MACRO (1 << 30) +#define SOR_SEQ_INST_ASSERT_PLL_RESET (1 << 29) +#define SOR_SEQ_INST_BLANK_V (1 << 28) +#define SOR_SEQ_INST_BLANK_H (1 << 27) +#define SOR_SEQ_INST_BLANK_DE (1 << 26) +#define SOR_SEQ_INST_BLACK_DATA (1 << 25) +#define SOR_SEQ_INST_TRISTATE_IOS (1 << 24) +#define SOR_SEQ_INST_DRIVE_PWM_OUT_LO (1 << 23) +#define SOR_SEQ_INST_PIN_B_LOW (0 << 22) +#define SOR_SEQ_INST_PIN_B_HIGH (1 << 22) +#define SOR_SEQ_INST_PIN_A_LOW (0 << 21) +#define SOR_SEQ_INST_PIN_A_HIGH (1 << 21) +#define SOR_SEQ_INST_SEQUENCE_UP (0 << 19) +#define SOR_SEQ_INST_SEQUENCE_DOWN (1 << 19) +#define SOR_SEQ_INST_LANE_SEQ_STOP (0 << 18) +#define SOR_SEQ_INST_LANE_SEQ_RUN (1 << 18) +#define SOR_SEQ_INST_PORT_POWERDOWN (1 << 17) +#define SOR_SEQ_INST_PLL_POWERDOWN (1 << 16) +#define SOR_SEQ_INST_HALT (1 << 15) +#define SOR_SEQ_INST_WAIT_US (0 << 12) +#define SOR_SEQ_INST_WAIT_MS (1 << 12) +#define SOR_SEQ_INST_WAIT_VSYNC (2 << 12) +#define SOR_SEQ_INST_WAIT(x) (((x) & 0x3ff) << 0) #define SOR_PWM_DIV 0x32 #define SOR_PWM_DIV_MASK 0xffffff @@ -165,6 +210,10 @@ #define SOR_TRIG 0x48 #define SOR_MSCHECK 0x49 #define SOR_XBAR_CTRL 0x4a +#define SOR_XBAR_CTRL_LINK1_XSEL(channel, value) ((((value) & 0x7) << ((channel) * 3)) << 17) +#define SOR_XBAR_CTRL_LINK0_XSEL(channel, value) ((((value) & 0x7) << ((channel) * 3)) << 2) +#define SOR_XBAR_CTRL_LINK_SWAP (1 << 1) +#define SOR_XBAR_CTRL_BYPASS (1 << 0) #define SOR_XBAR_POL 0x4b #define SOR_DP_LINKCTL0 0x4c @@ -237,6 +286,7 @@ #define SOR_DP_DEBUG1 0x5f #define SOR_DP_SPARE0 0x60 +#define SOR_DP_SPARE_DISP_VIDEO_PREAMBLE (1 << 3) #define SOR_DP_SPARE_MACRO_SOR_CLK (1 << 2) #define SOR_DP_SPARE_PANEL_INTERNAL (1 << 1) #define SOR_DP_SPARE_SEQ_ENABLE (1 << 0) @@ -281,4 +331,40 @@ #define SOR_DP_LQ_CSTM1 0x70 #define SOR_DP_LQ_CSTM2 0x71 +#define SOR_HDMI_AUDIO_INFOFRAME_CTRL 0x9a +#define SOR_HDMI_AUDIO_INFOFRAME_STATUS 0x9b +#define SOR_HDMI_AUDIO_INFOFRAME_HEADER 0x9c + +#define SOR_HDMI_AVI_INFOFRAME_CTRL 0x9f +#define INFOFRAME_CTRL_CHECKSUM_ENABLE (1 << 9) +#define INFOFRAME_CTRL_SINGLE (1 << 8) +#define INFOFRAME_CTRL_OTHER (1 << 4) +#define INFOFRAME_CTRL_ENABLE (1 << 0) + +#define SOR_HDMI_AVI_INFOFRAME_STATUS 0xa0 +#define INFOFRAME_STATUS_DONE (1 << 0) + +#define SOR_HDMI_AVI_INFOFRAME_HEADER 0xa1 +#define INFOFRAME_HEADER_LEN(x) (((x) & 0xff) << 16) +#define INFOFRAME_HEADER_VERSION(x) (((x) & 0xff) << 8) +#define INFOFRAME_HEADER_TYPE(x) (((x) & 0xff) << 0) + +#define SOR_HDMI_CTRL 0xc0 +#define SOR_HDMI_CTRL_ENABLE (1 << 30) +#define SOR_HDMI_CTRL_MAX_AC_PACKET(x) (((x) & 0x1f) << 16) +#define SOR_HDMI_CTRL_AUDIO_LAYOUT (1 << 10) +#define SOR_HDMI_CTRL_REKEY(x) (((x) & 0x7f) << 0) + +#define SOR_REFCLK 0xe6 +#define SOR_REFCLK_DIV_INT(x) ((((x) >> 2) & 0xff) << 8) +#define SOR_REFCLK_DIV_FRAC(x) (((x) & 0x3) << 6) + +#define SOR_INPUT_CONTROL 0xe8 +#define SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED (1 << 1) +#define SOR_INPUT_CONTROL_HDMI_SRC_SELECT(x) (((x) & 0x1) << 0) + +#define SOR_HDMI_VSI_INFOFRAME_CTRL 0x123 +#define SOR_HDMI_VSI_INFOFRAME_STATUS 0x124 +#define SOR_HDMI_VSI_INFOFRAME_HEADER 0x125 + #endif -- cgit v1.2.3 From d718d79e57039ccf59f638efe7c9ede2bfabc6f1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 8 Apr 2015 16:52:33 +0200 Subject: drm/panel: simple: Add support for AUO B080UAN01 The AUO B080UAN01 is an 8.0" WUXGA TFT LCD panel connected using four DSI lanes. It can be supported by the simple-panel driver. Signed-off-by: Thierry Reding --- .../devicetree/bindings/panel/auo,b080uan01.txt | 7 +++++ drivers/gpu/drm/panel/panel-simple.c | 31 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/auo,b080uan01.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/panel/auo,b080uan01.txt b/Documentation/devicetree/bindings/panel/auo,b080uan01.txt new file mode 100644 index 000000000000..bae0e2b51467 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/auo,b080uan01.txt @@ -0,0 +1,7 @@ +AU Optronics Corporation 8.0" WUXGA TFT LCD panel + +Required properties: +- compatible: should be "auo,b101ean01" + +This binding is compatible with the simple-panel binding, which is specified +in simple-panel.txt in this directory. diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 39e9dffe07d9..2bf2c47e29bf 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1175,6 +1175,34 @@ struct panel_desc_dsi { unsigned int lanes; }; +static const struct drm_display_mode auo_b080uan01_mode = { + .clock = 154500, + .hdisplay = 1200, + .hsync_start = 1200 + 62, + .hsync_end = 1200 + 62 + 4, + .htotal = 1200 + 62 + 4 + 62, + .vdisplay = 1920, + .vsync_start = 1920 + 9, + .vsync_end = 1920 + 9 + 2, + .vtotal = 1920 + 9 + 2 + 8, + .vrefresh = 60, +}; + +static const struct panel_desc_dsi auo_b080uan01 = { + .desc = { + .modes = &auo_b080uan01_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 108, + .height = 272, + }, + }, + .flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS, + .format = MIPI_DSI_FMT_RGB888, + .lanes = 4, +}; + static const struct drm_display_mode lg_ld070wx3_sl01_mode = { .clock = 71000, .hdisplay = 800, @@ -1262,6 +1290,9 @@ static const struct panel_desc_dsi panasonic_vvx10f004b00 = { static const struct of_device_id dsi_of_match[] = { { + .compatible = "auo,b080uan01", + .data = &auo_b080uan01 + }, { .compatible = "lg,ld070wx3-sl01", .data = &lg_ld070wx3_sl01 }, { -- cgit v1.2.3 From c6e87f91f0445e80656eddae84429ad7d687dc3f Mon Sep 17 00:00:00 2001 From: jianwei wang Date: Wed, 29 Jul 2015 16:30:02 +0800 Subject: drm/panel: simple: Add support for NEC NL4827HC19-05B 480x272 panel This adds support for the NEC NL4827HC19-05B 480x272 panel to the DRM simple panel driver. Signed-off-by: Alison Wang Signed-off-by: Xiubo Li Signed-off-by: Jianwei Wang Acked-by: Daniel Vetter [treding@nvidia.com: add .bpc field for panel] Signed-off-by: Thierry Reding --- .../bindings/panel/nec,nl4827hc19-05b.txt | 7 ++++++ drivers/gpu/drm/panel/panel-simple.c | 27 ++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/nec,nl4827hc19-05b.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/panel/nec,nl4827hc19-05b.txt b/Documentation/devicetree/bindings/panel/nec,nl4827hc19-05b.txt new file mode 100644 index 000000000000..8e1914d1edb8 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/nec,nl4827hc19-05b.txt @@ -0,0 +1,7 @@ +NEC LCD Technologies,Ltd. WQVGA TFT LCD panel + +Required properties: +- compatible: should be "nec,nl4827hc19-05b" + +This binding is compatible with the simple-panel binding, which is specified +in simple-panel.txt in this directory. diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 2bf2c47e29bf..57eceb24912d 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -949,6 +949,30 @@ static const struct panel_desc lg_lp129qe = { }, }; +static const struct drm_display_mode nec_nl4827hc19_05b_mode = { + .clock = 10870, + .hdisplay = 480, + .hsync_start = 480 + 2, + .hsync_end = 480 + 2 + 41, + .htotal = 480 + 2 + 41 + 2, + .vdisplay = 272, + .vsync_start = 272 + 2, + .vsync_end = 272 + 2 + 4, + .vtotal = 272 + 2 + 4 + 2, + .vrefresh = 74, +}; + +static const struct panel_desc nec_nl4827hc19_05b = { + .modes = &nec_nl4827hc19_05b_mode, + .num_modes = 1, + .bpc = 8, + .size = { + .width = 95, + .height = 54, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24 +}; + static const struct drm_display_mode ortustech_com43h4m85ulc_mode = { .clock = 25000, .hdisplay = 480, @@ -1118,6 +1142,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "lg,lp129qe", .data = &lg_lp129qe, + }, { + .compatible = "nec,nl4827hc19-05b", + .data = &nec_nl4827hc19_05b, }, { .compatible = "ortustech,com43h4m85ulc", .data = &ortustech_com43h4m85ulc, -- cgit v1.2.3 From 58c948d8c4145d354457bdfd654b828007722c44 Mon Sep 17 00:00:00 2001 From: Gary Bisson Date: Wed, 10 Jun 2015 18:44:22 +0200 Subject: of: Add Okaya Electric America vendor prefix This patch adds vendor prefix for Okaya Electronic America, a provider of LCD modules and display technologies. Signed-off-by: Gary Bisson Acked-by: Rob Herring Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/vendor-prefixes.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt index d444757c4d9e..bf529e77658d 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.txt +++ b/Documentation/devicetree/bindings/vendor-prefixes.txt @@ -148,6 +148,7 @@ nintendo Nintendo nokia Nokia nvidia NVIDIA nxp NXP Semiconductors +okaya Okaya Electric America, Inc. onnn ON Semiconductor Corp. opencores OpenCores.org ortustech Ortus Technology Co., Ltd. -- cgit v1.2.3 From a99fb6269d1af432c051ed552aaea807f9f906c9 Mon Sep 17 00:00:00 2001 From: Gary Bisson Date: Wed, 10 Jun 2015 18:44:23 +0200 Subject: drm/panel: Add display timing for Okaya RS800480T-7X0GP Add support for the Okaya RS800480T-7X0GP to the DRM simple panel driver. The RS800480T-7X0GP is a WVGA (800x480) panel with an 18-bit parallel LCD interface. It supports pixel clocks in the range of 30-40 MHz. This panel details can be found at: http://boundarydevices.com/product/7-800x480-display/ Signed-off-by: Gary Bisson Signed-off-by: Thierry Reding --- .../bindings/panel/okaya,rs800480t-7x0gp.txt | 7 +++++ drivers/gpu/drm/panel/panel-simple.c | 33 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/okaya,rs800480t-7x0gp.txt (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/panel/okaya,rs800480t-7x0gp.txt b/Documentation/devicetree/bindings/panel/okaya,rs800480t-7x0gp.txt new file mode 100644 index 000000000000..ddf8e211d382 --- /dev/null +++ b/Documentation/devicetree/bindings/panel/okaya,rs800480t-7x0gp.txt @@ -0,0 +1,7 @@ +OKAYA Electric America, Inc. RS800480T-7X0GP 7" WVGA LCD panel + +Required properties: +- compatible: should be "okaya,rs800480t-7x0gp" + +This binding is compatible with the simple-panel binding, which is specified +in simple-panel.txt in this directory. diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 57eceb24912d..f97b73ec4713 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -973,6 +973,36 @@ static const struct panel_desc nec_nl4827hc19_05b = { .bus_format = MEDIA_BUS_FMT_RGB888_1X24 }; +static const struct display_timing okaya_rs800480t_7x0gp_timing = { + .pixelclock = { 30000000, 30000000, 40000000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 40, 40, 40 }, + .hback_porch = { 40, 40, 40 }, + .hsync_len = { 1, 48, 48 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 13, 13, 13 }, + .vback_porch = { 29, 29, 29 }, + .vsync_len = { 3, 3, 3 }, + .flags = DISPLAY_FLAGS_DE_HIGH, +}; + +static const struct panel_desc okaya_rs800480t_7x0gp = { + .timings = &okaya_rs800480t_7x0gp_timing, + .num_timings = 1, + .bpc = 6, + .size = { + .width = 154, + .height = 87, + }, + .delay = { + .prepare = 41, + .enable = 50, + .unprepare = 41, + .disable = 50, + }, + .bus_format = MEDIA_BUS_FMT_RGB666_1X18, +}; + static const struct drm_display_mode ortustech_com43h4m85ulc_mode = { .clock = 25000, .hdisplay = 480, @@ -1145,6 +1175,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "nec,nl4827hc19-05b", .data = &nec_nl4827hc19_05b, + }, { + .compatible = "okaya,rs800480t-7x0gp", + .data = &okaya_rs800480t_7x0gp, }, { .compatible = "ortustech,com43h4m85ulc", .data = &ortustech_com43h4m85ulc, -- cgit v1.2.3 From 58c467ece486e9bd1e26b4fd68e8cdef8501952d Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Tue, 9 Jun 2015 07:51:22 +0200 Subject: drm/panel: Add support for LG LG4573 480x800 4.3" panel The LG4573 is used on the LG LCD LB043WV2-SD01, an industrial 4.3" TFT panel with SPI control interface. Signed-off-by: Heiko Schocher Signed-off-by: Thierry Reding --- .../devicetree/bindings/panel/lg,lg4573.txt | 19 ++ drivers/gpu/drm/panel/Kconfig | 8 + drivers/gpu/drm/panel/Makefile | 1 + drivers/gpu/drm/panel/panel-lg-lg4573.c | 298 +++++++++++++++++++++ 4 files changed, 326 insertions(+) create mode 100644 Documentation/devicetree/bindings/panel/lg,lg4573.txt create mode 100644 drivers/gpu/drm/panel/panel-lg-lg4573.c (limited to 'Documentation/devicetree') diff --git a/Documentation/devicetree/bindings/panel/lg,lg4573.txt b/Documentation/devicetree/bindings/panel/lg,lg4573.txt new file mode 100644 index 000000000000..824441f4e95a --- /dev/null +++ b/Documentation/devicetree/bindings/panel/lg,lg4573.txt @@ -0,0 +1,19 @@ +LG LG4573 TFT Liquid Crystal Display with SPI control bus + +Required properties: + - compatible: "lg,lg4573" + - reg: address of the panel on the SPI bus + +The panel must obey rules for SPI slave device specified in document [1]. + +[1]: Documentation/devicetree/bindings/spi/spi-bus.txt + +Example: + + lcd_panel: display@0 { + #address-cells = <1>; + #size-cells = <1>; + compatible = "lg,lg4573"; + spi-max-frequency = <10000000>; + reg = <0>; + }; diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 5be25d9282e3..7d4704b1292b 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -23,6 +23,14 @@ config DRM_PANEL_SAMSUNG_LD9040 depends on OF && SPI select VIDEOMODE_HELPERS +config DRM_PANEL_LG_LG4573 + tristate "LG4573 RGB/SPI panel" + depends on OF && SPI + select VIDEOMODE_HELPERS + help + Say Y here if you want to enable support for LG4573 RGB panel. + To compile this driver as a module, choose M here. + config DRM_PANEL_SAMSUNG_S6E8AA0 tristate "Samsung S6E8AA0 DSI video mode panel" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index 8026ce5d18b5..d0f016dd7ddb 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o +obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_LD9040) += panel-samsung-ld9040.o obj-$(CONFIG_DRM_PANEL_SAMSUNG_S6E8AA0) += panel-samsung-s6e8aa0.o obj-$(CONFIG_DRM_PANEL_SHARP_LQ101R1SX01) += panel-sharp-lq101r1sx01.o diff --git a/drivers/gpu/drm/panel/panel-lg-lg4573.c b/drivers/gpu/drm/panel/panel-lg-lg4573.c new file mode 100644 index 000000000000..a7b4939cee6d --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lg-lg4573.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2015 Heiko Schocher + * + * from: + * drivers/gpu/drm/panel/panel-ld9040.c + * ld9040 AMOLED LCD drm_panel driver. + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * Derived from drivers/video/backlight/ld9040.c + * + * Andrzej Hajda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include + +#include +#include +#include + +#include