From db4fd5197b8c7223af9be8cd3f8d8b7239e5d06c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 16 Oct 2014 15:40:58 +0200 Subject: [PATCH 01/44] drm/tegra: Depend on COMMON_CLK The introduction of the COMPILE_TEST dependency in commit 158b50aefa14 (drm/tegra: Increase compile test coverage) removes the dependency on COMMON_CLK (implicitly selected via ARCH_TEGRA, ARCH_MULTI_V7 and ARCH_MULTIPLATFORM). Reported-by: Russell King Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig index 354ddb29231f..74d9d621453d 100644 --- a/drivers/gpu/drm/tegra/Kconfig +++ b/drivers/gpu/drm/tegra/Kconfig @@ -1,6 +1,7 @@ config DRM_TEGRA tristate "NVIDIA Tegra DRM" depends on ARCH_TEGRA || (ARM && COMPILE_TEST) + depends on COMMON_CLK depends on DRM depends on RESET_CONTROLLER select DRM_KMS_HELPER From b40d02bf96e05851d1d16b4a75f66a9e16cfb2fb Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 12 Jun 2014 13:14:50 +0200 Subject: [PATCH 02/44] gpu: host1x: Use struct host1x_bo pointers in traces Rather than cast to a u32 use the struct host1x_bo pointers directly. This avoid annoying warnings for 64-bit builds. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/hw/channel_hw.c | 12 ++++++++---- include/trace/events/host1x.h | 27 +++++++++++++++------------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index 4608257ab656..946c332c3906 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -32,6 +32,7 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, u32 offset, u32 words) { + struct device *dev = cdma_to_channel(cdma)->dev; void *mem = NULL; if (host1x_debug_trace_cmdbuf) @@ -44,11 +45,14 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, * of how much you can output to ftrace at once. */ for (i = 0; i < words; i += TRACE_MAX_LENGTH) { - trace_host1x_cdma_push_gather( - dev_name(cdma_to_channel(cdma)->dev), - (u32)bo, min(words - i, TRACE_MAX_LENGTH), - offset + i * sizeof(u32), mem); + u32 num_words = min(words - i, TRACE_MAX_LENGTH); + offset += i * sizeof(u32); + + trace_host1x_cdma_push_gather(dev_name(dev), bo, + num_words, offset, + mem); } + host1x_bo_munmap(bo, mem); } } diff --git a/include/trace/events/host1x.h b/include/trace/events/host1x.h index 94db6a2c3540..63116362543c 100644 --- a/include/trace/events/host1x.h +++ b/include/trace/events/host1x.h @@ -29,6 +29,8 @@ #include #include +struct host1x_bo; + DECLARE_EVENT_CLASS(host1x, TP_PROTO(const char *name), TP_ARGS(name), @@ -79,14 +81,14 @@ TRACE_EVENT(host1x_cdma_push, ); TRACE_EVENT(host1x_cdma_push_gather, - TP_PROTO(const char *name, u32 mem_id, + TP_PROTO(const char *name, struct host1x_bo *bo, u32 words, u32 offset, void *cmdbuf), - TP_ARGS(name, mem_id, words, offset, cmdbuf), + TP_ARGS(name, bo, words, offset, cmdbuf), TP_STRUCT__entry( __field(const char *, name) - __field(u32, mem_id) + __field(struct host1x_bo *, bo) __field(u32, words) __field(u32, offset) __field(bool, cmdbuf) @@ -100,13 +102,13 @@ TRACE_EVENT(host1x_cdma_push_gather, } __entry->cmdbuf = cmdbuf; __entry->name = name; - __entry->mem_id = mem_id; + __entry->bo = bo; __entry->words = words; __entry->offset = offset; ), - TP_printk("name=%s, mem_id=%08x, words=%u, offset=%d, contents=[%s]", - __entry->name, __entry->mem_id, + TP_printk("name=%s, bo=%p, words=%u, offset=%d, contents=[%s]", + __entry->name, __entry->bo, __entry->words, __entry->offset, __print_hex(__get_dynamic_array(cmdbuf), __entry->cmdbuf ? __entry->words * 4 : 0)) @@ -221,12 +223,13 @@ TRACE_EVENT(host1x_syncpt_load_min, ); TRACE_EVENT(host1x_syncpt_wait_check, - TP_PROTO(void *mem_id, u32 offset, u32 syncpt_id, u32 thresh, u32 min), + TP_PROTO(struct host1x_bo *bo, u32 offset, u32 syncpt_id, u32 thresh, + u32 min), - TP_ARGS(mem_id, offset, syncpt_id, thresh, min), + TP_ARGS(bo, offset, syncpt_id, thresh, min), TP_STRUCT__entry( - __field(void *, mem_id) + __field(struct host1x_bo *, bo) __field(u32, offset) __field(u32, syncpt_id) __field(u32, thresh) @@ -234,15 +237,15 @@ TRACE_EVENT(host1x_syncpt_wait_check, ), TP_fast_assign( - __entry->mem_id = mem_id; + __entry->bo = bo; __entry->offset = offset; __entry->syncpt_id = syncpt_id; __entry->thresh = thresh; __entry->min = min; ), - TP_printk("mem_id=%p, offset=%05x, id=%d, thresh=%d, current=%d", - __entry->mem_id, __entry->offset, + TP_printk("bo=%p, offset=%05x, id=%d, thresh=%d, current=%d", + __entry->bo, __entry->offset, __entry->syncpt_id, __entry->thresh, __entry->min) ); From 0169b93f4492c74d046b456d93b37bd7b55ecd42 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 12 Jun 2014 13:16:54 +0200 Subject: [PATCH 03/44] gpu: host1x: Make mapped field of push buffers void * This reduces the amount of casting that needs to be done to get rid of annoying warnings on 64-bit builds. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/cdma.c | 2 +- drivers/gpu/host1x/cdma.h | 2 +- drivers/gpu/host1x/hw/cdma_hw.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index 3995255b16c7..5a8c8d55317a 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -97,7 +97,7 @@ static int host1x_pushbuffer_init(struct push_buffer *pb) static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2) { u32 pos = pb->pos; - u32 *p = (u32 *)((u32)pb->mapped + pos); + u32 *p = (u32 *)((void *)pb->mapped + pos); WARN_ON(pos == pb->fence); *(p++) = op1; *(p++) = op2; diff --git a/drivers/gpu/host1x/cdma.h b/drivers/gpu/host1x/cdma.h index 313c4b784348..470087af8fe5 100644 --- a/drivers/gpu/host1x/cdma.h +++ b/drivers/gpu/host1x/cdma.h @@ -42,7 +42,7 @@ struct host1x_job; */ struct push_buffer { - u32 *mapped; /* mapped pushbuffer memory */ + void *mapped; /* mapped pushbuffer memory */ dma_addr_t phys; /* physical address of pushbuffer */ u32 fence; /* index we've written */ u32 pos; /* index to write to */ diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 6b09b71940c2..a1197c40a454 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -30,7 +30,7 @@ */ static void push_buffer_init(struct push_buffer *pb) { - *(pb->mapped + (pb->size_bytes >> 2)) = host1x_opcode_restart(0); + *(u32 *)(pb->mapped + pb->size_bytes) = host1x_opcode_restart(0); } /* @@ -51,7 +51,7 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr, /* NOP all the PB slots */ while (nr_slots--) { - u32 *p = (u32 *)((u32)pb->mapped + getptr); + u32 *p = (u32 *)(pb->mapped + getptr); *(p++) = HOST1X_OPCODE_NOP; *(p++) = HOST1X_OPCODE_NOP; dev_dbg(host1x->dev, "%s: NOP at %#llx\n", __func__, From 7f27d60b28f6dbc9762407aac596b53f2a71a867 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 12 Jun 2014 13:17:48 +0200 Subject: [PATCH 04/44] gpu: host1x: Fix typo in comment Signed-off-by: Thierry Reding --- drivers/gpu/host1x/hw/cdma_hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index a1197c40a454..072d8209c06c 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -26,7 +26,7 @@ #include "../debug.h" /* - * Put the restart at the end of pushbuffer memor + * Put the restart at the end of pushbuffer memory */ static void push_buffer_init(struct push_buffer *pb) { From ba73fbc2ca221e80c6733e68496ad07bc3b58ff8 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 12 Jun 2014 13:24:17 +0200 Subject: [PATCH 05/44] gpu: host1x: Print address/offset pairs consistently Consistently use a format of %pad+%#x to print address/offset in debug messages. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/hw/cdma_hw.c | 4 ++-- drivers/gpu/host1x/hw/debug_hw.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 072d8209c06c..305ea8f3382d 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -54,8 +54,8 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr, u32 *p = (u32 *)(pb->mapped + getptr); *(p++) = HOST1X_OPCODE_NOP; *(p++) = HOST1X_OPCODE_NOP; - dev_dbg(host1x->dev, "%s: NOP at %#llx\n", __func__, - (u64)pb->phys + getptr); + dev_dbg(host1x->dev, "%s: NOP at %pad+%#x\n", __func__, + &pb->phys, getptr); getptr = (getptr + 8) & (pb->size_bytes - 1); } wmb(); diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c index f72c873eff81..791de9351eeb 100644 --- a/drivers/gpu/host1x/hw/debug_hw.c +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -163,8 +163,8 @@ static void show_channel_gathers(struct output *o, struct host1x_cdma *cdma) continue; } - host1x_debug_output(o, " GATHER at %#llx+%04x, %d words\n", - (u64)g->base, g->offset, g->words); + host1x_debug_output(o, " GATHER at %pad+%#x, %d words\n", + &g->base, g->offset, g->words); show_gather(o, g->base + g->offset, g->words, cdma, g->base, mapped); From 3880e95f2706e4ad9ba37e382e7f5bb82f911c68 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 12 Jun 2014 13:26:19 +0200 Subject: [PATCH 06/44] gpu: host1x: Make gather offsets unsigned Use the u32 type for the offset in the host1x_job_gather structure for consistentcy with other structures. Negative offsets don't make sense in this context. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/job.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/job.h b/drivers/gpu/host1x/job.h index 33a697d6dcef..8b3c15df0660 100644 --- a/drivers/gpu/host1x/job.h +++ b/drivers/gpu/host1x/job.h @@ -23,7 +23,7 @@ struct host1x_job_gather { u32 words; dma_addr_t base; struct host1x_bo *bo; - int offset; + u32 offset; bool handled; }; From 57b17ae71f412b870415b698655f00846e34ce0a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 2 Oct 2014 14:33:31 +0200 Subject: [PATCH 07/44] gpu: host1x: mipi: Registers are 32 bits wide On 64-bit platforms an unsigned long would be 64 bit and cause unnecessary casting when being passed to writel() or returned from readl(). Make register values 32 bits wide to avoid that. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/mipi.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/host1x/mipi.c b/drivers/gpu/host1x/mipi.c index 9882ea122024..a3e215a0116f 100644 --- a/drivers/gpu/host1x/mipi.c +++ b/drivers/gpu/host1x/mipi.c @@ -90,16 +90,16 @@ struct tegra_mipi_device { unsigned long pads; }; -static inline unsigned long tegra_mipi_readl(struct tegra_mipi *mipi, - unsigned long reg) +static inline u32 tegra_mipi_readl(struct tegra_mipi *mipi, + unsigned long offset) { - return readl(mipi->regs + (reg << 2)); + return readl(mipi->regs + (offset << 2)); } -static inline void tegra_mipi_writel(struct tegra_mipi *mipi, - unsigned long value, unsigned long reg) +static inline void tegra_mipi_writel(struct tegra_mipi *mipi, u32 value, + unsigned long offset) { - writel(value, mipi->regs + (reg << 2)); + writel(value, mipi->regs + (offset << 2)); } struct tegra_mipi_device *tegra_mipi_request(struct device *device) @@ -161,7 +161,7 @@ EXPORT_SYMBOL(tegra_mipi_free); static int tegra_mipi_wait(struct tegra_mipi *mipi) { unsigned long timeout = jiffies + msecs_to_jiffies(250); - unsigned long value; + u32 value; while (time_before(jiffies, timeout)) { value = tegra_mipi_readl(mipi, MIPI_CAL_STATUS); @@ -177,8 +177,8 @@ static int tegra_mipi_wait(struct tegra_mipi *mipi) int tegra_mipi_calibrate(struct tegra_mipi_device *device) { - unsigned long value; unsigned int i; + u32 value; int err; err = clk_enable(device->mipi->clk); From 26f7a92a3a275cad7b0f39063e8cd92e002aff1a Mon Sep 17 00:00:00 2001 From: Sean Paul Date: Wed, 10 Sep 2014 10:52:03 -0400 Subject: [PATCH 08/44] gpu: host1x: mipi: Preserve the contents of MIPI_CAL_CTRL By paving the CTRL reg value, the current code changes MIPI_CAL_PRESCALE ("Auto-cal calibration step prescale") from 1us to 0.1us (val=0). In the description for PHY's noise filter (MIPI_CAL_NOISE_FLT), the TRM states that if the value of the prescale is 0 (or 0.1us), the filter should be set between 2-5. However, the current code sets it to 0. For now, let's keep the prescale and filter values as-is, which is most likely the power-on-reset values of 0x2 and 0xa, respectively. Signed-off-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/host1x/mipi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/host1x/mipi.c b/drivers/gpu/host1x/mipi.c index a3e215a0116f..0d8c3ae8db6a 100644 --- a/drivers/gpu/host1x/mipi.c +++ b/drivers/gpu/host1x/mipi.c @@ -208,7 +208,9 @@ int tegra_mipi_calibrate(struct tegra_mipi_device *device) tegra_mipi_writel(device->mipi, value, modules[i].reg); } - tegra_mipi_writel(device->mipi, MIPI_CAL_CTRL_START, MIPI_CAL_CTRL); + value = tegra_mipi_readl(device->mipi, MIPI_CAL_CTRL); + value |= MIPI_CAL_CTRL_START; + tegra_mipi_writel(device->mipi, value, MIPI_CAL_CTRL); err = tegra_mipi_wait(device->mipi); From 08a15cc34ddf7b7247122de44687364bcd82c2bf Mon Sep 17 00:00:00 2001 From: Sean Paul Date: Wed, 10 Sep 2014 10:52:04 -0400 Subject: [PATCH 09/44] gpu: host1x: mipi: Calibrate clock lanes Include the clock lanes when calibrating the MIPI PHY on Tegra124 compatible devices. Signed-off-by: Sean Paul [treding@nvidia.com: bikeshedding] Signed-off-by: Thierry Reding --- drivers/gpu/host1x/mipi.c | 124 ++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/host1x/mipi.c b/drivers/gpu/host1x/mipi.c index 0d8c3ae8db6a..22e20c2399c5 100644 --- a/drivers/gpu/host1x/mipi.c +++ b/drivers/gpu/host1x/mipi.c @@ -49,11 +49,24 @@ #define MIPI_CAL_CONFIG_DSIC 0x10 #define MIPI_CAL_CONFIG_DSID 0x11 +#define MIPI_CAL_CONFIG_DSIAB_CLK 0x19 +#define MIPI_CAL_CONFIG_DSICD_CLK 0x1a +#define MIPI_CAL_CONFIG_CSIAB_CLK 0x1b +#define MIPI_CAL_CONFIG_CSICD_CLK 0x1c +#define MIPI_CAL_CONFIG_CSIE_CLK 0x1d + +/* for data and clock lanes */ #define MIPI_CAL_CONFIG_SELECT (1 << 21) + +/* for data lanes */ #define MIPI_CAL_CONFIG_HSPDOS(x) (((x) & 0x1f) << 16) #define MIPI_CAL_CONFIG_HSPUOS(x) (((x) & 0x1f) << 8) #define MIPI_CAL_CONFIG_TERMOS(x) (((x) & 0x1f) << 0) +/* for clock lanes */ +#define MIPI_CAL_CONFIG_HSCLKPDOSD(x) (((x) & 0x1f) << 8) +#define MIPI_CAL_CONFIG_HSCLKPUOSD(x) (((x) & 0x1f) << 0) + #define MIPI_CAL_BIAS_PAD_CFG0 0x16 #define MIPI_CAL_BIAS_PAD_PDVCLAMP (1 << 1) #define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF (1 << 0) @@ -63,21 +76,19 @@ #define MIPI_CAL_BIAS_PAD_CFG2 0x18 #define MIPI_CAL_BIAS_PAD_PDVREG (1 << 1) -static const struct module { - unsigned long reg; -} modules[] = { - { .reg = MIPI_CAL_CONFIG_CSIA }, - { .reg = MIPI_CAL_CONFIG_CSIB }, - { .reg = MIPI_CAL_CONFIG_CSIC }, - { .reg = MIPI_CAL_CONFIG_CSID }, - { .reg = MIPI_CAL_CONFIG_CSIE }, - { .reg = MIPI_CAL_CONFIG_DSIA }, - { .reg = MIPI_CAL_CONFIG_DSIB }, - { .reg = MIPI_CAL_CONFIG_DSIC }, - { .reg = MIPI_CAL_CONFIG_DSID }, +struct tegra_mipi_pad { + unsigned long data; + unsigned long clk; +}; + +struct tegra_mipi_soc { + bool has_clk_lane; + const struct tegra_mipi_pad *pads; + unsigned int num_pads; }; struct tegra_mipi { + const struct tegra_mipi_soc *soc; void __iomem *regs; struct mutex lock; struct clk *clk; @@ -117,36 +128,35 @@ struct tegra_mipi_device *tegra_mipi_request(struct device *device) dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { - of_node_put(args.np); err = -ENOMEM; goto out; } dev->pdev = of_find_device_by_node(args.np); if (!dev->pdev) { - of_node_put(args.np); err = -ENODEV; goto free; } - of_node_put(args.np); - dev->mipi = platform_get_drvdata(dev->pdev); if (!dev->mipi) { err = -EPROBE_DEFER; - goto pdev_put; + goto put; } + of_node_put(args.np); + dev->pads = args.args[0]; dev->device = device; return dev; -pdev_put: +put: platform_device_put(dev->pdev); free: kfree(dev); out: + of_node_put(args.np); return ERR_PTR(err); } EXPORT_SYMBOL(tegra_mipi_request); @@ -177,6 +187,7 @@ static int tegra_mipi_wait(struct tegra_mipi *mipi) int tegra_mipi_calibrate(struct tegra_mipi_device *device) { + const struct tegra_mipi_soc *soc = device->mipi->soc; unsigned int i; u32 value; int err; @@ -196,16 +207,23 @@ int tegra_mipi_calibrate(struct tegra_mipi_device *device) value &= ~MIPI_CAL_BIAS_PAD_PDVREG; tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG2); - for (i = 0; i < ARRAY_SIZE(modules); i++) { - if (device->pads & BIT(i)) - value = MIPI_CAL_CONFIG_SELECT | - MIPI_CAL_CONFIG_HSPDOS(0) | - MIPI_CAL_CONFIG_HSPUOS(4) | - MIPI_CAL_CONFIG_TERMOS(5); - else - value = 0; + for (i = 0; i < soc->num_pads; i++) { + u32 clk = 0, data = 0; - tegra_mipi_writel(device->mipi, value, modules[i].reg); + if (device->pads & BIT(i)) { + data = MIPI_CAL_CONFIG_SELECT | + MIPI_CAL_CONFIG_HSPDOS(0) | + MIPI_CAL_CONFIG_HSPUOS(4) | + MIPI_CAL_CONFIG_TERMOS(5); + clk = MIPI_CAL_CONFIG_SELECT | + MIPI_CAL_CONFIG_HSCLKPDOSD(0) | + MIPI_CAL_CONFIG_HSCLKPUOSD(4); + } + + tegra_mipi_writel(device->mipi, data, soc->pads[i].data); + + if (soc->has_clk_lane) + tegra_mipi_writel(device->mipi, clk, soc->pads[i].clk); } value = tegra_mipi_readl(device->mipi, MIPI_CAL_CTRL); @@ -221,16 +239,63 @@ int tegra_mipi_calibrate(struct tegra_mipi_device *device) } EXPORT_SYMBOL(tegra_mipi_calibrate); +static const struct tegra_mipi_pad tegra114_mipi_pads[] = { + { .data = MIPI_CAL_CONFIG_CSIA }, + { .data = MIPI_CAL_CONFIG_CSIB }, + { .data = MIPI_CAL_CONFIG_CSIC }, + { .data = MIPI_CAL_CONFIG_CSID }, + { .data = MIPI_CAL_CONFIG_CSIE }, + { .data = MIPI_CAL_CONFIG_DSIA }, + { .data = MIPI_CAL_CONFIG_DSIB }, + { .data = MIPI_CAL_CONFIG_DSIC }, + { .data = MIPI_CAL_CONFIG_DSID }, +}; + +static const struct tegra_mipi_soc tegra114_mipi_soc = { + .has_clk_lane = false, + .pads = tegra114_mipi_pads, + .num_pads = ARRAY_SIZE(tegra114_mipi_pads), +}; + +static const struct tegra_mipi_pad tegra124_mipi_pads[] = { + { .data = MIPI_CAL_CONFIG_CSIA, .clk = MIPI_CAL_CONFIG_CSIAB_CLK }, + { .data = MIPI_CAL_CONFIG_CSIB, .clk = MIPI_CAL_CONFIG_CSIAB_CLK }, + { .data = MIPI_CAL_CONFIG_CSIC, .clk = MIPI_CAL_CONFIG_CSICD_CLK }, + { .data = MIPI_CAL_CONFIG_CSID, .clk = MIPI_CAL_CONFIG_CSICD_CLK }, + { .data = MIPI_CAL_CONFIG_CSIE, .clk = MIPI_CAL_CONFIG_CSIE_CLK }, + { .data = MIPI_CAL_CONFIG_DSIA, .clk = MIPI_CAL_CONFIG_DSIAB_CLK }, + { .data = MIPI_CAL_CONFIG_DSIB, .clk = MIPI_CAL_CONFIG_DSIAB_CLK }, +}; + +static const struct tegra_mipi_soc tegra124_mipi_soc = { + .has_clk_lane = true, + .pads = tegra124_mipi_pads, + .num_pads = ARRAY_SIZE(tegra124_mipi_pads), +}; + +static struct of_device_id tegra_mipi_of_match[] = { + { .compatible = "nvidia,tegra114-mipi", .data = &tegra114_mipi_soc }, + { .compatible = "nvidia,tegra124-mipi", .data = &tegra124_mipi_soc }, + { }, +}; + static int tegra_mipi_probe(struct platform_device *pdev) { + const struct of_device_id *match; struct tegra_mipi *mipi; struct resource *res; int err; + match = of_match_node(tegra_mipi_of_match, pdev->dev.of_node); + if (!match) + return -ENODEV; + mipi = devm_kzalloc(&pdev->dev, sizeof(*mipi), GFP_KERNEL); if (!mipi) return -ENOMEM; + mipi->soc = match->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); mipi->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(mipi->regs)) @@ -262,11 +327,6 @@ static int tegra_mipi_remove(struct platform_device *pdev) return 0; } -static struct of_device_id tegra_mipi_of_match[] = { - { .compatible = "nvidia,tegra114-mipi", }, - { }, -}; - struct platform_driver tegra_mipi_driver = { .driver = { .name = "tegra-mipi", From b298e98ef6ab9c4279b427db717a1624ef722751 Mon Sep 17 00:00:00 2001 From: Sean Paul Date: Wed, 10 Sep 2014 10:52:05 -0400 Subject: [PATCH 10/44] gpu: host1x: mipi: Set MIPI_CAL_BIAS_PAD_CFG1 register During calibration, sets the "internal reference level for drive pull- down" to the value specified in the Tegra TRM. Signed-off-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/host1x/mipi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/host1x/mipi.c b/drivers/gpu/host1x/mipi.c index 22e20c2399c5..fbc6ee6ca337 100644 --- a/drivers/gpu/host1x/mipi.c +++ b/drivers/gpu/host1x/mipi.c @@ -72,6 +72,7 @@ #define MIPI_CAL_BIAS_PAD_E_VCLAMP_REF (1 << 0) #define MIPI_CAL_BIAS_PAD_CFG1 0x17 +#define MIPI_CAL_BIAS_PAD_DRV_DN_REF(x) (((x) & 0x7) << 16) #define MIPI_CAL_BIAS_PAD_CFG2 0x18 #define MIPI_CAL_BIAS_PAD_PDVREG (1 << 1) @@ -203,6 +204,9 @@ int tegra_mipi_calibrate(struct tegra_mipi_device *device) value |= MIPI_CAL_BIAS_PAD_E_VCLAMP_REF; tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG0); + tegra_mipi_writel(device->mipi, MIPI_CAL_BIAS_PAD_DRV_DN_REF(2), + MIPI_CAL_BIAS_PAD_CFG1); + value = tegra_mipi_readl(device->mipi, MIPI_CAL_BIAS_PAD_CFG2); value &= ~MIPI_CAL_BIAS_PAD_PDVREG; tegra_mipi_writel(device->mipi, value, MIPI_CAL_BIAS_PAD_CFG2); From 9c0127004ff4e891e475d6dfb22ddcbaeca6ec9b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 7 Jul 2014 15:32:53 +0200 Subject: [PATCH 11/44] drm/tegra: dc: Add powergate support Both display controllers are in their own power partition. Currently the driver relies on the assumption that these partitions are on (which is the hardware default). However some bootloaders may disable them, so the driver must make sure to turn them back on to avoid hangs. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 45 ++++++++++++++++++++++++++++++++++--- drivers/gpu/drm/tegra/drm.h | 1 + 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 6553fd238685..4a015232e2e8 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -11,6 +11,8 @@ #include #include +#include + #include "dc.h" #include "drm.h" #include "gem.h" @@ -20,6 +22,7 @@ struct tegra_dc_soc_info { bool supports_cursor; bool supports_block_linear; unsigned int pitch_align; + bool has_powergate; }; struct tegra_plane { @@ -1357,6 +1360,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = { .supports_cursor = false, .supports_block_linear = false, .pitch_align = 8, + .has_powergate = false, }; static const struct tegra_dc_soc_info tegra30_dc_soc_info = { @@ -1364,6 +1368,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = { .supports_cursor = false, .supports_block_linear = false, .pitch_align = 8, + .has_powergate = false, }; static const struct tegra_dc_soc_info tegra114_dc_soc_info = { @@ -1371,6 +1376,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = { .supports_cursor = false, .supports_block_linear = false, .pitch_align = 64, + .has_powergate = true, }; static const struct tegra_dc_soc_info tegra124_dc_soc_info = { @@ -1378,12 +1384,16 @@ static const struct tegra_dc_soc_info tegra124_dc_soc_info = { .supports_cursor = true, .supports_block_linear = true, .pitch_align = 64, + .has_powergate = true, }; static const struct of_device_id tegra_dc_of_match[] = { { .compatible = "nvidia,tegra124-dc", .data = &tegra124_dc_soc_info, + }, { + .compatible = "nvidia,tegra114-dc", + .data = &tegra114_dc_soc_info, }, { .compatible = "nvidia,tegra30-dc", .data = &tegra30_dc_soc_info, @@ -1467,9 +1477,34 @@ static int tegra_dc_probe(struct platform_device *pdev) return PTR_ERR(dc->rst); } - err = clk_prepare_enable(dc->clk); - if (err < 0) - return err; + if (dc->soc->has_powergate) { + if (dc->pipe == 0) + dc->powergate = TEGRA_POWERGATE_DIS; + else + dc->powergate = TEGRA_POWERGATE_DISB; + + err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk, + dc->rst); + if (err < 0) { + dev_err(&pdev->dev, "failed to power partition: %d\n", + err); + return err; + } + } else { + err = clk_prepare_enable(dc->clk); + if (err < 0) { + dev_err(&pdev->dev, "failed to enable clock: %d\n", + err); + return err; + } + + err = reset_control_deassert(dc->rst); + if (err < 0) { + dev_err(&pdev->dev, "failed to deassert reset: %d\n", + err); + return err; + } + } regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); dc->regs = devm_ioremap_resource(&pdev->dev, regs); @@ -1523,6 +1558,10 @@ static int tegra_dc_remove(struct platform_device *pdev) } reset_control_assert(dc->rst); + + if (dc->soc->has_powergate) + tegra_powergate_power_off(dc->powergate); + clk_disable_unprepare(dc->clk); return 0; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index e89c70fa82d5..b994c017971d 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -101,6 +101,7 @@ struct tegra_dc { spinlock_t lock; struct drm_crtc base; + int powergate; int pipe; struct clk *clk; From dc670e49e75d63174b838fd9e3405f481e50ab19 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 7 Oct 2014 16:26:24 +0200 Subject: [PATCH 12/44] drm/tegra: Do not enable output on .mode_set() The output is already enabled in .dpms(), doing it in .mode_set() too can cause noticeable flicker. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/output.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 0c67d7eebc94..6b393cfbb5e7 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -167,12 +167,6 @@ static void tegra_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adjusted) { - struct tegra_output *output = encoder_to_output(encoder); - int err; - - err = tegra_output_enable(output); - if (err < 0) - dev_err(encoder->dev->dev, "tegra_output_enable(): %d\n", err); } static const struct drm_encoder_helper_funcs encoder_helper_funcs = { From 0bfad396715a24e28d12d35f4a6ce87f65194609 Mon Sep 17 00:00:00 2001 From: Sean Paul Date: Wed, 29 Oct 2014 15:33:35 -0400 Subject: [PATCH 13/44] drm/tegra: DPMS off/on in encoder prepare/commit Previously the panel and output were only enabled on encoder->dpms(). If userspace called dpms on before doing a modeset, the driver would get into a state where the connector had a dpms state of ON, but the encoder and output were not enabled (because the encoder is not yet attached to the connector). Subsequent dpms ON calls are ignored b/c the connector's state already matches the desired state. This patch enables/disables the panel and output on modeset as well, so we can catch the above case. Signed-off-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/output.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 6b393cfbb5e7..e6cfbde36f97 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -157,10 +157,12 @@ static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder, static void tegra_encoder_prepare(struct drm_encoder *encoder) { + tegra_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); } static void tegra_encoder_commit(struct drm_encoder *encoder) { + tegra_encoder_dpms(encoder, DRM_MODE_DPMS_ON); } static void tegra_encoder_mode_set(struct drm_encoder *encoder, From 976cebc35bed0456a42bf96073a26f251d23b264 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 6 Aug 2014 09:14:28 +0200 Subject: [PATCH 14/44] drm/tegra: dsi: Make FIFO depths host parameters Rather than hardcoding them as macros, make the host and video FIFO depths parameters so that they can be more easily adjusted if a new generation of the Tegra SoC changes them. While at it, set the depth of the video FIFO to the correct value of 1920 *words* rather than *bytes*. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index f7874458926a..584b771d8b2f 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -26,9 +26,6 @@ #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; @@ -54,6 +51,9 @@ struct tegra_dsi { struct regulator *vdd; bool enabled; + + unsigned int video_fifo_depth; + unsigned int host_fifo_depth; }; static inline struct tegra_dsi * @@ -467,7 +467,7 @@ static int tegra_output_dsi_enable(struct tegra_output *output) DSI_CONTROL_SOURCE(dc->pipe); tegra_dsi_writel(dsi, value, DSI_CONTROL); - tegra_dsi_writel(dsi, DSI_VIDEO_FIFO_DEPTH, DSI_MAX_THRESHOLD); + tegra_dsi_writel(dsi, dsi->video_fifo_depth, DSI_MAX_THRESHOLD); value = DSI_HOST_CONTROL_HS | DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; @@ -843,6 +843,8 @@ static int tegra_dsi_probe(struct platform_device *pdev) return -ENOMEM; dsi->output.dev = dsi->dev = &pdev->dev; + dsi->video_fifo_depth = 1920; + dsi->host_fifo_depth = 64; err = tegra_output_probe(&dsi->output); if (err < 0) From 183ef2883d060d69d17d63f7c56fbf1a5d65e3f9 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:27:29 +0100 Subject: [PATCH 15/44] drm/tegra: dsi: Do not manage clock on enable/disable In preparation for supporting command mode panels, don't disable the clock when the output is disabled. The output will be enabled only after the panel has been programmed in command mode, so the clock must always remain on. As a side-effect, pad calibration now only needs to be done at driver probe time, since neither power nor controller state will go away before driver removal. While at it, use a 32-bit variable to store register content because the registers are 32-bit even on 64-bit Tegra. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 584b771d8b2f..c62f68071ade 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -456,12 +456,6 @@ static int tegra_output_dsi_enable(struct tegra_output *output) 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(format) | DSI_CONTROL_LANES(dsi->lanes - 1) | DSI_CONTROL_SOURCE(dc->pipe); @@ -576,8 +570,6 @@ static int tegra_output_dsi_disable(struct tegra_output *output) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } - clk_disable(dsi->clk); - dsi->enabled = false; return 0; @@ -695,7 +687,7 @@ static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) { - unsigned long value; + u32 value; tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); @@ -736,12 +728,6 @@ static int tegra_dsi_init(struct host1x_client *client) dev_err(dsi->dev, "debugfs setup failed: %d\n", err); } - err = tegra_dsi_pad_calibrate(dsi); - if (err < 0) { - dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); - return err; - } - return 0; } @@ -863,6 +849,13 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->rst)) return PTR_ERR(dsi->rst); + err = reset_control_deassert(dsi->rst); + if (err < 0) { + dev_err(&pdev->dev, "failed to bring DSI out of reset: %d\n", + err); + return err; + } + dsi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dsi->clk)) { dev_err(&pdev->dev, "cannot get DSI clock\n"); @@ -926,6 +919,12 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (IS_ERR(dsi->mipi)) return PTR_ERR(dsi->mipi); + err = tegra_dsi_pad_calibrate(dsi); + if (err < 0) { + dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); + return err; + } + dsi->host.ops = &tegra_dsi_host_ops; dsi->host.dev = &pdev->dev; From 41a8e72e0e32eca7acbcf11d4c75cf5d21d0c825 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:34:50 +0100 Subject: [PATCH 16/44] drm/tegra: dsi: Leave parent clock alone The common clock framework will take care of preparing and enabling the parent of the DSI clock automatically. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index c62f68071ade..e817ee11f0ec 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -886,12 +886,6 @@ static int tegra_dsi_probe(struct platform_device *pdev) 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; - } - dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); if (IS_ERR(dsi->vdd)) { dev_err(&pdev->dev, "cannot get VDD supply\n"); @@ -966,7 +960,6 @@ static int tegra_dsi_remove(struct platform_device *pdev) tegra_mipi_free(dsi->mipi); regulator_disable(dsi->vdd); - clk_disable_unprepare(dsi->clk_parent); clk_disable_unprepare(dsi->clk_lp); clk_disable_unprepare(dsi->clk); reset_control_assert(dsi->rst); From ba3df9792207601762060a313d18efdd6ae83501 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:54:01 +0100 Subject: [PATCH 17/44] drm/tegra: dsi: Mark connector hotpluggable DSI panels can always be hotplugged via the DSI bus' attach/detach infrastructure, so unconditionally mark the connector hotpluggable. While at it, also make sure that when a panel is detached the connector is marked unconnected before calling into the DRM hotplug helpers to reflect the correct state. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index e817ee11f0ec..704267c39586 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -804,10 +804,10 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host, struct tegra_output *output = &dsi->output; if (output->panel && &device->dev == output->panel->dev) { + output->panel = NULL; + if (output->connector.dev) drm_helper_hpd_irq_event(output->connector.dev); - - output->panel = NULL; } return 0; @@ -836,6 +836,8 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (err < 0) return err; + dsi->output.connector.polled = DRM_CONNECTOR_POLL_HPD; + /* * Assume these values by default. When a DSI peripheral driver * attaches to the DSI host, the parameters will be taken from From d2d0a9d21285ace23b7e99c89a52ee9b5ceef792 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:58:27 +0100 Subject: [PATCH 18/44] drm/tegra: dsi: Properly cleanup on probe failure The driver wasn't even attempting to do any cleanup when probing failed. Fix this by releasing any resources acquired up to the point of failure and putting the device back into the original state (reset, clocks off). Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 52 ++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 704267c39586..5dae8053e8c9 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -861,64 +861,72 @@ static int tegra_dsi_probe(struct platform_device *pdev) 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 = PTR_ERR(dsi->clk); + goto reset; } err = clk_prepare_enable(dsi->clk); if (err < 0) { dev_err(&pdev->dev, "cannot enable DSI clock\n"); - return err; + goto reset; } 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 = PTR_ERR(dsi->clk_lp); + goto disable_clk; } err = clk_prepare_enable(dsi->clk_lp); if (err < 0) { dev_err(&pdev->dev, "cannot enable low-power clock\n"); - return err; + goto disable_clk; } 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 = PTR_ERR(dsi->clk_parent); + goto disable_clk_lp; } dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); if (IS_ERR(dsi->vdd)) { dev_err(&pdev->dev, "cannot get VDD supply\n"); - return PTR_ERR(dsi->vdd); + err = PTR_ERR(dsi->vdd); + goto disable_clk_lp; } err = regulator_enable(dsi->vdd); if (err < 0) { dev_err(&pdev->dev, "cannot enable VDD supply\n"); - return err; + goto disable_clk_lp; } err = tegra_dsi_setup_clocks(dsi); if (err < 0) { dev_err(&pdev->dev, "cannot setup clocks\n"); - return err; + goto disable_vdd; } 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); + if (IS_ERR(dsi->regs)) { + err = PTR_ERR(dsi->regs); + goto disable_vdd; + } dsi->mipi = tegra_mipi_request(&pdev->dev); - if (IS_ERR(dsi->mipi)) - return PTR_ERR(dsi->mipi); + if (IS_ERR(dsi->mipi)) { + err = PTR_ERR(dsi->mipi); + goto disable_vdd; + } err = tegra_dsi_pad_calibrate(dsi); if (err < 0) { dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); - return err; + goto mipi_free; } dsi->host.ops = &tegra_dsi_host_ops; @@ -927,7 +935,7 @@ static int tegra_dsi_probe(struct platform_device *pdev) err = mipi_dsi_host_register(&dsi->host); if (err < 0) { dev_err(&pdev->dev, "failed to register DSI host: %d\n", err); - return err; + goto mipi_free; } INIT_LIST_HEAD(&dsi->client.list); @@ -938,12 +946,26 @@ static int tegra_dsi_probe(struct platform_device *pdev) if (err < 0) { dev_err(&pdev->dev, "failed to register host1x client: %d\n", err); - return err; + goto unregister; } platform_set_drvdata(pdev, dsi); return 0; + +unregister: + mipi_dsi_host_unregister(&dsi->host); +mipi_free: + tegra_mipi_free(dsi->mipi); +disable_vdd: + regulator_disable(dsi->vdd); +disable_clk_lp: + clk_disable_unprepare(dsi->clk_lp); +disable_clk: + clk_disable_unprepare(dsi->clk); +reset: + reset_control_assert(dsi->rst); + return err; } static int tegra_dsi_remove(struct platform_device *pdev) From 563eff1f989917779d8db4c5208e12adcbfcf655 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:44:27 +0100 Subject: [PATCH 19/44] drm/tegra: dsi: Refactor in preparation for command mode For command mode panels, the DSI controller needs to be enabled and configured so that panel drivers can send commands prior to the video stream being enabled. Move code from the monolithic output enable/disable functions into smaller, reusable units to allow more fine-grained control over the controller state. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 106 ++++++++++++++++++++++++++++-------- 1 file changed, 84 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 5dae8053e8c9..b91d9e4016bc 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -426,19 +426,23 @@ static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, return 0; } -static int tegra_output_dsi_enable(struct tegra_output *output) +static void tegra_dsi_enable(struct tegra_dsi *dsi) { - 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); - enum tegra_dsi_format format; - unsigned long value; - const u32 *pkt_seq; - int err; + u32 value; - if (dsi->enabled) - return 0; + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); +} + +static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, + const struct drm_display_mode *mode) +{ + unsigned int hact, hsw, hbp, hfp, i, mul, div; + enum tegra_dsi_format format; + const u32 *pkt_seq; + u32 value; + int err; if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); @@ -458,18 +462,19 @@ static int tegra_output_dsi_enable(struct tegra_output *output) value = DSI_CONTROL_CHANNEL(0) | DSI_CONTROL_FORMAT(format) | DSI_CONTROL_LANES(dsi->lanes - 1) | - DSI_CONTROL_SOURCE(dc->pipe); + DSI_CONTROL_SOURCE(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; + value = DSI_HOST_CONTROL_HS; tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); value = tegra_dsi_readl(dsi, DSI_CONTROL); + if (dsi->flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) value |= DSI_CONTROL_HS_CLK_CTRL; + value &= ~DSI_CONTROL_TX_TRIG(3); value &= ~DSI_CONTROL_DCS_ENABLE; value |= DSI_CONTROL_VIDEO_ENABLE; @@ -503,9 +508,27 @@ static int tegra_output_dsi_enable(struct tegra_output *output) tegra_dsi_writel(dsi, hfp, DSI_PKT_LEN_4_5); tegra_dsi_writel(dsi, 0x0f0f << 16, DSI_PKT_LEN_6_7); - /* set SOL delay */ + /* set SOL delay (for non-burst mode only) */ tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + return 0; +} + +static int tegra_output_dsi_enable(struct tegra_output *output) +{ + struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); + const struct drm_display_mode *mode = &dc->base.mode; + struct tegra_dsi *dsi = to_dsi(output); + u32 value; + int err; + + if (dsi->enabled) + return 0; + + err = tegra_dsi_configure(dsi, dc->pipe, mode); + if (err < 0) + return err; + /* enable display controller */ value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); value |= DSI_ENABLE; @@ -525,28 +548,61 @@ static int tegra_output_dsi_enable(struct tegra_output *output) 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); + tegra_dsi_enable(dsi); dsi->enabled = true; return 0; } +static int tegra_dsi_wait_idle(struct tegra_dsi *dsi, unsigned long timeout) +{ + u32 value; + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + value = tegra_dsi_readl(dsi, DSI_STATUS); + if (value & DSI_STATUS_IDLE) + return 0; + + usleep_range(1000, 2000); + } + + return -ETIMEDOUT; +} + +static void tegra_dsi_video_disable(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_CONTROL); + value &= ~DSI_CONTROL_VIDEO_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); +} + +static void tegra_dsi_disable(struct tegra_dsi *dsi) +{ + u32 value; + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value &= ~DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + usleep_range(5000, 10000); +} + 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; + int err; if (!dsi->enabled) return 0; - /* disable DSI controller */ - value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); - value &= ~DSI_POWER_CONTROL_ENABLE; - tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + tegra_dsi_video_disable(dsi); /* * The following accesses registers of the display controller, so make @@ -570,6 +626,12 @@ static int tegra_output_dsi_disable(struct tegra_output *output) tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); } + err = tegra_dsi_wait_idle(dsi, 100); + if (err < 0) + dev_dbg(dsi->dev, "failed to idle DSI: %d\n", err); + + tegra_dsi_disable(dsi); + dsi->enabled = false; return 0; From 337b443d58e2d7d04d23ed07ff61b1243d5f9f2d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 15:02:46 +0100 Subject: [PATCH 20/44] drm/tegra: dsi: Add command mode support Add support for DC-driven command mode. This is a mode where the video stream sent by the display controller is packed into DCS command packets (write_memory_start and write_memory_continue) by the DSI controller. It can be used for panels with a remote framebuffer and is useful to save power when used with a dynamic refresh rate (not yet supported by the driver). Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 82 ++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index b91d9e4016bc..50684a4aa4f0 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -318,6 +318,21 @@ static const u32 pkt_seq_video_non_burst_sync_events[NUM_PKT_SEQ] = { [11] = PKT_ID0(MIPI_DSI_BLANKING_PACKET) | PKT_LEN0(4), }; +static const u32 pkt_seq_command_mode[NUM_PKT_SEQ] = { + [ 0] = 0, + [ 1] = 0, + [ 2] = 0, + [ 3] = 0, + [ 4] = 0, + [ 5] = 0, + [ 6] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(3) | PKT_LP, + [ 7] = 0, + [ 8] = 0, + [ 9] = 0, + [10] = PKT_ID0(MIPI_DSI_DCS_LONG_WRITE) | PKT_LEN0(5) | PKT_LP, + [11] = 0, +}; + static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) { struct mipi_dphy_timing timing; @@ -447,9 +462,12 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, if (dsi->flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) { DRM_DEBUG_KMS("Non-burst video mode with sync pulses\n"); pkt_seq = pkt_seq_video_non_burst_sync_pulses; - } else { + } else if (dsi->flags & MIPI_DSI_MODE_VIDEO) { DRM_DEBUG_KMS("Non-burst video mode with sync events\n"); pkt_seq = pkt_seq_video_non_burst_sync_events; + } else { + DRM_DEBUG_KMS("Command mode\n"); + pkt_seq = pkt_seq_command_mode; } err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); @@ -476,7 +494,13 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, value |= DSI_CONTROL_HS_CLK_CTRL; value &= ~DSI_CONTROL_TX_TRIG(3); - value &= ~DSI_CONTROL_DCS_ENABLE; + + /* enable DCS commands for command mode */ + if (dsi->flags & MIPI_DSI_MODE_VIDEO) + value &= ~DSI_CONTROL_DCS_ENABLE; + else + value |= DSI_CONTROL_DCS_ENABLE; + value |= DSI_CONTROL_VIDEO_ENABLE; value &= ~DSI_CONTROL_HOST_ENABLE; tegra_dsi_writel(dsi, value, DSI_CONTROL); @@ -488,28 +512,48 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, 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; + if (dsi->flags & MIPI_DSI_MODE_VIDEO) { + /* horizontal active pixels */ + hact = mode->hdisplay * mul / div; - /* horizontal sync width */ - hsw = (mode->hsync_end - mode->hsync_start) * mul / div; - hsw -= 10; + /* 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 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; + /* 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); + 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 (for non-burst mode only) */ - tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + /* set SOL delay (for non-burst mode only) */ + tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + } else { + u16 bytes; + + /* 1 byte (DCS command) + pixel data */ + bytes = 1 + mode->hdisplay * mul / div; + + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1); + tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3); + tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_4_5); + tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_6_7); + + value = MIPI_DCS_WRITE_MEMORY_START << 8 | + MIPI_DCS_WRITE_MEMORY_CONTINUE; + tegra_dsi_writel(dsi, value, DSI_DCS_CMDS); + + value = 8 * mul / div; + + tegra_dsi_writel(dsi, value, DSI_SOL_DELAY); + } return 0; } From 3f6b406f7d716310c7a63648bbe6b2a4a30c3077 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:50:33 +0100 Subject: [PATCH 21/44] drm/tegra: dsi: Split out tegra_dsi_set_timeout() In preparation for adding ganged-mode support, this commit splits out the tegra_dsi_set_timeout() function so that it can be reused for the slave DSI controller. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 38 ++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 50684a4aa4f0..46fcf62658cf 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -681,15 +681,35 @@ static int tegra_output_dsi_disable(struct tegra_output *output) return 0; } +static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, + unsigned int vrefresh) +{ + unsigned int timeout; + u32 value; + + /* 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); +} + static int tegra_output_dsi_setup_clock(struct tegra_output *output, struct clk *clk, unsigned long pclk, unsigned int *divp) { 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; + unsigned int mul, div, vrefresh; + unsigned long bclk, plld; int err; err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); @@ -744,19 +764,7 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, * 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); + tegra_dsi_set_timeout(dsi, bclk, vrefresh); return 0; } From e94236cde4d519cdecd45e2435defba33abdc99f Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 7 Oct 2014 16:10:24 +0200 Subject: [PATCH 22/44] drm/tegra: dsi: Add ganged mode support Implement ganged mode support for the Tegra DSI driver. The DSI host controller to gang up with is specified via a phandle in the device tree and the resolved DSI host controller used for the programming of the ganged-mode registers. Signed-off-by: Thierry Reding --- .../bindings/gpu/nvidia,tegra20-host1x.txt | 2 + drivers/gpu/drm/tegra/dsi.c | 217 +++++++++++++++--- drivers/gpu/drm/tegra/dsi.h | 1 + 3 files changed, 193 insertions(+), 27 deletions(-) diff --git a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt index b48f4ef31d93..4c32ef0b7db8 100644 --- a/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt +++ b/Documentation/devicetree/bindings/gpu/nvidia,tegra20-host1x.txt @@ -191,6 +191,8 @@ of the following host1x client modules: - nvidia,hpd-gpio: specifies a GPIO used for hotplug detection - nvidia,edid: supplies a binary EDID blob - nvidia,panel: phandle of a display panel + - nvidia,ganged-mode: contains a phandle to a second DSI controller to gang + up with in order to support up to 8 data lanes - sor: serial output resource diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 46fcf62658cf..66816104ba72 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,10 @@ struct tegra_dsi { unsigned int video_fifo_depth; unsigned int host_fifo_depth; + + /* for ganged-mode support */ + struct tegra_dsi *master; + struct tegra_dsi *slave; }; static inline struct tegra_dsi * @@ -441,6 +446,18 @@ static int tegra_dsi_get_format(enum mipi_dsi_pixel_format format, return 0; } +static void tegra_dsi_ganged_enable(struct tegra_dsi *dsi, unsigned int start, + unsigned int size) +{ + u32 value; + + tegra_dsi_writel(dsi, start, DSI_GANGED_MODE_START); + tegra_dsi_writel(dsi, size << 16 | size, DSI_GANGED_MODE_SIZE); + + value = DSI_GANGED_MODE_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_GANGED_MODE_CONTROL); +} + static void tegra_dsi_enable(struct tegra_dsi *dsi) { u32 value; @@ -448,6 +465,20 @@ static void tegra_dsi_enable(struct tegra_dsi *dsi) value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); value |= DSI_POWER_CONTROL_ENABLE; tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + if (dsi->slave) + tegra_dsi_enable(dsi->slave); +} + +static unsigned int tegra_dsi_get_lanes(struct tegra_dsi *dsi) +{ + if (dsi->master) + return dsi->master->lanes + dsi->lanes; + + if (dsi->slave) + return dsi->lanes + dsi->slave->lanes; + + return dsi->lanes; } static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, @@ -535,11 +566,20 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, /* set SOL delay (for non-burst mode only) */ tegra_dsi_writel(dsi, 8 * mul / div, DSI_SOL_DELAY); + + /* TODO: implement ganged mode */ } else { u16 bytes; - /* 1 byte (DCS command) + pixel data */ - bytes = 1 + mode->hdisplay * mul / div; + if (dsi->master || dsi->slave) { + /* + * For ganged mode, assume symmetric left-right mode. + */ + bytes = 1 + (mode->hdisplay / 2) * mul / div; + } else { + /* 1 byte (DCS command) + pixel data */ + bytes = 1 + mode->hdisplay * mul / div; + } tegra_dsi_writel(dsi, 0, DSI_PKT_LEN_0_1); tegra_dsi_writel(dsi, bytes << 16, DSI_PKT_LEN_2_3); @@ -550,11 +590,42 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, MIPI_DCS_WRITE_MEMORY_CONTINUE; tegra_dsi_writel(dsi, value, DSI_DCS_CMDS); - value = 8 * mul / div; + /* set SOL delay */ + if (dsi->master || dsi->slave) { + unsigned int lanes = tegra_dsi_get_lanes(dsi); + unsigned long delay, bclk, bclk_ganged; + + /* SOL to valid, valid to FIFO and FIFO write delay */ + delay = 4 + 4 + 2; + delay = DIV_ROUND_UP(delay * mul, div * lanes); + /* FIFO read delay */ + delay = delay + 6; + + bclk = DIV_ROUND_UP(mode->htotal * mul, div * lanes); + bclk_ganged = DIV_ROUND_UP(bclk * lanes / 2, lanes); + value = bclk - bclk_ganged + delay + 20; + } else { + /* TODO: revisit for non-ganged mode */ + value = 8 * mul / div; + } tegra_dsi_writel(dsi, value, DSI_SOL_DELAY); } + if (dsi->slave) { + err = tegra_dsi_configure(dsi->slave, pipe, mode); + if (err < 0) + return err; + + /* + * TODO: Support modes other than symmetrical left-right + * split. + */ + tegra_dsi_ganged_enable(dsi, 0, mode->hdisplay / 2); + tegra_dsi_ganged_enable(dsi->slave, mode->hdisplay / 2, + mode->hdisplay / 2); + } + return 0; } @@ -623,16 +694,34 @@ static void tegra_dsi_video_disable(struct tegra_dsi *dsi) value = tegra_dsi_readl(dsi, DSI_CONTROL); value &= ~DSI_CONTROL_VIDEO_ENABLE; tegra_dsi_writel(dsi, value, DSI_CONTROL); + + if (dsi->slave) + tegra_dsi_video_disable(dsi->slave); +} + +static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi) +{ + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_START); + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_SIZE); + tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); } static void tegra_dsi_disable(struct tegra_dsi *dsi) { u32 value; + if (dsi->slave) { + tegra_dsi_ganged_disable(dsi->slave); + tegra_dsi_ganged_disable(dsi); + } + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); value &= ~DSI_POWER_CONTROL_ENABLE; tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + if (dsi->slave) + tegra_dsi_disable(dsi->slave); + usleep_range(5000, 10000); } @@ -699,6 +788,9 @@ static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, value = DSI_TALLY_TA(0) | DSI_TALLY_LRX(0) | DSI_TALLY_HTX(0); tegra_dsi_writel(dsi, value, DSI_TO_TALLY); + + if (dsi->slave) + tegra_dsi_set_timeout(dsi->slave, bclk, vrefresh); } static int tegra_output_dsi_setup_clock(struct tegra_output *output, @@ -708,20 +800,22 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, struct tegra_dc *dc = to_tegra_dc(output->encoder.crtc); struct drm_display_mode *mode = &dc->base.mode; struct tegra_dsi *dsi = to_dsi(output); - unsigned int mul, div, vrefresh; + unsigned int mul, div, vrefresh, lanes; unsigned long bclk, plld; int err; + lanes = tegra_dsi_get_lanes(dsi); + err = tegra_dsi_get_muldiv(dsi->format, &mul, &div); if (err < 0) return err; - DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, dsi->lanes); + DRM_DEBUG_KMS("mul: %u, div: %u, lanes: %u\n", mul, div, lanes); vrefresh = drm_mode_vrefresh(mode); DRM_DEBUG_KMS("vrefresh: %u\n", vrefresh); /* compute byte clock */ - bclk = (pclk * mul) / (div * dsi->lanes); + bclk = (pclk * mul) / (div * lanes); /* * Compute bit clock and round up to the next MHz. @@ -758,7 +852,7 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, * not working properly otherwise. Perhaps the PLLs cannot generate * frequencies sufficiently high. */ - *divp = ((8 * mul) / (div * dsi->lanes)) - 2; + *divp = ((8 * mul) / (div * lanes)) - 2; /* * XXX: Move the below somewhere else so that we don't need to have @@ -826,14 +920,17 @@ static int tegra_dsi_init(struct host1x_client *client) struct tegra_dsi *dsi = host1x_client_to_dsi(client); int err; - dsi->output.type = TEGRA_OUTPUT_DSI; - dsi->output.dev = client->dev; - dsi->output.ops = &dsi_ops; + /* Gangsters must not register their own outputs. */ + if (!dsi->master) { + dsi->output.type = TEGRA_OUTPUT_DSI; + dsi->output.dev = client->dev; + dsi->output.ops = &dsi_ops; - err = tegra_output_init(drm, &dsi->output); - if (err < 0) { - dev_err(client->dev, "output setup failed: %d\n", err); - return err; + err = tegra_output_init(drm, &dsi->output); + if (err < 0) { + dev_err(client->dev, "output setup failed: %d\n", err); + return err; + } } if (IS_ENABLED(CONFIG_DEBUG_FS)) { @@ -856,16 +953,20 @@ static int tegra_dsi_exit(struct host1x_client *client) 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; - } + if (!dsi->master) { + 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; + err = tegra_output_exit(&dsi->output); + if (err < 0) { + dev_err(client->dev, "output cleanup failed: %d\n", + err); + return err; + } } return 0; @@ -892,20 +993,58 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) return 0; } +static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi) +{ + struct clk *parent; + int err; + + /* make sure both DSI controllers share the same PLL */ + parent = clk_get_parent(dsi->slave->clk); + if (!parent) + return -EINVAL; + + err = clk_set_parent(parent, dsi->clk_parent); + if (err < 0) + return err; + + return 0; +} + 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->flags = device->mode_flags; 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) + if (dsi->slave) { + int err; + + dev_dbg(dsi->dev, "attaching dual-channel device %s\n", + dev_name(&device->dev)); + + err = tegra_dsi_ganged_setup(dsi); + if (err < 0) { + dev_err(dsi->dev, "failed to set up ganged mode: %d\n", + err); + return err; + } + } + + /* + * Slaves don't have a panel associated with them, so they provide + * merely the second channel. + */ + if (!dsi->master) { + struct tegra_output *output = &dsi->output; + + output->panel = of_drm_find_panel(device->dev.of_node); + if (output->panel && output->connector.dev) { + drm_panel_attach(output->panel, &output->connector); drm_helper_hpd_irq_event(output->connector.dev); + } } return 0; @@ -932,6 +1071,26 @@ static const struct mipi_dsi_host_ops tegra_dsi_host_ops = { .detach = tegra_dsi_host_detach, }; +static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi) +{ + struct device_node *np; + + np = of_parse_phandle(dsi->dev->of_node, "nvidia,ganged-mode", 0); + if (np) { + struct platform_device *gangster = of_find_device_by_node(np); + + dsi->slave = platform_get_drvdata(gangster); + of_node_put(np); + + if (!dsi->slave) + return -EPROBE_DEFER; + + dsi->slave->master = dsi; + } + + return 0; +} + static int tegra_dsi_probe(struct platform_device *pdev) { struct tegra_dsi *dsi; @@ -946,6 +1105,10 @@ static int tegra_dsi_probe(struct platform_device *pdev) dsi->video_fifo_depth = 1920; dsi->host_fifo_depth = 64; + err = tegra_dsi_ganged_probe(dsi); + if (err < 0) + return err; + err = tegra_output_probe(&dsi->output); if (err < 0) return err; diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h index 5ce610d08d77..1f6ca68108d8 100644 --- a/drivers/gpu/drm/tegra/dsi.h +++ b/drivers/gpu/drm/tegra/dsi.h @@ -104,6 +104,7 @@ #define DSI_PAD_CONTROL_3 0x51 #define DSI_PAD_CONTROL_4 0x52 #define DSI_GANGED_MODE_CONTROL 0x53 +#define DSI_GANGED_MODE_CONTROL_ENABLE (1 << 0) #define DSI_GANGED_MODE_START 0x54 #define DSI_GANGED_MODE_SIZE 0x55 #define DSI_RAW_DATA_BYTE_COUNT 0x56 From 0fffdf6ca9926243db9ca17701fcdc0a38c67d48 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Nov 2014 17:25:26 +0100 Subject: [PATCH 23/44] drm/tegra: dsi: Implement host transfers Add support for sending MIPI DSI command packets from the host to a peripheral. This is required for panels that need configuration before they accept video data. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 267 ++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/tegra/dsi.h | 13 +- 2 files changed, 279 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 66816104ba72..6646fa2adc38 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -993,6 +993,272 @@ static int tegra_dsi_setup_clocks(struct tegra_dsi *dsi) return 0; } +static const char * const error_report[16] = { + "SoT Error", + "SoT Sync Error", + "EoT Sync Error", + "Escape Mode Entry Command Error", + "Low-Power Transmit Sync Error", + "Peripheral Timeout Error", + "False Control Error", + "Contention Detected", + "ECC Error, single-bit", + "ECC Error, multi-bit", + "Checksum Error", + "DSI Data Type Not Recognized", + "DSI VC ID Invalid", + "Invalid Transmission Length", + "Reserved", + "DSI Protocol Violation", +}; + +static ssize_t tegra_dsi_read_response(struct tegra_dsi *dsi, + const struct mipi_dsi_msg *msg, + size_t count) +{ + u8 *rx = msg->rx_buf; + unsigned int i, j, k; + size_t size = 0; + u16 errors; + u32 value; + + /* read and parse packet header */ + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + + switch (value & 0x3f) { + case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT: + errors = (value >> 8) & 0xffff; + dev_dbg(dsi->dev, "Acknowledge and error report: %04x\n", + errors); + for (i = 0; i < ARRAY_SIZE(error_report); i++) + if (errors & BIT(i)) + dev_dbg(dsi->dev, " %2u: %s\n", i, + error_report[i]); + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE: + rx[0] = (value >> 8) & 0xff; + size = 1; + break; + + case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE: + rx[0] = (value >> 8) & 0xff; + rx[1] = (value >> 16) & 0xff; + size = 2; + break; + + case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE: + size = ((value >> 8) & 0xff00) | ((value >> 8) & 0xff); + break; + + default: + dev_err(dsi->dev, "unhandled response type: %02x\n", + value & 0x3f); + return -EPROTO; + } + + size = min(size, msg->rx_len); + + if (msg->rx_buf && size > 0) { + for (i = 0, j = 0; i < count - 1; i++, j += 4) { + u8 *rx = msg->rx_buf + j; + + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + + for (k = 0; k < 4 && (j + k) < msg->rx_len; k++) + rx[j + k] = (value >> (k << 3)) & 0xff; + } + } + + return size; +} + +static int tegra_dsi_transmit(struct tegra_dsi *dsi, unsigned long timeout) +{ + tegra_dsi_writel(dsi, DSI_TRIGGER_HOST, DSI_TRIGGER); + + timeout = jiffies + msecs_to_jiffies(timeout); + + while (time_before(jiffies, timeout)) { + u32 value = tegra_dsi_readl(dsi, DSI_TRIGGER); + if ((value & DSI_TRIGGER_HOST) == 0) + return 0; + + usleep_range(1000, 2000); + } + + DRM_DEBUG_KMS("timeout waiting for transmission to complete\n"); + return -ETIMEDOUT; +} + +static int tegra_dsi_wait_for_response(struct tegra_dsi *dsi, + unsigned long timeout) +{ + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + u32 value = tegra_dsi_readl(dsi, DSI_STATUS); + u8 count = value & 0x1f; + + if (count > 0) + return count; + + usleep_range(1000, 2000); + } + + DRM_DEBUG_KMS("peripheral returned no data\n"); + return -ETIMEDOUT; +} + +static void tegra_dsi_writesl(struct tegra_dsi *dsi, unsigned long offset, + const void *buffer, size_t size) +{ + const u8 *buf = buffer; + size_t i, j; + u32 value; + + for (j = 0; j < size; j += 4) { + value = 0; + + for (i = 0; i < 4 && j + i < size; i++) + value |= buf[j + i] << (i << 3); + + tegra_dsi_writel(dsi, value, DSI_WR_DATA); + } +} + +static ssize_t tegra_dsi_host_transfer(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg) +{ + struct tegra_dsi *dsi = host_to_tegra(host); + struct mipi_dsi_packet packet; + const u8 *header; + size_t count; + ssize_t err; + u32 value; + + err = mipi_dsi_create_packet(&packet, msg); + if (err < 0) + return err; + + header = packet.header; + + /* maximum FIFO depth is 1920 words */ + if (packet.size > dsi->video_fifo_depth * 4) + return -ENOSPC; + + /* reset underflow/overflow flags */ + value = tegra_dsi_readl(dsi, DSI_STATUS); + if (value & (DSI_STATUS_UNDERFLOW | DSI_STATUS_OVERFLOW)) { + value = DSI_HOST_CONTROL_FIFO_RESET; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + usleep_range(10, 20); + } + + value = tegra_dsi_readl(dsi, DSI_POWER_CONTROL); + value |= DSI_POWER_CONTROL_ENABLE; + tegra_dsi_writel(dsi, value, DSI_POWER_CONTROL); + + usleep_range(5000, 10000); + + value = DSI_HOST_CONTROL_CRC_RESET | DSI_HOST_CONTROL_TX_TRIG_HOST | + DSI_HOST_CONTROL_CS | DSI_HOST_CONTROL_ECC; + + if ((msg->flags & MIPI_DSI_MSG_USE_LPM) == 0) + value |= DSI_HOST_CONTROL_HS; + + /* + * The host FIFO has a maximum of 64 words, so larger transmissions + * need to use the video FIFO. + */ + if (packet.size > dsi->host_fifo_depth * 4) + value |= DSI_HOST_CONTROL_FIFO_SEL; + + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + + /* + * For reads and messages with explicitly requested ACK, generate a + * BTA sequence after the transmission of the packet. + */ + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + value = tegra_dsi_readl(dsi, DSI_HOST_CONTROL); + value |= DSI_HOST_CONTROL_PKT_BTA; + tegra_dsi_writel(dsi, value, DSI_HOST_CONTROL); + } + + value = DSI_CONTROL_LANES(0) | DSI_CONTROL_HOST_ENABLE; + tegra_dsi_writel(dsi, value, DSI_CONTROL); + + /* write packet header, ECC is generated by hardware */ + value = header[2] << 16 | header[1] << 8 | header[0]; + tegra_dsi_writel(dsi, value, DSI_WR_DATA); + + /* write payload (if any) */ + if (packet.payload_length > 0) + tegra_dsi_writesl(dsi, DSI_WR_DATA, packet.payload, + packet.payload_length); + + err = tegra_dsi_transmit(dsi, 250); + if (err < 0) + return err; + + if ((msg->flags & MIPI_DSI_MSG_REQ_ACK) || + (msg->rx_buf && msg->rx_len > 0)) { + err = tegra_dsi_wait_for_response(dsi, 250); + if (err < 0) + return err; + + count = err; + + value = tegra_dsi_readl(dsi, DSI_RD_DATA); + switch (value) { + case 0x84: + /* + dev_dbg(dsi->dev, "ACK\n"); + */ + break; + + case 0x87: + /* + dev_dbg(dsi->dev, "ESCAPE\n"); + */ + break; + + default: + dev_err(dsi->dev, "unknown status: %08x\n", value); + break; + } + + if (count > 1) { + err = tegra_dsi_read_response(dsi, msg, count); + if (err < 0) + dev_err(dsi->dev, + "failed to parse response: %zd\n", + err); + else { + /* + * For read commands, return the number of + * bytes returned by the peripheral. + */ + count = err; + } + } + } else { + /* + * For write commands, we have transmitted the 4-byte header + * plus the variable-length payload. + */ + count = 4 + packet.payload_length; + } + + return count; +} + static int tegra_dsi_ganged_setup(struct tegra_dsi *dsi) { struct clk *parent; @@ -1069,6 +1335,7 @@ static int tegra_dsi_host_detach(struct mipi_dsi_host *host, static const struct mipi_dsi_host_ops tegra_dsi_host_ops = { .attach = tegra_dsi_host_attach, .detach = tegra_dsi_host_detach, + .transfer = tegra_dsi_host_transfer, }; static int tegra_dsi_ganged_probe(struct tegra_dsi *dsi) diff --git a/drivers/gpu/drm/tegra/dsi.h b/drivers/gpu/drm/tegra/dsi.h index 1f6ca68108d8..bad1006a5150 100644 --- a/drivers/gpu/drm/tegra/dsi.h +++ b/drivers/gpu/drm/tegra/dsi.h @@ -21,9 +21,16 @@ #define DSI_INT_STATUS 0x0d #define DSI_INT_MASK 0x0e #define DSI_HOST_CONTROL 0x0f +#define DSI_HOST_CONTROL_FIFO_RESET (1 << 21) +#define DSI_HOST_CONTROL_CRC_RESET (1 << 20) +#define DSI_HOST_CONTROL_TX_TRIG_SOL (0 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_FIFO (1 << 12) +#define DSI_HOST_CONTROL_TX_TRIG_HOST (2 << 12) #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_FIFO_SEL (1 << 4) +#define DSI_HOST_CONTROL_IMM_BTA (1 << 3) +#define DSI_HOST_CONTROL_PKT_BTA (1 << 2) #define DSI_HOST_CONTROL_CS (1 << 1) #define DSI_HOST_CONTROL_ECC (1 << 0) #define DSI_CONTROL 0x10 @@ -39,9 +46,13 @@ #define DSI_SOL_DELAY 0x11 #define DSI_MAX_THRESHOLD 0x12 #define DSI_TRIGGER 0x13 +#define DSI_TRIGGER_HOST (1 << 1) +#define DSI_TRIGGER_VIDEO (1 << 0) #define DSI_TX_CRC 0x14 #define DSI_STATUS 0x15 #define DSI_STATUS_IDLE (1 << 10) +#define DSI_STATUS_UNDERFLOW (1 << 9) +#define DSI_STATUS_OVERFLOW (1 << 8) #define DSI_INIT_SEQ_CONTROL 0x1a #define DSI_INIT_SEQ_DATA_0 0x1b #define DSI_INIT_SEQ_DATA_1 0x1c From 369bc65b6b996595310a4c6a73367332fc32b4df Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Nov 2014 17:17:41 +0100 Subject: [PATCH 24/44] drm/tegra: dsi: Replace 1000000000UL by NSEC_PER_SEC Using the symbolic constant instantly provides a lot more context. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 6646fa2adc38..2f12258ecd74 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -349,7 +349,7 @@ static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) if (rate < 0) return rate; - period = DIV_ROUND_CLOSEST(1000000000UL, rate * 2); + period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate * 2); err = mipi_dphy_timing_get_default(&timing, period); if (err < 0) From 030611ecc5d8b1daf8de110600c8771de45d398d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 13 Nov 2014 14:32:06 +0100 Subject: [PATCH 25/44] drm/tegra: dsi: Replace 1000000 by USEC_PER_SEC Using the symbolic constant instantly provides a lot more context. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 2f12258ecd74..8940360ccc9c 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -820,7 +820,7 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, /* * Compute bit clock and round up to the next MHz. */ - plld = DIV_ROUND_UP(bclk * 8, 1000000) * 1000000; + plld = DIV_ROUND_UP(bclk * 8, USEC_PER_SEC) * USEC_PER_SEC; /* * We divide the frequency by two here, but we make up for that by From 7e3bc3a98fd1df5839cdc5cbce4dfdb9e4c03655 Mon Sep 17 00:00:00 2001 From: Sean Paul Date: Tue, 7 Oct 2014 16:04:42 +0200 Subject: [PATCH 26/44] drm/tegra: dsi: Set up PHY_TIMING & BTA_TIMING registers earlier Make sure the DSI PHY_TIMING and BTA_TIMING registers are initialized when the clocks are set up as opposed to when the output is enabled. This makes sure that the PHY timings are properly set up when the panel is prepared and that DCS commands sent at that time use the appropriate timings. Signed-off-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 8940360ccc9c..33f67fd601c6 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -389,6 +389,9 @@ static int tegra_dsi_set_phy_timing(struct tegra_dsi *dsi) DSI_TIMING_FIELD(timing.tago, period, 1); tegra_dsi_writel(dsi, value, DSI_BTA_TIMING); + if (dsi->slave) + return tegra_dsi_set_phy_timing(dsi->slave); + return 0; } @@ -536,10 +539,6 @@ static int tegra_dsi_configure(struct tegra_dsi *dsi, unsigned int pipe, 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); @@ -860,6 +859,10 @@ static int tegra_output_dsi_setup_clock(struct tegra_output *output, */ tegra_dsi_set_timeout(dsi, bclk, vrefresh); + err = tegra_dsi_set_phy_timing(dsi); + if (err < 0) + return err; + return 0; } From c28d4a317fef0401be180b34f48d193ff2a6787b Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 16 Oct 2014 14:18:50 +0200 Subject: [PATCH 27/44] drm/tegra: gem: Extract tegra_bo_alloc_object() This function implements the common buffer object allocation used for both allocation and import paths. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 79 ++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index ce023fa3e8ae..d86ded791935 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -91,13 +91,8 @@ static const struct host1x_bo_ops tegra_bo_ops = { .kunmap = tegra_bo_kunmap, }; -static void tegra_bo_destroy(struct drm_device *drm, struct tegra_bo *bo) -{ - dma_free_writecombine(drm->dev, bo->gem.size, bo->vaddr, bo->paddr); -} - -struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, - unsigned long flags) +static struct tegra_bo *tegra_bo_alloc_object(struct drm_device *drm, + size_t size) { struct tegra_bo *bo; int err; @@ -109,6 +104,38 @@ struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, host1x_bo_init(&bo->base, &tegra_bo_ops); size = round_up(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; + + return bo; + +release: + drm_gem_object_release(&bo->gem); +free: + kfree(bo); + return ERR_PTR(err); +} + +static void tegra_bo_destroy(struct drm_device *drm, struct tegra_bo *bo) +{ + dma_free_writecombine(drm->dev, bo->gem.size, bo->vaddr, bo->paddr); +} + +struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, + unsigned long flags) +{ + struct tegra_bo *bo; + int err; + + bo = tegra_bo_alloc_object(drm, size); + if (IS_ERR(bo)) + return bo; + bo->vaddr = dma_alloc_writecombine(drm->dev, size, &bo->paddr, GFP_KERNEL | __GFP_NOWARN); if (!bo->vaddr) { @@ -118,14 +145,6 @@ struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, goto err_dma; } - err = drm_gem_object_init(drm, &bo->gem, size); - if (err) - goto err_init; - - err = drm_gem_create_mmap_offset(&bo->gem); - if (err) - goto err_mmap; - if (flags & DRM_TEGRA_GEM_CREATE_TILED) bo->tiling.mode = TEGRA_BO_TILING_MODE_TILED; @@ -134,10 +153,6 @@ struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, return bo; -err_mmap: - drm_gem_object_release(&bo->gem); -err_init: - tegra_bo_destroy(drm, bo); err_dma: kfree(bo); @@ -175,28 +190,16 @@ static struct tegra_bo *tegra_bo_import(struct drm_device *drm, { 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; + bo = tegra_bo_alloc_object(drm, buf->size); + if (IS_ERR(bo)) + return bo; attach = dma_buf_attach(buf, drm->dev); if (IS_ERR(attach)) { err = PTR_ERR(attach); - goto free_mmap; + goto free; } get_dma_buf(buf); @@ -228,13 +231,9 @@ static struct tegra_bo *tegra_bo_import(struct drm_device *drm, 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: + drm_gem_object_release(&bo->gem); kfree(bo); - return ERR_PTR(err); } From a8b48df5925fad5ba9ebc49c80ef60774cc97628 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 16 Oct 2014 14:22:50 +0200 Subject: [PATCH 28/44] drm/tegra: gem: Cleanup tegra_bo_create_with_handle() There is only a single location where the function needs to do cleanup. Skip the error unwinding path and call the cleanup function directly instead. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index d86ded791935..b1d778a7f3e7 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -166,23 +166,21 @@ struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, unsigned int *handle) { struct tegra_bo *bo; - int ret; + int err; bo = tegra_bo_create(drm, size, flags); if (IS_ERR(bo)) return bo; - ret = drm_gem_handle_create(file, &bo->gem, handle); - if (ret) - goto err; + err = drm_gem_handle_create(file, &bo->gem, handle); + if (err) { + tegra_bo_free_object(&bo->gem); + return ERR_PTR(err); + } drm_gem_object_unreference_unlocked(&bo->gem); return bo; - -err: - tegra_bo_free_object(&bo->gem); - return ERR_PTR(ret); } static struct tegra_bo *tegra_bo_import(struct drm_device *drm, From e55a8bd8ead046e5b7f78c321afc7594034257f9 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 16 Oct 2014 14:23:36 +0200 Subject: [PATCH 29/44] drm/tegra: gem: Remove redundant drm_gem_free_mmap_offset() The drm_gem_object_release() function already performs this cleanup, so there is no reason to do it explicitly. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index b1d778a7f3e7..a42d4c9a4d7d 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -247,9 +247,7 @@ void tegra_bo_free_object(struct drm_gem_object *gem) tegra_bo_destroy(gem->dev, bo); } - drm_gem_free_mmap_offset(gem); drm_gem_object_release(gem); - kfree(bo); } From 53ea72132df8dd62bb77b28df7b05074dcea96fe Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 24 Sep 2014 16:14:04 +0200 Subject: [PATCH 30/44] drm/tegra: gem: Use dma_mmap_writecombine() Use the existing API rather than open-coding equivalent functionality in the driver. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index a42d4c9a4d7d..9905598ebfc4 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -306,6 +306,7 @@ const struct vm_operations_struct tegra_bo_vm_ops = { int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma) { + unsigned long vm_pgoff = vma->vm_pgoff; struct drm_gem_object *gem; struct tegra_bo *bo; int ret; @@ -317,12 +318,19 @@ int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma) gem = vma->vm_private_data; bo = to_tegra_bo(gem); - ret = remap_pfn_range(vma, vma->vm_start, bo->paddr >> PAGE_SHIFT, - vma->vm_end - vma->vm_start, vma->vm_page_prot); - if (ret) - drm_gem_vm_close(vma); + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_pgoff = 0; - return ret; + ret = dma_mmap_writecombine(gem->dev->dev, vma, bo->vaddr, bo->paddr, + gem->size); + if (ret) { + drm_gem_vm_close(vma); + return ret; + } + + vma->vm_pgoff = vm_pgoff; + + return 0; } static struct sg_table * From 1d1e6fe9b5cd9e51c0b064b60f673a973cac38ba Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Nov 2014 14:12:08 +0100 Subject: [PATCH 31/44] drm/tegra: Fix error handling cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DRM driver's ->load() implementation didn't do a good job (no job at all really) cleaning up on failure. Fix that by undoing any prior setup when an error occurs. This requires a bit of rework to make it possible to clean up fbdev midway. This was tested by injecting errors at various points during the initialization sequence and verifying that error cleanup didn't crash and no memory leaked (using kmemleak). Reported-by: Stéphane Marchesin Reported-by: Sean Paul Reviewed-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 20 ++++++++++++++++---- drivers/gpu/drm/tegra/drm.h | 1 + drivers/gpu/drm/tegra/fb.c | 20 +++++++++++++++++--- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 59736bb810cd..e1632fb03a89 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -42,13 +42,13 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) err = tegra_drm_fb_prepare(drm); if (err < 0) - return err; + goto config; drm_kms_helper_poll_init(drm); err = host1x_device_init(device); if (err < 0) - return err; + goto fbdev; /* * We don't use the drm_irq_install() helpers provided by the DRM @@ -59,13 +59,25 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) err = drm_vblank_init(drm, drm->mode_config.num_crtc); if (err < 0) - return err; + goto device; err = tegra_drm_fb_init(drm); if (err < 0) - return err; + goto vblank; return 0; + +vblank: + drm_vblank_cleanup(drm); +device: + host1x_device_exit(device); +fbdev: + drm_kms_helper_poll_fini(drm); + tegra_drm_fb_free(drm); +config: + drm_mode_config_cleanup(drm); + kfree(tegra); + return err; } static int tegra_drm_unload(struct drm_device *drm) diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index b994c017971d..ef2faaef5936 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -288,6 +288,7 @@ bool tegra_fb_is_bottom_up(struct drm_framebuffer *framebuffer); int tegra_fb_get_tiling(struct drm_framebuffer *framebuffer, struct tegra_bo_tiling *tiling); int tegra_drm_fb_prepare(struct drm_device *drm); +void tegra_drm_fb_free(struct drm_device *drm); int tegra_drm_fb_init(struct drm_device *drm); void tegra_drm_fb_exit(struct drm_device *drm); #ifdef CONFIG_DRM_TEGRA_FBDEV diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index 3513d12d5aa1..0474241ac2a4 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -289,6 +289,11 @@ static struct tegra_fbdev *tegra_fbdev_create(struct drm_device *drm) return fbdev; } +static void tegra_fbdev_free(struct tegra_fbdev *fbdev) +{ + kfree(fbdev); +} + static int tegra_fbdev_init(struct tegra_fbdev *fbdev, unsigned int preferred_bpp, unsigned int num_crtc, @@ -322,7 +327,7 @@ static int tegra_fbdev_init(struct tegra_fbdev *fbdev, return err; } -static void tegra_fbdev_free(struct tegra_fbdev *fbdev) +static void tegra_fbdev_exit(struct tegra_fbdev *fbdev) { struct fb_info *info = fbdev->base.fbdev; @@ -345,7 +350,7 @@ static void tegra_fbdev_free(struct tegra_fbdev *fbdev) } drm_fb_helper_fini(&fbdev->base); - kfree(fbdev); + tegra_fbdev_free(fbdev); } void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev) @@ -393,6 +398,15 @@ int tegra_drm_fb_prepare(struct drm_device *drm) return 0; } +void tegra_drm_fb_free(struct drm_device *drm) +{ +#ifdef CONFIG_DRM_TEGRA_FBDEV + struct tegra_drm *tegra = drm->dev_private; + + tegra_fbdev_free(tegra->fbdev); +#endif +} + int tegra_drm_fb_init(struct drm_device *drm) { #ifdef CONFIG_DRM_TEGRA_FBDEV @@ -413,6 +427,6 @@ 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); + tegra_fbdev_exit(tegra->fbdev); #endif } From df06b759f2cf4690fa9991edb1504ba39932b2bb Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 26 Jun 2014 21:41:53 +0200 Subject: [PATCH 32/44] drm/tegra: Add IOMMU support When an IOMMU device is available on the platform bus, allocate an IOMMU domain and attach the display controllers to it. The display controllers can then scan out non-contiguous buffers by mapping them through the IOMMU. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 17 +++ drivers/gpu/drm/tegra/drm.c | 24 ++++ drivers/gpu/drm/tegra/drm.h | 5 + drivers/gpu/drm/tegra/fb.c | 16 ++- drivers/gpu/drm/tegra/gem.c | 275 +++++++++++++++++++++++++++++++----- drivers/gpu/drm/tegra/gem.h | 6 + 6 files changed, 309 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 4a015232e2e8..5f138b7c81bf 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -1290,6 +1291,17 @@ static int tegra_dc_init(struct host1x_client *client) struct tegra_drm *tegra = drm->dev_private; int err; + if (tegra->domain) { + err = iommu_attach_device(tegra->domain, dc->dev); + if (err < 0) { + dev_err(dc->dev, "failed to attach to domain: %d\n", + err); + return err; + } + + dc->domain = tegra->domain; + } + drm_crtc_init(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); @@ -1347,6 +1359,11 @@ static int tegra_dc_exit(struct host1x_client *client) return err; } + if (dc->domain) { + iommu_detach_device(dc->domain, dc->dev); + dc->domain = NULL; + } + return 0; } diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index e1632fb03a89..6c9df794b3be 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -8,6 +8,7 @@ */ #include +#include #include "drm.h" #include "gem.h" @@ -33,6 +34,17 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) if (!tegra) return -ENOMEM; + if (iommu_present(&platform_bus_type)) { + tegra->domain = iommu_domain_alloc(&platform_bus_type); + if (IS_ERR(tegra->domain)) { + err = PTR_ERR(tegra->domain); + goto free; + } + + DRM_DEBUG("IOMMU context initialized\n"); + drm_mm_init(&tegra->mm, 0, SZ_2G); + } + mutex_init(&tegra->clients_lock); INIT_LIST_HEAD(&tegra->clients); drm->dev_private = tegra; @@ -76,6 +88,12 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) tegra_drm_fb_free(drm); config: drm_mode_config_cleanup(drm); + + if (tegra->domain) { + iommu_domain_free(tegra->domain); + drm_mm_takedown(&tegra->mm); + } +free: kfree(tegra); return err; } @@ -83,6 +101,7 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) static int tegra_drm_unload(struct drm_device *drm) { struct host1x_device *device = to_host1x_device(drm->dev); + struct tegra_drm *tegra = drm->dev_private; int err; drm_kms_helper_poll_fini(drm); @@ -94,6 +113,11 @@ static int tegra_drm_unload(struct drm_device *drm) if (err < 0) return err; + if (tegra->domain) { + iommu_domain_free(tegra->domain); + drm_mm_takedown(&tegra->mm); + } + return 0; } diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index ef2faaef5936..96ff47d586a2 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -39,6 +39,9 @@ struct tegra_fbdev { struct tegra_drm { struct drm_device *drm; + struct iommu_domain *domain; + struct drm_mm mm; + struct mutex clients_lock; struct list_head clients; @@ -121,6 +124,8 @@ struct tegra_dc { struct drm_pending_vblank_event *event; const struct tegra_dc_soc_info *soc; + + struct iommu_domain *domain; }; static inline struct tegra_dc * diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index 0474241ac2a4..fab39eb2dae8 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -65,8 +65,12 @@ static void tegra_fb_destroy(struct drm_framebuffer *framebuffer) for (i = 0; i < fb->num_planes; i++) { struct tegra_bo *bo = fb->planes[i]; - if (bo) + if (bo) { + if (bo->pages && bo->vaddr) + vunmap(bo->vaddr); + drm_gem_object_unreference_unlocked(&bo->gem); + } } drm_framebuffer_cleanup(framebuffer); @@ -254,6 +258,16 @@ static int tegra_fbdev_probe(struct drm_fb_helper *helper, offset = info->var.xoffset * bytes_per_pixel + info->var.yoffset * fb->pitches[0]; + if (bo->pages) { + bo->vaddr = vmap(bo->pages, bo->num_pages, VM_MAP, + pgprot_writecombine(PAGE_KERNEL)); + if (!bo->vaddr) { + dev_err(drm->dev, "failed to vmap() framebuffer\n"); + err = -ENOMEM; + goto destroy; + } + } + drm->mode_config.fb_base = (resource_size_t)bo->paddr; info->screen_base = (void __iomem *)bo->vaddr + offset; info->screen_size = size; diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 9905598ebfc4..8b1095d05c58 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -14,6 +14,7 @@ */ #include +#include #include #include "drm.h" @@ -91,6 +92,88 @@ static const struct host1x_bo_ops tegra_bo_ops = { .kunmap = tegra_bo_kunmap, }; +/* + * A generic iommu_map_sg() function is being reviewed and will hopefully be + * merged soon. At that point this function can be dropped in favour of the + * one provided by the IOMMU API. + */ +static ssize_t __iommu_map_sg(struct iommu_domain *domain, unsigned long iova, + struct scatterlist *sg, unsigned int nents, + int prot) +{ + struct scatterlist *s; + size_t offset = 0; + unsigned int i; + int err; + + for_each_sg(sg, s, nents, i) { + phys_addr_t phys = page_to_phys(sg_page(s)); + size_t length = s->offset + s->length; + + err = iommu_map(domain, iova + offset, phys, length, prot); + if (err < 0) { + iommu_unmap(domain, iova, offset); + return err; + } + + offset += length; + } + + return offset; +} + +static int tegra_bo_iommu_map(struct tegra_drm *tegra, struct tegra_bo *bo) +{ + int prot = IOMMU_READ | IOMMU_WRITE; + ssize_t err; + + if (bo->mm) + return -EBUSY; + + bo->mm = kzalloc(sizeof(*bo->mm), GFP_KERNEL); + if (!bo->mm) + return -ENOMEM; + + err = drm_mm_insert_node_generic(&tegra->mm, bo->mm, bo->gem.size, + PAGE_SIZE, 0, 0, 0); + if (err < 0) { + dev_err(tegra->drm->dev, "out of I/O virtual memory: %zd\n", + err); + goto free; + } + + bo->paddr = bo->mm->start; + + err = __iommu_map_sg(tegra->domain, bo->paddr, bo->sgt->sgl, + bo->sgt->nents, prot); + if (err < 0) { + dev_err(tegra->drm->dev, "failed to map buffer: %zd\n", err); + goto remove; + } + + bo->size = err; + + return 0; + +remove: + drm_mm_remove_node(bo->mm); +free: + kfree(bo->mm); + return err; +} + +static int tegra_bo_iommu_unmap(struct tegra_drm *tegra, struct tegra_bo *bo) +{ + if (!bo->mm) + return 0; + + iommu_unmap(tegra->domain, bo->paddr, bo->size); + drm_mm_remove_node(bo->mm); + kfree(bo->mm); + + return 0; +} + static struct tegra_bo *tegra_bo_alloc_object(struct drm_device *drm, size_t size) { @@ -121,9 +204,64 @@ static struct tegra_bo *tegra_bo_alloc_object(struct drm_device *drm, return ERR_PTR(err); } -static void tegra_bo_destroy(struct drm_device *drm, struct tegra_bo *bo) +static void tegra_bo_free(struct drm_device *drm, struct tegra_bo *bo) { - dma_free_writecombine(drm->dev, bo->gem.size, bo->vaddr, bo->paddr); + if (bo->pages) { + drm_gem_put_pages(&bo->gem, bo->pages, true, true); + sg_free_table(bo->sgt); + kfree(bo->sgt); + } else { + dma_free_writecombine(drm->dev, bo->gem.size, bo->vaddr, + bo->paddr); + } +} + +static int tegra_bo_get_pages(struct drm_device *drm, struct tegra_bo *bo, + size_t size) +{ + bo->pages = drm_gem_get_pages(&bo->gem); + if (IS_ERR(bo->pages)) + return PTR_ERR(bo->pages); + + bo->num_pages = size >> PAGE_SHIFT; + + bo->sgt = drm_prime_pages_to_sg(bo->pages, bo->num_pages); + if (IS_ERR(bo->sgt)) { + drm_gem_put_pages(&bo->gem, bo->pages, false, false); + return PTR_ERR(bo->sgt); + } + + return 0; +} + +static int tegra_bo_alloc(struct drm_device *drm, struct tegra_bo *bo, + size_t size) +{ + struct tegra_drm *tegra = drm->dev_private; + int err; + + if (tegra->domain) { + err = tegra_bo_get_pages(drm, bo, size); + if (err < 0) + return err; + + err = tegra_bo_iommu_map(tegra, bo); + if (err < 0) { + tegra_bo_free(drm, bo); + return err; + } + } else { + bo->vaddr = dma_alloc_writecombine(drm->dev, size, &bo->paddr, + GFP_KERNEL | __GFP_NOWARN); + if (!bo->vaddr) { + dev_err(drm->dev, + "failed to allocate buffer of size %zu\n", + size); + return -ENOMEM; + } + } + + return 0; } struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, @@ -136,14 +274,9 @@ struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, if (IS_ERR(bo)) return bo; - bo->vaddr = dma_alloc_writecombine(drm->dev, size, &bo->paddr, - GFP_KERNEL | __GFP_NOWARN); - if (!bo->vaddr) { - dev_err(drm->dev, "failed to allocate buffer with size %u\n", - size); - err = -ENOMEM; - goto err_dma; - } + err = tegra_bo_alloc(drm, bo, size); + if (err < 0) + goto release; if (flags & DRM_TEGRA_GEM_CREATE_TILED) bo->tiling.mode = TEGRA_BO_TILING_MODE_TILED; @@ -153,9 +286,9 @@ struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, return bo; -err_dma: +release: + drm_gem_object_release(&bo->gem); kfree(bo); - return ERR_PTR(err); } @@ -186,6 +319,7 @@ struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, static struct tegra_bo *tegra_bo_import(struct drm_device *drm, struct dma_buf *buf) { + struct tegra_drm *tegra = drm->dev_private; struct dma_buf_attachment *attach; struct tegra_bo *bo; int err; @@ -213,12 +347,19 @@ static struct tegra_bo *tegra_bo_import(struct drm_device *drm, goto detach; } - if (bo->sgt->nents > 1) { - err = -EINVAL; - goto detach; + if (tegra->domain) { + err = tegra_bo_iommu_map(tegra, bo); + if (err < 0) + goto detach; + } else { + if (bo->sgt->nents > 1) { + err = -EINVAL; + goto detach; + } + + bo->paddr = sg_dma_address(bo->sgt->sgl); } - bo->paddr = sg_dma_address(bo->sgt->sgl); bo->gem.import_attach = attach; return bo; @@ -237,14 +378,18 @@ static struct tegra_bo *tegra_bo_import(struct drm_device *drm, void tegra_bo_free_object(struct drm_gem_object *gem) { + struct tegra_drm *tegra = gem->dev->dev_private; struct tegra_bo *bo = to_tegra_bo(gem); + if (tegra->domain) + tegra_bo_iommu_unmap(tegra, bo); + 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); + tegra_bo_free(gem->dev, bo); } drm_gem_object_release(gem); @@ -299,14 +444,44 @@ int tegra_bo_dumb_map_offset(struct drm_file *file, struct drm_device *drm, return 0; } +static int tegra_bo_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *gem = vma->vm_private_data; + struct tegra_bo *bo = to_tegra_bo(gem); + struct page *page; + pgoff_t offset; + int err; + + if (!bo->pages) + return VM_FAULT_SIGBUS; + + offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >> PAGE_SHIFT; + page = bo->pages[offset]; + + err = vm_insert_page(vma, (unsigned long)vmf->virtual_address, page); + switch (err) { + case -EAGAIN: + case 0: + case -ERESTARTSYS: + case -EINTR: + case -EBUSY: + return VM_FAULT_NOPAGE; + + case -ENOMEM: + return VM_FAULT_OOM; + } + + return VM_FAULT_SIGBUS; +} + const struct vm_operations_struct tegra_bo_vm_ops = { + .fault = tegra_bo_fault, .open = drm_gem_vm_open, .close = drm_gem_vm_close, }; int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma) { - unsigned long vm_pgoff = vma->vm_pgoff; struct drm_gem_object *gem; struct tegra_bo *bo; int ret; @@ -318,18 +493,29 @@ int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma) gem = vma->vm_private_data; bo = to_tegra_bo(gem); - vma->vm_flags &= ~VM_PFNMAP; - vma->vm_pgoff = 0; + if (!bo->pages) { + unsigned long vm_pgoff = vma->vm_pgoff; - ret = dma_mmap_writecombine(gem->dev->dev, vma, bo->vaddr, bo->paddr, - gem->size); - if (ret) { - drm_gem_vm_close(vma); - return ret; + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_pgoff = 0; + + ret = dma_mmap_writecombine(gem->dev->dev, vma, bo->vaddr, + bo->paddr, gem->size); + if (ret) { + drm_gem_vm_close(vma); + return ret; + } + + vma->vm_pgoff = vm_pgoff; + } else { + pgprot_t prot = vm_get_page_prot(vma->vm_flags); + + vma->vm_flags |= VM_MIXEDMAP; + vma->vm_flags &= ~VM_PFNMAP; + + vma->vm_page_prot = pgprot_writecombine(prot); } - vma->vm_pgoff = vm_pgoff; - return 0; } @@ -345,21 +531,44 @@ tegra_gem_prime_map_dma_buf(struct dma_buf_attachment *attach, if (!sgt) return NULL; - if (sg_alloc_table(sgt, 1, GFP_KERNEL)) { - kfree(sgt); - return NULL; + if (bo->pages) { + struct scatterlist *sg; + unsigned int i; + + if (sg_alloc_table(sgt, bo->num_pages, GFP_KERNEL)) + goto free; + + for_each_sg(sgt->sgl, sg, bo->num_pages, i) + sg_set_page(sg, bo->pages[i], PAGE_SIZE, 0); + + if (dma_map_sg(attach->dev, sgt->sgl, sgt->nents, dir) == 0) + goto free; + } else { + if (sg_alloc_table(sgt, 1, GFP_KERNEL)) + goto free; + + sg_dma_address(sgt->sgl) = bo->paddr; + sg_dma_len(sgt->sgl) = gem->size; } - sg_dma_address(sgt->sgl) = bo->paddr; - sg_dma_len(sgt->sgl) = gem->size; - return sgt; + +free: + sg_free_table(sgt); + kfree(sgt); + return NULL; } static void tegra_gem_prime_unmap_dma_buf(struct dma_buf_attachment *attach, struct sg_table *sgt, enum dma_data_direction dir) { + struct drm_gem_object *gem = attach->dmabuf->priv; + struct tegra_bo *bo = to_tegra_bo(gem); + + if (bo->pages) + dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents, dir); + sg_free_table(sgt); kfree(sgt); } diff --git a/drivers/gpu/drm/tegra/gem.h b/drivers/gpu/drm/tegra/gem.h index 6538b56780c2..3dd4165f812a 100644 --- a/drivers/gpu/drm/tegra/gem.h +++ b/drivers/gpu/drm/tegra/gem.h @@ -38,6 +38,12 @@ struct tegra_bo { dma_addr_t paddr; void *vaddr; + struct drm_mm_node *mm; + unsigned long num_pages; + struct page **pages; + /* size of IOMMU mapping */ + size_t size; + struct tegra_bo_tiling tiling; }; From 205d48edee844740041c07450fedd7314352ba44 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Oct 2014 13:41:46 +0200 Subject: [PATCH 33/44] drm/tegra: dc: Factor out DC, window and cursor commit The sequence to commit changes to the DC, window or cursor configuration is repetitive and can be extracted into separate functions for ease of use. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 52 ++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 5f138b7c81bf..8fb815dde969 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -36,6 +36,26 @@ static inline struct tegra_plane *to_tegra_plane(struct drm_plane *plane) return container_of(plane, struct tegra_plane, base); } +static void tegra_dc_window_commit(struct tegra_dc *dc, unsigned int index) +{ + u32 value = WIN_A_ACT_REQ << index; + + tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); +} + +static void tegra_dc_cursor_commit(struct tegra_dc *dc) +{ + tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); +} + +static void tegra_dc_commit(struct tegra_dc *dc) +{ + tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); + tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); +} + static unsigned int tegra_dc_format(uint32_t format, uint32_t *swap) { /* assume no swapping of fetched data */ @@ -307,8 +327,7 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, break; } - tegra_dc_writel(dc, WIN_A_UPDATE << index, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, WIN_A_ACT_REQ << index, DC_CMD_STATE_CONTROL); + tegra_dc_window_commit(dc, index); return 0; } @@ -379,8 +398,7 @@ static int tegra_plane_disable(struct drm_plane *plane) value &= ~WIN_ENABLE; tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - tegra_dc_writel(dc, WIN_A_UPDATE << p->index, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, WIN_A_ACT_REQ << p->index, DC_CMD_STATE_CONTROL); + tegra_dc_window_commit(dc, p->index); return 0; } @@ -517,10 +535,8 @@ static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); - value = GENERAL_UPDATE | WIN_A_UPDATE; - tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); - value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; + tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL); tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); return 0; @@ -625,11 +641,8 @@ static int tegra_dc_cursor_set2(struct drm_crtc *crtc, struct drm_file *file, tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); } - tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); - - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_cursor_commit(dc); + tegra_dc_commit(dc); return 0; } @@ -645,12 +658,9 @@ static int tegra_dc_cursor_move(struct drm_crtc *crtc, int x, int y) value = ((y & 0x3fff) << 16) | (x & 0x3fff); tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); - tegra_dc_writel(dc, CURSOR_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, CURSOR_ACT_REQ, DC_CMD_STATE_CONTROL); - + tegra_dc_cursor_commit(dc); /* XXX: only required on generations earlier than Tegra124? */ - tegra_dc_writel(dc, GENERAL_ACT_REQ << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, GENERAL_ACT_REQ, DC_CMD_STATE_CONTROL); + tegra_dc_commit(dc); return 0; } @@ -939,15 +949,9 @@ static void tegra_crtc_prepare(struct drm_crtc *crtc) static void tegra_crtc_commit(struct drm_crtc *crtc) { struct tegra_dc *dc = to_tegra_dc(crtc); - unsigned long value; - - value = GENERAL_UPDATE | WIN_A_UPDATE; - tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); - - value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; - tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); drm_vblank_post_modeset(crtc->dev, dc->pipe); + tegra_dc_commit(dc); } static void tegra_crtc_load_lut(struct drm_crtc *crtc) From 03a605697658ac7af722764cef3f0fed889d2033 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Oct 2014 13:48:48 +0200 Subject: [PATCH 34/44] drm/tegra: dc: Registers are 32 bits wide Using an unsigned long type will cause these variables to become 64-bit on 64-bit SoCs. In practice this should always work, but there's no need for carrying around the additional 32 bits. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 2 +- drivers/gpu/drm/tegra/drm.h | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 8fb815dde969..517a257cccaf 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -1005,7 +1005,7 @@ static int tegra_dc_show_regs(struct seq_file *s, void *data) struct tegra_dc *dc = node->info_ent->data; #define DUMP_REG(name) \ - seq_printf(s, "%-40s %#05x %08lx\n", #name, name, \ + seq_printf(s, "%-40s %#05x %08x\n", #name, name, \ tegra_dc_readl(dc, name)) DUMP_REG(DC_CMD_GENERAL_INCR_SYNCPT); diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 96ff47d586a2..3a3b2e7b5b3f 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -139,16 +139,15 @@ static inline struct tegra_dc *to_tegra_dc(struct drm_crtc *crtc) return crtc ? container_of(crtc, struct tegra_dc, base) : NULL; } -static inline void tegra_dc_writel(struct tegra_dc *dc, unsigned long value, - unsigned long reg) +static inline void tegra_dc_writel(struct tegra_dc *dc, u32 value, + unsigned long offset) { - writel(value, dc->regs + (reg << 2)); + writel(value, dc->regs + (offset << 2)); } -static inline unsigned long tegra_dc_readl(struct tegra_dc *dc, - unsigned long reg) +static inline u32 tegra_dc_readl(struct tegra_dc *dc, unsigned long offset) { - return readl(dc->regs + (reg << 2)); + return readl(dc->regs + (offset << 2)); } struct tegra_dc_window { From c7679306a923c2feb383f709446c1110db1c56e4 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Oct 2014 13:51:53 +0200 Subject: [PATCH 35/44] drm/tegra: dc: Universal plane support This allows the primary plane and cursor to be exposed as regular DRM/KMS planes, which is a prerequisite for atomic modesetting and gives userspace more flexibility over controlling them. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 487 +++++++++++++++++++++++++------------ 1 file changed, 330 insertions(+), 157 deletions(-) diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 517a257cccaf..7ef16a2409b1 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -332,11 +332,255 @@ static int tegra_dc_setup_window(struct tegra_dc *dc, unsigned int index, return 0; } -static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, - struct drm_framebuffer *fb, int crtc_x, - int crtc_y, unsigned int crtc_w, - unsigned int crtc_h, uint32_t src_x, - uint32_t src_y, uint32_t src_w, uint32_t src_h) +static int tegra_window_plane_disable(struct drm_plane *plane) +{ + struct tegra_dc *dc = to_tegra_dc(plane->crtc); + struct tegra_plane *p = to_tegra_plane(plane); + u32 value; + + if (!plane->crtc) + return 0; + + value = WINDOW_A_SELECT << p->index; + tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); + + value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); + value &= ~WIN_ENABLE; + tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); + + tegra_dc_window_commit(dc, p->index); + + return 0; +} + +static void tegra_plane_destroy(struct drm_plane *plane) +{ + struct tegra_plane *p = to_tegra_plane(plane); + + drm_plane_cleanup(plane); + kfree(p); +} + +static const u32 tegra_primary_plane_formats[] = { + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB565, +}; + +static int tegra_primary_plane_update(struct drm_plane *plane, + struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, + int crtc_y, unsigned int crtc_w, + unsigned int crtc_h, uint32_t src_x, + uint32_t src_y, uint32_t src_w, + uint32_t src_h) +{ + struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); + struct tegra_plane *p = to_tegra_plane(plane); + struct tegra_dc *dc = to_tegra_dc(crtc); + struct tegra_dc_window window; + int err; + + memset(&window, 0, sizeof(window)); + window.src.x = src_x >> 16; + window.src.y = src_y >> 16; + window.src.w = src_w >> 16; + window.src.h = src_h >> 16; + window.dst.x = crtc_x; + window.dst.y = crtc_y; + window.dst.w = crtc_w; + window.dst.h = crtc_h; + window.format = tegra_dc_format(fb->pixel_format, &window.swap); + window.bits_per_pixel = fb->bits_per_pixel; + window.bottom_up = tegra_fb_is_bottom_up(fb); + + err = tegra_fb_get_tiling(fb, &window.tiling); + if (err < 0) + return err; + + window.base[0] = bo->paddr + fb->offsets[0]; + window.stride[0] = fb->pitches[0]; + + err = tegra_dc_setup_window(dc, p->index, &window); + if (err < 0) + return err; + + return 0; +} + +static void tegra_primary_plane_destroy(struct drm_plane *plane) +{ + tegra_window_plane_disable(plane); + tegra_plane_destroy(plane); +} + +static const struct drm_plane_funcs tegra_primary_plane_funcs = { + .update_plane = tegra_primary_plane_update, + .disable_plane = tegra_window_plane_disable, + .destroy = tegra_primary_plane_destroy, +}; + +static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm, + struct tegra_dc *dc) +{ + struct tegra_plane *plane; + unsigned int num_formats; + const u32 *formats; + int err; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + num_formats = ARRAY_SIZE(tegra_primary_plane_formats); + formats = tegra_primary_plane_formats; + + err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, + &tegra_primary_plane_funcs, formats, + num_formats, DRM_PLANE_TYPE_PRIMARY); + if (err < 0) { + kfree(plane); + return ERR_PTR(err); + } + + return &plane->base; +} + +static const u32 tegra_cursor_plane_formats[] = { + DRM_FORMAT_RGBA8888, +}; + +static int tegra_cursor_plane_update(struct drm_plane *plane, + struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, + int crtc_y, unsigned int crtc_w, + unsigned int crtc_h, uint32_t src_x, + uint32_t src_y, uint32_t src_w, + uint32_t src_h) +{ + struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); + struct tegra_dc *dc = to_tegra_dc(crtc); + u32 value = CURSOR_CLIP_DISPLAY; + + /* scaling not supported for cursor */ + if ((src_w >> 16 != crtc_w) || (src_h >> 16 != crtc_h)) + return -EINVAL; + + /* only square cursors supported */ + if (src_w != src_h) + return -EINVAL; + + switch (crtc_w) { + case 32: + value |= CURSOR_SIZE_32x32; + break; + + case 64: + value |= CURSOR_SIZE_64x64; + break; + + case 128: + value |= CURSOR_SIZE_128x128; + break; + + case 256: + value |= CURSOR_SIZE_256x256; + break; + + default: + return -EINVAL; + } + + value |= (bo->paddr >> 10) & 0x3fffff; + tegra_dc_writel(dc, value, DC_DISP_CURSOR_START_ADDR); + +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT + value = (bo->paddr >> 32) & 0x3; + tegra_dc_writel(dc, value, DC_DISP_CURSOR_START_ADDR_HI); +#endif + + /* enable cursor and set blend mode */ + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value |= CURSOR_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + value = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL); + value &= ~CURSOR_DST_BLEND_MASK; + value &= ~CURSOR_SRC_BLEND_MASK; + value |= CURSOR_MODE_NORMAL; + value |= CURSOR_DST_BLEND_NEG_K1_TIMES_SRC; + value |= CURSOR_SRC_BLEND_K1_TIMES_SRC; + value |= CURSOR_ALPHA; + tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL); + + /* position the cursor */ + value = (crtc_y & 0x3fff) << 16 | (crtc_x & 0x3fff); + tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); + + /* apply changes */ + tegra_dc_cursor_commit(dc); + tegra_dc_commit(dc); + + return 0; +} + +static int tegra_cursor_plane_disable(struct drm_plane *plane) +{ + struct tegra_dc *dc = to_tegra_dc(plane->crtc); + u32 value; + + if (!plane->crtc) + return 0; + + value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); + value &= ~CURSOR_ENABLE; + tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); + + tegra_dc_cursor_commit(dc); + tegra_dc_commit(dc); + + return 0; +} + +static const struct drm_plane_funcs tegra_cursor_plane_funcs = { + .update_plane = tegra_cursor_plane_update, + .disable_plane = tegra_cursor_plane_disable, + .destroy = tegra_plane_destroy, +}; + +static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm, + struct tegra_dc *dc) +{ + struct tegra_plane *plane; + unsigned int num_formats; + const u32 *formats; + int err; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + num_formats = ARRAY_SIZE(tegra_cursor_plane_formats); + formats = tegra_cursor_plane_formats; + + err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, + &tegra_cursor_plane_funcs, formats, + num_formats, DRM_PLANE_TYPE_CURSOR); + if (err < 0) { + kfree(plane); + return ERR_PTR(err); + } + + return &plane->base; +} + +static int tegra_overlay_plane_update(struct drm_plane *plane, + struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, + int crtc_y, unsigned int crtc_w, + unsigned int crtc_h, uint32_t src_x, + uint32_t src_y, uint32_t src_w, + uint32_t src_h) { struct tegra_plane *p = to_tegra_plane(plane); struct tegra_dc *dc = to_tegra_dc(crtc); @@ -382,43 +626,19 @@ static int tegra_plane_update(struct drm_plane *plane, struct drm_crtc *crtc, return tegra_dc_setup_window(dc, p->index, &window); } -static int tegra_plane_disable(struct drm_plane *plane) +static void tegra_overlay_plane_destroy(struct drm_plane *plane) { - struct tegra_dc *dc = to_tegra_dc(plane->crtc); - struct tegra_plane *p = to_tegra_plane(plane); - unsigned long value; - - if (!plane->crtc) - return 0; - - value = WINDOW_A_SELECT << p->index; - tegra_dc_writel(dc, value, DC_CMD_DISPLAY_WINDOW_HEADER); - - value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value &= ~WIN_ENABLE; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - - tegra_dc_window_commit(dc, p->index); - - return 0; + tegra_window_plane_disable(plane); + tegra_plane_destroy(plane); } -static void tegra_plane_destroy(struct drm_plane *plane) -{ - struct tegra_plane *p = to_tegra_plane(plane); - - tegra_plane_disable(plane); - drm_plane_cleanup(plane); - kfree(p); -} - -static const struct drm_plane_funcs tegra_plane_funcs = { - .update_plane = tegra_plane_update, - .disable_plane = tegra_plane_disable, - .destroy = tegra_plane_destroy, +static const struct drm_plane_funcs tegra_overlay_plane_funcs = { + .update_plane = tegra_overlay_plane_update, + .disable_plane = tegra_window_plane_disable, + .destroy = tegra_overlay_plane_destroy, }; -static const uint32_t plane_formats[] = { +static const uint32_t tegra_overlay_plane_formats[] = { DRM_FORMAT_XBGR8888, DRM_FORMAT_XRGB8888, DRM_FORMAT_RGB565, @@ -428,27 +648,44 @@ static const uint32_t plane_formats[] = { DRM_FORMAT_YUV422, }; +static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm, + struct tegra_dc *dc, + unsigned int index) +{ + struct tegra_plane *plane; + unsigned int num_formats; + const u32 *formats; + int err; + + plane = kzalloc(sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + plane->index = index; + + num_formats = ARRAY_SIZE(tegra_overlay_plane_formats); + formats = tegra_overlay_plane_formats; + + err = drm_universal_plane_init(drm, &plane->base, 1 << dc->pipe, + &tegra_overlay_plane_funcs, formats, + num_formats, DRM_PLANE_TYPE_OVERLAY); + if (err < 0) { + kfree(plane); + return ERR_PTR(err); + } + + return &plane->base; +} + static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) { + struct drm_plane *plane; unsigned int i; - int err = 0; for (i = 0; i < 2; i++) { - struct tegra_plane *plane; - - plane = kzalloc(sizeof(*plane), GFP_KERNEL); - if (!plane) - return -ENOMEM; - - plane->index = 1 + i; - - err = drm_plane_init(drm, &plane->base, 1 << dc->pipe, - &tegra_plane_funcs, plane_formats, - ARRAY_SIZE(plane_formats), false); - if (err < 0) { - kfree(plane); - return err; - } + plane = tegra_dc_overlay_plane_create(drm, dc, 1 + i); + if (IS_ERR(plane)) + return PTR_ERR(plane); } return 0; @@ -568,103 +805,6 @@ void tegra_dc_disable_vblank(struct tegra_dc *dc) spin_unlock_irqrestore(&dc->lock, flags); } -static int tegra_dc_cursor_set2(struct drm_crtc *crtc, struct drm_file *file, - uint32_t handle, uint32_t width, - uint32_t height, int32_t hot_x, int32_t hot_y) -{ - unsigned long value = CURSOR_CLIP_DISPLAY; - struct tegra_dc *dc = to_tegra_dc(crtc); - struct drm_gem_object *gem; - struct tegra_bo *bo = NULL; - - if (!dc->soc->supports_cursor) - return -ENXIO; - - if (width != height) - return -EINVAL; - - switch (width) { - case 32: - value |= CURSOR_SIZE_32x32; - break; - - case 64: - value |= CURSOR_SIZE_64x64; - break; - - case 128: - value |= CURSOR_SIZE_128x128; - - case 256: - value |= CURSOR_SIZE_256x256; - break; - - default: - return -EINVAL; - } - - if (handle) { - gem = drm_gem_object_lookup(crtc->dev, file, handle); - if (!gem) - return -ENOENT; - - bo = to_tegra_bo(gem); - } - - if (bo) { - unsigned long addr = (bo->paddr & 0xfffffc00) >> 10; -#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT - unsigned long high = (bo->paddr & 0xfffffffc) >> 32; -#endif - - tegra_dc_writel(dc, value | addr, DC_DISP_CURSOR_START_ADDR); - -#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT - tegra_dc_writel(dc, high, DC_DISP_CURSOR_START_ADDR_HI); -#endif - - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value |= CURSOR_ENABLE; - tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - - value = tegra_dc_readl(dc, DC_DISP_BLEND_CURSOR_CONTROL); - value &= ~CURSOR_DST_BLEND_MASK; - value &= ~CURSOR_SRC_BLEND_MASK; - value |= CURSOR_MODE_NORMAL; - value |= CURSOR_DST_BLEND_NEG_K1_TIMES_SRC; - value |= CURSOR_SRC_BLEND_K1_TIMES_SRC; - value |= CURSOR_ALPHA; - tegra_dc_writel(dc, value, DC_DISP_BLEND_CURSOR_CONTROL); - } else { - value = tegra_dc_readl(dc, DC_DISP_DISP_WIN_OPTIONS); - value &= ~CURSOR_ENABLE; - tegra_dc_writel(dc, value, DC_DISP_DISP_WIN_OPTIONS); - } - - tegra_dc_cursor_commit(dc); - tegra_dc_commit(dc); - - return 0; -} - -static int tegra_dc_cursor_move(struct drm_crtc *crtc, int x, int y) -{ - struct tegra_dc *dc = to_tegra_dc(crtc); - unsigned long value; - - if (!dc->soc->supports_cursor) - return -ENXIO; - - value = ((y & 0x3fff) << 16) | (x & 0x3fff); - tegra_dc_writel(dc, value, DC_DISP_CURSOR_POSITION); - - tegra_dc_cursor_commit(dc); - /* XXX: only required on generations earlier than Tegra124? */ - tegra_dc_commit(dc); - - return 0; -} - static void tegra_dc_finish_page_flip(struct tegra_dc *dc) { struct drm_device *drm = dc->base.dev; @@ -741,8 +881,6 @@ static void tegra_dc_destroy(struct drm_crtc *crtc) } static const struct drm_crtc_funcs tegra_crtc_funcs = { - .cursor_set2 = tegra_dc_cursor_set2, - .cursor_move = tegra_dc_cursor_move, .page_flip = tegra_dc_page_flip, .set_config = drm_crtc_helper_set_config, .destroy = tegra_dc_destroy, @@ -756,7 +894,7 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) drm_for_each_legacy_plane(plane, &drm->mode_config.plane_list) { if (plane->crtc == crtc) { - tegra_plane_disable(plane); + tegra_window_plane_disable(plane); plane->crtc = NULL; if (plane->fb) { @@ -767,6 +905,7 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) } drm_vblank_off(drm, dc->pipe); + tegra_dc_commit(dc); } static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc, @@ -1293,6 +1432,8 @@ static int tegra_dc_init(struct host1x_client *client) struct drm_device *drm = dev_get_drvdata(client->parent); struct tegra_dc *dc = host1x_client_to_dc(client); struct tegra_drm *tegra = drm->dev_private; + struct drm_plane *primary = NULL; + struct drm_plane *cursor = NULL; int err; if (tegra->domain) { @@ -1306,7 +1447,25 @@ static int tegra_dc_init(struct host1x_client *client) dc->domain = tegra->domain; } - drm_crtc_init(drm, &dc->base, &tegra_crtc_funcs); + primary = tegra_dc_primary_plane_create(drm, dc); + if (IS_ERR(primary)) { + err = PTR_ERR(primary); + goto cleanup; + } + + if (dc->soc->supports_cursor) { + cursor = tegra_dc_cursor_plane_create(drm, dc); + if (IS_ERR(cursor)) { + err = PTR_ERR(cursor); + goto cleanup; + } + } + + err = drm_crtc_init_with_planes(drm, &dc->base, primary, cursor, + &tegra_crtc_funcs); + if (err < 0) + goto cleanup; + drm_mode_crtc_set_gamma_size(&dc->base, 256); drm_crtc_helper_add(&dc->base, &tegra_crtc_helper_funcs); @@ -1320,12 +1479,12 @@ static int tegra_dc_init(struct host1x_client *client) err = tegra_dc_rgb_init(drm, dc); if (err < 0 && err != -ENODEV) { dev_err(dc->dev, "failed to initialize RGB output: %d\n", err); - return err; + goto cleanup; } err = tegra_dc_add_planes(drm, dc); if (err < 0) - return err; + goto cleanup; if (IS_ENABLED(CONFIG_DEBUG_FS)) { err = tegra_dc_debugfs_init(dc, drm->primary); @@ -1338,10 +1497,24 @@ static int tegra_dc_init(struct host1x_client *client) if (err < 0) { dev_err(dc->dev, "failed to request IRQ#%u: %d\n", dc->irq, err); - return err; + goto cleanup; } return 0; + +cleanup: + if (cursor) + drm_plane_cleanup(cursor); + + if (primary) + drm_plane_cleanup(primary); + + if (tegra->domain) { + iommu_detach_device(tegra->domain, dc->dev); + dc->domain = NULL; + } + + return err; } static int tegra_dc_exit(struct host1x_client *client) From 8fc8f7da9719c2d28fb32cdd74af9b6cd9bac20a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Oct 2014 14:00:09 +0200 Subject: [PATCH 36/44] drm/tegra: Enable the hotplug interrupt only when necessary The hotplug handling needs access to the DRM device, which only appears at ->init() time. Disable interrupts up until that time. Similarly, when an output is removed, disable the hotplug interrupt again because the DRM device (and with it the hotplug infrastructure) is going away. Also make sure to only access the DRM device if it's available. Given the above change for the hotplug interrupt this should really never happen, but the extra check doesn't hurt either. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/output.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index e6cfbde36f97..022462d3a413 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -183,7 +183,8 @@ static irqreturn_t hpd_irq(int irq, void *data) { struct tegra_output *output = data; - drm_helper_hpd_irq_event(output->connector.dev); + if (output->connector.dev) + drm_helper_hpd_irq_event(output->connector.dev); return IRQ_HANDLED; } @@ -255,6 +256,13 @@ int tegra_output_probe(struct tegra_output *output) } output->connector.polled = DRM_CONNECTOR_POLL_HPD; + + /* + * Disable the interrupt until the connector has been + * initialized to avoid a race in the hotplug interrupt + * handler. + */ + disable_irq(output->hpd_irq); } return 0; @@ -320,10 +328,24 @@ int tegra_output_init(struct drm_device *drm, struct tegra_output *output) output->encoder.possible_crtcs = 0x3; + /* + * The connector is now registered and ready to receive hotplug events + * so the hotplug interrupt can be enabled. + */ + if (gpio_is_valid(output->hpd_gpio)) + enable_irq(output->hpd_irq); + return 0; } int tegra_output_exit(struct tegra_output *output) { + /* + * The connector is going away, so the interrupt must be disabled to + * prevent the hotplug interrupt handler from potentially crashing. + */ + if (gpio_is_valid(output->hpd_gpio)) + disable_irq(output->hpd_irq); + return 0; } From dc6057ecb39edb34b0461ca55382094410bd257a Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 30 Oct 2014 15:32:56 +0100 Subject: [PATCH 37/44] drm/tegra: gem: dumb: pitch and size are outputs When creating a dumb buffer object using the DRM_IOCTL_MODE_CREATE_DUMB IOCTL, only the width, height, bpp and flags parameters are inputs. The caller is not guaranteed to zero out or set handle, pitch and size, so the driver must not treat these values as possible inputs. Fixes a bug where running the Weston compositor on Tegra DRM would cause an attempt to allocate a 3 GiB framebuffer to be allocated. Fixes: de2ba664c30f ("gpu: host1x: drm: Add memory manager and fb") Cc: stable@vger.kernel.org Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 8b1095d05c58..8348783f7d64 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -399,16 +399,12 @@ void tegra_bo_free_object(struct drm_gem_object *gem) int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm, struct drm_mode_create_dumb *args) { - int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8); struct tegra_drm *tegra = drm->dev_private; struct tegra_bo *bo; - min_pitch = round_up(min_pitch, tegra->pitch_align); - if (args->pitch < min_pitch) - args->pitch = min_pitch; - - if (args->size < args->pitch * args->height) - args->size = args->pitch * args->height; + args->pitch = round_up(min_pitch, tegra->pitch_align); + args->size = args->pitch * args->height; bo = tegra_bo_create_with_handle(file, drm, args->size, 0, &args->handle); From 3feaf3e5ae89347a2ab3b9e994f1f3d85c09185f Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Oct 2014 13:56:59 +0200 Subject: [PATCH 38/44] drm/tegra: fb: Do not destroy framebuffer Drop a reference instead of directly calling the framebuffer .destroy() callback at fbdev free time. This is necessary to make sure the object isn't destroyed if anyone else still has a reference. Reviewed-by: Daniel Vetter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index fab39eb2dae8..ec5ae7cf4349 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -360,7 +360,7 @@ static void tegra_fbdev_exit(struct tegra_fbdev *fbdev) if (fbdev->fb) { drm_framebuffer_unregister_private(&fbdev->fb->base); - tegra_fb_destroy(&fbdev->fb->base); + drm_framebuffer_remove(&fbdev->fb->base); } drm_fb_helper_fini(&fbdev->base); From 71c38629d6bd4be74009fc73946255254477c77e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Nov 2014 13:23:02 +0100 Subject: [PATCH 39/44] drm/tegra: gem: Use more consistent data types Use size_t consistently for sizes and u32/u64 instead of uint32_t and uint64_t. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 8 ++++---- drivers/gpu/drm/tegra/gem.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 8348783f7d64..4a4f967c6397 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -264,7 +264,7 @@ static int tegra_bo_alloc(struct drm_device *drm, struct tegra_bo *bo, return 0; } -struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, +struct tegra_bo *tegra_bo_create(struct drm_device *drm, size_t size, unsigned long flags) { struct tegra_bo *bo; @@ -294,9 +294,9 @@ struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, struct drm_device *drm, - unsigned int size, + size_t size, unsigned long flags, - unsigned int *handle) + u32 *handle) { struct tegra_bo *bo; int err; @@ -415,7 +415,7 @@ int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm, } int tegra_bo_dumb_map_offset(struct drm_file *file, struct drm_device *drm, - uint32_t handle, uint64_t *offset) + u32 handle, u64 *offset) { struct drm_gem_object *gem; struct tegra_bo *bo; diff --git a/drivers/gpu/drm/tegra/gem.h b/drivers/gpu/drm/tegra/gem.h index 3dd4165f812a..6c5f12ac0087 100644 --- a/drivers/gpu/drm/tegra/gem.h +++ b/drivers/gpu/drm/tegra/gem.h @@ -52,18 +52,18 @@ static inline struct tegra_bo *to_tegra_bo(struct drm_gem_object *gem) return container_of(gem, struct tegra_bo, gem); } -struct tegra_bo *tegra_bo_create(struct drm_device *drm, unsigned int size, +struct tegra_bo *tegra_bo_create(struct drm_device *drm, size_t size, unsigned long flags); struct tegra_bo *tegra_bo_create_with_handle(struct drm_file *file, struct drm_device *drm, - unsigned int size, + size_t size, unsigned long flags, - unsigned int *handle); + u32 *handle); void tegra_bo_free_object(struct drm_gem_object *gem); int tegra_bo_dumb_create(struct drm_file *file, struct drm_device *drm, struct drm_mode_create_dumb *args); int tegra_bo_dumb_map_offset(struct drm_file *file, struct drm_device *drm, - uint32_t handle, uint64_t *offset); + u32 handle, u64 *offset); int tegra_drm_mmap(struct file *file, struct vm_area_struct *vma); From 1053f4dd82338ca82de46a23a11d51d7455b02c6 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 4 Nov 2014 16:17:55 +0100 Subject: [PATCH 40/44] drm/tegra: Plug memory leak Free the DRM device-private memory upon driver unload to make sure the memory doesn't leak. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/drm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 6c9df794b3be..e549afeece1f 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -118,6 +118,8 @@ static int tegra_drm_unload(struct drm_device *drm) drm_mm_takedown(&tegra->mm); } + kfree(tegra); + return 0; } From 9aaa0cebcb3ebabd52a2a269a50fe3525d8deed1 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 4 Nov 2014 16:20:20 +0100 Subject: [PATCH 41/44] drm/tegra: Detach panel when a connector is removed When the DRM device is torn down and the connector is removed, make sure to detach the panel to make sure there are no dangling pointers. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/output.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 022462d3a413..6a5c7b81fbc5 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -347,5 +347,8 @@ int tegra_output_exit(struct tegra_output *output) if (gpio_is_valid(output->hpd_gpio)) disable_irq(output->hpd_irq); + if (output->panel) + drm_panel_detach(output->panel); + return 0; } From b88f005ea38f29b50e55fc7eb25d92ea78a2eeab Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Nov 2014 14:33:19 +0100 Subject: [PATCH 42/44] drm/tegra: fb: Properly release GEM objects on failure When fbdev initialization fails, make sure to unreference the GEM objects properly. Note that we can't do this in the general error unwinding path because ownership of the GEM object references is transferred to the framebuffer upon creation. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/fb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index ec5ae7cf4349..a64f23af0841 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -227,7 +227,7 @@ static int tegra_fbdev_probe(struct drm_fb_helper *helper, info = framebuffer_alloc(0, drm->dev); if (!info) { dev_err(drm->dev, "failed to allocate framebuffer info\n"); - tegra_bo_free_object(&bo->gem); + drm_gem_object_unreference_unlocked(&bo->gem); return -ENOMEM; } @@ -235,6 +235,7 @@ static int tegra_fbdev_probe(struct drm_fb_helper *helper, if (IS_ERR(fbdev->fb)) { dev_err(drm->dev, "failed to allocate DRM framebuffer\n"); err = PTR_ERR(fbdev->fb); + drm_gem_object_unreference_unlocked(&bo->gem); goto release; } From cb10c81fdf8c77df4fffc8cb740ddf33427869df Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Nov 2014 14:36:19 +0100 Subject: [PATCH 43/44] drm/tegra: fb: Add error codes to error messages This helps in determining what errors happened at specifics points in the initialization sequence. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/fb.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/tegra/fb.c b/drivers/gpu/drm/tegra/fb.c index a64f23af0841..e9c715d89261 100644 --- a/drivers/gpu/drm/tegra/fb.c +++ b/drivers/gpu/drm/tegra/fb.c @@ -233,8 +233,9 @@ static int tegra_fbdev_probe(struct drm_fb_helper *helper, fbdev->fb = tegra_fb_alloc(drm, &cmd, &bo, 1); if (IS_ERR(fbdev->fb)) { - dev_err(drm->dev, "failed to allocate DRM framebuffer\n"); err = PTR_ERR(fbdev->fb); + dev_err(drm->dev, "failed to allocate DRM framebuffer: %d\n", + err); drm_gem_object_unreference_unlocked(&bo->gem); goto release; } @@ -319,19 +320,21 @@ static int tegra_fbdev_init(struct tegra_fbdev *fbdev, err = drm_fb_helper_init(drm, &fbdev->base, num_crtc, max_connectors); if (err < 0) { - dev_err(drm->dev, "failed to initialize DRM FB helper\n"); + dev_err(drm->dev, "failed to initialize DRM FB helper: %d\n", + err); return err; } err = drm_fb_helper_single_add_all_connectors(&fbdev->base); if (err < 0) { - dev_err(drm->dev, "failed to add connectors\n"); + dev_err(drm->dev, "failed to add connectors: %d\n", err); goto fini; } err = drm_fb_helper_initial_config(&fbdev->base, preferred_bpp); if (err < 0) { - dev_err(drm->dev, "failed to set initial configuration\n"); + dev_err(drm->dev, "failed to set initial configuration: %d\n", + err); goto fini; } From 7e0180e3570cc791e95e6b6cd5fbeb0aedc62776 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 6 Nov 2014 14:41:31 +0100 Subject: [PATCH 44/44] drm/tegra: gem: Check before freeing CMA memory dma_free_writecombine() must not be called on a buffer that couldn't be allocated. Check for a valid virtual address before attempting to free the memory to avoid a crash. Reported-by: Sean Paul Reviewed-by: Sean Paul Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/gem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/tegra/gem.c b/drivers/gpu/drm/tegra/gem.c index 4a4f967c6397..da32086cbeaf 100644 --- a/drivers/gpu/drm/tegra/gem.c +++ b/drivers/gpu/drm/tegra/gem.c @@ -210,7 +210,7 @@ static void tegra_bo_free(struct drm_device *drm, struct tegra_bo *bo) drm_gem_put_pages(&bo->gem, bo->pages, true, true); sg_free_table(bo->sgt); kfree(bo->sgt); - } else { + } else if (bo->vaddr) { dma_free_writecombine(drm->dev, bo->gem.size, bo->vaddr, bo->paddr); }