diff options
Diffstat (limited to 'drivers/gpu/drm/tegra')
-rw-r--r-- | drivers/gpu/drm/tegra/Kconfig | 25 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/bus.c | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.c | 117 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dc.h | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.c | 21 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/drm.h | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.c | 971 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/dsi.h | 134 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/fb.c | 45 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gem.c | 183 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gem.h | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/gr3d.c | 22 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/hdmi.c | 78 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/mipi-phy.c | 138 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/mipi-phy.h | 65 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/output.c | 51 | ||||
-rw-r--r-- | drivers/gpu/drm/tegra/rgb.c | 47 |
18 files changed, 1846 insertions, 87 deletions
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig index 8961ba6a34b..354ddb29231 100644 --- a/drivers/gpu/drm/tegra/Kconfig +++ b/drivers/gpu/drm/tegra/Kconfig @@ -1,13 +1,12 @@ config DRM_TEGRA - bool "NVIDIA Tegra DRM" - depends on ARCH_TEGRA || ARCH_MULTIPLATFORM + tristate "NVIDIA Tegra DRM" + depends on ARCH_TEGRA || (ARM && COMPILE_TEST) depends on DRM - select TEGRA_HOST1X + depends on RESET_CONTROLLER select DRM_KMS_HELPER - select DRM_KMS_FB_HELPER - select FB_SYS_FILLRECT - select FB_SYS_COPYAREA - select FB_SYS_IMAGEBLIT + select DRM_MIPI_DSI + select DRM_PANEL + select TEGRA_HOST1X help Choose this option if you have an NVIDIA Tegra SoC. @@ -16,6 +15,18 @@ config DRM_TEGRA if DRM_TEGRA +config DRM_TEGRA_FBDEV + bool "Enable legacy fbdev support" + select DRM_KMS_FB_HELPER + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + default y + help + Choose this option if you have a need for the legacy fbdev support. + Note that this support also provides the Linux console on top of + the Tegra modesetting driver. + config DRM_TEGRA_DEBUG bool "NVIDIA Tegra DRM debug support" help diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile index edc76abd58b..8d220afbd85 100644 --- a/drivers/gpu/drm/tegra/Makefile +++ b/drivers/gpu/drm/tegra/Makefile @@ -9,6 +9,8 @@ tegra-drm-y := \ output.o \ rgb.o \ hdmi.o \ + mipi-phy.o \ + dsi.o \ gr2d.o \ gr3d.o diff --git a/drivers/gpu/drm/tegra/bus.c b/drivers/gpu/drm/tegra/bus.c index 565f8f7b9a4..e38e5967d77 100644 --- a/drivers/gpu/drm/tegra/bus.c +++ b/drivers/gpu/drm/tegra/bus.c @@ -46,7 +46,6 @@ int drm_host1x_init(struct drm_driver *driver, struct host1x_device *device) struct drm_device *drm; int ret; - INIT_LIST_HEAD(&driver->device_list); driver->bus = &drm_host1x_bus; drm = drm_dev_alloc(driver, &device->dev); diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index ae1cb31ead7..9336006b475 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -8,13 +8,17 @@ */ #include <linux/clk.h> -#include <linux/clk/tegra.h> #include <linux/debugfs.h> +#include <linux/reset.h> #include "dc.h" #include "drm.h" #include "gem.h" +struct tegra_dc_soc_info { + bool supports_interlacing; +}; + struct tegra_plane { struct drm_plane base; unsigned int index; @@ -658,19 +662,12 @@ static int tegra_crtc_mode_set(struct drm_crtc *crtc, /* program display mode */ tegra_dc_set_timings(dc, mode); - value = DE_SELECT_ACTIVE | DE_CONTROL_NORMAL; - tegra_dc_writel(dc, value, DC_DISP_DATA_ENABLE_OPTIONS); - - value = tegra_dc_readl(dc, DC_COM_PIN_OUTPUT_POLARITY(1)); - value &= ~LVS_OUTPUT_POLARITY_LOW; - value &= ~LHS_OUTPUT_POLARITY_LOW; - tegra_dc_writel(dc, value, DC_COM_PIN_OUTPUT_POLARITY(1)); - - value = DISP_DATA_FORMAT_DF1P1C | DISP_ALIGNMENT_MSB | - DISP_ORDER_RED_BLUE; - tegra_dc_writel(dc, value, DC_DISP_DISP_INTERFACE_CONTROL); - - tegra_dc_writel(dc, 0x00010001, DC_DISP_SHIFT_CLOCK_OPTIONS); + /* interlacing isn't supported yet, so disable it */ + if (dc->soc->supports_interlacing) { + value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL); + value &= ~INTERLACE_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL); + } value = SHIFT_CLK_DIVIDER(div) | PIXEL_CLK_DIVIDER_PCD1; tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL); @@ -712,7 +709,7 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) unsigned long value; /* hardware initialization */ - tegra_periph_reset_deassert(dc->clk); + reset_control_deassert(dc->rst); usleep_range(10000, 20000); if (dc->pipe) @@ -735,10 +732,6 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); - value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); - value |= DISP_CTRL_MODE_C_DISPLAY; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); - /* initialize timer */ value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); @@ -1107,8 +1100,6 @@ static int tegra_dc_init(struct host1x_client *client) struct tegra_dc *dc = host1x_client_to_dc(client); int err; - dc->pipe = tegra->drm->mode_config.num_crtc; - drm_crtc_init(tegra->drm, &dc->base, &tegra_crtc_funcs); drm_mode_crtc_set_gamma_size(&dc->base, 256); drm_crtc_helper_add(&dc->base, &tegra_crtc_helper_funcs); @@ -1167,8 +1158,71 @@ static const struct host1x_client_ops dc_client_ops = { .exit = tegra_dc_exit, }; +static const struct tegra_dc_soc_info tegra20_dc_soc_info = { + .supports_interlacing = false, +}; + +static const struct tegra_dc_soc_info tegra30_dc_soc_info = { + .supports_interlacing = false, +}; + +static const struct tegra_dc_soc_info tegra124_dc_soc_info = { + .supports_interlacing = true, +}; + +static const struct of_device_id tegra_dc_of_match[] = { + { + .compatible = "nvidia,tegra124-dc", + .data = &tegra124_dc_soc_info, + }, { + .compatible = "nvidia,tegra30-dc", + .data = &tegra30_dc_soc_info, + }, { + .compatible = "nvidia,tegra20-dc", + .data = &tegra20_dc_soc_info, + }, { + /* sentinel */ + } +}; + +static int tegra_dc_parse_dt(struct tegra_dc *dc) +{ + struct device_node *np; + u32 value = 0; + int err; + + err = of_property_read_u32(dc->dev->of_node, "nvidia,head", &value); + if (err < 0) { + dev_err(dc->dev, "missing \"nvidia,head\" property\n"); + + /* + * If the nvidia,head property isn't present, try to find the + * correct head number by looking up the position of this + * display controller's node within the device tree. Assuming + * that the nodes are ordered properly in the DTS file and + * that the translation into a flattened device tree blob + * preserves that ordering this will actually yield the right + * head number. + * + * If those assumptions don't hold, this will still work for + * cases where only a single display controller is used. + */ + for_each_matching_node(np, tegra_dc_of_match) { + if (np == dc->dev->of_node) + break; + + value++; + } + } + + dc->pipe = value; + + return 0; +} + static int tegra_dc_probe(struct platform_device *pdev) { + const struct of_device_id *id; struct resource *regs; struct tegra_dc *dc; int err; @@ -1177,9 +1231,18 @@ static int tegra_dc_probe(struct platform_device *pdev) if (!dc) return -ENOMEM; + id = of_match_node(tegra_dc_of_match, pdev->dev.of_node); + if (!id) + return -ENODEV; + spin_lock_init(&dc->lock); INIT_LIST_HEAD(&dc->list); dc->dev = &pdev->dev; + dc->soc = id->data; + + err = tegra_dc_parse_dt(dc); + if (err < 0) + return err; dc->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dc->clk)) { @@ -1187,6 +1250,12 @@ static int tegra_dc_probe(struct platform_device *pdev) return PTR_ERR(dc->clk); } + dc->rst = devm_reset_control_get(&pdev->dev, "dc"); + if (IS_ERR(dc->rst)) { + dev_err(&pdev->dev, "failed to get reset\n"); + return PTR_ERR(dc->rst); + } + err = clk_prepare_enable(dc->clk); if (err < 0) return err; @@ -1247,12 +1316,6 @@ static int tegra_dc_remove(struct platform_device *pdev) return 0; } -static struct of_device_id tegra_dc_of_match[] = { - { .compatible = "nvidia,tegra30-dc", }, - { .compatible = "nvidia,tegra20-dc", }, - { }, -}; - struct platform_driver tegra_dc_driver = { .driver = { .name = "tegra-dc", diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h index 91bbda29147..3c2c0ea1cd8 100644 --- a/drivers/gpu/drm/tegra/dc.h +++ b/drivers/gpu/drm/tegra/dc.h @@ -28,6 +28,7 @@ #define DISP_CTRL_MODE_STOP (0 << 5) #define DISP_CTRL_MODE_C_DISPLAY (1 << 5) #define DISP_CTRL_MODE_NC_DISPLAY (2 << 5) +#define DISP_CTRL_MODE_MASK (3 << 5) #define DC_CMD_SIGNAL_RAISE 0x033 #define DC_CMD_DISPLAY_POWER_CONTROL 0x036 #define PW0_ENABLE (1 << 0) @@ -116,6 +117,7 @@ #define DC_DISP_DISP_WIN_OPTIONS 0x402 #define HDMI_ENABLE (1 << 30) +#define DSI_ENABLE (1 << 29) #define DC_DISP_DISP_MEM_HIGH_PRIORITY 0x403 #define CURSOR_THRESHOLD(x) (((x) & 0x03) << 24) @@ -238,6 +240,8 @@ #define DITHER_CONTROL_ERRDIFF (3 << 8) #define DC_DISP_SHIFT_CLOCK_OPTIONS 0x431 +#define SC1_H_QUALIFIER_NONE (1 << 16) +#define SC0_H_QUALIFIER_NONE (1 << 0) #define DC_DISP_DATA_ENABLE_OPTIONS 0x432 #define DE_SELECT_ACTIVE_BLANK (0 << 0) @@ -292,6 +296,11 @@ #define DC_DISP_SD_HW_K_VALUES 0x4dd #define DC_DISP_SD_MAN_K_VALUES 0x4de +#define DC_DISP_INTERLACE_CONTROL 0x4e5 +#define INTERLACE_STATUS (1 << 2) +#define INTERLACE_START (1 << 1) +#define INTERLACE_ENABLE (1 << 0) + #define DC_WIN_CSC_YOF 0x611 #define DC_WIN_CSC_KYRGB 0x612 #define DC_WIN_CSC_KUR 0x613 diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 07eba596d45..88a529008ce 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -104,9 +104,11 @@ static void tegra_drm_context_free(struct tegra_drm_context *context) static void tegra_drm_lastclose(struct drm_device *drm) { +#ifdef CONFIG_TEGRA_DRM_FBDEV struct tegra_drm *tegra = drm->dev_private; tegra_fbdev_restore_mode(tegra->fbdev); +#endif } static struct host1x_bo * @@ -578,7 +580,7 @@ static void tegra_debugfs_cleanup(struct drm_minor *minor) #endif static struct drm_driver tegra_drm_driver = { - .driver_features = DRIVER_MODESET | DRIVER_GEM, + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, .load = tegra_drm_load, .unload = tegra_drm_unload, .open = tegra_drm_open, @@ -596,6 +598,12 @@ static struct drm_driver tegra_drm_driver = { .gem_free_object = tegra_bo_free_object, .gem_vm_ops = &tegra_bo_vm_ops, + + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = tegra_gem_prime_export, + .gem_prime_import = tegra_gem_prime_import, + .dumb_create = tegra_bo_dumb_create, .dumb_map_offset = tegra_bo_dumb_map_offset, .dumb_destroy = drm_gem_dumb_destroy, @@ -653,8 +661,10 @@ static const struct of_device_id host1x_drm_subdevs[] = { { .compatible = "nvidia,tegra30-hdmi", }, { .compatible = "nvidia,tegra30-gr2d", }, { .compatible = "nvidia,tegra30-gr3d", }, + { .compatible = "nvidia,tegra114-dsi", }, { .compatible = "nvidia,tegra114-hdmi", }, { .compatible = "nvidia,tegra114-gr3d", }, + { .compatible = "nvidia,tegra124-dc", }, { /* sentinel */ } }; @@ -677,10 +687,14 @@ static int __init host1x_drm_init(void) if (err < 0) goto unregister_host1x; - err = platform_driver_register(&tegra_hdmi_driver); + err = platform_driver_register(&tegra_dsi_driver); if (err < 0) goto unregister_dc; + err = platform_driver_register(&tegra_hdmi_driver); + if (err < 0) + goto unregister_dsi; + err = platform_driver_register(&tegra_gr2d_driver); if (err < 0) goto unregister_hdmi; @@ -695,6 +709,8 @@ unregister_gr2d: platform_driver_unregister(&tegra_gr2d_driver); unregister_hdmi: platform_driver_unregister(&tegra_hdmi_driver); +unregister_dsi: + platform_driver_unregister(&tegra_dsi_driver); unregister_dc: platform_driver_unregister(&tegra_dc_driver); unregister_host1x: @@ -708,6 +724,7 @@ static void __exit host1x_drm_exit(void) platform_driver_unregister(&tegra_gr3d_driver); platform_driver_unregister(&tegra_gr2d_driver); platform_driver_unregister(&tegra_hdmi_driver); + platform_driver_unregister(&tegra_dsi_driver); platform_driver_unregister(&tegra_dc_driver); host1x_driver_unregister(&host1x_drm_driver); } diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 7da0b923131..bf1cac7658f 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -19,16 +19,20 @@ #include <drm/drm_fb_helper.h> #include <drm/drm_fixed.h> +struct reset_control; + struct tegra_fb { struct drm_framebuffer base; struct tegra_bo **planes; unsigned int num_planes; }; +#ifdef CONFIG_DRM_TEGRA_FBDEV struct tegra_fbdev { struct drm_fb_helper base; struct tegra_fb *fb; }; +#endif struct tegra_drm { struct drm_device *drm; @@ -36,7 +40,9 @@ struct tegra_drm { struct mutex clients_lock; struct list_head clients; +#ifdef CONFIG_DRM_TEGRA_FBDEV struct tegra_fbdev *fbdev; +#endif }; struct tegra_drm_client; @@ -82,6 +88,7 @@ extern int tegra_drm_unregister_client(struct tegra_drm *tegra, extern int tegra_drm_init(struct tegra_drm *tegra, struct drm_device *drm); extern int tegra_drm_exit(struct tegra_drm *tegra); +struct tegra_dc_soc_info; struct tegra_output; struct tegra_dc { @@ -93,6 +100,7 @@ struct tegra_dc { int pipe; struct clk *clk; + struct reset_control *rst; void __iomem *regs; int irq; @@ -106,6 +114,8 @@ struct tegra_dc { /* page-flip handling */ struct drm_pending_vblank_event *event; + + const struct tegra_dc_soc_info *soc; }; static inline struct tegra_dc * @@ -174,6 +184,7 @@ struct tegra_output_ops { enum tegra_output_type { TEGRA_OUTPUT_RGB, TEGRA_OUTPUT_HDMI, + TEGRA_OUTPUT_DSI, }; struct tegra_output { @@ -183,6 +194,7 @@ struct tegra_output { const struct tegra_output_ops *ops; enum tegra_output_type type; + struct drm_panel *panel; struct i2c_adapter *ddc; const struct edid *edid; unsigned int hpd_irq; @@ -260,9 +272,12 @@ bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer); bool tegra_fb_is_tiled(struct drm_framebuffer *framebuffer); extern int tegra_drm_fb_init(struct drm_device *drm); extern void tegra_drm_fb_exit(struct drm_device *drm); +#ifdef CONFIG_DRM_TEGRA_FBDEV extern void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev); +#endif extern struct platform_driver tegra_dc_driver; +extern struct platform_driver tegra_dsi_driver; extern struct platform_driver tegra_hdmi_driver; extern struct platform_driver tegra_gr2d_driver; extern struct platform_driver tegra_gr3d_driver; diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c new file mode 100644 index 00000000000..d452faab023 --- /dev/null +++ b/drivers/gpu/drm/tegra/dsi.c @@ -0,0 +1,971 @@ +/* + * Copyright (C) 2013 NVIDIA Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/host1x.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +#include <drm/drm_mipi_dsi.h> +#include <drm/drm_panel.h> + +#include <video/mipi_display.h> + +#include "dc.h" +#include "drm.h" +#include "dsi.h" +#include "mipi-phy.h" + +#define DSI_VIDEO_FIFO_DEPTH (1920 / 4) +#define DSI_HOST_FIFO_DEPTH 64 + +struct tegra_dsi { + struct host1x_client client; + struct tegra_output output; + struct device *dev; + + void __iomem *regs; + + struct reset_control *rst; + struct clk *clk_parent; + struct clk *clk_lp; + struct clk *clk; + + struct drm_info_list *debugfs_files; + struct drm_minor *minor; + struct dentry *debugfs; + + enum mipi_dsi_pixel_format format; + unsigned int lanes; + + struct tegra_mipi_device *mipi; + struct mipi_dsi_host host; +}; + +static inline struct tegra_dsi * +host1x_client_to_dsi(struct host1x_client *client) +{ + return container_of(client, struct tegra_dsi, client); +} + +static inline struct tegra_dsi *host_to_tegra(struct mipi_dsi_host *host) +{ + return container_of(host, struct tegra_dsi, host); +} + +static inline struct tegra_dsi *to_dsi(struct tegra_output *output) +{ + return container_of(output, struct tegra_dsi, output); +} + +static inline unsigned long tegra_dsi_readl(struct tegra_dsi *dsi, + unsigned long reg) +{ + return readl(dsi->regs + (reg << 2)); +} + +static inline void tegra_dsi_writel(struct tegra_dsi *dsi, unsigned long value, + unsigned long reg) +{ + writel(value, dsi->regs + (reg << 2)); +} + +static int tegra_dsi_show_regs(struct seq_file *s, void *data) +{ + struct drm_info_node *node = s->private; + struct tegra_dsi *dsi = node->info_ent->data; + +#define DUMP_REG(name) \ + seq_printf(s, "%-32s %#05x %08lx\n", #name, name, \ + tegra_dsi_readl(dsi, name)) + + DUMP_REG(DSI_INCR_SYNCPT); + DUMP_REG(DSI_INCR_SYNCPT_CONTROL); + DUMP_REG(DSI_INCR_SYNCPT_ERROR); + DUMP_REG(DSI_CTXSW); + DUMP_REG(DSI_RD_DATA); + DUMP_REG(DSI_WR_DATA); + DUMP_REG(DSI_POWER_CONTROL); + DUMP_REG(DSI_INT_ENABLE); + DUMP_REG(DSI_INT_STATUS); + DUMP_REG(DSI_INT_MASK); + DUMP_REG(DSI_HOST_CONTROL); + DUMP_REG(DSI_CONTROL); + DUMP_REG(DSI_SOL_DELAY); + DUMP_REG(DSI_MAX_THRESHOLD); + DUMP_REG(DSI_TRIGGER); + DUMP_REG(DSI_TX_CRC); + DUMP_REG(DSI_STATUS); + + DUMP_REG(DSI_INIT_SEQ_CONTROL); + DUMP_REG(DSI_INIT_SEQ_DATA_0); + DUMP_REG(DSI_INIT_SEQ_DATA_1); + DUMP_REG(DSI_INIT_SEQ_DATA_2); + DUMP_REG(DSI_INIT_SEQ_DATA_3); + DUMP_REG(DSI_INIT_SEQ_DATA_4); + DUMP_REG(DSI_INIT_SEQ_DATA_5); + DUMP_REG(DSI_INIT_SEQ_DATA_6); + DUMP_REG(DSI_INIT_SEQ_DATA_7); + + DUMP_REG(DSI_PKT_SEQ_0_LO); + DUMP_REG(DSI_PKT_SEQ_0_HI); + DUMP_REG(DSI_PKT_SEQ_1_LO); + DUMP_REG(DSI_PKT_SEQ_1_HI); + DUMP_REG(DSI_PKT_SEQ_2_LO); + DUMP_REG(DSI_PKT_SEQ_2_HI); + DUMP_REG(DSI_PKT_SEQ_3_LO); + DUMP_REG(DSI_PKT_SEQ_3_HI); + DUMP_REG(DSI_PKT_SEQ_4_LO); + DUMP_REG(DSI_PKT_SEQ_4_HI); + DUMP_REG(DSI_PKT_SEQ_5_LO); + DUMP_REG(DSI_PKT_SEQ_5_HI); + + DUMP_REG(DSI_DCS_CMDS); + + DUMP_REG(DSI_PKT_LEN_0_1); + DUMP_REG(DSI_PKT_LEN_2_3); + DUMP_REG(DSI_PKT_LEN_4_5); + DUMP_REG(DSI_PKT_LEN_6_7); + + DUMP_REG(DSI_PHY_TIMING_0); + DUMP_REG(DSI_PHY_TIMING_1); + DUMP_REG(DSI_PHY_TIMING_2); + DUMP_REG(DSI_BTA_TIMING); + + DUMP_REG(DSI_TIMEOUT_0); + DUMP_REG(DSI_TIMEOUT_1); + DUMP_REG(DSI_TO_TALLY); + + DUMP_REG(DSI_PAD_CONTROL_0); + DUMP_REG(DSI_PAD_CONTROL_CD); + DUMP_REG(DSI_PAD_CD_STATUS); + DUMP_REG(DSI_VIDEO_MODE_CONTROL); + DUMP_REG(DSI_PAD_CONTROL_1); + DUMP_REG(DSI_PAD_CONTROL_2); + DUMP_REG(DSI_PAD_CONTROL_3); + DUMP_REG(DSI_PAD_CONTROL_4); + + DUMP_REG(DSI_GANGED_MODE_CONTROL); + DUMP_REG(DSI_GANGED_MODE_START); + DUMP_REG(DSI_GANGED_MODE_SIZE); + + DUMP_REG(DSI_RAW_DATA_BYTE_COUNT); + DUMP_REG(DSI_ULTRA_LOW_POWER_CONTROL); + + DUMP_REG(DSI_INIT_SEQ_DATA_8); + DUMP_REG(DSI_INIT_SEQ_DATA_9); + DUMP_REG(DSI_INIT_SEQ_DATA_10); + DUMP_REG(DSI_INIT_SEQ_DATA_11); + DUMP_REG(DSI_INIT_SEQ_DATA_12); + DUMP_REG(DSI_INIT_SEQ_DATA_13); + DUMP_REG(DSI_INIT_SEQ_DATA_14); + DUMP_REG(DSI_INIT_SEQ_DATA_15); + +#undef DUMP_REG + + return 0; +} + +static struct drm_info_list debugfs_files[] = { + { "regs", tegra_dsi_show_regs, 0, NULL }, +}; + +static int tegra_dsi_debugfs_init(struct tegra_dsi *dsi, + struct drm_minor *minor) +{ + const char *name = dev_name(dsi->dev); + unsigned int i; + int err; + + dsi->debugfs = debugfs_create_dir(name, minor->debugfs_root); + if (!dsi->debugfs) + return -ENOMEM; + + dsi->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files), + GFP_KERNEL); + if (!dsi->debugfs_files) { + err = -ENOMEM; + goto remove; + } + + for (i = 0; i < ARRAY_SIZE(debugfs_files); i++) + dsi->debugfs_files[i].data = dsi; + + err = drm_debugfs_create_files(dsi->debugfs_files, + ARRAY_SIZE(debugfs_files), + dsi->debugfs, minor); + if (err < 0) + goto free; + + dsi->minor = minor; + + return 0; + +free: + kfree(dsi->debugfs_files); + dsi->debugfs_files = NULL; +remove: + debugfs_remove(dsi->debugfs); + dsi->debugfs = NULL; + + return err; +} + +static int tegra_dsi_debugfs_exit(struct tegra_dsi *dsi) +{ + drm_debugfs_remove_files(dsi->debugfs_files, ARRAY_SIZE(debugfs_files), + dsi->minor); + dsi->minor = NULL; + + kfree(dsi->debugfs_files); + dsi->debugfs_files = NULL; + + debugfs_remove(dsi->debugfs); + dsi->debugfs = NULL; + + return 0; +} + +#define PKT_ID0(id) ((((id) & 0x3f) << 3) | (1 << 9)) +#define PKT_LEN0(len) (((len) & 0x07) << 0) +#define PKT_ID1(id) ((((id) & 0x3f) << 13) | (1 << 19)) +#define PKT_LEN1(len) (((len) & 0x07) << 10) +#define PKT_ID2(id) ((((id) & 0x3f) << 23) | (1 << 29)) +#define PKT_LEN2(len) (((len) & 0x07) << 20) + +#define PKT_LP (1 << 30) +#define NUM_PKT_SEQ 12 + +/* non-burst mode with sync-end */ +static const u32 pkt_seq_vnb_syne[NUM_PKT_SEQ] = { + [ 0] = PKT_ID0(MIPI_DSI_V_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 1] = 0, + [ 2] = PKT_ID0(MIPI_DSI_V_SYNC_END) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 3] = 0, + [ 4] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [ 7] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), + [ 8] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0) | + PKT_LP, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_H_SYNC_START) | PKT_LEN0(0) | + PKT_ID1(MIPI_DSI_BLANKING_PACKET) | PKT_LEN1(1) | + PKT_ID2(MIPI_DSI_H_SYNC_END) | PKT_LEN2(0), + [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(2) | + PKT_ID1(MIPI_DSI_PACKED_PIXEL_STREAM_24) | PKT_LEN1(3) | + PKT_ID2(MIPI_DSI_BLANKING_PACKET) | PKT_LEN2(4), +}; + +static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) +{ + struct mipi_dphy_timing timing; + unsigned long value, period; + long rate; + int err; + + rate = clk_get_rate(dsi->clk); + if (rate < 0) + return rate; + + period = DIV_ROUND_CLOSEST(1000000000UL, rate * 2); + + err = mipi_dphy_timing_get_default(&timing, period); + if (err < 0) + return err; + + err = mipi_dphy_timing_validate(&timing, period); + if (err < 0) { + dev_err(dsi->dev, "failed to validate D-PHY timing: %d\n", err); + return err; + } + + /* + * The D-PHY timing fields below are expressed in byte-clock cycles, + * so multiply the period by 8. + */ + period *= 8; + + value = DSI_TIMING_FIELD(timing.hsexit, period, 1) << 24 | + DSI_TIMING_FIELD(timing.hstrail, period, 0) << 16 | + DSI_TIMING_FIELD(timing.hszero, period, 3) << 8 | + DSI_TIMING_FIELD(timing.hsprepare, period, 1); + tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_0); + + value = DSI_TIMING_FIELD(timing.clktrail, period, 1) << 24 | + DSI_TIMING_FIELD(timing.clkpost, period, 1) << 16 | + DSI_TIMING_FIELD(timing.clkzero, period, 1) << 8 | + DSI_TIMING_FIELD(timing.lpx, period, 1); + tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_1); + + value = DSI_TIMING_FIELD(timing.clkprepare, period, 1) << 16 | + DSI_TIMING_FIELD(timing.clkpre, period, 1) << 8 | + DSI_TIMING_FIELD(0xff * period, period, 0) << 0; + tegra_dsi_writel(dsi, value, DSI_PHY_TIMING_2); + + value = DSI_TIMING_FIELD(timing.taget, period, 1) << 16 | + DSI_TIMING_FIELD(timing.tasure, period, 1) << 8 | + DSI_TIMING_FIELD(timing.tago, period, 1); + tegra_dsi_writel(dsi, value, DSI_BTA_TIMING); + + return 0; +} + +static int tegra_dsi_get_muldiv(enum mipi_dsi_pixel_format format, + unsigned int *mulp, unsigned int *divp) +{ + switch (format) { + case MIPI_DSI_FMT_RGB666_PACKED: + case MIPI_DSI_FMT_RGB888: + *mulp = 3; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB565: + *mulp = 2; + *divp = 1; + break; + + case MIPI_DSI_FMT_RGB666: + *mulp = 9; + *divp = 4; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_output_dsi_enable(struct tegra_output *output) +{ + struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + struct drm_display_mode *mode = &dc->base.mode; + unsigned int hact, hsw, hbp, hfp, i, mul, div; + struct tegra_dsi *dsi = to_dsi(output); + /* FIXME: don't hardcode this */ + const u32 *pkt_seq = pkt_seq_vnb_syne; + unsigned long value; + int err; + + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); + if (err < 0) + return err; + + err = clk_enable(dsi->clk); + if (err < 0) + return err; + + reset_control_deassert(dsi->rst); + + value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(dsi->format) | + DSI_CONTROL_LANES(dsi->lanes - 1) | + DSI_CONTROL_SOURCE(dc->pipe); + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + tegra_dsi_writel(dsi, DSI_VIDEO_FIFO_DEPTH, DSI_MAX_THRESHOLD); + + value = DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS | + DSI_HOST_CONTROL_ECC; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + + value = tegra_dsi_readl(dsi, DSI_CONTROL); + value |= DSI_CONTROL_HS_CLK_CTRL; + value &= ~DSI_CONTROL_TX_TRIG(3); + value &= ~DSI_CONTROL_DCS_ENABLE; + value |= DSI_CONTROL_VIDEO_ENABLE; + value &= ~DSI_CONTROL_HOST_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + err = tegra_dsi_set_phy_timing(dsi); + if (err < 0) + return err; + + for (i = 0; i < NUM_PKT_SEQ; i++) + tegra_dsi_writel(dsi, pkt_seq[i], DSI_PKT_SEQ_0_LO + i); + + /* horizontal active pixels */ + hact = mode->hdisplay * mul / div; + + /* horizontal sync width */ + hsw = (mode->hsync_end - mode->hsync_start) * mul / div; + hsw -= 10; + + /* horizontal back porch */ + hbp = (mode->htotal - mode->hsync_end) * mul / div; + hbp -= 14; + + /* horizontal front porch */ + hfp = (mode->hsync_start - mode->hdisplay) * mul / div; + hfp -= 8; + + tegra_dsi_writel(dsi, hsw << 16 | 0, DSI_PKT_LEN_0_1); + tegra_dsi_writel(dsi, hact << 16 | hbp, DSI_PKT_LEN_2_3); + tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5); + tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7); + + /* set SOL delay */ + tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + + /* enable display controller */ + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= DSI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_C_DISPLAY; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + + /* enable DSI controller */ + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + return 0; +} + +static int tegra_output_dsi_disable(struct tegra_output *output) +{ + struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + struct tegra_dsi *dsi = to_dsi(output); + unsigned long value; + + /* disable DSI controller */ + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value &= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + /* + * The following accesses registers of the display controller, so make + * sure it's only executed when the output is attached to one. + */ + if (dc) { + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~DSI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + } + + clk_disable(dsi->clk); + + return 0; +} + +static int tegra_output_dsi_setup_clock(struct tegra_output *output, + struct clk *clk, unsigned long pclk) +{ + struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + struct drm_display_mode *mode = &dc->base.mode; + unsigned int timeout, mul, div, vrefresh; + struct tegra_dsi *dsi = to_dsi(output); + unsigned long bclk, plld, value; + struct clk *base; + int err; + + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); + if (err < 0) + return err; + + vrefresh = drm_mode_vrefresh(mode); + + pclk = mode->htotal * mode->vtotal * vrefresh; + bclk = (pclk * mul) / (div * dsi->lanes); + plld = DIV_ROUND_UP(bclk * 8, 1000000); + pclk = (plld * 1000000) / 2; + + err = clk_set_parent(clk, dsi->clk_parent); + if (err < 0) { + dev_err(dsi->dev, "failed to set parent clock: %d\n", err); + return err; + } + + base = clk_get_parent(dsi->clk_parent); + + /* + * This assumes that the parent clock is pll_d_out0 or pll_d2_out + * respectively, each of which divides the base pll_d by 2. + */ + err = clk_set_rate(base, pclk * 2); + if (err < 0) { + dev_err(dsi->dev, "failed to set base clock rate to %lu Hz\n", + pclk * 2); + return err; + } + + /* + * XXX: Move the below somewhere else so that we don't need to have + * access to the vrefresh in this function? + */ + + /* one frame high-speed transmission timeout */ + timeout = (bclk / vrefresh) / 512; + value = DSI_TIMEOUT_LRX(0x2000) | DSI_TIMEOUT_HTX(timeout); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_0); + + /* 2 ms peripheral timeout for panel */ + timeout = 2 * bclk / 512 * 1000; + value = DSI_TIMEOUT_PR(timeout) | DSI_TIMEOUT_TA(0x2000); + tegra_dsi_writel(dsi, value, DSI_TIMEOUT_1); + + value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); + tegra_dsi_writel(dsi, value, DSI_TO_TALLY); + + return 0; +} + +static int tegra_output_dsi_check_mode(struct tegra_output *output, + struct drm_display_mode *mode, + enum drm_mode_status *status) +{ + /* + * FIXME: For now, always assume that the mode is okay. + */ + + *status = MODE_OK; + + return 0; +} + +static const struct tegra_output_ops dsi_ops = { + .enable = tegra_output_dsi_enable, + .disable = tegra_output_dsi_disable, + .setup_clock = tegra_output_dsi_setup_clock, + .check_mode = tegra_output_dsi_check_mode, +}; + +static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) +{ + unsigned long value; + + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); + + return 0; +} + +static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) +{ + unsigned long value; + + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); + + /* start calibration */ + tegra_dsi_pad_enable(dsi); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2); + + return tegra_mipi_calibrate(dsi->mipi); +} + +static int tegra_dsi_init(struct host1x_client *client) +{ + struct tegra_drm *tegra = dev_get_drvdata(client->parent); + struct tegra_dsi *dsi = host1x_client_to_dsi(client); + unsigned long value, i; + int err; + + dsi->output.type = TEGRA_OUTPUT_DSI; + dsi->output.dev = client->dev; + dsi->output.ops = &dsi_ops; + + err = tegra_output_init(tegra->drm, &dsi->output); + if (err < 0) { + dev_err(client->dev, "output setup failed: %d\n", err); + return err; + } + + if (IS_ENABLED(CONFIG_DEBUG_FS)) { + err = tegra_dsi_debugfs_init(dsi, tegra->drm->primary); + if (err < 0) + dev_err(dsi->dev, "debugfs setup failed: %d\n", err); + } + + /* + * enable high-speed mode, checksum generation, ECC generation and + * disable raw mode + */ + value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL); + value |= DSI_HOST_CONTROL_ECC | DSI_HOST_CONTROL_CS | + DSI_HOST_CONTROL_HS; + value &= ~DSI_HOST_CONTROL_RAW; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + + tegra_dsi_writel(dsi, 0, DSI_SOL_DELAY); + tegra_dsi_writel(dsi, 0, DSI_MAX_THRESHOLD); + + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL); + + for (i = 0; i < 8; i++) { + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + i); + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_8 + i); + } + + for (i = 0; i < 12; i++) + tegra_dsi_writel(dsi, 0, DSI_PKT_SEQ_0_LO + i); + + tegra_dsi_writel(dsi, 0, DSI_DCS_CMDS); + + err = tegra_dsi_pad_calibrate(dsi); + if (err < 0) { + dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); + return err; + } + + tegra_dsi_writel(dsi, DSI_POWER_CONTROL_ENABLE, DSI_POWER_CONTROL); + usleep_range(300, 1000); + + return 0; +} + +static int tegra_dsi_exit(struct host1x_client *client) +{ + struct tegra_dsi *dsi = host1x_client_to_dsi(client); + int err; + + if (IS_ENABLED(CONFIG_DEBUG_FS)) { + err = tegra_dsi_debugfs_exit(dsi); + if (err < 0) + dev_err(dsi->dev, "debugfs cleanup failed: %d\n", err); + } + + err = tegra_output_disable(&dsi->output); + if (err < 0) { + dev_err(client->dev, "output failed to disable: %d\n", err); + return err; + } + + err = tegra_output_exit(&dsi->output); + if (err < 0) { + dev_err(client->dev, "output cleanup failed: %d\n", err); + return err; + } + + return 0; +} + +static const struct host1x_client_ops dsi_client_ops = { + .init = tegra_dsi_init, + .exit = tegra_dsi_exit, +}; + +static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) +{ + struct clk *parent; + int err; + + parent = clk_get_parent(dsi->clk); + if (!parent) + return -EINVAL; + + err = clk_set_parent(parent, dsi->clk_parent); + if (err < 0) + return err; + + return 0; +} + +static void tegra_dsi_initialize(struct tegra_dsi *dsi) +{ + unsigned int i; + + tegra_dsi_writel(dsi, 0, DSI_POWER_CONTROL); + + tegra_dsi_writel(dsi, 0, DSI_INT_ENABLE); + tegra_dsi_writel(dsi, 0, DSI_INT_STATUS); + tegra_dsi_writel(dsi, 0, DSI_INT_MASK); + + tegra_dsi_writel(dsi, 0, DSI_HOST_CONTROL); + tegra_dsi_writel(dsi, 0, DSI_CONTROL); + + tegra_dsi_writel(dsi, 0, DSI_SOL_DELAY); + tegra_dsi_writel(dsi, 0, DSI_MAX_THRESHOLD); + + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_CONTROL); + + for (i = 0; i < 8; i++) { + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_0 + i); + tegra_dsi_writel(dsi, 0, DSI_INIT_SEQ_DATA_8 + i); + } + + for (i = 0; i < 12; i++) + tegra_dsi_writel(dsi, 0, DSI_PKT_SEQ_0_LO + i); + + tegra_dsi_writel(dsi, 0, DSI_DCS_CMDS); + + for (i = 0; i < 4; i++) + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1 + i); + + tegra_dsi_writel(dsi, 0x00000000, DSI_PHY_TIMING_0); + tegra_dsi_writel(dsi, 0x00000000, DSI_PHY_TIMING_1); + tegra_dsi_writel(dsi, 0x000000ff, DSI_PHY_TIMING_2); + tegra_dsi_writel(dsi, 0x00000000, DSI_BTA_TIMING); + + tegra_dsi_writel(dsi, 0, DSI_TIMEOUT_0); + tegra_dsi_writel(dsi, 0, DSI_TIMEOUT_1); + tegra_dsi_writel(dsi, 0, DSI_TO_TALLY); + + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_CD); + tegra_dsi_writel(dsi, 0, DSI_PAD_CD_STATUS); + tegra_dsi_writel(dsi, 0, DSI_VIDEO_MODE_CONTROL); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); + + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START); + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE); +} + +static int tegra_dsi_host_attach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct tegra_dsi *dsi = host_to_tegra(host); + struct tegra_output *output = &dsi->output; + + dsi->format = device->format; + dsi->lanes = device->lanes; + + output->panel = of_drm_find_panel(device->dev.of_node); + if (output->panel) { + if (output->connector.dev) + drm_helper_hpd_irq_event(output->connector.dev); + } + + return 0; +} + +static int tegra_dsi_host_detach(struct mipi_dsi_host *host, + struct mipi_dsi_device *device) +{ + struct tegra_dsi *dsi = host_to_tegra(host); + struct tegra_output *output = &dsi->output; + + if (output->panel && &device->dev == output->panel->dev) { + if (output->connector.dev) + drm_helper_hpd_irq_event(output->connector.dev); + + output->panel = NULL; + } + + return 0; +} + +static const struct mipi_dsi_host_ops tegra_dsi_host_ops = { + .attach = tegra_dsi_host_attach, + .detach = tegra_dsi_host_detach, +}; + +static int tegra_dsi_probe(struct platform_device *pdev) +{ + struct tegra_dsi *dsi; + struct resource *regs; + int err; + + dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL); + if (!dsi) + return -ENOMEM; + + dsi->output.dev = dsi->dev = &pdev->dev; + + err = tegra_output_probe(&dsi->output); + if (err < 0) + return err; + + /* + * Assume these values by default. When a DSI peripheral driver + * attaches to the DSI host, the parameters will be taken from + * the attached device. + */ + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->lanes = 4; + + dsi->rst = devm_reset_control_get(&pdev->dev, "dsi"); + if (IS_ERR(dsi->rst)) + return PTR_ERR(dsi->rst); + + dsi->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dsi->clk)) { + dev_err(&pdev->dev, "cannot get DSI clock\n"); + return PTR_ERR(dsi->clk); + } + + err = clk_prepare_enable(dsi->clk); + if (err < 0) { + dev_err(&pdev->dev, "cannot enable DSI clock\n"); + return err; + } + + dsi->clk_lp = devm_clk_get(&pdev->dev, "lp"); + if (IS_ERR(dsi->clk_lp)) { + dev_err(&pdev->dev, "cannot get low-power clock\n"); + return PTR_ERR(dsi->clk_lp); + } + + err = clk_prepare_enable(dsi->clk_lp); + if (err < 0) { + dev_err(&pdev->dev, "cannot enable low-power clock\n"); + return err; + } + + dsi->clk_parent = devm_clk_get(&pdev->dev, "parent"); + if (IS_ERR(dsi->clk_parent)) { + dev_err(&pdev->dev, "cannot get parent clock\n"); + return PTR_ERR(dsi->clk_parent); + } + + err = clk_prepare_enable(dsi->clk_parent); + if (err < 0) { + dev_err(&pdev->dev, "cannot enable parent clock\n"); + return err; + } + + err = tegra_dsi_setup_clocks(dsi); + if (err < 0) { + dev_err(&pdev->dev, "cannot setup clocks\n"); + return err; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dsi->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(dsi->regs)) + return PTR_ERR(dsi->regs); + + tegra_dsi_initialize(dsi); + + dsi->mipi = tegra_mipi_request(&pdev->dev); + if (IS_ERR(dsi->mipi)) + return PTR_ERR(dsi->mipi); + + dsi->host.ops = &tegra_dsi_host_ops; + dsi->host.dev = &pdev->dev; + + err = mipi_dsi_host_register(&dsi->host); + if (err < 0) { + dev_err(&pdev->dev, "failed to register DSI host: %d\n", err); + return err; + } + + INIT_LIST_HEAD(&dsi->client.list); + dsi->client.ops = &dsi_client_ops; + dsi->client.dev = &pdev->dev; + + err = host1x_client_register(&dsi->client); + if (err < 0) { + dev_err(&pdev->dev, "failed to register host1x client: %d\n", + err); + return err; + } + + platform_set_drvdata(pdev, dsi); + + return 0; +} + +static int tegra_dsi_remove(struct platform_device *pdev) +{ + struct tegra_dsi *dsi = platform_get_drvdata(pdev); + int err; + + err = host1x_client_unregister(&dsi->client); + if (err < 0) { + dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", + err); + return err; + } + + mipi_dsi_host_unregister(&dsi->host); + tegra_mipi_free(dsi->mipi); + + clk_disable_unprepare(dsi->clk_parent); + clk_disable_unprepare(dsi->clk_lp); + clk_disable_unprepare(dsi->clk); + + err = tegra_output_remove(&dsi->output); + if (err < 0) { + dev_err(&pdev->dev, "failed to remove output: %d\n", err); + return err; + } + + return 0; +} + +static const struct of_device_id tegra_dsi_of_match[] = { + { .compatible = "nvidia,tegra114-dsi", }, + { }, +}; + +struct platform_driver tegra_dsi_driver = { + .driver = { + .name = "tegra-dsi", + .of_match_table = tegra_dsi_of_match, + }, + .probe = tegra_dsi_probe, + .remove = tegra_dsi_remove, +}; diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h new file mode 100644 index 00000000000..00e79c1f448 --- /dev/null +++ b/drivers/gpu/drm/tegra/dsi.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 NVIDIA Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef DRM_TEGRA_DSI_H +#define DRM_TEGRA_DSI_H + +#define DSI_INCR_SYNCPT 0x00 +#define DSI_INCR_SYNCPT_CONTROL 0x01 +#define DSI_INCR_SYNCPT_ERROR 0x02 +#define DSI_CTXSW 0x08 +#define DSI_RD_DATA 0x09 +#define DSI_WR_DATA 0x0a +#define DSI_POWER_CONTROL 0x0b +#define DSI_POWER_CONTROL_ENABLE (1 << 0) +#define DSI_INT_ENABLE 0x0c +#define DSI_INT_STATUS 0x0d +#define DSI_INT_MASK 0x0e +#define DSI_HOST_CONTROL 0x0f +#define DSI_HOST_CONTROL_RAW (1 << 6) +#define DSI_HOST_CONTROL_HS (1 << 5) +#define DSI_HOST_CONTROL_BTA (1 << 2) +#define DSI_HOST_CONTROL_CS (1 << 1) +#define DSI_HOST_CONTROL_ECC (1 << 0) +#define DSI_CONTROL 0x10 +#define DSI_CONTROL_HS_CLK_CTRL (1 << 20) +#define DSI_CONTROL_CHANNEL(c) (((c) & 0x3) << 16) +#define DSI_CONTROL_FORMAT(f) (((f) & 0x3) << 12) +#define DSI_CONTROL_TX_TRIG(x) (((x) & 0x3) << 8) +#define DSI_CONTROL_LANES(n) (((n) & 0x3) << 4) +#define DSI_CONTROL_DCS_ENABLE (1 << 3) +#define DSI_CONTROL_SOURCE(s) (((s) & 0x1) << 2) +#define DSI_CONTROL_VIDEO_ENABLE (1 << 1) +#define DSI_CONTROL_HOST_ENABLE (1 << 0) +#define DSI_SOL_DELAY 0x11 +#define DSI_MAX_THRESHOLD 0x12 +#define DSI_TRIGGER 0x13 +#define DSI_TX_CRC 0x14 +#define DSI_STATUS 0x15 +#define DSI_STATUS_IDLE (1 << 10) +#define DSI_INIT_SEQ_CONTROL 0x1a +#define DSI_INIT_SEQ_DATA_0 0x1b +#define DSI_INIT_SEQ_DATA_1 0x1c +#define DSI_INIT_SEQ_DATA_2 0x1d +#define DSI_INIT_SEQ_DATA_3 0x1e +#define DSI_INIT_SEQ_DATA_4 0x1f +#define DSI_INIT_SEQ_DATA_5 0x20 +#define DSI_INIT_SEQ_DATA_6 0x21 +#define DSI_INIT_SEQ_DATA_7 0x22 +#define DSI_PKT_SEQ_0_LO 0x23 +#define DSI_PKT_SEQ_0_HI 0x24 +#define DSI_PKT_SEQ_1_LO 0x25 +#define DSI_PKT_SEQ_1_HI 0x26 +#define DSI_PKT_SEQ_2_LO 0x27 +#define DSI_PKT_SEQ_2_HI 0x28 +#define DSI_PKT_SEQ_3_LO 0x29 +#define DSI_PKT_SEQ_3_HI 0x2a +#define DSI_PKT_SEQ_4_LO 0x2b +#define DSI_PKT_SEQ_4_HI 0x2c +#define DSI_PKT_SEQ_5_LO 0x2d +#define DSI_PKT_SEQ_5_HI 0x2e +#define DSI_DCS_CMDS 0x33 +#define DSI_PKT_LEN_0_1 0x34 +#define DSI_PKT_LEN_2_3 0x35 +#define DSI_PKT_LEN_4_5 0x36 +#define DSI_PKT_LEN_6_7 0x37 +#define DSI_PHY_TIMING_0 0x3c +#define DSI_PHY_TIMING_1 0x3d +#define DSI_PHY_TIMING_2 0x3e +#define DSI_BTA_TIMING 0x3f + +#define DSI_TIMING_FIELD(value, period, hwinc) \ + ((DIV_ROUND_CLOSEST(value, period) - (hwinc)) & 0xff) + +#define DSI_TIMEOUT_0 0x44 +#define DSI_TIMEOUT_LRX(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_HTX(x) (((x) & 0xffff) << 0) +#define DSI_TIMEOUT_1 0x45 +#define DSI_TIMEOUT_PR(x) (((x) & 0xffff) << 16) +#define DSI_TIMEOUT_TA(x) (((x) & 0xffff) << 0) +#define DSI_TO_TALLY 0x46 +#define DSI_TALLY_TA(x) (((x) & 0xff) << 16) +#define DSI_TALLY_LRX(x) (((x) & 0xff) << 8) +#define DSI_TALLY_HTX(x) (((x) & 0xff) << 0) +#define DSI_PAD_CONTROL_0 0x4b +#define DSI_PAD_CONTROL_VS1_PDIO(x) (((x) & 0xf) << 0) +#define DSI_PAD_CONTROL_VS1_PDIO_CLK (1 << 8) +#define DSI_PAD_CONTROL_VS1_PULLDN(x) (((x) & 0xf) << 16) +#define DSI_PAD_CONTROL_VS1_PULLDN_CLK (1 << 24) +#define DSI_PAD_CONTROL_CD 0x4c +#define DSI_PAD_CD_STATUS 0x4d +#define DSI_VIDEO_MODE_CONTROL 0x4e +#define DSI_PAD_CONTROL_1 0x4f +#define DSI_PAD_CONTROL_2 0x50 +#define DSI_PAD_OUT_CLK(x) (((x) & 0x7) << 0) +#define DSI_PAD_LP_DN(x) (((x) & 0x7) << 4) +#define DSI_PAD_LP_UP(x) (((x) & 0x7) << 8) +#define DSI_PAD_SLEW_DN(x) (((x) & 0x7) << 12) +#define DSI_PAD_SLEW_UP(x) (((x) & 0x7) << 16) +#define DSI_PAD_CONTROL_3 0x51 +#define DSI_PAD_CONTROL_4 0x52 +#define DSI_GANGED_MODE_CONTROL 0x53 +#define DSI_GANGED_MODE_START 0x54 +#define DSI_GANGED_MODE_SIZE 0x55 +#define DSI_RAW_DATA_BYTE_COUNT 0x56 +#define DSI_ULTRA_LOW_POWER_CONTROL 0x57 +#define DSI_INIT_SEQ_DATA_8 0x58 +#define DSI_INIT_SEQ_DATA_9 0x59 +#define DSI_INIT_SEQ_DATA_10 0x5a +#define DSI_INIT_SEQ_DATA_11 0x5b +#define DSI_INIT_SEQ_DATA_12 0x5c +#define DSI_INIT_SEQ_DATA_13 0x5d +#define DSI_INIT_SEQ_DATA_14 0x5e +#define DSI_INIT_SEQ_DATA_15 0x5f + +#endif diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index a3835e7de18..f7fca09d492 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -18,10 +18,12 @@ static inline struct tegra_fb *to_tegra_fb(struct drm_framebuffer *fb) return container_of(fb, struct tegra_fb, base); } +#ifdef CONFIG_DRM_TEGRA_FBDEV static inline struct tegra_fbdev *to_tegra_fbdev(struct drm_fb_helper *helper) { return container_of(helper, struct tegra_fbdev, base); } +#endif struct tegra_bo *tegra_fb_get_plane(struct drm_framebuffer *framebuffer, unsigned int index) @@ -98,8 +100,10 @@ static struct tegra_fb *tegra_fb_alloc(struct drm_device *drm, return ERR_PTR(-ENOMEM); fb->planes = kzalloc(num_planes * sizeof(*planes), GFP_KERNEL); - if (!fb->planes) + if (!fb->planes) { + kfree(fb); return ERR_PTR(-ENOMEM); + } fb->num_planes = num_planes; @@ -172,6 +176,7 @@ unreference: return ERR_PTR(err); } +#ifdef CONFIG_DRM_TEGRA_FBDEV static struct fb_ops tegra_fb_ops = { .owner = THIS_MODULE, .fb_fillrect = sys_fillrect, @@ -339,6 +344,15 @@ static void tegra_fbdev_free(struct tegra_fbdev *fbdev) kfree(fbdev); } +void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) +{ + if (fbdev) { + drm_modeset_lock_all(fbdev->base.dev); + drm_fb_helper_restore_fbdev_mode(&fbdev->base); + drm_modeset_unlock_all(fbdev->base.dev); + } +} + static void tegra_fb_output_poll_changed(struct drm_device *drm) { struct tegra_drm *tegra = drm->dev_private; @@ -346,16 +360,20 @@ static void tegra_fb_output_poll_changed(struct drm_device *drm) if (tegra->fbdev) drm_fb_helper_hotplug_event(&tegra->fbdev->base); } +#endif static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { .fb_create = tegra_fb_create, +#ifdef CONFIG_DRM_TEGRA_FBDEV .output_poll_changed = tegra_fb_output_poll_changed, +#endif }; int tegra_drm_fb_init(struct drm_device *drm) { +#ifdef CONFIG_DRM_TEGRA_FBDEV struct tegra_drm *tegra = drm->dev_private; - struct tegra_fbdev *fbdev; +#endif drm->mode_config.min_width = 0; drm->mode_config.min_height = 0; @@ -365,28 +383,21 @@ int tegra_drm_fb_init(struct drm_device *drm) drm->mode_config.funcs = &tegra_drm_mode_funcs; - fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc, - drm->mode_config.num_connector); - if (IS_ERR(fbdev)) - return PTR_ERR(fbdev); - - tegra->fbdev = fbdev; +#ifdef CONFIG_DRM_TEGRA_FBDEV + tegra->fbdev = tegra_fbdev_create(drm, 32, drm->mode_config.num_crtc, + drm->mode_config.num_connector); + if (IS_ERR(tegra->fbdev)) + return PTR_ERR(tegra->fbdev); +#endif return 0; } void tegra_drm_fb_exit(struct drm_device *drm) { +#ifdef CONFIG_DRM_TEGRA_FBDEV struct tegra_drm *tegra = drm->dev_private; tegra_fbdev_free(tegra->fbdev); -} - -void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) -{ - if (fbdev) { - drm_modeset_lock_all(fbdev->base.dev); - drm_fb_helper_restore_fbdev_mode(&fbdev->base); - drm_modeset_unlock_all(fbdev->base.dev); - } +#endif } diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 28a9cbc07ab..ef853e55803 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -18,6 +18,7 @@ * GNU General Public License for more details. */ +#include <linux/dma-buf.h> #include <drm/tegra_drm.h> #include "gem.h" @@ -83,7 +84,7 @@ static struct host1x_bo *tegra_bo_get(struct host1x_bo *bo) return bo; } -const struct host1x_bo_ops tegra_bo_ops = { +static const struct host1x_bo_ops tegra_bo_ops = { .get = tegra_bo_get, .put = tegra_bo_put, .pin = tegra_bo_pin, @@ -145,7 +146,6 @@ err_dma: kfree(bo); return ERR_PTR(err); - } struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, @@ -174,13 +174,87 @@ err: return ERR_PTR(ret); } +struct tegra_bo *tegra_bo_import(struct drm_device *drm, struct dma_buf *buf) +{ + struct dma_buf_attachment *attach; + struct tegra_bo *bo; + ssize_t size; + int err; + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (!bo) + return ERR_PTR(-ENOMEM); + + host1x_bo_init(&bo->base, &tegra_bo_ops); + size = round_up(buf->size, PAGE_SIZE); + + err = drm_gem_object_init(drm, &bo->gem, size); + if (err < 0) + goto free; + + err = drm_gem_create_mmap_offset(&bo->gem); + if (err < 0) + goto release; + + attach = dma_buf_attach(buf, drm->dev); + if (IS_ERR(attach)) { + err = PTR_ERR(attach); + goto free_mmap; + } + + get_dma_buf(buf); + + bo->sgt = dma_buf_map_attachment(attach, DMA_TO_DEVICE); + if (!bo->sgt) { + err = -ENOMEM; + goto detach; + } + + if (IS_ERR(bo->sgt)) { + err = PTR_ERR(bo->sgt); + goto detach; + } + + if (bo->sgt->nents > 1) { + err = -EINVAL; + goto detach; + } + + bo->paddr = sg_dma_address(bo->sgt->sgl); + bo->gem.import_attach = attach; + + return bo; + +detach: + if (!IS_ERR_OR_NULL(bo->sgt)) + dma_buf_unmap_attachment(attach, bo->sgt, DMA_TO_DEVICE); + + dma_buf_detach(buf, attach); + dma_buf_put(buf); +free_mmap: + drm_gem_free_mmap_offset(&bo->gem); +release: + drm_gem_object_release(&bo->gem); +free: + kfree(bo); + + return ERR_PTR(err); +} + void tegra_bo_free_object(struct drm_gem_object *gem) { struct tegra_bo *bo = to_tegra_bo(gem); + if (gem->import_attach) { + dma_buf_unmap_attachment(gem->import_attach, bo->sgt, + DMA_TO_DEVICE); + drm_prime_gem_destroy(gem, NULL); + } else { + tegra_bo_destroy(gem->dev, bo); + } + drm_gem_free_mmap_offset(gem); drm_gem_object_release(gem); - tegra_bo_destroy(gem->dev, bo); kfree(bo); } @@ -256,3 +330,106 @@ int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma) return ret; } + +static struct sg_table * +tegra_gem_prime_map_dma_buf(struct dma_buf_attachment *attach, + enum dma_data_direction dir) +{ + struct drm_gem_object *gem = attach->dmabuf->priv; + struct tegra_bo *bo = to_tegra_bo(gem); + struct sg_table *sgt; + + sgt = kmalloc(sizeof(*sgt), GFP_KERNEL); + if (!sgt) + return NULL; + + if (sg_alloc_table(sgt, 1, GFP_KERNEL)) { + kfree(sgt); + return NULL; + } + + sg_dma_address(sgt->sgl) = bo->paddr; + sg_dma_len(sgt->sgl) = gem->size; + + return sgt; +} + +static void tegra_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach, + struct sg_table *sgt, + enum dma_data_direction dir) +{ + sg_free_table(sgt); + kfree(sgt); +} + +static void tegra_gem_prime_release(struct dma_buf *buf) +{ + drm_gem_dmabuf_release(buf); +} + +static void *tegra_gem_prime_kmap_atomic(struct dma_buf *buf, + unsigned long page) +{ + return NULL; +} + +static void tegra_gem_prime_kunmap_atomic(struct dma_buf *buf, + unsigned long page, + void *addr) +{ +} + +static void *tegra_gem_prime_kmap(struct dma_buf *buf, unsigned long page) +{ + return NULL; +} + +static void tegra_gem_prime_kunmap(struct dma_buf *buf, unsigned long page, + void *addr) +{ +} + +static int tegra_gem_prime_mmap(struct dma_buf *buf, struct vm_area_struct *vma) +{ + return -EINVAL; +} + +static const struct dma_buf_ops tegra_gem_prime_dmabuf_ops = { + .map_dma_buf = tegra_gem_prime_map_dma_buf, + .unmap_dma_buf = tegra_gem_prime_unmap_dma_buf, + .release = tegra_gem_prime_release, + .kmap_atomic = tegra_gem_prime_kmap_atomic, + .kunmap_atomic = tegra_gem_prime_kunmap_atomic, + .kmap = tegra_gem_prime_kmap, + .kunmap = tegra_gem_prime_kunmap, + .mmap = tegra_gem_prime_mmap, +}; + +struct dma_buf *tegra_gem_prime_export(struct drm_device *drm, + struct drm_gem_object *gem, + int flags) +{ + return dma_buf_export(gem, &tegra_gem_prime_dmabuf_ops, gem->size, + flags); +} + +struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm, + struct dma_buf *buf) +{ + struct tegra_bo *bo; + + if (buf->ops == &tegra_gem_prime_dmabuf_ops) { + struct drm_gem_object *gem = buf->priv; + + if (gem->dev == drm) { + drm_gem_object_reference(gem); + return gem; + } + } + + bo = tegra_bo_import(drm, buf); + if (IS_ERR(bo)) + return ERR_CAST(bo); + + return &bo->gem; +} diff --git a/drivers/gpu/drm/tegra/gem.h b/drivers/gpu/drm/tegra/gem.h index 7674000bf47..ffd4f792b41 100644 --- a/drivers/gpu/drm/tegra/gem.h +++ b/drivers/gpu/drm/tegra/gem.h @@ -31,6 +31,7 @@ struct tegra_bo { struct drm_gem_object gem; struct host1x_bo base; unsigned long flags; + struct sg_table *sgt; dma_addr_t paddr; void *vaddr; }; @@ -40,8 +41,6 @@ static inline struct tegra_bo *to_tegra_bo(struct drm_gem_object *gem) return container_of(gem, struct tegra_bo, gem); } -extern const struct host1x_bo_ops tegra_bo_ops; - struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, unsigned long flags); struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, @@ -59,4 +58,10 @@ int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma); extern const struct vm_operations_struct tegra_bo_vm_ops; +struct dma_buf *tegra_gem_prime_export(struct drm_device *drm, + struct drm_gem_object *gem, + int flags); +struct drm_gem_object *tegra_gem_prime_import(struct drm_device *drm, + struct dma_buf *buf); + #endif diff --git a/drivers/gpu/drm/tegra/gr3d.c b/drivers/gpu/drm/tegra/gr3d.c index 4cec8f526af..0cbb24b1ae0 100644 --- a/drivers/gpu/drm/tegra/gr3d.c +++ b/drivers/gpu/drm/tegra/gr3d.c @@ -11,6 +11,7 @@ #include <linux/host1x.h> #include <linux/module.h> #include <linux/platform_device.h> +#include <linux/reset.h> #include <linux/tegra-powergate.h> #include "drm.h" @@ -22,6 +23,8 @@ struct gr3d { struct host1x_channel *channel; struct clk *clk_secondary; struct clk *clk; + struct reset_control *rst_secondary; + struct reset_control *rst; DECLARE_BITMAP(addr_regs, GR3D_NUM_REGS); }; @@ -255,15 +258,29 @@ static int gr3d_probe(struct platform_device *pdev) return PTR_ERR(gr3d->clk); } + gr3d->rst = devm_reset_control_get(&pdev->dev, "3d"); + if (IS_ERR(gr3d->rst)) { + dev_err(&pdev->dev, "cannot get reset\n"); + return PTR_ERR(gr3d->rst); + } + if (of_device_is_compatible(np, "nvidia,tegra30-gr3d")) { gr3d->clk_secondary = devm_clk_get(&pdev->dev, "3d2"); if (IS_ERR(gr3d->clk)) { dev_err(&pdev->dev, "cannot get secondary clock\n"); return PTR_ERR(gr3d->clk); } + + gr3d->rst_secondary = devm_reset_control_get(&pdev->dev, + "3d2"); + if (IS_ERR(gr3d->rst_secondary)) { + dev_err(&pdev->dev, "cannot get secondary reset\n"); + return PTR_ERR(gr3d->rst_secondary); + } } - err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk); + err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk, + gr3d->rst); if (err < 0) { dev_err(&pdev->dev, "failed to power up 3D unit\n"); return err; @@ -271,7 +288,8 @@ static int gr3d_probe(struct platform_device *pdev) if (gr3d->clk_secondary) { err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D1, - gr3d->clk_secondary); + gr3d->clk_secondary, + gr3d->rst_secondary); if (err < 0) { dev_err(&pdev->dev, "failed to power up secondary 3D unit\n"); diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 0cd9bc2056e..6928015d11a 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -8,10 +8,10 @@ */ #include <linux/clk.h> -#include <linux/clk/tegra.h> #include <linux/debugfs.h> #include <linux/hdmi.h> #include <linux/regulator/consumer.h> +#include <linux/reset.h> #include "hdmi.h" #include "drm.h" @@ -40,6 +40,7 @@ struct tegra_hdmi { struct host1x_client client; struct tegra_output output; struct device *dev; + bool enabled; struct regulator *vdd; struct regulator *pll; @@ -49,6 +50,7 @@ struct tegra_hdmi { struct clk *clk_parent; struct clk *clk; + struct reset_control *rst; const struct tegra_hdmi_config *config; @@ -378,7 +380,7 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi) if (f > 96000) delta = 2; - else if (f > 480000) + else if (f > 48000) delta = 6; else delta = 9; @@ -698,6 +700,9 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) int retries = 1000; int err; + if (hdmi->enabled) + return 0; + hdmi->dvi = !tegra_output_is_hdmi(output); pclk = mode->clock * 1000; @@ -731,9 +736,9 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) return err; } - tegra_periph_reset_assert(hdmi->clk); + reset_control_assert(hdmi->rst); usleep_range(1000, 2000); - tegra_periph_reset_deassert(hdmi->clk); + reset_control_deassert(hdmi->rst); tegra_dc_writel(dc, VSYNC_H_POSITION(1), DC_DISP_DISP_TIMING_OPTIONS); @@ -838,10 +843,6 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) value |= SOR_CSTM_ROTCLK(2); tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_CSTM); - tegra_dc_writel(dc, DISP_CTRL_MODE_STOP, DC_CMD_DISPLAY_COMMAND); - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); - /* start SOR */ tegra_hdmi_writel(hdmi, SOR_PWR_NORMAL_STATE_PU | @@ -891,31 +892,67 @@ static int tegra_output_hdmi_enable(struct tegra_output *output) HDMI_NV_PDISP_SOR_STATE1); tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_STATE0); - tegra_dc_writel(dc, HDMI_ENABLE, DC_DISP_DISP_WIN_OPTIONS); - - value = PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | - PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= HDMI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - value = DISP_CTRL_MODE_C_DISPLAY; + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_C_DISPLAY; tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); /* TODO: add HDCP support */ + hdmi->enabled = true; + return 0; } static int tegra_output_hdmi_disable(struct tegra_output *output) { + struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct tegra_hdmi *hdmi = to_hdmi(output); + unsigned long value; - tegra_periph_reset_assert(hdmi->clk); + if (!hdmi->enabled) + return 0; + + /* + * The following accesses registers of the display controller, so make + * sure it's only executed when the output is attached to one. + */ + if (dc) { + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL); + value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND); + + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~HDMI_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + } + + reset_control_assert(hdmi->rst); clk_disable(hdmi->clk); regulator_disable(hdmi->pll); + hdmi->enabled = false; + return 0; } @@ -959,7 +996,7 @@ static int tegra_output_hdmi_check_mode(struct tegra_output *output, parent = clk_get_parent(hdmi->clk_parent); err = clk_round_rate(parent, pclk * 4); - if (err < 0) + if (err <= 0) *status = MODE_NOCLOCK; else *status = MODE_OK; @@ -1338,6 +1375,12 @@ static int tegra_hdmi_probe(struct platform_device *pdev) return PTR_ERR(hdmi->clk); } + hdmi->rst = devm_reset_control_get(&pdev->dev, "hdmi"); + if (IS_ERR(hdmi->rst)) { + dev_err(&pdev->dev, "failed to get reset\n"); + return PTR_ERR(hdmi->rst); + } + err = clk_prepare(hdmi->clk); if (err < 0) return err; @@ -1375,9 +1418,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev) return err; regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs) - return -ENXIO; - hdmi->regs = devm_ioremap_resource(&pdev->dev, regs); if (IS_ERR(hdmi->regs)) return PTR_ERR(hdmi->regs); diff --git a/drivers/gpu/drm/tegra/mipi-phy.c b/drivers/gpu/drm/tegra/mipi-phy.c new file mode 100644 index 00000000000..e2c4aedaee7 --- /dev/null +++ b/drivers/gpu/drm/tegra/mipi-phy.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 NVIDIA Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> + +#include "mipi-phy.h" + +/* + * Default D-PHY timings based on MIPI D-PHY specification. Derived from + * the valid ranges specified in Section 5.9 of the D-PHY specification + * with minor adjustments. + */ +int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, + unsigned long period) +{ + timing->clkmiss = 0; + timing->clkpost = 70 + 52 * period; + timing->clkpre = 8; + timing->clkprepare = 65; + timing->clksettle = 95; + timing->clktermen = 0; + timing->clktrail = 80; + timing->clkzero = 260; + timing->dtermen = 0; + timing->eot = 0; + timing->hsexit = 120; + timing->hsprepare = 65 + 5 * period; + timing->hszero = 145 + 5 * period; + timing->hssettle = 85 + 6 * period; + timing->hsskip = 40; + timing->hstrail = max(8 * period, 60 + 4 * period); + timing->init = 100000; + timing->lpx = 60; + timing->taget = 5 * timing->lpx; + timing->tago = 4 * timing->lpx; + timing->tasure = 2 * timing->lpx; + timing->wakeup = 1000000; + + return 0; +} + +/* + * Validate D-PHY timing according to MIPI Alliance Specification for D-PHY, + * Section 5.9 "Global Operation Timing Parameters". + */ +int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, + unsigned long period) +{ + if (timing->clkmiss > 60) + return -EINVAL; + + if (timing->clkpost < (60 + 52 * period)) + return -EINVAL; + + if (timing->clkpre < 8) + return -EINVAL; + + if (timing->clkprepare < 38 || timing->clkprepare > 95) + return -EINVAL; + + if (timing->clksettle < 95 || timing->clksettle > 300) + return -EINVAL; + + if (timing->clktermen > 38) + return -EINVAL; + + if (timing->clktrail < 60) + return -EINVAL; + + if (timing->clkprepare + timing->clkzero < 300) + return -EINVAL; + + if (timing->dtermen > 35 + 4 * period) + return -EINVAL; + + if (timing->eot > 105 + 12 * period) + return -EINVAL; + + if (timing->hsexit < 100) + return -EINVAL; + + if (timing->hsprepare < 40 + 4 * period || + timing->hsprepare > 85 + 6 * period) + return -EINVAL; + + if (timing->hsprepare + timing->hszero < 145 + 10 * period) + return -EINVAL; + + if ((timing->hssettle < 85 + 6 * period) || + (timing->hssettle > 145 + 10 * period)) + return -EINVAL; + + if (timing->hsskip < 40 || timing->hsskip > 55 + 4 * period) + return -EINVAL; + + if (timing->hstrail < max(8 * period, 60 + 4 * period)) + return -EINVAL; + + if (timing->init < 100000) + return -EINVAL; + + if (timing->lpx < 50) + return -EINVAL; + + if (timing->taget != 5 * timing->lpx) + return -EINVAL; + + if (timing->tago != 4 * timing->lpx) + return -EINVAL; + + if (timing->tasure < timing->lpx || timing->tasure > 2 * timing->lpx) + return -EINVAL; + + if (timing->wakeup < 1000000) + return -EINVAL; + + return 0; +} diff --git a/drivers/gpu/drm/tegra/mipi-phy.h b/drivers/gpu/drm/tegra/mipi-phy.h new file mode 100644 index 00000000000..d3591694432 --- /dev/null +++ b/drivers/gpu/drm/tegra/mipi-phy.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 NVIDIA Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#ifndef DRM_TEGRA_MIPI_PHY_H +#define DRM_TEGRA_MIPI_PHY_H + +/* + * D-PHY timing parameters + * + * A detailed description of these parameters can be found in the MIPI + * Alliance Specification for D-PHY, Section 5.9 "Global Operation Timing + * Parameters". + * + * All parameters are specified in nanoseconds. + */ +struct mipi_dphy_timing { + unsigned int clkmiss; + unsigned int clkpost; + unsigned int clkpre; + unsigned int clkprepare; + unsigned int clksettle; + unsigned int clktermen; + unsigned int clktrail; + unsigned int clkzero; + unsigned int dtermen; + unsigned int eot; + unsigned int hsexit; + unsigned int hsprepare; + unsigned int hszero; + unsigned int hssettle; + unsigned int hsskip; + unsigned int hstrail; + unsigned int init; + unsigned int lpx; + unsigned int taget; + unsigned int tago; + unsigned int tasure; + unsigned int wakeup; +}; + +int mipi_dphy_timing_get_default(struct mipi_dphy_timing *timing, + unsigned long period); +int mipi_dphy_timing_validate(struct mipi_dphy_timing *timing, + unsigned long period); + +#endif diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 2cb0065e057..57cecbd18ca 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -9,6 +9,7 @@ #include <linux/of_gpio.h> +#include <drm/drm_panel.h> #include "drm.h" static int tegra_connector_get_modes(struct drm_connector *connector) @@ -17,6 +18,16 @@ static int tegra_connector_get_modes(struct drm_connector *connector) struct edid *edid = NULL; int err = 0; + /* + * If the panel provides one or more modes, use them exclusively and + * ignore any other means of obtaining a mode. + */ + if (output->panel) { + err = output->panel->funcs->get_modes(output->panel); + if (err > 0) + return err; + } + if (output->edid) edid = kmemdup(output->edid, sizeof(*edid), GFP_KERNEL); else if (output->ddc) @@ -72,6 +83,11 @@ tegra_connector_detect(struct drm_connector *connector, bool force) else status = connector_status_connected; } else { + if (!output->panel) + status = connector_status_disconnected; + else + status = connector_status_connected; + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) status = connector_status_connected; } @@ -115,6 +131,16 @@ static const struct drm_encoder_funcs encoder_funcs = { static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode) { + struct tegra_output *output = encoder_to_output(encoder); + struct drm_panel *panel = output->panel; + + if (mode != DRM_MODE_DPMS_ON) { + drm_panel_disable(panel); + tegra_output_disable(output); + } else { + tegra_output_enable(output); + drm_panel_enable(panel); + } } static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder, @@ -163,14 +189,22 @@ static irqreturn_t hpd_irq(int irq, void *data) int tegra_output_probe(struct tegra_output *output) { + struct device_node *ddc, *panel; enum of_gpio_flags flags; - struct device_node *ddc; - size_t size; - int err; + int err, size; if (!output->of_node) output->of_node = output->dev->of_node; + panel = of_parse_phandle(output->of_node, "nvidia,panel", 0); + if (panel) { + output->panel = of_drm_find_panel(panel); + if (!output->panel) + return -EPROBE_DEFER; + + of_node_put(panel); + } + output->edid = of_get_property(output->of_node, "nvidia,edid", &size); ddc = of_parse_phandle(output->of_node, "nvidia,ddc-i2c-bus", 0); @@ -185,9 +219,6 @@ int tegra_output_probe(struct tegra_output *output) of_node_put(ddc); } - if (!output->edid && !output->ddc) - return -ENODEV; - output->hpd_gpio = of_get_named_gpio_flags(output->of_node, "nvidia,hpd-gpio", 0, &flags); @@ -256,6 +287,11 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) encoder = DRM_MODE_ENCODER_TMDS; break; + case TEGRA_OUTPUT_DSI: + connector = DRM_MODE_CONNECTOR_DSI; + encoder = DRM_MODE_ENCODER_DSI; + break; + default: connector = DRM_MODE_CONNECTOR_Unknown; encoder = DRM_MODE_ENCODER_NONE; @@ -267,6 +303,9 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) drm_connector_helper_add(&output->connector, &connector_helper_funcs); output->connector.dpms = DRM_MODE_DPMS_OFF; + if (output->panel) + drm_panel_attach(output->panel, &output->connector); + drm_encoder_init(drm, &output->encoder, &encoder_funcs, encoder); drm_encoder_helper_add(&output->encoder, &encoder_helper_funcs); diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c index 3b29018913a..338f7f6561d 100644 --- a/drivers/gpu/drm/tegra/rgb.c +++ b/drivers/gpu/drm/tegra/rgb.c @@ -87,15 +87,60 @@ static void tegra_dc_write_regs(struct tegra_dc *dc, static int tegra_output_rgb_enable(struct tegra_output *output) { struct tegra_rgb *rgb = to_rgb(output); + unsigned long value; tegra_dc_write_regs(rgb->dc, rgb_enable, ARRAY_SIZE(rgb_enable)); + value = DE_SELECT_ACTIVE | DE_CONTROL_NORMAL; + tegra_dc_writel(rgb->dc, value, DC_DISP_DATA_ENABLE_OPTIONS); + + /* XXX: parameterize? */ + value = tegra_dc_readl(rgb->dc, DC_COM_PIN_OUTPUT_POLARITY(1)); + value &= ~LVS_OUTPUT_POLARITY_LOW; + value &= ~LHS_OUTPUT_POLARITY_LOW; + tegra_dc_writel(rgb->dc, value, DC_COM_PIN_OUTPUT_POLARITY(1)); + + /* XXX: parameterize? */ + value = DISP_DATA_FORMAT_DF1P1C | DISP_ALIGNMENT_MSB | + DISP_ORDER_RED_BLUE; + tegra_dc_writel(rgb->dc, value, DC_DISP_DISP_INTERFACE_CONTROL); + + /* XXX: parameterize? */ + value = SC0_H_QUALIFIER_NONE | SC1_H_QUALIFIER_NONE; + tegra_dc_writel(rgb->dc, value, DC_DISP_SHIFT_CLOCK_OPTIONS); + + value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + value |= DISP_CTRL_MODE_C_DISPLAY; + tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND); + + value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL); + value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE; + tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + return 0; } static int tegra_output_rgb_disable(struct tegra_output *output) { struct tegra_rgb *rgb = to_rgb(output); + unsigned long value; + + value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_POWER_CONTROL); + value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE | + PW4_ENABLE | PM0_ENABLE | PM1_ENABLE); + tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_POWER_CONTROL); + + value = tegra_dc_readl(rgb->dc, DC_CMD_DISPLAY_COMMAND); + value &= ~DISP_CTRL_MODE_MASK; + tegra_dc_writel(rgb->dc, value, DC_CMD_DISPLAY_COMMAND); + + tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(rgb->dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); tegra_dc_write_regs(rgb->dc, rgb_disable, ARRAY_SIZE(rgb_disable)); @@ -213,7 +258,7 @@ int tegra_dc_rgb_init(struct drm_device *drm, struct tegra_dc *dc) * RGB outputs are an exception, so we make sure they can be attached * to only their parent display controller. */ - rgb->output.encoder.possible_crtcs = 1 << dc->pipe; + rgb->output.encoder.possible_crtcs = drm_crtc_mask(&dc->base); return 0; } |