From 8dfebf57bd3668adbca747d54406fb3237e47b23 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 17 Jun 2016 13:03:40 +0100 Subject: pinctrl: pinconf: Add generic helper function for freeing mappings The pinconf-generic.h file exposes functions for creating generic mappings but it does not expose a function for freeing the mappings. Add a function for freeing generic mappings. Signed-off-by: Jon Hunter Signed-off-by: Linus Walleij --- drivers/pinctrl/pinconf-generic.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/pinctrl/pinconf-generic.c b/drivers/pinctrl/pinconf-generic.c index d5bf9fae2ddd..a87439ee4cdd 100644 --- a/drivers/pinctrl/pinconf-generic.c +++ b/drivers/pinctrl/pinconf-generic.c @@ -391,4 +391,12 @@ exit: } EXPORT_SYMBOL_GPL(pinconf_generic_dt_node_to_map); +void pinconf_generic_dt_free_map(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, + unsigned num_maps) +{ + pinctrl_utils_free_map(pctldev, map, num_maps); +} +EXPORT_SYMBOL_GPL(pinconf_generic_dt_free_map); + #endif -- cgit v1.2.3 From 57574bd779852bb7328ade70c951b681b54a7ece Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 18 Jun 2016 14:36:32 +0530 Subject: gpu: host1x: hw: intr_hw: Remove create_workqueue System workqueues have been able to handle high level of concurrency for a long time now and there's no reason to use dedicated workqueues just to gain concurrency. Since the workqueue host->intr_wq is involved in sync point interrupts, and sync point wait and is not being used on a memory reclaim path, dedicated host->intr_wq has been replaced with the use of system_wq. Unlike a dedicated per-cpu workqueue created with create_workqueue(), system_wq allows multiple work items to overlap executions even on the same CPU; however, a per-cpu workqueue doesn't have any CPU locality or global ordering guarantees unless the target CPU is explicitly specified and thus the increase of local concurrency shouldn't make any difference. cancel_work_sync() has been used in _host1x_free_syncpt_irq() to ensure that no work is pending by the time exit path runs. Signed-off-by: Bhaktipriya Shridhar Acked-by: Tejun Heo Signed-off-by: Thierry Reding --- drivers/gpu/host1x/dev.h | 1 - drivers/gpu/host1x/hw/intr_hw.c | 8 ++++++-- drivers/gpu/host1x/intr.c | 4 ---- 3 files changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h index dace124994bb..136c3a9d4997 100644 --- a/drivers/gpu/host1x/dev.h +++ b/drivers/gpu/host1x/dev.h @@ -109,7 +109,6 @@ struct host1x { struct clk *clk; struct mutex intr_mutex; - struct workqueue_struct *intr_wq; int intr_syncpt_irq; const struct host1x_syncpt_ops *syncpt_op; diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c index e1e31e9e67cd..10f81687b566 100644 --- a/drivers/gpu/host1x/hw/intr_hw.c +++ b/drivers/gpu/host1x/hw/intr_hw.c @@ -38,7 +38,7 @@ static void host1x_intr_syncpt_handle(struct host1x_syncpt *syncpt) host1x_sync_writel(host, BIT_MASK(id), HOST1X_SYNC_SYNCPT_THRESH_CPU0_INT_STATUS(BIT_WORD(id))); - queue_work(host->intr_wq, &syncpt->intr.work); + schedule_work(&syncpt->intr.work); } static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id) @@ -127,8 +127,12 @@ static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id) static int _host1x_free_syncpt_irq(struct host1x *host) { + int i; + devm_free_irq(host->dev, host->intr_syncpt_irq, host); - flush_workqueue(host->intr_wq); + + for (i = 0; i < host->info->nb_pts; i++) + cancel_work_sync(&host->syncpt[i].intr.work); return 0; } diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index 2491bf82e30c..81de286a2f60 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -277,9 +277,6 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync) mutex_init(&host->intr_mutex); host->intr_syncpt_irq = irq_sync; - host->intr_wq = create_workqueue("host_syncpt"); - if (!host->intr_wq) - return -ENOMEM; for (id = 0; id < nb_pts; ++id) { struct host1x_syncpt *syncpt = host->syncpt + id; @@ -299,7 +296,6 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync) void host1x_intr_deinit(struct host1x *host) { host1x_intr_stop(host); - destroy_workqueue(host->intr_wq); } void host1x_intr_start(struct host1x *host) -- cgit v1.2.3 From 14c95fc896e1d3929abde448fd86c07bdbae56d5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 22 Jun 2016 16:44:07 +0200 Subject: gpu: host1x: Consistently use unsigned int for counts The number of channels, syncpoints, bases and mlocks can never be negative, so use unsigned int instead of int. Also make loop variables the same type for consistency. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/channel.c | 2 +- drivers/gpu/host1x/debug.c | 7 ++++--- drivers/gpu/host1x/dev.h | 14 +++++++------- drivers/gpu/host1x/hw/cdma_hw.c | 2 +- drivers/gpu/host1x/hw/debug_hw.c | 11 ++++++----- drivers/gpu/host1x/hw/intr_hw.c | 9 +++++---- drivers/gpu/host1x/hw/syncpt_hw.c | 3 ++- drivers/gpu/host1x/syncpt.c | 15 ++++++++------- drivers/gpu/host1x/syncpt.h | 6 +++--- 9 files changed, 37 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c index b4ae3affb987..78f0ef0f6c89 100644 --- a/drivers/gpu/host1x/channel.c +++ b/drivers/gpu/host1x/channel.c @@ -83,7 +83,7 @@ EXPORT_SYMBOL(host1x_channel_put); struct host1x_channel *host1x_channel_request(struct device *dev) { struct host1x *host = dev_get_drvdata(dev->parent); - int max_channels = host->info->nb_channels; + unsigned int max_channels = host->info->nb_channels; struct host1x_channel *channel = NULL; int index, err; diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index ee3d12b51c50..797f8f048117 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -62,14 +62,15 @@ static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo) static void show_syncpts(struct host1x *m, struct output *o) { - int i; + unsigned int i; host1x_debug_output(o, "---- syncpts ----\n"); for (i = 0; i < host1x_syncpt_nb_pts(m); i++) { u32 max = host1x_syncpt_read_max(m->syncpt + i); u32 min = host1x_syncpt_load(m->syncpt + i); if (!min && !max) continue; - host1x_debug_output(o, "id %d (%s) min %d max %d\n", + + host1x_debug_output(o, "id %u (%s) min %d max %d\n", i, m->syncpt[i].name, min, max); } @@ -77,7 +78,7 @@ static void show_syncpts(struct host1x *m, struct output *o) u32 base_val; base_val = host1x_syncpt_load_wait_base(m->syncpt + i); if (base_val) - host1x_debug_output(o, "waitbase id %d val %d\n", i, + host1x_debug_output(o, "waitbase id %u val %d\n", i, base_val); } diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h index 136c3a9d4997..e3fd1f0694c6 100644 --- a/drivers/gpu/host1x/dev.h +++ b/drivers/gpu/host1x/dev.h @@ -90,13 +90,13 @@ struct host1x_intr_ops { }; struct host1x_info { - int nb_channels; /* host1x: num channels supported */ - int nb_pts; /* host1x: num syncpoints supported */ - int nb_bases; /* host1x: num syncpoints supported */ - int nb_mlocks; /* host1x: number of mlocks */ - int (*init)(struct host1x *); /* initialize per SoC ops */ - int sync_offset; - u64 dma_mask; /* mask of addressable memory */ + unsigned int nb_channels; /* host1x: number of channels supported */ + unsigned int nb_pts; /* host1x: number of syncpoints supported */ + unsigned int nb_bases; /* host1x: number of syncpoint bases supported */ + unsigned int nb_mlocks; /* host1x: number of mlocks supported */ + int (*init)(struct host1x *host1x); /* initialize per SoC ops */ + unsigned int sync_offset; /* offset of syncpoint registers */ + u64 dma_mask; /* mask of addressable memory */ }; struct host1x { diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 305ea8f3382d..233246e1235a 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -41,7 +41,7 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr, { struct host1x *host1x = cdma_to_host1x(cdma); struct push_buffer *pb = &cdma->push_buffer; - u32 i; + unsigned int i; for (i = 0; i < syncpt_incrs; i++) host1x_syncpt_incr(cdma->timeout.syncpt); diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c index cc3f1825c735..81e94fef5e42 100644 --- a/drivers/gpu/host1x/hw/debug_hw.c +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -143,7 +143,8 @@ static void show_channel_gathers(struct output *o, struct host1x_cdma *cdma) struct host1x_job *job; list_for_each_entry(job, &cdma->sync_queue, list) { - int i; + unsigned int i; + host1x_debug_output(o, "\n%p: JOB, syncpt_id=%d, syncpt_val=%d, first_get=%08x, timeout=%d num_slots=%d, num_handles=%d\n", job, job->syncpt_id, job->syncpt_end, job->first_get, job->timeout, @@ -290,19 +291,19 @@ static void host1x_debug_show_channel_fifo(struct host1x *host, static void host1x_debug_show_mlocks(struct host1x *host, struct output *o) { - int i; + unsigned int i; host1x_debug_output(o, "---- mlocks ----\n"); for (i = 0; i < host1x_syncpt_nb_mlocks(host); i++) { u32 owner = host1x_sync_readl(host, HOST1X_SYNC_MLOCK_OWNER(i)); if (HOST1X_SYNC_MLOCK_OWNER_CH_OWNS_V(owner)) - host1x_debug_output(o, "%d: locked by channel %d\n", + host1x_debug_output(o, "%u: locked by channel %u\n", i, HOST1X_SYNC_MLOCK_OWNER_CHID_V(owner)); else if (HOST1X_SYNC_MLOCK_OWNER_CPU_OWNS_V(owner)) - host1x_debug_output(o, "%d: locked by cpu\n", i); + host1x_debug_output(o, "%u: locked by cpu\n", i); else - host1x_debug_output(o, "%d: unlocked\n", i); + host1x_debug_output(o, "%u: unlocked\n", i); } host1x_debug_output(o, "\n"); } diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c index 10f81687b566..cf49d72fb172 100644 --- a/drivers/gpu/host1x/hw/intr_hw.c +++ b/drivers/gpu/host1x/hw/intr_hw.c @@ -45,7 +45,7 @@ static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id) { struct host1x *host = dev_id; unsigned long reg; - int i, id; + unsigned int i, id; for (i = 0; i < DIV_ROUND_UP(host->info->nb_pts, 32); i++) { reg = host1x_sync_readl(host, @@ -62,7 +62,7 @@ static irqreturn_t syncpt_thresh_isr(int irq, void *dev_id) static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host) { - u32 i; + unsigned int i; for (i = 0; i < DIV_ROUND_UP(host->info->nb_pts, 32); ++i) { host1x_sync_writel(host, 0xffffffffu, @@ -75,7 +75,8 @@ static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host) static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm, void (*syncpt_thresh_work)(struct work_struct *)) { - int i, err; + unsigned int i; + int err; host1x_hw_intr_disable_all_syncpt_intrs(host); @@ -127,7 +128,7 @@ static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id) static int _host1x_free_syncpt_irq(struct host1x *host) { - int i; + unsigned int i; devm_free_irq(host->dev, host->intr_syncpt_irq, host); diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c index 56e85395ac24..b60a4b97e4a8 100644 --- a/drivers/gpu/host1x/hw/syncpt_hw.c +++ b/drivers/gpu/host1x/hw/syncpt_hw.c @@ -26,8 +26,9 @@ */ static void syncpt_restore(struct host1x_syncpt *sp) { + u32 min = host1x_syncpt_read_min(sp); struct host1x *host = sp->host; - int min = host1x_syncpt_read_min(sp); + host1x_sync_writel(host, min, HOST1X_SYNC_SYNCPT(sp->id)); } diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 6b7fdc1e2ed0..e2c499aa33e1 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -110,7 +110,7 @@ EXPORT_SYMBOL(host1x_syncpt_incr_max); void host1x_syncpt_restore(struct host1x *host) { struct host1x_syncpt *sp_base = host->syncpt; - u32 i; + unsigned int i; for (i = 0; i < host1x_syncpt_nb_pts(host); i++) host1x_hw_syncpt_restore(host, sp_base + i); @@ -126,7 +126,7 @@ void host1x_syncpt_restore(struct host1x *host) void host1x_syncpt_save(struct host1x *host) { struct host1x_syncpt *sp_base = host->syncpt; - u32 i; + unsigned int i; for (i = 0; i < host1x_syncpt_nb_pts(host); i++) { if (host1x_syncpt_client_managed(sp_base + i)) @@ -341,7 +341,7 @@ int host1x_syncpt_init(struct host1x *host) { struct host1x_syncpt_base *bases; struct host1x_syncpt *syncpt; - int i; + unsigned int i; syncpt = devm_kzalloc(host->dev, sizeof(*syncpt) * host->info->nb_pts, GFP_KERNEL); @@ -398,8 +398,9 @@ EXPORT_SYMBOL(host1x_syncpt_free); void host1x_syncpt_deinit(struct host1x *host) { - int i; struct host1x_syncpt *sp = host->syncpt; + unsigned int i; + for (i = 0; i < host->info->nb_pts; i++, sp++) kfree(sp->name); } @@ -431,17 +432,17 @@ u32 host1x_syncpt_read(struct host1x_syncpt *sp) } EXPORT_SYMBOL(host1x_syncpt_read); -int host1x_syncpt_nb_pts(struct host1x *host) +unsigned int host1x_syncpt_nb_pts(struct host1x *host) { return host->info->nb_pts; } -int host1x_syncpt_nb_bases(struct host1x *host) +unsigned int host1x_syncpt_nb_bases(struct host1x *host) { return host->info->nb_bases; } -int host1x_syncpt_nb_mlocks(struct host1x *host) +unsigned int host1x_syncpt_nb_mlocks(struct host1x *host) { return host->info->nb_mlocks; } diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h index 9056465ecd3f..6916feff0c29 100644 --- a/drivers/gpu/host1x/syncpt.h +++ b/drivers/gpu/host1x/syncpt.h @@ -58,13 +58,13 @@ int host1x_syncpt_init(struct host1x *host); void host1x_syncpt_deinit(struct host1x *host); /* Return number of sync point supported. */ -int host1x_syncpt_nb_pts(struct host1x *host); +unsigned int host1x_syncpt_nb_pts(struct host1x *host); /* Return number of wait bases supported. */ -int host1x_syncpt_nb_bases(struct host1x *host); +unsigned int host1x_syncpt_nb_bases(struct host1x *host); /* Return number of mlocks supported. */ -int host1x_syncpt_nb_mlocks(struct host1x *host); +unsigned int host1x_syncpt_nb_mlocks(struct host1x *host); /* * Check sync point sanity. If max is larger than min, there have too many -- cgit v1.2.3 From 5c0d8d386ba67ce07bfddcebf05233e8606771ff Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:19:00 +0200 Subject: gpu: host1x: Use unsigned int consistently for IDs IDs can never be negative so use unsigned int. In some instances an explicitly sized type (such as u32) was used for no particular reason, so turn those into unsigned int as well for consistency. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/dev.h | 23 ++++++++++++----------- drivers/gpu/host1x/hw/cdma_hw.c | 6 +++--- drivers/gpu/host1x/hw/channel_hw.c | 3 ++- drivers/gpu/host1x/hw/debug_hw.c | 4 ++-- drivers/gpu/host1x/hw/intr_hw.c | 9 ++++++--- drivers/gpu/host1x/intr.c | 8 ++++---- drivers/gpu/host1x/intr.h | 4 ++-- drivers/gpu/host1x/job.c | 2 +- drivers/gpu/host1x/syncpt.c | 6 +++--- drivers/gpu/host1x/syncpt.h | 2 +- 10 files changed, 36 insertions(+), 31 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/dev.h b/drivers/gpu/host1x/dev.h index e3fd1f0694c6..5220510f39da 100644 --- a/drivers/gpu/host1x/dev.h +++ b/drivers/gpu/host1x/dev.h @@ -45,7 +45,7 @@ struct host1x_cdma_ops { void (*start)(struct host1x_cdma *cdma); void (*stop)(struct host1x_cdma *cdma); void (*flush)(struct host1x_cdma *cdma); - int (*timeout_init)(struct host1x_cdma *cdma, u32 syncpt_id); + int (*timeout_init)(struct host1x_cdma *cdma, unsigned int syncpt); void (*timeout_destroy)(struct host1x_cdma *cdma); void (*freeze)(struct host1x_cdma *cdma); void (*resume)(struct host1x_cdma *cdma, u32 getptr); @@ -82,9 +82,9 @@ struct host1x_intr_ops { int (*init_host_sync)(struct host1x *host, u32 cpm, void (*syncpt_thresh_work)(struct work_struct *work)); void (*set_syncpt_threshold)( - struct host1x *host, u32 id, u32 thresh); - void (*enable_syncpt_intr)(struct host1x *host, u32 id); - void (*disable_syncpt_intr)(struct host1x *host, u32 id); + struct host1x *host, unsigned int id, u32 thresh); + void (*enable_syncpt_intr)(struct host1x *host, unsigned int id); + void (*disable_syncpt_intr)(struct host1x *host, unsigned int id); void (*disable_all_syncpt_intrs)(struct host1x *host); int (*free_syncpt_irq)(struct host1x *host); }; @@ -182,19 +182,20 @@ static inline int host1x_hw_intr_init_host_sync(struct host1x *host, u32 cpm, } static inline void host1x_hw_intr_set_syncpt_threshold(struct host1x *host, - u32 id, u32 thresh) + unsigned int id, + u32 thresh) { host->intr_op->set_syncpt_threshold(host, id, thresh); } static inline void host1x_hw_intr_enable_syncpt_intr(struct host1x *host, - u32 id) + unsigned int id) { host->intr_op->enable_syncpt_intr(host, id); } static inline void host1x_hw_intr_disable_syncpt_intr(struct host1x *host, - u32 id) + unsigned int id) { host->intr_op->disable_syncpt_intr(host, id); } @@ -211,9 +212,9 @@ static inline int host1x_hw_intr_free_syncpt_irq(struct host1x *host) static inline int host1x_hw_channel_init(struct host1x *host, struct host1x_channel *channel, - int chid) + unsigned int id) { - return host->channel_op->init(channel, host, chid); + return host->channel_op->init(channel, host, id); } static inline int host1x_hw_channel_submit(struct host1x *host, @@ -242,9 +243,9 @@ static inline void host1x_hw_cdma_flush(struct host1x *host, static inline int host1x_hw_cdma_timeout_init(struct host1x *host, struct host1x_cdma *cdma, - u32 syncpt_id) + unsigned int syncpt) { - return host->cdma_op->timeout_init(cdma, syncpt_id); + return host->cdma_op->timeout_init(cdma, syncpt); } static inline void host1x_hw_cdma_timeout_destroy(struct host1x *host, diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 233246e1235a..e0db00613bca 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -213,7 +213,7 @@ static void cdma_resume(struct host1x_cdma *cdma, u32 getptr) u32 cmdproc_stop; dev_dbg(host1x->dev, - "resuming channel (id %d, DMAGET restart = 0x%x)\n", + "resuming channel (id %u, DMAGET restart = 0x%x)\n", ch->id, getptr); cmdproc_stop = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP); @@ -277,7 +277,7 @@ static void cdma_timeout_handler(struct work_struct *work) return; } - dev_warn(host1x->dev, "%s: timeout: %d (%s), HW thresh %d, done %d\n", + dev_warn(host1x->dev, "%s: timeout: %u (%s), HW thresh %d, done %d\n", __func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name, syncpt_val, cdma->timeout.syncpt_val); @@ -291,7 +291,7 @@ static void cdma_timeout_handler(struct work_struct *work) /* * Init timeout resources */ -static int cdma_timeout_init(struct host1x_cdma *cdma, u32 syncpt_id) +static int cdma_timeout_init(struct host1x_cdma *cdma, unsigned int syncpt) { INIT_DELAYED_WORK(&cdma->timeout.wq, cdma_timeout_handler); cdma->timeout.initialized = true; diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index 946c332c3906..f359b7ec5e3c 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -75,7 +75,8 @@ static inline void synchronize_syncpt_base(struct host1x_job *job) { struct host1x *host = dev_get_drvdata(job->channel->dev->parent); struct host1x_syncpt *sp = host->syncpt + job->syncpt_id; - u32 id, value; + unsigned int id; + u32 value; value = host1x_syncpt_read_max(sp); id = sp->base->id; diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c index 81e94fef5e42..47da9a62ff7e 100644 --- a/drivers/gpu/host1x/hw/debug_hw.c +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -191,7 +191,7 @@ static void host1x_debug_show_channel_cdma(struct host1x *host, cbread = host1x_sync_readl(host, HOST1X_SYNC_CBREAD(ch->id)); cbstat = host1x_sync_readl(host, HOST1X_SYNC_CBSTAT(ch->id)); - host1x_debug_output(o, "%d-%s: ", ch->id, dev_name(ch->dev)); + host1x_debug_output(o, "%u-%s: ", ch->id, dev_name(ch->dev)); if (HOST1X_CHANNEL_DMACTRL_DMASTOP_V(dmactrl) || !ch->cdma.push_buffer.mapped) { @@ -237,7 +237,7 @@ static void host1x_debug_show_channel_fifo(struct host1x *host, u32 val, rd_ptr, wr_ptr, start, end; unsigned int data_count = 0; - host1x_debug_output(o, "%d: fifo:\n", ch->id); + host1x_debug_output(o, "%u: fifo:\n", ch->id); val = host1x_ch_readl(ch, HOST1X_CHANNEL_FIFOSTAT); host1x_debug_output(o, "FIFOSTAT %08x\n", val); diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c index cf49d72fb172..f50c68957c12 100644 --- a/drivers/gpu/host1x/hw/intr_hw.c +++ b/drivers/gpu/host1x/hw/intr_hw.c @@ -107,18 +107,21 @@ static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm, } static void _host1x_intr_set_syncpt_threshold(struct host1x *host, - u32 id, u32 thresh) + unsigned int id, + u32 thresh) { host1x_sync_writel(host, thresh, HOST1X_SYNC_SYNCPT_INT_THRESH(id)); } -static void _host1x_intr_enable_syncpt_intr(struct host1x *host, u32 id) +static void _host1x_intr_enable_syncpt_intr(struct host1x *host, + unsigned int id) { host1x_sync_writel(host, BIT_MASK(id), HOST1X_SYNC_SYNCPT_THRESH_INT_ENABLE_CPU0(BIT_WORD(id))); } -static void _host1x_intr_disable_syncpt_intr(struct host1x *host, u32 id) +static void _host1x_intr_disable_syncpt_intr(struct host1x *host, + unsigned int id) { host1x_sync_writel(host, BIT_MASK(id), HOST1X_SYNC_SYNCPT_THRESH_INT_DISABLE(BIT_WORD(id))); diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index 81de286a2f60..c85ca44ff461 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -209,7 +209,7 @@ static void syncpt_thresh_work(struct work_struct *work) host1x_syncpt_load(host->syncpt + id)); } -int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh, +int host1x_intr_add_action(struct host1x *host, unsigned int id, u32 thresh, enum host1x_intr_action action, void *data, struct host1x_waitlist *waiter, void **ref) { @@ -254,7 +254,7 @@ int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh, return 0; } -void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref) +void host1x_intr_put_ref(struct host1x *host, unsigned int id, void *ref) { struct host1x_waitlist *waiter = ref; struct host1x_syncpt *syncpt; @@ -285,7 +285,7 @@ int host1x_intr_init(struct host1x *host, unsigned int irq_sync) INIT_LIST_HEAD(&syncpt->intr.wait_head); snprintf(syncpt->intr.thresh_irq_name, sizeof(syncpt->intr.thresh_irq_name), - "host1x_sp_%02d", id); + "host1x_sp_%02u", id); } host1x_intr_start(host); @@ -338,7 +338,7 @@ void host1x_intr_stop(struct host1x *host) if (!list_empty(&syncpt[id].intr.wait_head)) { /* output diagnostics */ mutex_unlock(&host->intr_mutex); - pr_warn("%s cannot stop syncpt intr id=%d\n", + pr_warn("%s cannot stop syncpt intr id=%u\n", __func__, id); return; } diff --git a/drivers/gpu/host1x/intr.h b/drivers/gpu/host1x/intr.h index 2b8adf016a05..1370c2bb75b8 100644 --- a/drivers/gpu/host1x/intr.h +++ b/drivers/gpu/host1x/intr.h @@ -75,7 +75,7 @@ struct host1x_waitlist { * * This is a non-blocking api. */ -int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh, +int host1x_intr_add_action(struct host1x *host, unsigned int id, u32 thresh, enum host1x_intr_action action, void *data, struct host1x_waitlist *waiter, void **ref); @@ -84,7 +84,7 @@ int host1x_intr_add_action(struct host1x *host, u32 id, u32 thresh, * You must call this if you passed non-NULL as ref. * @ref the ref returned from host1x_intr_add_action() */ -void host1x_intr_put_ref(struct host1x *host, u32 id, void *ref); +void host1x_intr_put_ref(struct host1x *host, unsigned int id, void *ref); /* Initialize host1x sync point interrupt */ int host1x_intr_init(struct host1x *host, unsigned int irq_sync); diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index b4515d544039..626707585d9b 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -161,7 +161,7 @@ static int do_waitchks(struct host1x_job *job, struct host1x *host, if (host1x_syncpt_is_expired(sp, wait->thresh)) { dev_dbg(host->dev, - "drop WAIT id %d (%s) thresh 0x%x, min 0x%x\n", + "drop WAIT id %u (%s) thresh 0x%x, min 0x%x\n", wait->syncpt_id, sp->name, wait->thresh, host1x_syncpt_read_min(sp)); diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index e2c499aa33e1..3e9119301f6c 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -73,7 +73,7 @@ static struct host1x_syncpt *host1x_syncpt_alloc(struct host1x *host, return NULL; } - name = kasprintf(GFP_KERNEL, "%02d-%s", sp->id, + name = kasprintf(GFP_KERNEL, "%02u-%s", sp->id, dev ? dev_name(dev) : NULL); if (!name) return NULL; @@ -255,7 +255,7 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, timeout -= check; if (timeout && check_count <= MAX_STUCK_CHECK_COUNT) { dev_warn(sp->host->dev, - "%s: syncpoint id %d (%s) stuck waiting %d, timeout=%ld\n", + "%s: syncpoint id %u (%s) stuck waiting %d, timeout=%ld\n", current->comm, sp->id, sp->name, thresh, timeout); @@ -447,7 +447,7 @@ unsigned int host1x_syncpt_nb_mlocks(struct host1x *host) return host->info->nb_mlocks; } -struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, u32 id) +struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, unsigned int id) { if (host->info->nb_pts < id) return NULL; diff --git a/drivers/gpu/host1x/syncpt.h b/drivers/gpu/host1x/syncpt.h index 6916feff0c29..f719205105ac 100644 --- a/drivers/gpu/host1x/syncpt.h +++ b/drivers/gpu/host1x/syncpt.h @@ -37,7 +37,7 @@ struct host1x_syncpt_base { }; struct host1x_syncpt { - int id; + unsigned int id; atomic_t min_val; atomic_t max_val; u32 base_val; -- cgit v1.2.3 From e18e33afa4011b0670e5040eda1b8a2d0b995e4e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 22 Jun 2016 16:47:30 +0200 Subject: gpu: host1x: channel: Use correct type find_first_zero_bit() returns an unsigned long, so make the local variable that stores the result the same type for consistency. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/channel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/channel.c b/drivers/gpu/host1x/channel.c index 78f0ef0f6c89..8f437d924c10 100644 --- a/drivers/gpu/host1x/channel.c +++ b/drivers/gpu/host1x/channel.c @@ -85,7 +85,8 @@ struct host1x_channel *host1x_channel_request(struct device *dev) struct host1x *host = dev_get_drvdata(dev->parent); unsigned int max_channels = host->info->nb_channels; struct host1x_channel *channel = NULL; - int index, err; + unsigned long index; + int err; mutex_lock(&host->chlist_mutex); -- cgit v1.2.3 From ebb2475c4731d9c9940db6ff6514fab723e1f489 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:21:51 +0200 Subject: gpu: host1x: cdma: Drop unnecessary local variable The local 'pos' variable doesn't serve any purpose other than being a shortcut for pb->pos, but the result doesn't remove much, so simply drop the local variable. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/cdma.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index a18db4d5347c..fe1b216b8d17 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -96,12 +96,12 @@ fail: */ static void host1x_pushbuffer_push(struct push_buffer *pb, u32 op1, u32 op2) { - u32 pos = pb->pos; - u32 *p = (u32 *)((void *)pb->mapped + pos); - WARN_ON(pos == pb->fence); + u32 *p = (u32 *)((void *)pb->mapped + pb->pos); + + WARN_ON(pb->pos == pb->fence); *(p++) = op1; *(p++) = op2; - pb->pos = (pos + 8) & (pb->size_bytes - 1); + pb->pos = (pb->pos + 8) & (pb->size_bytes - 1); } /* -- cgit v1.2.3 From b47a04919680185c3bdaa8f8d832addd38172a65 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:24:59 +0200 Subject: gpu: host1x: syncpt: Use kcalloc() instead of kzalloc() Use kcalloc() to allocate arrays rather than passing the product of the size per element by the number of elements to kzalloc(). Signed-off-by: Thierry Reding --- drivers/gpu/host1x/syncpt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 3e9119301f6c..d70aad5c9f05 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -343,12 +343,12 @@ int host1x_syncpt_init(struct host1x *host) struct host1x_syncpt *syncpt; unsigned int i; - syncpt = devm_kzalloc(host->dev, sizeof(*syncpt) * host->info->nb_pts, + syncpt = devm_kcalloc(host->dev, host->info->nb_pts, sizeof(*syncpt), GFP_KERNEL); if (!syncpt) return -ENOMEM; - bases = devm_kzalloc(host->dev, sizeof(*bases) * host->info->nb_bases, + bases = devm_kcalloc(host->dev, host->info->nb_bases, sizeof(*bases), GFP_KERNEL); if (!bases) return -ENOMEM; -- cgit v1.2.3 From 6df633d0dc333ffce683f46f2856024479b170b9 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:33:31 +0200 Subject: gpu: host1x: Fix a couple of checkpatch warnings Fix a couple of occurrences where no blank line was used to separate variable declarations from code or where block comments were wrongly formatted. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/cdma.c | 8 ++++++-- drivers/gpu/host1x/debug.c | 3 +++ drivers/gpu/host1x/dev.c | 2 +- drivers/gpu/host1x/hw/debug_hw.c | 7 +++---- drivers/gpu/host1x/hw/syncpt_hw.c | 2 ++ drivers/gpu/host1x/intr.c | 2 ++ drivers/gpu/host1x/job.c | 3 +++ drivers/gpu/host1x/syncpt.c | 5 ++++- 8 files changed, 24 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index fe1b216b8d17..b83b0a04d9c5 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -247,6 +247,7 @@ static void update_cdma_locked(struct host1x_cdma *cdma) /* Pop push buffer slots */ if (job->num_slots) { struct push_buffer *pb = &cdma->push_buffer; + host1x_pushbuffer_pop(pb, job->num_slots); if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE) signal = true; @@ -342,9 +343,11 @@ void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma, syncpt_val += syncpt_incrs; } - /* The following sumbits from the same client may be dependent on the + /* + * The following sumbits from the same client may be dependent on the * failed submit and therefore they may fail. Force a small timeout - * to make the queue cleanup faster */ + * to make the queue cleanup faster. + */ list_for_each_entry_from(job, &cdma->sync_queue, list) if (job->client == cdma->timeout.client) @@ -410,6 +413,7 @@ int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job) /* init state on first submit with timeout value */ if (!cdma->timeout.initialized) { int err; + err = host1x_hw_cdma_timeout_init(host1x, cdma, job->syncpt_id); if (err) { diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index 797f8f048117..7bb86b92ff09 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -63,10 +63,12 @@ static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo) static void show_syncpts(struct host1x *m, struct output *o) { unsigned int i; + host1x_debug_output(o, "---- syncpts ----\n"); for (i = 0; i < host1x_syncpt_nb_pts(m); i++) { u32 max = host1x_syncpt_read_max(m->syncpt + i); u32 min = host1x_syncpt_load(m->syncpt + i); + if (!min && !max) continue; @@ -76,6 +78,7 @@ static void show_syncpts(struct host1x *m, struct output *o) for (i = 0; i < host1x_syncpt_nb_bases(m); i++) { u32 base_val; + base_val = host1x_syncpt_load_wait_base(m->syncpt + i); if (base_val) host1x_debug_output(o, "waitbase id %u val %d\n", i, diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index ff348690df94..320caf99cff4 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -102,7 +102,7 @@ static const struct host1x_info host1x05_info = { .dma_mask = DMA_BIT_MASK(34), }; -static struct of_device_id host1x_of_match[] = { +static const struct of_device_id host1x_of_match[] = { { .compatible = "nvidia,tegra210-host1x", .data = &host1x05_info, }, { .compatible = "nvidia,tegra124-host1x", .data = &host1x04_info, }, { .compatible = "nvidia,tegra114-host1x", .data = &host1x02_info, }, diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c index 47da9a62ff7e..d2c96dd630ea 100644 --- a/drivers/gpu/host1x/hw/debug_hw.c +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -51,12 +51,11 @@ static unsigned int show_channel_command(struct output *o, u32 val) val >> 6 & 0x3ff, val >> 16 & 0xfff, mask); return hweight8(mask); - } else { - host1x_debug_output(o, "SETCL(class=%03x)\n", - val >> 6 & 0x3ff); - return 0; } + host1x_debug_output(o, "SETCL(class=%03x)\n", val >> 6 & 0x3ff); + return 0; + case HOST1X_OPCODE_INCR: host1x_debug_output(o, "INCR(offset=%03x, [", val >> 16 & 0xfff); diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c index b60a4b97e4a8..310f24fcd261 100644 --- a/drivers/gpu/host1x/hw/syncpt_hw.c +++ b/drivers/gpu/host1x/hw/syncpt_hw.c @@ -38,6 +38,7 @@ static void syncpt_restore(struct host1x_syncpt *sp) static void syncpt_restore_wait_base(struct host1x_syncpt *sp) { struct host1x *host = sp->host; + host1x_sync_writel(host, sp->base_val, HOST1X_SYNC_SYNCPT_BASE(sp->id)); } @@ -48,6 +49,7 @@ static void syncpt_restore_wait_base(struct host1x_syncpt *sp) static void syncpt_read_wait_base(struct host1x_syncpt *sp) { struct host1x *host = sp->host; + sp->base_val = host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(sp->id)); } diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index c85ca44ff461..a04d10407639 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -122,12 +122,14 @@ static void action_submit_complete(struct host1x_waitlist *waiter) static void action_wakeup(struct host1x_waitlist *waiter) { wait_queue_head_t *wq = waiter->data; + wake_up(wq); } static void action_wakeup_interruptible(struct host1x_waitlist *waiter) { wait_queue_head_t *wq = waiter->data; + wake_up_interruptible(wq); } diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 626707585d9b..1bff367f8830 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -464,6 +464,7 @@ static inline int copy_gathers(struct host1x_job *job, struct device *dev) for (i = 0; i < job->num_gathers; i++) { struct host1x_job_gather *g = &job->gathers[i]; + size += g->words * sizeof(u32); } @@ -514,6 +515,7 @@ int host1x_job_pin(struct host1x_job *job, struct device *dev) bitmap_zero(waitchk_mask, host1x_syncpt_nb_pts(host)); for (i = 0; i < job->num_waitchk; i++) { u32 syncpt_id = job->waitchk[i].syncpt_id; + if (syncpt_id < host1x_syncpt_nb_pts(host)) set_bit(syncpt_id, waitchk_mask); } @@ -571,6 +573,7 @@ void host1x_job_unpin(struct host1x_job *job) for (i = 0; i < job->num_unpins; i++) { struct host1x_job_unpin_data *unpin = &job->unpins[i]; + host1x_bo_unpin(unpin->bo, unpin->sgt); host1x_bo_put(unpin->bo); } diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index d70aad5c9f05..7c4848f4ce59 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -146,6 +146,7 @@ void host1x_syncpt_save(struct host1x *host) u32 host1x_syncpt_load(struct host1x_syncpt *sp) { u32 val; + val = host1x_hw_syncpt_load(sp->host, sp); trace_host1x_syncpt_load_min(sp->id, val); @@ -158,6 +159,7 @@ u32 host1x_syncpt_load(struct host1x_syncpt *sp) u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp) { u32 val; + host1x_hw_syncpt_load_wait_base(sp->host, sp); val = sp->base_val; return val; @@ -279,6 +281,7 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh) { u32 current_val; u32 future_val; + smp_rmb(); current_val = (u32)atomic_read(&sp->min_val); future_val = (u32)atomic_read(&sp->max_val); @@ -408,7 +411,7 @@ void host1x_syncpt_deinit(struct host1x *host) /* * Read max. It indicates how many operations there are in queue, either in * channel or in a software thread. - * */ + */ u32 host1x_syncpt_read_max(struct host1x_syncpt *sp) { smp_rmb(); -- cgit v1.2.3 From 0b8070d12ee2ef62e0b07213d843eb508fba966e Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:35:50 +0200 Subject: gpu: host1x: Whitespace cleanup for readability Insert a number of blank lines in places where they increase readability of the code. Also collapse various variable declarations to shorten some functions and finally rewrite some code for readability. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/cdma.c | 26 ++++++++++++++++++-------- drivers/gpu/host1x/debug.c | 28 ++++++++++++++++++++-------- drivers/gpu/host1x/dev.c | 14 +++++++------- drivers/gpu/host1x/hw/cdma_hw.c | 13 +++++++------ drivers/gpu/host1x/hw/channel_hw.c | 2 ++ drivers/gpu/host1x/hw/debug_hw.c | 14 +++++++------- drivers/gpu/host1x/hw/intr_hw.c | 6 ++++-- drivers/gpu/host1x/hw/syncpt_hw.c | 5 +++-- drivers/gpu/host1x/job.c | 3 ++- drivers/gpu/host1x/syncpt.c | 24 ++++++++++++++++++++++-- 10 files changed, 92 insertions(+), 43 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/cdma.c b/drivers/gpu/host1x/cdma.c index b83b0a04d9c5..c5d82a8a2ec9 100644 --- a/drivers/gpu/host1x/cdma.c +++ b/drivers/gpu/host1x/cdma.c @@ -134,14 +134,19 @@ unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma, enum cdma_event event) { for (;;) { + struct push_buffer *pb = &cdma->push_buffer; unsigned int space; - if (event == CDMA_EVENT_SYNC_QUEUE_EMPTY) + switch (event) { + case CDMA_EVENT_SYNC_QUEUE_EMPTY: space = list_empty(&cdma->sync_queue) ? 1 : 0; - else if (event == CDMA_EVENT_PUSH_BUFFER_SPACE) { - struct push_buffer *pb = &cdma->push_buffer; + break; + + case CDMA_EVENT_PUSH_BUFFER_SPACE: space = host1x_pushbuffer_space(pb); - } else { + break; + + default: WARN_ON(1); return -EINVAL; } @@ -159,12 +164,14 @@ unsigned int host1x_cdma_wait_locked(struct host1x_cdma *cdma, mutex_lock(&cdma->lock); continue; } + cdma->event = event; mutex_unlock(&cdma->lock); down(&cdma->sem); mutex_lock(&cdma->lock); } + return 0; } @@ -234,6 +241,7 @@ static void update_cdma_locked(struct host1x_cdma *cdma) /* Start timer on next pending syncpt */ if (job->timeout) cdma_start_timer_locked(cdma, job); + break; } @@ -249,6 +257,7 @@ static void update_cdma_locked(struct host1x_cdma *cdma) struct push_buffer *pb = &cdma->push_buffer; host1x_pushbuffer_pop(pb, job->num_slots); + if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE) signal = true; } @@ -270,11 +279,9 @@ static void update_cdma_locked(struct host1x_cdma *cdma) void host1x_cdma_update_sync_queue(struct host1x_cdma *cdma, struct device *dev) { - u32 restart_addr; - u32 syncpt_incrs; - struct host1x_job *job = NULL; - u32 syncpt_val; struct host1x *host1x = cdma_to_host1x(cdma); + u32 restart_addr, syncpt_incrs, syncpt_val; + struct host1x_job *job = NULL; syncpt_val = host1x_syncpt_load(cdma->timeout.syncpt); @@ -378,6 +385,7 @@ int host1x_cdma_init(struct host1x_cdma *cdma) err = host1x_pushbuffer_init(&cdma->push_buffer); if (err) return err; + return 0; } @@ -422,6 +430,7 @@ int host1x_cdma_begin(struct host1x_cdma *cdma, struct host1x_job *job) } } } + if (!cdma->running) host1x_hw_cdma_start(host1x, cdma); @@ -452,6 +461,7 @@ void host1x_cdma_push(struct host1x_cdma *cdma, u32 op1, u32 op2) slots_free = host1x_cdma_wait_locked(cdma, CDMA_EVENT_PUSH_BUFFER_SPACE); } + cdma->slots_free = slots_free - 1; cdma->slots_used++; host1x_pushbuffer_push(pb, op1, op2); diff --git a/drivers/gpu/host1x/debug.c b/drivers/gpu/host1x/debug.c index 7bb86b92ff09..d9330fcc62ad 100644 --- a/drivers/gpu/host1x/debug.c +++ b/drivers/gpu/host1x/debug.c @@ -39,6 +39,7 @@ void host1x_debug_output(struct output *o, const char *fmt, ...) va_start(args, fmt); len = vsnprintf(o->buf, sizeof(o->buf), fmt, args); va_end(args); + o->fn(o->ctx, o->buf, len); } @@ -48,13 +49,17 @@ static int show_channels(struct host1x_channel *ch, void *data, bool show_fifo) struct output *o = data; mutex_lock(&ch->reflock); + if (ch->refcount) { mutex_lock(&ch->cdma.lock); + if (show_fifo) host1x_hw_show_channel_fifo(m, ch, o); + host1x_hw_show_channel_cdma(m, ch, o); mutex_unlock(&ch->cdma.lock); } + mutex_unlock(&ch->reflock); return 0; @@ -65,6 +70,7 @@ static void show_syncpts(struct host1x *m, struct output *o) unsigned int i; host1x_debug_output(o, "---- syncpts ----\n"); + for (i = 0; i < host1x_syncpt_nb_pts(m); i++) { u32 max = host1x_syncpt_read_max(m->syncpt + i); u32 min = host1x_syncpt_load(m->syncpt + i); @@ -118,7 +124,9 @@ static int host1x_debug_show_all(struct seq_file *s, void *unused) .fn = write_to_seqfile, .ctx = s }; + show_all(s->private, &o); + return 0; } @@ -128,7 +136,9 @@ static int host1x_debug_show(struct seq_file *s, void *unused) .fn = write_to_seqfile, .ctx = s }; + show_all_no_fifo(s->private, &o); + return 0; } @@ -138,10 +148,10 @@ static int host1x_debug_open_all(struct inode *inode, struct file *file) } static const struct file_operations host1x_debug_all_fops = { - .open = host1x_debug_open_all, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, + .open = host1x_debug_open_all, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static int host1x_debug_open(struct inode *inode, struct file *file) @@ -150,10 +160,10 @@ static int host1x_debug_open(struct inode *inode, struct file *file) } static const struct file_operations host1x_debug_fops = { - .open = host1x_debug_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, + .open = host1x_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static void host1x_debugfs_init(struct host1x *host1x) @@ -205,6 +215,7 @@ void host1x_debug_dump(struct host1x *host1x) struct output o = { .fn = write_to_printk }; + show_all(host1x, &o); } @@ -213,5 +224,6 @@ void host1x_debug_dump_syncpts(struct host1x *host1x) struct output o = { .fn = write_to_printk }; + show_syncpts(host1x, &o); } diff --git a/drivers/gpu/host1x/dev.c b/drivers/gpu/host1x/dev.c index 320caf99cff4..a62317af76ad 100644 --- a/drivers/gpu/host1x/dev.c +++ b/drivers/gpu/host1x/dev.c @@ -63,13 +63,13 @@ u32 host1x_ch_readl(struct host1x_channel *ch, u32 r) } static const struct host1x_info host1x01_info = { - .nb_channels = 8, - .nb_pts = 32, - .nb_mlocks = 16, - .nb_bases = 8, - .init = host1x01_init, - .sync_offset = 0x3000, - .dma_mask = DMA_BIT_MASK(32), + .nb_channels = 8, + .nb_pts = 32, + .nb_mlocks = 16, + .nb_bases = 8, + .init = host1x01_init, + .sync_offset = 0x3000, + .dma_mask = DMA_BIT_MASK(32), }; static const struct host1x_info host1x02_info = { diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index e0db00613bca..5c39f1b111f7 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -58,6 +58,7 @@ static void cdma_timeout_cpu_incr(struct host1x_cdma *cdma, u32 getptr, &pb->phys, getptr); getptr = (getptr + 8) & (pb->size_bytes - 1); } + wmb(); } @@ -162,12 +163,14 @@ static void cdma_stop(struct host1x_cdma *cdma) struct host1x_channel *ch = cdma_to_channel(cdma); mutex_lock(&cdma->lock); + if (cdma->running) { host1x_cdma_wait_locked(cdma, CDMA_EVENT_SYNC_QUEUE_EMPTY); host1x_ch_writel(ch, HOST1X_CHANNEL_DMACTRL_DMASTOP, HOST1X_CHANNEL_DMACTRL); cdma->running = false; } + mutex_unlock(&cdma->lock); } @@ -231,14 +234,11 @@ static void cdma_resume(struct host1x_cdma *cdma, u32 getptr) */ static void cdma_timeout_handler(struct work_struct *work) { + u32 prev_cmdproc, cmdproc_stop, syncpt_val; struct host1x_cdma *cdma; struct host1x *host1x; struct host1x_channel *ch; - u32 syncpt_val; - - u32 prev_cmdproc, cmdproc_stop; - cdma = container_of(to_delayed_work(work), struct host1x_cdma, timeout.wq); host1x = cdma_to_host1x(cdma); @@ -278,8 +278,8 @@ static void cdma_timeout_handler(struct work_struct *work) } dev_warn(host1x->dev, "%s: timeout: %u (%s), HW thresh %d, done %d\n", - __func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name, - syncpt_val, cdma->timeout.syncpt_val); + __func__, cdma->timeout.syncpt->id, cdma->timeout.syncpt->name, + syncpt_val, cdma->timeout.syncpt_val); /* stop HW, resetting channel/module */ host1x_hw_cdma_freeze(host1x, cdma); @@ -306,6 +306,7 @@ static void cdma_timeout_destroy(struct host1x_cdma *cdma) { if (cdma->timeout.initialized) cancel_delayed_work(&cdma->timeout.wq); + cdma->timeout.initialized = false; } diff --git a/drivers/gpu/host1x/hw/channel_hw.c b/drivers/gpu/host1x/hw/channel_hw.c index f359b7ec5e3c..5e8df78b7acd 100644 --- a/drivers/gpu/host1x/hw/channel_hw.c +++ b/drivers/gpu/host1x/hw/channel_hw.c @@ -46,6 +46,7 @@ static void trace_write_gather(struct host1x_cdma *cdma, struct host1x_bo *bo, */ for (i = 0; i < words; i += TRACE_MAX_LENGTH) { u32 num_words = min(words - i, TRACE_MAX_LENGTH); + offset += i * sizeof(u32); trace_host1x_cdma_push_gather(dev_name(dev), bo, @@ -66,6 +67,7 @@ static void submit_gathers(struct host1x_job *job) struct host1x_job_gather *g = &job->gathers[i]; u32 op1 = host1x_opcode_gather(g->words); u32 op2 = g->base + g->offset; + trace_write_gather(cdma, g->bo, g->offset, op1 & 0xffff); host1x_cdma_push(cdma, op1, op2); } diff --git a/drivers/gpu/host1x/hw/debug_hw.c b/drivers/gpu/host1x/hw/debug_hw.c index d2c96dd630ea..7a4a3286e4a7 100644 --- a/drivers/gpu/host1x/hw/debug_hw.c +++ b/drivers/gpu/host1x/hw/debug_hw.c @@ -40,8 +40,7 @@ enum { static unsigned int show_channel_command(struct output *o, u32 val) { - unsigned mask; - unsigned subop; + unsigned int mask, subop; switch (val >> 28) { case HOST1X_OPCODE_SETCLASS: @@ -200,14 +199,13 @@ static void host1x_debug_show_channel_cdma(struct host1x *host, if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == HOST1X_CLASS_HOST1X && HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == - HOST1X_UCLASS_WAIT_SYNCPT) + HOST1X_UCLASS_WAIT_SYNCPT) host1x_debug_output(o, "waiting on syncpt %d val %d\n", cbread >> 24, cbread & 0xffffff); else if (HOST1X_SYNC_CBSTAT_CBCLASS_V(cbstat) == - HOST1X_CLASS_HOST1X && - HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == - HOST1X_UCLASS_WAIT_SYNCPT_BASE) { - + HOST1X_CLASS_HOST1X && + HOST1X_SYNC_CBSTAT_CBOFFSET_V(cbstat) == + HOST1X_UCLASS_WAIT_SYNCPT_BASE) { base = (cbread >> 16) & 0xff; baseval = host1x_sync_readl(host, HOST1X_SYNC_SYNCPT_BASE(base)); @@ -293,6 +291,7 @@ static void host1x_debug_show_mlocks(struct host1x *host, struct output *o) unsigned int i; host1x_debug_output(o, "---- mlocks ----\n"); + for (i = 0; i < host1x_syncpt_nb_mlocks(host); i++) { u32 owner = host1x_sync_readl(host, HOST1X_SYNC_MLOCK_OWNER(i)); @@ -304,6 +303,7 @@ static void host1x_debug_show_mlocks(struct host1x *host, struct output *o) else host1x_debug_output(o, "%u: unlocked\n", i); } + host1x_debug_output(o, "\n"); } diff --git a/drivers/gpu/host1x/hw/intr_hw.c b/drivers/gpu/host1x/hw/intr_hw.c index f50c68957c12..dacb8009a605 100644 --- a/drivers/gpu/host1x/hw/intr_hw.c +++ b/drivers/gpu/host1x/hw/intr_hw.c @@ -72,8 +72,9 @@ static void _host1x_intr_disable_all_syncpt_intrs(struct host1x *host) } } -static int _host1x_intr_init_host_sync(struct host1x *host, u32 cpm, - void (*syncpt_thresh_work)(struct work_struct *)) +static int +_host1x_intr_init_host_sync(struct host1x *host, u32 cpm, + void (*syncpt_thresh_work)(struct work_struct *)) { unsigned int i; int err; @@ -137,6 +138,7 @@ static int _host1x_free_syncpt_irq(struct host1x *host) for (i = 0; i < host->info->nb_pts; i++) cancel_work_sync(&host->syncpt[i].intr.work); + return 0; } diff --git a/drivers/gpu/host1x/hw/syncpt_hw.c b/drivers/gpu/host1x/hw/syncpt_hw.c index 310f24fcd261..c93f74fcce72 100644 --- a/drivers/gpu/host1x/hw/syncpt_hw.c +++ b/drivers/gpu/host1x/hw/syncpt_hw.c @@ -88,6 +88,7 @@ static int syncpt_cpu_incr(struct host1x_syncpt *sp) if (!host1x_syncpt_client_managed(sp) && host1x_syncpt_idle(sp)) return -EINVAL; + host1x_sync_writel(host, BIT_MASK(sp->id), HOST1X_SYNC_SYNCPT_CPU_INCR(reg_offset)); wmb(); @@ -98,10 +99,10 @@ static int syncpt_cpu_incr(struct host1x_syncpt *sp) /* remove a wait pointed to by patch_addr */ static int syncpt_patch_wait(struct host1x_syncpt *sp, void *patch_addr) { - u32 override = host1x_class_host_wait_syncpt( - HOST1X_SYNCPT_RESERVED, 0); + u32 override = host1x_class_host_wait_syncpt(HOST1X_SYNCPT_RESERVED, 0); *((u32 *)patch_addr) = override; + return 0; } diff --git a/drivers/gpu/host1x/job.c b/drivers/gpu/host1x/job.c index 1bff367f8830..a91b7c4a6110 100644 --- a/drivers/gpu/host1x/job.c +++ b/drivers/gpu/host1x/job.c @@ -577,11 +577,12 @@ void host1x_job_unpin(struct host1x_job *job) host1x_bo_unpin(unpin->bo, unpin->sgt); host1x_bo_put(unpin->bo); } + job->num_unpins = 0; if (job->gather_copy_size) dma_free_wc(job->channel->dev, job->gather_copy_size, - job->gather_copy_mapped, job->gather_copy); + job->gather_copy_mapped, job->gather_copy); } EXPORT_SYMBOL(host1x_job_unpin); diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 7c4848f4ce59..73b43c25b04b 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -114,8 +114,10 @@ void host1x_syncpt_restore(struct host1x *host) for (i = 0; i < host1x_syncpt_nb_pts(host); i++) host1x_hw_syncpt_restore(host, sp_base + i); + for (i = 0; i < host1x_syncpt_nb_bases(host); i++) host1x_hw_syncpt_restore_wait_base(host, sp_base + i); + wmb(); } @@ -181,6 +183,7 @@ EXPORT_SYMBOL(host1x_syncpt_incr); static bool syncpt_load_min_is_expired(struct host1x_syncpt *sp, u32 thresh) { host1x_hw_syncpt_load(sp->host, sp); + return host1x_syncpt_is_expired(sp, thresh); } @@ -188,7 +191,7 @@ static bool syncpt_load_min_is_expired(struct host1x_syncpt *sp, u32 thresh) * Main entrypoint for syncpoint value waits. */ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, - u32 *value) + u32 *value) { DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq); void *ref; @@ -203,6 +206,7 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, if (host1x_syncpt_is_expired(sp, thresh)) { if (value) *value = host1x_syncpt_load(sp); + return 0; } @@ -211,6 +215,7 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, if (host1x_syncpt_is_expired(sp, thresh)) { if (value) *value = val; + goto done; } @@ -241,20 +246,27 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, /* wait for the syncpoint, or timeout, or signal */ while (timeout) { long check = min_t(long, SYNCPT_CHECK_PERIOD, timeout); - int remain = wait_event_interruptible_timeout(wq, + int remain; + + remain = wait_event_interruptible_timeout(wq, syncpt_load_min_is_expired(sp, thresh), check); if (remain > 0 || host1x_syncpt_is_expired(sp, thresh)) { if (value) *value = host1x_syncpt_load(sp); + err = 0; + break; } + if (remain < 0) { err = remain; break; } + timeout -= check; + if (timeout && check_count <= MAX_STUCK_CHECK_COUNT) { dev_warn(sp->host->dev, "%s: syncpoint id %u (%s) stuck waiting %d, timeout=%ld\n", @@ -262,11 +274,14 @@ int host1x_syncpt_wait(struct host1x_syncpt *sp, u32 thresh, long timeout, thresh, timeout); host1x_debug_dump_syncpts(sp->host); + if (check_count == MAX_STUCK_CHECK_COUNT) host1x_debug_dump(sp->host); + check_count++; } } + host1x_intr_put_ref(sp->host, sp->id, ref); done: @@ -283,6 +298,7 @@ bool host1x_syncpt_is_expired(struct host1x_syncpt *sp, u32 thresh) u32 future_val; smp_rmb(); + current_val = (u32)atomic_read(&sp->min_val); future_val = (u32)atomic_read(&sp->max_val); @@ -381,6 +397,7 @@ struct host1x_syncpt *host1x_syncpt_request(struct device *dev, unsigned long flags) { struct host1x *host = dev_get_drvdata(dev->parent); + return host1x_syncpt_alloc(host, dev, flags); } EXPORT_SYMBOL(host1x_syncpt_request); @@ -415,6 +432,7 @@ void host1x_syncpt_deinit(struct host1x *host) u32 host1x_syncpt_read_max(struct host1x_syncpt *sp) { smp_rmb(); + return (u32)atomic_read(&sp->max_val); } EXPORT_SYMBOL(host1x_syncpt_read_max); @@ -425,6 +443,7 @@ EXPORT_SYMBOL(host1x_syncpt_read_max); u32 host1x_syncpt_read_min(struct host1x_syncpt *sp) { smp_rmb(); + return (u32)atomic_read(&sp->min_val); } EXPORT_SYMBOL(host1x_syncpt_read_min); @@ -454,6 +473,7 @@ struct host1x_syncpt *host1x_syncpt_get(struct host1x *host, unsigned int id) { if (host->info->nb_pts < id) return NULL; + return host->syncpt + id; } EXPORT_SYMBOL(host1x_syncpt_get); -- cgit v1.2.3 From 813a9d4ecdf0f6a3bcb5cf57ea4b9ebd69b31ee8 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:37:31 +0200 Subject: gpu: host1x: Remove redundant parentheses There's no need to wrap the BIT() macro into an extra set of parentheses because it's already implemented to use its own set. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/hw/cdma_hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/hw/cdma_hw.c b/drivers/gpu/host1x/hw/cdma_hw.c index 5c39f1b111f7..659c1bbfeeba 100644 --- a/drivers/gpu/host1x/hw/cdma_hw.c +++ b/drivers/gpu/host1x/hw/cdma_hw.c @@ -220,7 +220,7 @@ static void cdma_resume(struct host1x_cdma *cdma, u32 getptr) ch->id, getptr); cmdproc_stop = host1x_sync_readl(host1x, HOST1X_SYNC_CMDPROC_STOP); - cmdproc_stop &= ~(BIT(ch->id)); + cmdproc_stop &= ~BIT(ch->id); host1x_sync_writel(host1x, cmdproc_stop, HOST1X_SYNC_CMDPROC_STOP); cdma->torndown = false; -- cgit v1.2.3 From 9f2e57cada0483c1d564053a254d6004cddf1c31 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:38:41 +0200 Subject: gpu: host1x: Constify array of action handlers This array never needs to be modified and therefore can be read-only data. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/intr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/intr.c b/drivers/gpu/host1x/intr.c index a04d10407639..8b4fad0ab35d 100644 --- a/drivers/gpu/host1x/intr.c +++ b/drivers/gpu/host1x/intr.c @@ -135,7 +135,7 @@ static void action_wakeup_interruptible(struct host1x_waitlist *waiter) typedef void (*action_handler)(struct host1x_waitlist *waiter); -static action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = { +static const action_handler action_handlers[HOST1X_INTR_ACTION_COUNT] = { action_submit_complete, action_wakeup, action_wakeup_interruptible, -- cgit v1.2.3 From 4b92e2948f8fb2bc2182a2e74703bf0cee777753 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 23 Jun 2016 11:39:11 +0200 Subject: gpu: host1x: Remove useless local variable The local 'val' variable is used to store a value and immediately return it to its caller, and hence serves no purpose. Just drop it and directly return the value. Signed-off-by: Thierry Reding --- drivers/gpu/host1x/syncpt.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/host1x/syncpt.c b/drivers/gpu/host1x/syncpt.c index 73b43c25b04b..95589328ad52 100644 --- a/drivers/gpu/host1x/syncpt.c +++ b/drivers/gpu/host1x/syncpt.c @@ -160,11 +160,9 @@ u32 host1x_syncpt_load(struct host1x_syncpt *sp) */ u32 host1x_syncpt_load_wait_base(struct host1x_syncpt *sp) { - u32 val; - host1x_hw_syncpt_load_wait_base(sp->host, sp); - val = sp->base_val; - return val; + + return sp->base_val; } /* -- cgit v1.2.3 From bcbd63dfc90a3ae515013b0f300e4f3ce62f7249 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 29 Jun 2016 10:17:48 +0100 Subject: drm/tegra: dpaux: Clean-up on probe failure If the probing of the DPAUX fails, then clocks are left enabled and the DPAUX reset de-asserted. Add code to perform the necessary clean-up on probe failure by disabling clocks and asserting the reset. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dpaux.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index b24a0f14821a..0874a7e5b37b 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -321,28 +321,30 @@ static int tegra_dpaux_probe(struct platform_device *pdev) if (IS_ERR(dpaux->clk_parent)) { dev_err(&pdev->dev, "failed to get parent clock: %ld\n", PTR_ERR(dpaux->clk_parent)); - return PTR_ERR(dpaux->clk_parent); + err = PTR_ERR(dpaux->clk_parent); + goto assert_reset; } err = clk_prepare_enable(dpaux->clk_parent); if (err < 0) { dev_err(&pdev->dev, "failed to enable parent clock: %d\n", err); - return err; + goto assert_reset; } err = clk_set_rate(dpaux->clk_parent, 270000000); if (err < 0) { dev_err(&pdev->dev, "failed to set clock to 270 MHz: %d\n", err); - return err; + goto disable_parent_clk; } dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd"); if (IS_ERR(dpaux->vdd)) { dev_err(&pdev->dev, "failed to get VDD supply: %ld\n", PTR_ERR(dpaux->vdd)); - return PTR_ERR(dpaux->vdd); + err = PTR_ERR(dpaux->vdd); + goto disable_parent_clk; } err = devm_request_irq(dpaux->dev, dpaux->irq, tegra_dpaux_irq, 0, @@ -350,7 +352,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) if (err < 0) { dev_err(dpaux->dev, "failed to request IRQ#%u: %d\n", dpaux->irq, err); - return err; + goto disable_parent_clk; } disable_irq(dpaux->irq); @@ -360,7 +362,7 @@ static int tegra_dpaux_probe(struct platform_device *pdev) err = drm_dp_aux_register(&dpaux->aux); if (err < 0) - return err; + goto disable_parent_clk; /* * Assume that by default the DPAUX/I2C pads will be used for HDMI, @@ -393,6 +395,14 @@ static int tegra_dpaux_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dpaux); return 0; + +disable_parent_clk: + clk_disable_unprepare(dpaux->clk_parent); +assert_reset: + reset_control_assert(dpaux->rst); + clk_disable_unprepare(dpaux->clk); + + return err; } static int tegra_dpaux_remove(struct platform_device *pdev) -- cgit v1.2.3 From 9d0e09c15c4c8d73a83b5244a33732a7b82c52cd Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 29 Jun 2016 10:17:49 +0100 Subject: drm/tegra: dpaux: Add helpers for setting up pads In preparation for adding pinctrl support for the DPAUX pads, add a couple of helpers functions to configure the pads and control their power. Please note that although a simple if-statement could be used instead of a case statement for configuring the pads as there are only two possible modes, a case statement is used because when integrating with the pinctrl framework, we need to be able to handle invalid modes that could be passed. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dpaux.c | 83 +++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 30 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index 0874a7e5b37b..b0b1f84e98b1 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -267,6 +267,53 @@ static irqreturn_t tegra_dpaux_irq(int irq, void *data) return ret; } +static void tegra_dpaux_pad_power_down(struct tegra_dpaux *dpaux) +{ + u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); + + value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; + + tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); +} + +static void tegra_dpaux_pad_power_up(struct tegra_dpaux *dpaux) +{ + u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); + + value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; + + tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); +} + +static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function) +{ + u32 value; + + switch (function) { + case DPAUX_HYBRID_PADCTL_MODE_AUX: + value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) | + DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | + DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | + DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV | + DPAUX_HYBRID_PADCTL_MODE_AUX; + break; + + case DPAUX_HYBRID_PADCTL_MODE_I2C: + value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV | + DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV | + DPAUX_HYBRID_PADCTL_MODE_I2C; + break; + + default: + return -ENOTSUPP; + } + + tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL); + tegra_dpaux_pad_power_up(dpaux); + + return 0; +} + static int tegra_dpaux_probe(struct platform_device *pdev) { struct tegra_dpaux *dpaux; @@ -372,15 +419,9 @@ static int tegra_dpaux_probe(struct platform_device *pdev) * is no possibility to perform the I2C mode configuration in the * HDMI path. */ - value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); - value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; - tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); - - value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_PADCTL); - value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV | - DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV | - DPAUX_HYBRID_PADCTL_MODE_I2C; - tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL); + err = tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_I2C); + if (err < 0) + return err; /* enable and clear all interrupts */ value = DPAUX_INTR_AUX_DONE | DPAUX_INTR_IRQ_EVENT | @@ -408,12 +449,9 @@ assert_reset: static int tegra_dpaux_remove(struct platform_device *pdev) { struct tegra_dpaux *dpaux = platform_get_drvdata(pdev); - u32 value; /* make sure pads are powered down when not in use */ - value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); - value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; - tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); + tegra_dpaux_pad_power_down(dpaux); drm_dp_aux_unregister(&dpaux->aux); @@ -538,30 +576,15 @@ enum drm_connector_status drm_dp_aux_detect(struct drm_dp_aux *aux) int drm_dp_aux_enable(struct drm_dp_aux *aux) { struct tegra_dpaux *dpaux = to_dpaux(aux); - u32 value; - - value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) | - DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | - DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | - DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV | - DPAUX_HYBRID_PADCTL_MODE_AUX; - tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL); - - value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); - value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; - tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); - return 0; + return tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_AUX); } int drm_dp_aux_disable(struct drm_dp_aux *aux) { struct tegra_dpaux *dpaux = to_dpaux(aux); - u32 value; - value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); - value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; - tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); + tegra_dpaux_pad_power_down(dpaux); return 0; } -- cgit v1.2.3 From 9b99044afc125c9dc6f739272d8b6f945a066064 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 29 Jun 2016 10:17:51 +0100 Subject: drm/tegra: Prepare DPAUX for supporting generic PM domains To utilise the DPAUX on Tegra, the SOR power partition must be enabled. Now that Tegra supports the generic PM domain framework we manage the SOR power partition via this framework for DPAUX. However, the sequence for gating/ungating the SOR power partition requires that the DPAUX reset is asserted/de-asserted at the time the SOR power partition is gated/ungated, respectively. Now that the reset control core assumes that resets are exclusive, the Tegra generic PM domain code and the DPAUX driver cannot request the same reset unless we mark the resets as shared. Sharing resets will not work in this case because we cannot guarantee that the reset will be asserted/de-asserted at the appropriate time. Therefore, given that the Tegra generic PM domain code will handle the DPAUX reset, do not request the reset in the DPAUX driver if the DPAUX device has a PM domain associated. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dpaux.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index b0b1f84e98b1..9575114cf719 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -341,11 +341,14 @@ static int tegra_dpaux_probe(struct platform_device *pdev) return -ENXIO; } - dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux"); - if (IS_ERR(dpaux->rst)) { - dev_err(&pdev->dev, "failed to get reset control: %ld\n", - PTR_ERR(dpaux->rst)); - return PTR_ERR(dpaux->rst); + if (!pdev->dev.pm_domain) { + dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux"); + if (IS_ERR(dpaux->rst)) { + dev_err(&pdev->dev, + "failed to get reset control: %ld\n", + PTR_ERR(dpaux->rst)); + return PTR_ERR(dpaux->rst); + } } dpaux->clk = devm_clk_get(&pdev->dev, NULL); @@ -362,7 +365,8 @@ static int tegra_dpaux_probe(struct platform_device *pdev) return err; } - reset_control_deassert(dpaux->rst); + if (dpaux->rst) + reset_control_deassert(dpaux->rst); dpaux->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(dpaux->clk_parent)) { @@ -440,7 +444,9 @@ static int tegra_dpaux_probe(struct platform_device *pdev) disable_parent_clk: clk_disable_unprepare(dpaux->clk_parent); assert_reset: - reset_control_assert(dpaux->rst); + if (dpaux->rst) + reset_control_assert(dpaux->rst); + clk_disable_unprepare(dpaux->clk); return err; @@ -462,7 +468,10 @@ static int tegra_dpaux_remove(struct platform_device *pdev) cancel_work_sync(&dpaux->work); clk_disable_unprepare(dpaux->clk_parent); - reset_control_assert(dpaux->rst); + + if (dpaux->rst) + reset_control_assert(dpaux->rst); + clk_disable_unprepare(dpaux->clk); return 0; -- cgit v1.2.3 From 0751bb5c44fe1aa9494ce259d974c3d249b73a84 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Wed, 29 Jun 2016 10:17:55 +0100 Subject: drm/tegra: dpaux: Add pinctrl support The DPAUX pins are shared with an internal I2C controller. To allow these pins to be muxed to the I2C controller, register a pinctrl device for the DPAUX device. This is based upon work by Thierry Reding . Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dpaux.c | 123 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c index 9575114cf719..059f409556d5 100644 --- a/drivers/gpu/drm/tegra/dpaux.c +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -44,6 +47,11 @@ struct tegra_dpaux { struct completion complete; struct work_struct work; struct list_head list; + +#ifdef CONFIG_GENERIC_PINCONF + struct pinctrl_dev *pinctrl; + struct pinctrl_desc desc; +#endif }; static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux) @@ -267,6 +275,12 @@ static irqreturn_t tegra_dpaux_irq(int irq, void *data) return ret; } +enum tegra_dpaux_functions { + DPAUX_PADCTL_FUNC_AUX, + DPAUX_PADCTL_FUNC_I2C, + DPAUX_PADCTL_FUNC_OFF, +}; + static void tegra_dpaux_pad_power_down(struct tegra_dpaux *dpaux) { u32 value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); @@ -290,7 +304,7 @@ static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function) u32 value; switch (function) { - case DPAUX_HYBRID_PADCTL_MODE_AUX: + case DPAUX_PADCTL_FUNC_AUX: value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) | DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | @@ -298,12 +312,16 @@ static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function) DPAUX_HYBRID_PADCTL_MODE_AUX; break; - case DPAUX_HYBRID_PADCTL_MODE_I2C: + case DPAUX_PADCTL_FUNC_I2C: value = DPAUX_HYBRID_PADCTL_I2C_SDA_INPUT_RCV | DPAUX_HYBRID_PADCTL_I2C_SCL_INPUT_RCV | DPAUX_HYBRID_PADCTL_MODE_I2C; break; + case DPAUX_PADCTL_FUNC_OFF: + tegra_dpaux_pad_power_down(dpaux); + return 0; + default: return -ENOTSUPP; } @@ -314,6 +332,91 @@ static int tegra_dpaux_pad_config(struct tegra_dpaux *dpaux, unsigned function) return 0; } +#ifdef CONFIG_GENERIC_PINCONF +static const struct pinctrl_pin_desc tegra_dpaux_pins[] = { + PINCTRL_PIN(0, "DP_AUX_CHx_P"), + PINCTRL_PIN(1, "DP_AUX_CHx_N"), +}; + +static const unsigned tegra_dpaux_pin_numbers[] = { 0, 1 }; + +static const char * const tegra_dpaux_groups[] = { + "dpaux-io", +}; + +static const char * const tegra_dpaux_functions[] = { + "aux", + "i2c", + "off", +}; + +static int tegra_dpaux_get_groups_count(struct pinctrl_dev *pinctrl) +{ + return ARRAY_SIZE(tegra_dpaux_groups); +} + +static const char *tegra_dpaux_get_group_name(struct pinctrl_dev *pinctrl, + unsigned int group) +{ + return tegra_dpaux_groups[group]; +} + +static int tegra_dpaux_get_group_pins(struct pinctrl_dev *pinctrl, + unsigned group, const unsigned **pins, + unsigned *num_pins) +{ + *pins = tegra_dpaux_pin_numbers; + *num_pins = ARRAY_SIZE(tegra_dpaux_pin_numbers); + + return 0; +} + +static const struct pinctrl_ops tegra_dpaux_pinctrl_ops = { + .get_groups_count = tegra_dpaux_get_groups_count, + .get_group_name = tegra_dpaux_get_group_name, + .get_group_pins = tegra_dpaux_get_group_pins, + .dt_node_to_map = pinconf_generic_dt_node_to_map_group, + .dt_free_map = pinconf_generic_dt_free_map, +}; + +static int tegra_dpaux_get_functions_count(struct pinctrl_dev *pinctrl) +{ + return ARRAY_SIZE(tegra_dpaux_functions); +} + +static const char *tegra_dpaux_get_function_name(struct pinctrl_dev *pinctrl, + unsigned int function) +{ + return tegra_dpaux_functions[function]; +} + +static int tegra_dpaux_get_function_groups(struct pinctrl_dev *pinctrl, + unsigned int function, + const char * const **groups, + unsigned * const num_groups) +{ + *num_groups = ARRAY_SIZE(tegra_dpaux_groups); + *groups = tegra_dpaux_groups; + + return 0; +} + +static int tegra_dpaux_set_mux(struct pinctrl_dev *pinctrl, + unsigned int function, unsigned int group) +{ + struct tegra_dpaux *dpaux = pinctrl_dev_get_drvdata(pinctrl); + + return tegra_dpaux_pad_config(dpaux, function); +} + +static const struct pinmux_ops tegra_dpaux_pinmux_ops = { + .get_functions_count = tegra_dpaux_get_functions_count, + .get_function_name = tegra_dpaux_get_function_name, + .get_function_groups = tegra_dpaux_get_function_groups, + .set_mux = tegra_dpaux_set_mux, +}; +#endif + static int tegra_dpaux_probe(struct platform_device *pdev) { struct tegra_dpaux *dpaux; @@ -427,6 +530,20 @@ static int tegra_dpaux_probe(struct platform_device *pdev) if (err < 0) return err; +#ifdef CONFIG_GENERIC_PINCONF + dpaux->desc.name = dev_name(&pdev->dev); + dpaux->desc.pins = tegra_dpaux_pins; + dpaux->desc.npins = ARRAY_SIZE(tegra_dpaux_pins); + dpaux->desc.pctlops = &tegra_dpaux_pinctrl_ops; + dpaux->desc.pmxops = &tegra_dpaux_pinmux_ops; + dpaux->desc.owner = THIS_MODULE; + + dpaux->pinctrl = devm_pinctrl_register(&pdev->dev, &dpaux->desc, dpaux); + if (!dpaux->pinctrl) { + dev_err(&pdev->dev, "failed to register pincontrol\n"); + return -ENODEV; + } +#endif /* enable and clear all interrupts */ value = DPAUX_INTR_AUX_DONE | DPAUX_INTR_IRQ_EVENT | DPAUX_INTR_UNPLUG_EVENT | DPAUX_INTR_PLUG_EVENT; @@ -586,7 +703,7 @@ int drm_dp_aux_enable(struct drm_dp_aux *aux) { struct tegra_dpaux *dpaux = to_dpaux(aux); - return tegra_dpaux_pad_config(dpaux, DPAUX_HYBRID_PADCTL_MODE_AUX); + return tegra_dpaux_pad_config(dpaux, DPAUX_PADCTL_FUNC_AUX); } int drm_dp_aux_disable(struct drm_dp_aux *aux) -- cgit v1.2.3 From 25bb2cec88401a512c01adc8b815f8a579da2558 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Aug 2015 14:23:29 +0200 Subject: drm/tegra: sor: Factor out tegra_sor_set_parent_clock() Switching the SOR parent clock can glitch if done while the clock is enabled. Extract a common function that can be used to disable the module clock, switch the parent and reenable the module clock. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 757c6e8603af..ed03a1f5b692 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -225,6 +225,23 @@ static inline void tegra_sor_writel(struct tegra_sor *sor, u32 value, writel(value, sor->regs + (offset << 2)); } +static int tegra_sor_set_parent_clock(struct tegra_sor *sor, struct clk *parent) +{ + int err; + + clk_disable_unprepare(sor->clk); + + err = clk_set_parent(sor->clk, parent); + if (err < 0) + return err; + + err = clk_prepare_enable(sor->clk); + if (err < 0) + return err; + + return 0; +} + static int tegra_sor_dp_train_fast(struct tegra_sor *sor, struct drm_dp_link *link) { @@ -733,7 +750,8 @@ static int tegra_sor_power_down(struct tegra_sor *sor) if ((value & SOR_PWR_TRIGGER) != 0) return -ETIMEDOUT; - err = clk_set_parent(sor->clk, sor->clk_safe); + /* switch to safe parent clock */ + err = tegra_sor_set_parent_clock(sor, sor->clk_safe); if (err < 0) dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); @@ -1219,7 +1237,8 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) return; } - err = clk_set_parent(sor->clk, sor->clk_safe); + /* switch to safe parent clock */ + err = tegra_sor_set_parent_clock(sor, sor->clk_safe); if (err < 0) dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); @@ -1326,10 +1345,10 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) value &= ~SOR_PLL2_PORT_POWERDOWN; tegra_sor_writel(sor, value, SOR_PLL2); - /* switch to DP clock */ - err = clk_set_parent(sor->clk, sor->clk_dp); + /* switch to DP parent clock */ + err = tegra_sor_set_parent_clock(sor, sor->clk_dp); if (err < 0) - dev_err(sor->dev, "failed to set DP parent clock: %d\n", err); + dev_err(sor->dev, "failed to set parent clock: %d\n", err); /* power DP lanes */ value = tegra_sor_readl(sor, SOR_DP_PADCTL0); @@ -1781,7 +1800,8 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) reset_control_deassert(sor->rst); - err = clk_set_parent(sor->clk, sor->clk_safe); + /* switch to safe parent clock */ + err = tegra_sor_set_parent_clock(sor, sor->clk_safe); if (err < 0) dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); @@ -1892,7 +1912,8 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL); - err = clk_set_parent(sor->clk, sor->clk_parent); + /* switch to parent clock */ + err = tegra_sor_set_parent_clock(sor, sor->clk_parent); if (err < 0) dev_err(sor->dev, "failed to set parent clock: %d\n", err); -- cgit v1.2.3 From a198359e39c0e47a995d8e88638d5738ae4cfbe2 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Jul 2015 16:46:52 +0200 Subject: drm/tegra: sor: Rename tegra_sor_calc_config() Use a slightly more sensible name, tegra_sor_compute_config(). Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index ed03a1f5b692..fb536c1f5111 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -586,10 +586,10 @@ static int tegra_sor_compute_params(struct tegra_sor *sor, return false; } -static int tegra_sor_calc_config(struct tegra_sor *sor, - const struct drm_display_mode *mode, - struct tegra_sor_config *config, - struct drm_dp_link *link) +static int tegra_sor_compute_config(struct tegra_sor *sor, + const struct drm_display_mode *mode, + struct tegra_sor_config *config, + struct drm_dp_link *link) { const u64 f = 100000, link_rate = link->rate * 1000; const u64 pclk = mode->clock * 1000; @@ -1245,10 +1245,9 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) memset(&config, 0, sizeof(config)); config.bits_per_pixel = output->connector.display_info.bpc * 3; - err = tegra_sor_calc_config(sor, mode, &config, &link); + err = tegra_sor_compute_config(sor, mode, &config, &link); if (err < 0) - dev_err(sor->dev, "failed to compute link configuration: %d\n", - err); + dev_err(sor->dev, "failed to compute configuration: %d\n", err); value = tegra_sor_readl(sor, SOR_CLK_CNTRL); value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK; -- cgit v1.2.3 From 402f6bcd94fa3437b833931da8e1fbfc1fb6c444 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 21 Jul 2015 16:48:19 +0200 Subject: drm/tegra: sor: Split out tegra_sor_apply_config() This function is useful in both eDP and DP modes, so split it out in anticipation of adding DP support. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 77 +++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 34 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index fb536c1f5111..5d9a9f2ba4de 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -678,6 +678,46 @@ static int tegra_sor_compute_config(struct tegra_sor *sor, return 0; } +static void tegra_sor_apply_config(struct tegra_sor *sor, + const struct tegra_sor_config *config) +{ + u32 value; + + value = tegra_sor_readl(sor, SOR_DP_LINKCTL0); + value &= ~SOR_DP_LINKCTL_TU_SIZE_MASK; + value |= SOR_DP_LINKCTL_TU_SIZE(config->tu_size); + tegra_sor_writel(sor, value, SOR_DP_LINKCTL0); + + value = tegra_sor_readl(sor, SOR_DP_CONFIG0); + value &= ~SOR_DP_CONFIG_WATERMARK_MASK; + value |= SOR_DP_CONFIG_WATERMARK(config->watermark); + + value &= ~SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK; + value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(config->active_count); + + value &= ~SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK; + value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(config->active_frac); + + if (config->active_polarity) + value |= SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; + else + value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; + + value |= SOR_DP_CONFIG_ACTIVE_SYM_ENABLE; + value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE; + tegra_sor_writel(sor, value, SOR_DP_CONFIG0); + + value = tegra_sor_readl(sor, SOR_DP_AUDIO_HBLANK_SYMBOLS); + value &= ~SOR_DP_AUDIO_HBLANK_SYMBOLS_MASK; + value |= config->hblank_symbols & 0xffff; + tegra_sor_writel(sor, value, SOR_DP_AUDIO_HBLANK_SYMBOLS); + + value = tegra_sor_readl(sor, SOR_DP_AUDIO_VBLANK_SYMBOLS); + value &= ~SOR_DP_AUDIO_VBLANK_SYMBOLS_MASK; + value |= config->vblank_symbols & 0xffff; + tegra_sor_writel(sor, value, SOR_DP_AUDIO_VBLANK_SYMBOLS); +} + static int tegra_sor_detach(struct tegra_sor *sor) { unsigned long value, timeout; @@ -1393,13 +1433,11 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) value |= drm_dp_link_rate_to_bw_code(link.rate) << 2; tegra_sor_writel(sor, value, SOR_CLK_CNTRL); - /* set linkctl */ + tegra_sor_apply_config(sor, &config); + + /* enable link */ value = tegra_sor_readl(sor, SOR_DP_LINKCTL0); value |= SOR_DP_LINKCTL_ENABLE; - - value &= ~SOR_DP_LINKCTL_TU_SIZE_MASK; - value |= SOR_DP_LINKCTL_TU_SIZE(config.tu_size); - value |= SOR_DP_LINKCTL_ENHANCED_FRAME; tegra_sor_writel(sor, value, SOR_DP_LINKCTL0); @@ -1412,35 +1450,6 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, value, SOR_DP_TPG); - value = tegra_sor_readl(sor, SOR_DP_CONFIG0); - value &= ~SOR_DP_CONFIG_WATERMARK_MASK; - value |= SOR_DP_CONFIG_WATERMARK(config.watermark); - - value &= ~SOR_DP_CONFIG_ACTIVE_SYM_COUNT_MASK; - value |= SOR_DP_CONFIG_ACTIVE_SYM_COUNT(config.active_count); - - value &= ~SOR_DP_CONFIG_ACTIVE_SYM_FRAC_MASK; - value |= SOR_DP_CONFIG_ACTIVE_SYM_FRAC(config.active_frac); - - if (config.active_polarity) - value |= SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; - else - value &= ~SOR_DP_CONFIG_ACTIVE_SYM_POLARITY; - - value |= SOR_DP_CONFIG_ACTIVE_SYM_ENABLE; - value |= SOR_DP_CONFIG_DISPARITY_NEGATIVE; - tegra_sor_writel(sor, value, SOR_DP_CONFIG0); - - value = tegra_sor_readl(sor, SOR_DP_AUDIO_HBLANK_SYMBOLS); - value &= ~SOR_DP_AUDIO_HBLANK_SYMBOLS_MASK; - value |= config.hblank_symbols & 0xffff; - tegra_sor_writel(sor, value, SOR_DP_AUDIO_HBLANK_SYMBOLS); - - value = tegra_sor_readl(sor, SOR_DP_AUDIO_VBLANK_SYMBOLS); - value &= ~SOR_DP_AUDIO_VBLANK_SYMBOLS_MASK; - value |= config.vblank_symbols & 0xffff; - tegra_sor_writel(sor, value, SOR_DP_AUDIO_VBLANK_SYMBOLS); - /* enable pad calibration logic */ value = tegra_sor_readl(sor, SOR_DP_PADCTL0); value |= SOR_DP_PADCTL_PAD_CAL_PD; -- cgit v1.2.3 From 2bd1dd399fcd2e23efb1583df3ba846b20429739 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Aug 2015 15:46:15 +0200 Subject: drm/tegra: sor: Extract tegra_sor_mode_set() The code to set a video mode is common to all types of outputs that the SOR can drive. Extract it into a separate function so that it can be shared. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 226 ++++++++++++++++++-------------------------- 1 file changed, 93 insertions(+), 133 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 5d9a9f2ba4de..01b31805f719 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -718,6 +718,83 @@ static void tegra_sor_apply_config(struct tegra_sor *sor, tegra_sor_writel(sor, value, SOR_DP_AUDIO_VBLANK_SYMBOLS); } +static void tegra_sor_mode_set(struct tegra_sor *sor, + const struct drm_display_mode *mode, + const struct drm_display_info *info) +{ + struct tegra_dc *dc = to_tegra_dc(sor->output.encoder.crtc); + unsigned int vbe, vse, hbe, hse, vbs, hbs; + u32 value; + + value = tegra_sor_readl(sor, SOR_STATE1); + value &= ~SOR_STATE_ASY_PIXELDEPTH_MASK; + value &= ~SOR_STATE_ASY_CRC_MODE_MASK; + value &= ~SOR_STATE_ASY_OWNER_MASK; + + value |= SOR_STATE_ASY_CRC_MODE_COMPLETE | + SOR_STATE_ASY_OWNER(dc->pipe + 1); + + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + value &= ~SOR_STATE_ASY_HSYNCPOL; + + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + value |= SOR_STATE_ASY_HSYNCPOL; + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + value &= ~SOR_STATE_ASY_VSYNCPOL; + + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + value |= SOR_STATE_ASY_VSYNCPOL; + + switch (info->bpc) { + case 8: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; + break; + + case 6: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444; + break; + + default: + BUG(); + break; + } + + tegra_sor_writel(sor, value, SOR_STATE1); + + /* + * TODO: The video timing programming below doesn't seem to match the + * register definitions. + */ + + value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe)); + + /* sync end = sync width - 1 */ + vse = mode->vsync_end - mode->vsync_start - 1; + hse = mode->hsync_end - mode->hsync_start - 1; + + value = ((vse & 0x7fff) << 16) | (hse & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe)); + + /* blank end = sync end + back porch */ + vbe = vse + (mode->vtotal - mode->vsync_end); + hbe = hse + (mode->htotal - mode->hsync_end); + + value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe)); + + /* blank start = blank end + active */ + vbs = vbe + mode->vdisplay; + hbs = hbe + mode->hdisplay; + + value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff); + tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe)); + + /* XXX interlacing support */ + tegra_sor_writel(sor, 0x001, SOR_HEAD_STATE5(dc->pipe)); +} + static int tegra_sor_detach(struct tegra_sor *sor) { unsigned long value, timeout; @@ -1250,14 +1327,17 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) struct drm_display_mode *mode = &encoder->crtc->state->adjusted_mode; struct tegra_output *output = encoder_to_output(encoder); struct tegra_dc *dc = to_tegra_dc(encoder->crtc); - unsigned int vbe, vse, hbe, hse, vbs, hbs, i; struct tegra_sor *sor = to_sor(output); struct tegra_sor_config config; + struct drm_display_info *info; struct drm_dp_link link; u8 rate, lanes; + unsigned int i; int err = 0; u32 value; + info = &output->connector.display_info; + err = clk_prepare_enable(sor->clk); if (err < 0) dev_err(sor->dev, "failed to enable clock: %d\n", err); @@ -1505,75 +1585,19 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to power up SOR: %d\n", err); - /* - * configure panel (24bpp, vsync-, hsync-, DP-A protocol, complete - * raster, associate with display controller) - */ - value = SOR_STATE_ASY_PROTOCOL_DP_A | - SOR_STATE_ASY_CRC_MODE_COMPLETE | - SOR_STATE_ASY_OWNER(dc->pipe + 1); - - if (mode->flags & DRM_MODE_FLAG_PHSYNC) - value &= ~SOR_STATE_ASY_HSYNCPOL; - - if (mode->flags & DRM_MODE_FLAG_NHSYNC) - value |= SOR_STATE_ASY_HSYNCPOL; - - if (mode->flags & DRM_MODE_FLAG_PVSYNC) - value &= ~SOR_STATE_ASY_VSYNCPOL; - - if (mode->flags & DRM_MODE_FLAG_NVSYNC) - value |= SOR_STATE_ASY_VSYNCPOL; - - switch (config.bits_per_pixel) { - case 24: - value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; - break; - - case 18: - value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444; - break; - - default: - BUG(); - break; - } - - tegra_sor_writel(sor, value, SOR_STATE1); - - /* - * TODO: The video timing programming below doesn't seem to match the - * register definitions. - */ - - value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe)); - - vse = mode->vsync_end - mode->vsync_start - 1; - hse = mode->hsync_end - mode->hsync_start - 1; - - value = ((vse & 0x7fff) << 16) | (hse & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe)); - - vbe = vse + (mode->vsync_start - mode->vdisplay); - hbe = hse + (mode->hsync_start - mode->hdisplay); - - value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe)); - - vbs = vbe + mode->vdisplay; - hbs = hbe + mode->hdisplay; - - value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe)); - - tegra_sor_writel(sor, 0x1, SOR_HEAD_STATE5(dc->pipe)); - /* CSTM (LVDS, link A/B, upper) */ value = SOR_CSTM_LVDS | SOR_CSTM_LINK_ACT_A | SOR_CSTM_LINK_ACT_B | SOR_CSTM_UPPER; tegra_sor_writel(sor, value, SOR_CSTM); + /* use DP-A protocol */ + value = tegra_sor_readl(sor, SOR_STATE1); + value &= ~SOR_STATE_ASY_PROTOCOL_MASK; + value |= SOR_STATE_ASY_PROTOCOL_DP_A; + tegra_sor_writel(sor, value, SOR_STATE1); + + tegra_sor_mode_set(sor, mode, info); + /* PWM setup */ err = tegra_sor_setup_pwm(sor, 250); if (err < 0) @@ -1789,11 +1813,11 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) struct tegra_output *output = encoder_to_output(encoder); unsigned int h_ref_to_sync = 1, pulse_start, max_ac; struct tegra_dc *dc = to_tegra_dc(encoder->crtc); - unsigned int vbe, vse, hbe, hse, vbs, hbs, div; struct tegra_sor_hdmi_settings *settings; struct tegra_sor *sor = to_sor(output); struct drm_display_mode *mode; struct drm_display_info *info; + unsigned int div; u32 value; int err; @@ -2051,83 +2075,19 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to power up SOR: %d\n", err); - /* configure mode */ - value = tegra_sor_readl(sor, SOR_STATE1); - value &= ~SOR_STATE_ASY_PIXELDEPTH_MASK; - value &= ~SOR_STATE_ASY_CRC_MODE_MASK; - value &= ~SOR_STATE_ASY_OWNER_MASK; - - value |= SOR_STATE_ASY_CRC_MODE_COMPLETE | - SOR_STATE_ASY_OWNER(dc->pipe + 1); - - if (mode->flags & DRM_MODE_FLAG_PHSYNC) - value &= ~SOR_STATE_ASY_HSYNCPOL; - - if (mode->flags & DRM_MODE_FLAG_NHSYNC) - value |= SOR_STATE_ASY_HSYNCPOL; - - if (mode->flags & DRM_MODE_FLAG_PVSYNC) - value &= ~SOR_STATE_ASY_VSYNCPOL; - - if (mode->flags & DRM_MODE_FLAG_NVSYNC) - value |= SOR_STATE_ASY_VSYNCPOL; - - switch (info->bpc) { - case 8: - value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; - break; - - case 6: - value |= SOR_STATE_ASY_PIXELDEPTH_BPP_18_444; - break; - - default: - BUG(); - break; - } - - tegra_sor_writel(sor, value, SOR_STATE1); - + /* configure dynamic range of output */ value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe)); value &= ~SOR_HEAD_STATE_RANGECOMPRESS_MASK; value &= ~SOR_HEAD_STATE_DYNRANGE_MASK; tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); + /* configure colorspace */ value = tegra_sor_readl(sor, SOR_HEAD_STATE0(dc->pipe)); value &= ~SOR_HEAD_STATE_COLORSPACE_MASK; value |= SOR_HEAD_STATE_COLORSPACE_RGB; tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); - /* - * TODO: The video timing programming below doesn't seem to match the - * register definitions. - */ - - value = ((mode->vtotal & 0x7fff) << 16) | (mode->htotal & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE1(dc->pipe)); - - /* sync end = sync width - 1 */ - vse = mode->vsync_end - mode->vsync_start - 1; - hse = mode->hsync_end - mode->hsync_start - 1; - - value = ((vse & 0x7fff) << 16) | (hse & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE2(dc->pipe)); - - /* blank end = sync end + back porch */ - vbe = vse + (mode->vtotal - mode->vsync_end); - hbe = hse + (mode->htotal - mode->hsync_end); - - value = ((vbe & 0x7fff) << 16) | (hbe & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE3(dc->pipe)); - - /* blank start = blank end + active */ - vbs = vbe + mode->vdisplay; - hbs = hbe + mode->hdisplay; - - value = ((vbs & 0x7fff) << 16) | (hbs & 0x7fff); - tegra_sor_writel(sor, value, SOR_HEAD_STATE4(dc->pipe)); - - tegra_sor_writel(sor, 0x1, SOR_HEAD_STATE5(dc->pipe)); + tegra_sor_mode_set(sor, mode, info); tegra_sor_update(sor); -- cgit v1.2.3 From c31efa7a30ed04cbd17cac6e8fc91fce425773cd Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 8 Sep 2015 16:09:22 +0200 Subject: drm/tegra: sor: Do not support deep color modes Current generations of Tegra do not support deep color modes, so force 8 bits per color even if the connected monitor or panel supports more. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 100 +++++++++++++++++++++++++++++++++++++------- drivers/gpu/drm/tegra/sor.h | 3 ++ 2 files changed, 89 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 01b31805f719..8c893b6c6a4c 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -190,6 +190,18 @@ struct tegra_sor { struct regulator *hdmi_supply; }; +struct tegra_sor_state { + struct drm_connector_state base; + + unsigned int bpc; +}; + +static inline struct tegra_sor_state * +to_sor_state(struct drm_connector_state *state) +{ + return container_of(state, struct tegra_sor_state, base); +} + struct tegra_sor_config { u32 bits_per_pixel; @@ -720,7 +732,7 @@ static void tegra_sor_apply_config(struct tegra_sor *sor, static void tegra_sor_mode_set(struct tegra_sor *sor, const struct drm_display_mode *mode, - const struct drm_display_info *info) + struct tegra_sor_state *state) { struct tegra_dc *dc = to_tegra_dc(sor->output.encoder.crtc); unsigned int vbe, vse, hbe, hse, vbs, hbs; @@ -746,7 +758,19 @@ static void tegra_sor_mode_set(struct tegra_sor *sor, if (mode->flags & DRM_MODE_FLAG_NVSYNC) value |= SOR_STATE_ASY_VSYNCPOL; - switch (info->bpc) { + switch (state->bpc) { + case 16: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_48_444; + break; + + case 12: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_36_444; + break; + + case 10: + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_30_444; + break; + case 8: value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; break; @@ -756,7 +780,7 @@ static void tegra_sor_mode_set(struct tegra_sor *sor, break; default: - BUG(); + value |= SOR_STATE_ASY_PIXELDEPTH_BPP_24_444; break; } @@ -1173,6 +1197,22 @@ static void tegra_sor_debugfs_exit(struct tegra_sor *sor) sor->debugfs = NULL; } +static void tegra_sor_connector_reset(struct drm_connector *connector) +{ + struct tegra_sor_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return; + + if (connector->state) { + __drm_atomic_helper_connector_destroy_state(connector->state); + kfree(connector->state); + } + + __drm_atomic_helper_connector_reset(connector, &state->base); +} + static enum drm_connector_status tegra_sor_connector_detect(struct drm_connector *connector, bool force) { @@ -1185,13 +1225,28 @@ tegra_sor_connector_detect(struct drm_connector *connector, bool force) return tegra_output_connector_detect(connector, force); } +static struct drm_connector_state * +tegra_sor_connector_duplicate_state(struct drm_connector *connector) +{ + struct tegra_sor_state *state = to_sor_state(connector->state); + struct tegra_sor_state *copy; + + copy = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!copy) + return NULL; + + __drm_atomic_helper_connector_duplicate_state(connector, ©->base); + + return ©->base; +} + static const struct drm_connector_funcs tegra_sor_connector_funcs = { .dpms = drm_atomic_helper_connector_dpms, - .reset = drm_atomic_helper_connector_reset, + .reset = tegra_sor_connector_reset, .detect = tegra_sor_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, - .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_duplicate_state = tegra_sor_connector_duplicate_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; @@ -1329,14 +1384,14 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) struct tegra_dc *dc = to_tegra_dc(encoder->crtc); struct tegra_sor *sor = to_sor(output); struct tegra_sor_config config; - struct drm_display_info *info; + struct tegra_sor_state *state; struct drm_dp_link link; u8 rate, lanes; unsigned int i; int err = 0; u32 value; - info = &output->connector.display_info; + state = to_sor_state(output->connector.state); err = clk_prepare_enable(sor->clk); if (err < 0) @@ -1363,7 +1418,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) dev_err(sor->dev, "failed to set safe parent clock: %d\n", err); memset(&config, 0, sizeof(config)); - config.bits_per_pixel = output->connector.display_info.bpc * 3; + config.bits_per_pixel = state->bpc * 3; err = tegra_sor_compute_config(sor, mode, &config, &link); if (err < 0) @@ -1596,7 +1651,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) value |= SOR_STATE_ASY_PROTOCOL_DP_A; tegra_sor_writel(sor, value, SOR_STATE1); - tegra_sor_mode_set(sor, mode, info); + tegra_sor_mode_set(sor, mode, state); /* PWM setup */ err = tegra_sor_setup_pwm(sor, 250); @@ -1629,11 +1684,15 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder, struct drm_connector_state *conn_state) { struct tegra_output *output = encoder_to_output(encoder); + struct tegra_sor_state *state = to_sor_state(conn_state); struct tegra_dc *dc = to_tegra_dc(conn_state->crtc); unsigned long pclk = crtc_state->mode.clock * 1000; struct tegra_sor *sor = to_sor(output); + struct drm_display_info *info; int err; + info = &output->connector.display_info; + err = tegra_dc_state_setup_clock(dc, crtc_state, sor->clk_parent, pclk, 0); if (err < 0) { @@ -1641,6 +1700,18 @@ tegra_sor_encoder_atomic_check(struct drm_encoder *encoder, return err; } + switch (info->bpc) { + case 8: + case 6: + state->bpc = info->bpc; + break; + + default: + DRM_DEBUG_KMS("%u bits-per-color not supported\n", info->bpc); + state->bpc = 8; + break; + } + return 0; } @@ -1815,14 +1886,14 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) struct tegra_dc *dc = to_tegra_dc(encoder->crtc); struct tegra_sor_hdmi_settings *settings; struct tegra_sor *sor = to_sor(output); + struct tegra_sor_state *state; struct drm_display_mode *mode; - struct drm_display_info *info; unsigned int div; u32 value; int err; + state = to_sor_state(output->connector.state); mode = &encoder->crtc->state->adjusted_mode; - info = &output->connector.display_info; err = clk_prepare_enable(sor->clk); if (err < 0) @@ -2055,7 +2126,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) value &= ~DITHER_CONTROL_MASK; value &= ~BASE_COLOR_SIZE_MASK; - switch (info->bpc) { + switch (state->bpc) { case 6: value |= BASE_COLOR_SIZE_666; break; @@ -2065,7 +2136,8 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) break; default: - WARN(1, "%u bits-per-color not supported\n", info->bpc); + WARN(1, "%u bits-per-color not supported\n", state->bpc); + value |= BASE_COLOR_SIZE_888; break; } @@ -2087,7 +2159,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) value |= SOR_HEAD_STATE_COLORSPACE_RGB; tegra_sor_writel(sor, value, SOR_HEAD_STATE0(dc->pipe)); - tegra_sor_mode_set(sor, mode, info); + tegra_sor_mode_set(sor, mode, state); tegra_sor_update(sor); diff --git a/drivers/gpu/drm/tegra/sor.h b/drivers/gpu/drm/tegra/sor.h index 2d31d027e3f6..865c73b48968 100644 --- a/drivers/gpu/drm/tegra/sor.h +++ b/drivers/gpu/drm/tegra/sor.h @@ -27,6 +27,9 @@ #define SOR_STATE_ASY_PIXELDEPTH_MASK (0xf << 17) #define SOR_STATE_ASY_PIXELDEPTH_BPP_18_444 (0x2 << 17) #define SOR_STATE_ASY_PIXELDEPTH_BPP_24_444 (0x5 << 17) +#define SOR_STATE_ASY_PIXELDEPTH_BPP_30_444 (0x6 << 17) +#define SOR_STATE_ASY_PIXELDEPTH_BPP_36_444 (0x8 << 17) +#define SOR_STATE_ASY_PIXELDEPTH_BPP_48_444 (0x9 << 17) #define SOR_STATE_ASY_VSYNCPOL (1 << 13) #define SOR_STATE_ASY_HSYNCPOL (1 << 12) #define SOR_STATE_ASY_PROTOCOL_MASK (0xf << 8) -- cgit v1.2.3 From 2ccb396e9dd4536cfb7e8c4fd892d215c7aec2b6 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 15 Jan 2015 13:43:18 +0100 Subject: drm/tegra: hdmi: Enable audio over HDMI In order to use the HDA codec to forward audio data to the HDMI codec it needs the ELD that is parsed from the monitor's EDID. Also implement an interoperability mechanism between the HDA controller and the HDMI codec. This uses vendor-defined scratch registers to pass data from the HDMI codec driver to the HDMI driver (that implements the receiving end of the HDMI codec). A custom format is used to pass audio sample rate and channel count to the HDMI driver. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/hdmi.c | 444 +++++++++++++++++++++++++++++++---------- drivers/gpu/drm/tegra/hdmi.h | 21 +- drivers/gpu/drm/tegra/output.c | 1 + 3 files changed, 361 insertions(+), 105 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index b7ef4929e347..529768d977b8 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -18,10 +18,14 @@ #include #include +#include + #include "hdmi.h" #include "drm.h" #include "dc.h" +#define HDMI_ELD_BUFFER_SIZE 96 + struct tmds_config { unsigned int pclk; u32 pll0; @@ -39,6 +43,8 @@ struct tegra_hdmi_config { u32 fuse_override_value; bool has_sor_io_peak_current; + bool has_hda; + bool has_hbr; }; struct tegra_hdmi { @@ -60,7 +66,10 @@ struct tegra_hdmi { const struct tegra_hdmi_config *config; unsigned int audio_source; - unsigned int audio_freq; + unsigned int audio_sample_rate; + unsigned int audio_channels; + + unsigned int pixel_clock; bool stereo; bool dvi; @@ -402,11 +411,11 @@ static const struct tmds_config tegra124_tmds_config[] = { }; static const struct tegra_hdmi_audio_config * -tegra_hdmi_get_audio_config(unsigned int audio_freq, unsigned int pclk) +tegra_hdmi_get_audio_config(unsigned int sample_rate, unsigned int pclk) { const struct tegra_hdmi_audio_config *table; - switch (audio_freq) { + switch (sample_rate) { case 32000: table = tegra_hdmi_audio_32k; break; @@ -476,44 +485,114 @@ static void tegra_hdmi_setup_audio_fs_tables(struct tegra_hdmi *hdmi) } } -static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) +static void tegra_hdmi_write_aval(struct tegra_hdmi *hdmi, u32 value) +{ + static const struct { + unsigned int sample_rate; + unsigned int offset; + } regs[] = { + { 32000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320 }, + { 44100, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441 }, + { 48000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480 }, + { 88200, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882 }, + { 96000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960 }, + { 176400, HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764 }, + { 192000, HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920 }, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + if (regs[i].sample_rate == hdmi->audio_sample_rate) { + tegra_hdmi_writel(hdmi, value, regs[i].offset); + break; + } + } +} + +static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi) { - struct device_node *node = hdmi->dev->of_node; const struct tegra_hdmi_audio_config *config; - unsigned int offset = 0; - u32 value; + u32 source, value; switch (hdmi->audio_source) { case HDA: - value = AUDIO_CNTRL0_SOURCE_SELECT_HDAL; + if (hdmi->config->has_hda) + source = SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL; + else + return -EINVAL; + break; case SPDIF: - value = AUDIO_CNTRL0_SOURCE_SELECT_SPDIF; + if (hdmi->config->has_hda) + source = SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF; + else + source = AUDIO_CNTRL0_SOURCE_SELECT_SPDIF; break; default: - value = AUDIO_CNTRL0_SOURCE_SELECT_AUTO; + if (hdmi->config->has_hda) + source = SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO; + else + source = AUDIO_CNTRL0_SOURCE_SELECT_AUTO; break; } - if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) { - value |= AUDIO_CNTRL0_ERROR_TOLERANCE(6) | - AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0); - tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0); - } else { - value |= AUDIO_CNTRL0_INJECT_NULLSMPL; + /* + * Tegra30 and later use a slightly modified version of the register + * layout to accomodate for changes related to supporting HDA as the + * audio input source for HDMI. The source select field has moved to + * the SOR_AUDIO_CNTRL0 register, but the error tolerance and frames + * per block fields remain in the AUDIO_CNTRL0 register. + */ + if (hdmi->config->has_hda) { + /* + * Inject null samples into the audio FIFO for every frame in + * which the codec did not receive any samples. This applies + * to stereo LPCM only. + * + * XXX: This seems to be a remnant of MCP days when this was + * used to work around issues with monitors not being able to + * play back system startup sounds early. It is possibly not + * needed on Linux at all. + */ + if (hdmi->audio_channels == 2) + value = SOR_AUDIO_CNTRL0_INJECT_NULLSMPL; + else + value = 0; + + value |= source; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_CNTRL0); + } - value = AUDIO_CNTRL0_ERROR_TOLERANCE(6) | - AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0); - tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0); + /* + * On Tegra20, HDA is not a supported audio source and the source + * select field is part of the AUDIO_CNTRL0 register. + */ + value = AUDIO_CNTRL0_FRAMES_PER_BLOCK(0xc0) | + AUDIO_CNTRL0_ERROR_TOLERANCE(6); + + if (!hdmi->config->has_hda) + value |= source; + + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_CNTRL0); + + /* + * Advertise support for High Bit-Rate on Tegra114 and later. + */ + if (hdmi->config->has_hbr) { + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_AUDIO_SPARE0); + value |= SOR_AUDIO_SPARE0_HBR_ENABLE; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_SPARE0); } - config = tegra_hdmi_get_audio_config(hdmi->audio_freq, pclk); + config = tegra_hdmi_get_audio_config(hdmi->audio_sample_rate, + hdmi->pixel_clock); if (!config) { - dev_err(hdmi->dev, "cannot set audio to %u at %u pclk\n", - hdmi->audio_freq, pclk); + dev_err(hdmi->dev, + "cannot set audio to %u Hz at %u Hz pixel clock\n", + hdmi->audio_sample_rate, hdmi->pixel_clock); return -EINVAL; } @@ -526,8 +605,8 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) tegra_hdmi_writel(hdmi, ACR_SUBPACK_N(config->n) | ACR_ENABLE, HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_HIGH); - value = ACR_SUBPACK_CTS(config->cts); - tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW); + tegra_hdmi_writel(hdmi, ACR_SUBPACK_CTS(config->cts), + HDMI_NV_PDISP_HDMI_ACR_0441_SUBPACK_LOW); value = SPARE_HW_CTS | SPARE_FORCE_SW_CTS | SPARE_CTS_RESET_VAL(1); tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_SPARE); @@ -536,43 +615,30 @@ static int tegra_hdmi_setup_audio(struct tegra_hdmi *hdmi, unsigned int pclk) value &= ~AUDIO_N_RESETF; tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_AUDIO_N); - if (of_device_is_compatible(node, "nvidia,tegra30-hdmi")) { - switch (hdmi->audio_freq) { - case 32000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320; - break; - - case 44100: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441; - break; - - case 48000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0480; - break; + if (hdmi->config->has_hda) + tegra_hdmi_write_aval(hdmi, config->aval); - case 88200: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0882; - break; - - case 96000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_0960; - break; + tegra_hdmi_setup_audio_fs_tables(hdmi); - case 176400: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1764; - break; + return 0; +} - case 192000: - offset = HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920; - break; - } +static void tegra_hdmi_disable_audio(struct tegra_hdmi *hdmi) +{ + u32 value; - tegra_hdmi_writel(hdmi, config->aval, offset); - } + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + value &= ~GENERIC_CTRL_AUDIO; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +} - tegra_hdmi_setup_audio_fs_tables(hdmi); +static void tegra_hdmi_enable_audio(struct tegra_hdmi *hdmi) +{ + u32 value; - return 0; + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + value |= GENERIC_CTRL_AUDIO; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); } static inline u32 tegra_hdmi_subpack(const u8 *ptr, size_t size) @@ -644,12 +710,6 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi, u8 buffer[17]; ssize_t err; - if (hdmi->dvi) { - tegra_hdmi_writel(hdmi, 0, - HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); - return; - } - err = drm_hdmi_avi_infoframe_from_display_mode(&frame, mode); if (err < 0) { dev_err(hdmi->dev, "failed to setup AVI infoframe: %zd\n", err); @@ -663,9 +723,24 @@ static void tegra_hdmi_setup_avi_infoframe(struct tegra_hdmi *hdmi, } tegra_hdmi_write_infopack(hdmi, buffer, err); +} + +static void tegra_hdmi_disable_avi_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; - tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE, - HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); + value &= ~INFOFRAME_CTRL_ENABLE; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); +} + +static void tegra_hdmi_enable_avi_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); + value |= INFOFRAME_CTRL_ENABLE; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AVI_INFOFRAME_CTRL); } static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) @@ -674,12 +749,6 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) u8 buffer[14]; ssize_t err; - if (hdmi->dvi) { - tegra_hdmi_writel(hdmi, 0, - HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); - return; - } - err = hdmi_audio_infoframe_init(&frame); if (err < 0) { dev_err(hdmi->dev, "failed to setup audio infoframe: %zd\n", @@ -687,7 +756,7 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) return; } - frame.channels = 2; + frame.channels = hdmi->audio_channels; err = hdmi_audio_infoframe_pack(&frame, buffer, sizeof(buffer)); if (err < 0) { @@ -703,9 +772,24 @@ static void tegra_hdmi_setup_audio_infoframe(struct tegra_hdmi *hdmi) * bytes can be programmed. */ tegra_hdmi_write_infopack(hdmi, buffer, min_t(size_t, 10, err)); +} - tegra_hdmi_writel(hdmi, INFOFRAME_CTRL_ENABLE, - HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); +static void tegra_hdmi_disable_audio_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); + value &= ~INFOFRAME_CTRL_ENABLE; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); +} + +static void tegra_hdmi_enable_audio_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); + value |= INFOFRAME_CTRL_ENABLE; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_AUDIO_INFOFRAME_CTRL); } static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) @@ -713,14 +797,6 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) struct hdmi_vendor_infoframe frame; u8 buffer[10]; ssize_t err; - u32 value; - - if (!hdmi->stereo) { - value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); - value &= ~GENERIC_CTRL_ENABLE; - tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); - return; - } hdmi_vendor_infoframe_init(&frame); frame.s3d_struct = HDMI_3D_STRUCTURE_FRAME_PACKING; @@ -733,6 +809,20 @@ static void tegra_hdmi_setup_stereo_infoframe(struct tegra_hdmi *hdmi) } tegra_hdmi_write_infopack(hdmi, buffer, err); +} + +static void tegra_hdmi_disable_stereo_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + value &= ~GENERIC_CTRL_ENABLE; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); +} + +static void tegra_hdmi_enable_stereo_infoframe(struct tegra_hdmi *hdmi) +{ + u32 value; value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); value |= GENERIC_CTRL_ENABLE; @@ -772,10 +862,25 @@ static bool tegra_output_is_hdmi(struct tegra_output *output) return drm_detect_hdmi_monitor(edid); } +static enum drm_connector_status +tegra_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct tegra_output *output = connector_to_output(connector); + struct tegra_hdmi *hdmi = to_hdmi(output); + enum drm_connector_status status; + + status = tegra_output_connector_detect(connector, force); + if (status == connector_status_connected) + return status; + + tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); + return status; +} + static const struct drm_connector_funcs tegra_hdmi_connector_funcs = { .dpms = drm_atomic_helper_connector_dpms, .reset = drm_atomic_helper_connector_reset, - .detect = tegra_output_connector_detect, + .detect = tegra_hdmi_connector_detect, .fill_modes = drm_helper_probe_single_connector_modes, .destroy = tegra_output_connector_destroy, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, @@ -815,7 +920,9 @@ static const struct drm_encoder_funcs tegra_hdmi_encoder_funcs = { static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) { + struct tegra_output *output = encoder_to_output(encoder); struct tegra_dc *dc = to_tegra_dc(encoder->crtc); + struct tegra_hdmi *hdmi = to_hdmi(output); u32 value; /* @@ -829,6 +936,38 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) tegra_dc_commit(dc); } + + if (!hdmi->dvi) { + if (hdmi->stereo) + tegra_hdmi_disable_stereo_infoframe(hdmi); + + tegra_hdmi_disable_audio_infoframe(hdmi); + tegra_hdmi_disable_avi_infoframe(hdmi); + tegra_hdmi_disable_audio(hdmi); + } +} + +static void tegra_hdmi_write_eld(struct tegra_hdmi *hdmi) +{ + size_t length = drm_eld_size(hdmi->output.connector.eld), i; + u32 value; + + for (i = 0; i < length; i++) + tegra_hdmi_writel(hdmi, i << 8 | hdmi->output.connector.eld[i], + HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); + + /* + * The HDA codec will always report an ELD buffer size of 96 bytes and + * the HDA codec driver will check that each byte read from the buffer + * is valid. Therefore every byte must be written, even if no 96 bytes + * were parsed from EDID. + */ + for (i = length; i < HDMI_ELD_BUFFER_SIZE; i++) + tegra_hdmi_writel(hdmi, i << 8 | 0, + HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); + + value = SOR_AUDIO_HDA_PRESENSE_VALID | SOR_AUDIO_HDA_PRESENSE_PRESENT; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); } static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) @@ -837,21 +976,18 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) unsigned int h_sync_width, h_front_porch, h_back_porch, i, rekey; struct tegra_output *output = encoder_to_output(encoder); struct tegra_dc *dc = to_tegra_dc(encoder->crtc); - struct device_node *node = output->dev->of_node; struct tegra_hdmi *hdmi = to_hdmi(output); - unsigned int pulse_start, div82, pclk; + unsigned int pulse_start, div82; int retries = 1000; u32 value; int err; - hdmi->dvi = !tegra_output_is_hdmi(output); - - pclk = mode->clock * 1000; + hdmi->pixel_clock = mode->clock * 1000; h_sync_width = mode->hsync_end - mode->hsync_start; h_back_porch = mode->htotal - mode->hsync_end; h_front_porch = mode->hsync_start - mode->hdisplay; - err = clk_set_rate(hdmi->clk, pclk); + err = clk_set_rate(hdmi->clk, hdmi->pixel_clock); if (err < 0) { dev_err(hdmi->dev, "failed to set HDMI clock frequency: %d\n", err); @@ -910,17 +1046,15 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) value = SOR_REFCLK_DIV_INT(div82 >> 2) | SOR_REFCLK_DIV_FRAC(div82); tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_REFCLK); + hdmi->dvi = !tegra_output_is_hdmi(output); if (!hdmi->dvi) { - err = tegra_hdmi_setup_audio(hdmi, pclk); + err = tegra_hdmi_setup_audio(hdmi); if (err < 0) hdmi->dvi = true; } - if (of_device_is_compatible(node, "nvidia,tegra20-hdmi")) { - /* - * TODO: add ELD support - */ - } + if (hdmi->config->has_hda) + tegra_hdmi_write_eld(hdmi); rekey = HDMI_REKEY_DEFAULT; value = HDMI_CTRL_REKEY(rekey); @@ -932,20 +1066,17 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_CTRL); - if (hdmi->dvi) - tegra_hdmi_writel(hdmi, 0x0, - HDMI_NV_PDISP_HDMI_GENERIC_CTRL); - else - tegra_hdmi_writel(hdmi, GENERIC_CTRL_AUDIO, - HDMI_NV_PDISP_HDMI_GENERIC_CTRL); + if (!hdmi->dvi) { + tegra_hdmi_setup_avi_infoframe(hdmi, mode); + tegra_hdmi_setup_audio_infoframe(hdmi); - tegra_hdmi_setup_avi_infoframe(hdmi, mode); - tegra_hdmi_setup_audio_infoframe(hdmi); - tegra_hdmi_setup_stereo_infoframe(hdmi); + if (hdmi->stereo) + tegra_hdmi_setup_stereo_infoframe(hdmi); + } /* TMDS CONFIG */ for (i = 0; i < hdmi->config->num_tmds; i++) { - if (pclk <= hdmi->config->tmds[i].pclk) { + if (hdmi->pixel_clock <= hdmi->config->tmds[i].pclk) { tegra_hdmi_setup_tmds(hdmi, &hdmi->config->tmds[i]); break; } @@ -1032,6 +1163,15 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) tegra_dc_commit(dc); + if (!hdmi->dvi) { + tegra_hdmi_enable_avi_infoframe(hdmi); + tegra_hdmi_enable_audio_infoframe(hdmi); + tegra_hdmi_enable_audio(hdmi); + + if (hdmi->stereo) + tegra_hdmi_enable_stereo_infoframe(hdmi); + } + /* TODO: add HDCP support */ } @@ -1236,8 +1376,14 @@ static int tegra_hdmi_show_regs(struct seq_file *s, void *data) DUMP_REG(HDMI_NV_PDISP_KEY_HDCP_KEY_TRIG); DUMP_REG(HDMI_NV_PDISP_KEY_SKEY_INDEX); DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_CNTRL0); + DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_SPARE0); + DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0); + DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1); DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); DUMP_REG(HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); + DUMP_REG(HDMI_NV_PDISP_INT_STATUS); + DUMP_REG(HDMI_NV_PDISP_INT_MASK); + DUMP_REG(HDMI_NV_PDISP_INT_ENABLE); DUMP_REG(HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT); #undef DUMP_REG @@ -1369,6 +1515,14 @@ static int tegra_hdmi_init(struct host1x_client *client) reset_control_deassert(hdmi->rst); + /* + * Enable and unmask the HDA codec SCRATCH0 register interrupt. This + * is used for interoperability between the HDA codec driver and the + * HDMI driver. + */ + tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_ENABLE); + tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_MASK); + return 0; } @@ -1376,6 +1530,9 @@ static int tegra_hdmi_exit(struct host1x_client *client) { struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); + tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK); + tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE); + tegra_output_exit(&hdmi->output); reset_control_assert(hdmi->rst); @@ -1402,6 +1559,8 @@ static const struct tegra_hdmi_config tegra20_hdmi_config = { .fuse_override_offset = HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT, .fuse_override_value = 1 << 31, .has_sor_io_peak_current = false, + .has_hda = false, + .has_hbr = false, }; static const struct tegra_hdmi_config tegra30_hdmi_config = { @@ -1410,6 +1569,8 @@ static const struct tegra_hdmi_config tegra30_hdmi_config = { .fuse_override_offset = HDMI_NV_PDISP_SOR_LANE_DRIVE_CURRENT, .fuse_override_value = 1 << 31, .has_sor_io_peak_current = false, + .has_hda = true, + .has_hbr = false, }; static const struct tegra_hdmi_config tegra114_hdmi_config = { @@ -1418,6 +1579,8 @@ static const struct tegra_hdmi_config tegra114_hdmi_config = { .fuse_override_offset = HDMI_NV_PDISP_SOR_PAD_CTLS0, .fuse_override_value = 1 << 31, .has_sor_io_peak_current = true, + .has_hda = true, + .has_hbr = true, }; static const struct tegra_hdmi_config tegra124_hdmi_config = { @@ -1426,6 +1589,8 @@ static const struct tegra_hdmi_config tegra124_hdmi_config = { .fuse_override_offset = HDMI_NV_PDISP_SOR_PAD_CTLS0, .fuse_override_value = 1 << 31, .has_sor_io_peak_current = true, + .has_hda = true, + .has_hbr = true, }; static const struct of_device_id tegra_hdmi_of_match[] = { @@ -1437,6 +1602,67 @@ static const struct of_device_id tegra_hdmi_of_match[] = { }; MODULE_DEVICE_TABLE(of, tegra_hdmi_of_match); +static void hda_format_parse(unsigned int format, unsigned int *rate, + unsigned int *channels) +{ + unsigned int mul, div; + + if (format & AC_FMT_BASE_44K) + *rate = 44100; + else + *rate = 48000; + + mul = (format & AC_FMT_MULT_MASK) >> AC_FMT_MULT_SHIFT; + div = (format & AC_FMT_DIV_MASK) >> AC_FMT_DIV_SHIFT; + + *rate = *rate * (mul + 1) / (div + 1); + + *channels = (format & AC_FMT_CHAN_MASK) >> AC_FMT_CHAN_SHIFT; +} + +static irqreturn_t tegra_hdmi_irq(int irq, void *data) +{ + struct tegra_hdmi *hdmi = data; + u32 value; + int err; + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_INT_STATUS); + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_INT_STATUS); + + if (value & INT_CODEC_SCRATCH0) { + unsigned int format; + u32 value; + + value = tegra_hdmi_readl(hdmi, HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0); + + if (value & SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID) { + unsigned int sample_rate, channels; + + format = value & SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK; + + hda_format_parse(format, &sample_rate, &channels); + + hdmi->audio_sample_rate = sample_rate; + hdmi->audio_channels = channels; + + err = tegra_hdmi_setup_audio(hdmi); + if (err < 0) { + tegra_hdmi_disable_audio_infoframe(hdmi); + tegra_hdmi_disable_audio(hdmi); + } else { + tegra_hdmi_setup_audio_infoframe(hdmi); + tegra_hdmi_enable_audio_infoframe(hdmi); + tegra_hdmi_enable_audio(hdmi); + } + } else { + tegra_hdmi_disable_audio_infoframe(hdmi); + tegra_hdmi_disable_audio(hdmi); + } + } + + return IRQ_HANDLED; +} + static int tegra_hdmi_probe(struct platform_device *pdev) { const struct of_device_id *match; @@ -1454,8 +1680,10 @@ static int tegra_hdmi_probe(struct platform_device *pdev) hdmi->config = match->data; hdmi->dev = &pdev->dev; + hdmi->audio_source = AUTO; - hdmi->audio_freq = 44100; + hdmi->audio_sample_rate = 48000; + hdmi->audio_channels = 2; hdmi->stereo = false; hdmi->dvi = false; @@ -1516,6 +1744,14 @@ static int tegra_hdmi_probe(struct platform_device *pdev) hdmi->irq = err; + err = devm_request_irq(hdmi->dev, hdmi->irq, tegra_hdmi_irq, 0, + dev_name(hdmi->dev), hdmi); + if (err < 0) { + dev_err(&pdev->dev, "failed to request IRQ#%u: %d\n", + hdmi->irq, err); + return err; + } + INIT_LIST_HEAD(&hdmi->client.list); hdmi->client.ops = &hdmi_client_ops; hdmi->client.dev = &pdev->dev; diff --git a/drivers/gpu/drm/tegra/hdmi.h b/drivers/gpu/drm/tegra/hdmi.h index a882514389cd..2339f134a09a 100644 --- a/drivers/gpu/drm/tegra/hdmi.h +++ b/drivers/gpu/drm/tegra/hdmi.h @@ -468,9 +468,20 @@ #define HDMI_NV_PDISP_KEY_SKEY_INDEX 0xa3 #define HDMI_NV_PDISP_SOR_AUDIO_CNTRL0 0xac -#define AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_AUTO (0 << 20) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_SPDIF (1 << 20) +#define SOR_AUDIO_CNTRL0_SOURCE_SELECT_HDAL (2 << 20) +#define SOR_AUDIO_CNTRL0_INJECT_NULLSMPL (1 << 29) +#define HDMI_NV_PDISP_SOR_AUDIO_SPARE0 0xae +#define SOR_AUDIO_SPARE0_HBR_ENABLE (1 << 27) +#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH0 0xba +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_VALID (1 << 30) +#define SOR_AUDIO_HDA_CODEC_SCRATCH0_FMT_MASK 0xffff +#define HDMI_NV_PDISP_SOR_AUDIO_HDA_CODEC_SCRATCH1 0xbb #define HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR 0xbc #define HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE 0xbd +#define SOR_AUDIO_HDA_PRESENSE_VALID (1 << 1) +#define SOR_AUDIO_HDA_PRESENSE_PRESENT (1 << 0) #define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0320 0xbf #define HDMI_NV_PDISP_SOR_AUDIO_AVAL_0441 0xc0 @@ -481,6 +492,14 @@ #define HDMI_NV_PDISP_SOR_AUDIO_AVAL_1920 0xc5 #define HDMI_NV_PDISP_SOR_AUDIO_AVAL_DEFAULT 0xc5 +#define HDMI_NV_PDISP_INT_STATUS 0xcc +#define INT_SCRATCH (1 << 3) +#define INT_CP_REQUEST (1 << 2) +#define INT_CODEC_SCRATCH1 (1 << 1) +#define INT_CODEC_SCRATCH0 (1 << 0) +#define HDMI_NV_PDISP_INT_MASK 0xcd +#define HDMI_NV_PDISP_INT_ENABLE 0xce + #define HDMI_NV_PDISP_SOR_IO_PEAK_CURRENT 0xd1 #define PEAK_CURRENT_LANE0(x) (((x) & 0x7f) << 0) #define PEAK_CURRENT_LANE1(x) (((x) & 0x7f) << 8) diff --git a/drivers/gpu/drm/tegra/output.c b/drivers/gpu/drm/tegra/output.c index 46664b622270..8dba78a10c7d 100644 --- a/drivers/gpu/drm/tegra/output.c +++ b/drivers/gpu/drm/tegra/output.c @@ -36,6 +36,7 @@ int tegra_output_connector_get_modes(struct drm_connector *connector) if (edid) { err = drm_add_edid_modes(connector, edid); + drm_edid_to_eld(connector, edid); kfree(edid); } -- cgit v1.2.3 From 33a8eb8d40ee7fc07f23a407607bdbaa46893b2d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Aug 2015 13:20:49 +0200 Subject: drm/tegra: dc: Implement runtime PM Use runtime PM to clock-gate, assert reset and powergate the display controller. This ties in nicely with atomic DPMS in that a runtime PM reference is taken before a pipe is enabled and dropped after it has been shut down. To make sure this works, make sure to only ever update planes on active CRTCs, otherwise register accesses to a clock-gated and reset CRTC will hang the CPU. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dc.c | 176 ++++++++++++++++++++++++++------------------ drivers/gpu/drm/tegra/drm.c | 2 +- 2 files changed, 107 insertions(+), 71 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index 39940f5b7c91..8495bd01b544 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -1216,6 +1217,8 @@ static void tegra_crtc_disable(struct drm_crtc *crtc) tegra_dc_stats_reset(&dc->stats); drm_crtc_vblank_off(crtc); + + pm_runtime_put_sync(dc->dev); } static void tegra_crtc_enable(struct drm_crtc *crtc) @@ -1225,6 +1228,48 @@ static void tegra_crtc_enable(struct drm_crtc *crtc) struct tegra_dc *dc = to_tegra_dc(crtc); u32 value; + pm_runtime_get_sync(dc->dev); + + /* initialize display controller */ + if (dc->syncpt) { + u32 syncpt = host1x_syncpt_id(dc->syncpt); + + value = SYNCPT_CNTRL_NO_STALL; + tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); + + value = SYNCPT_VSYNC_ENABLE | syncpt; + tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC); + } + + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_TYPE); + + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); + + /* initialize timer */ + value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | + WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); + tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY); + + value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) | + WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1); + tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); + + value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); + + value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | + WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; + tegra_dc_writel(dc, value, DC_CMD_INT_MASK); + + if (dc->soc->supports_border_color) + tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR); + + /* apply PLL and pixel clock changes */ tegra_dc_commit_state(dc, state); /* program display mode */ @@ -1685,7 +1730,6 @@ static int tegra_dc_init(struct host1x_client *client) struct tegra_drm *tegra = drm->dev_private; struct drm_plane *primary = NULL; struct drm_plane *cursor = NULL; - u32 value; int err; dc->syncpt = host1x_syncpt_request(dc->dev, flags); @@ -1755,47 +1799,6 @@ static int tegra_dc_init(struct host1x_client *client) goto cleanup; } - /* initialize display controller */ - if (dc->syncpt) { - u32 syncpt = host1x_syncpt_id(dc->syncpt); - - value = SYNCPT_CNTRL_NO_STALL; - tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL); - - value = SYNCPT_VSYNC_ENABLE | syncpt; - tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC); - } - - value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_TYPE); - - value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY); - - /* initialize timer */ - value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) | - WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20); - tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY); - - value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) | - WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1); - tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER); - - value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE); - - value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT | - WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT; - tegra_dc_writel(dc, value, DC_CMD_INT_MASK); - - if (dc->soc->supports_border_color) - tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR); - - tegra_dc_stats_reset(&dc->stats); - return 0; cleanup: @@ -1987,33 +1990,15 @@ static int tegra_dc_probe(struct platform_device *pdev) return PTR_ERR(dc->rst); } + reset_control_assert(dc->rst); + 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; - } + tegra_powergate_power_off(dc->powergate); } regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -2027,16 +2012,19 @@ static int tegra_dc_probe(struct platform_device *pdev) return -ENXIO; } - INIT_LIST_HEAD(&dc->client.list); - dc->client.ops = &dc_client_ops; - dc->client.dev = &pdev->dev; - err = tegra_dc_rgb_probe(dc); if (err < 0 && err != -ENODEV) { dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err); return err; } + platform_set_drvdata(pdev, dc); + pm_runtime_enable(&pdev->dev); + + INIT_LIST_HEAD(&dc->client.list); + dc->client.ops = &dc_client_ops; + dc->client.dev = &pdev->dev; + err = host1x_client_register(&dc->client); if (err < 0) { dev_err(&pdev->dev, "failed to register host1x client: %d\n", @@ -2044,8 +2032,6 @@ static int tegra_dc_probe(struct platform_device *pdev) return err; } - platform_set_drvdata(pdev, dc); - return 0; } @@ -2067,7 +2053,22 @@ static int tegra_dc_remove(struct platform_device *pdev) return err; } - reset_control_assert(dc->rst); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int tegra_dc_suspend(struct device *dev) +{ + struct tegra_dc *dc = dev_get_drvdata(dev); + int err; + + err = reset_control_assert(dc->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } if (dc->soc->has_powergate) tegra_powergate_power_off(dc->powergate); @@ -2077,10 +2078,45 @@ static int tegra_dc_remove(struct platform_device *pdev) return 0; } +static int tegra_dc_resume(struct device *dev) +{ + struct tegra_dc *dc = dev_get_drvdata(dev); + int err; + + if (dc->soc->has_powergate) { + err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk, + dc->rst); + if (err < 0) { + dev_err(dev, "failed to power partition: %d\n", err); + return err; + } + } else { + err = clk_prepare_enable(dc->clk); + if (err < 0) { + dev_err(dev, "failed to enable clock: %d\n", err); + return err; + } + + err = reset_control_deassert(dc->rst); + if (err < 0) { + dev_err(dev, "failed to deassert reset: %d\n", err); + return err; + } + } + + return 0; +} +#endif + +static const struct dev_pm_ops tegra_dc_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_dc_suspend, tegra_dc_resume, NULL) +}; + struct platform_driver tegra_dc_driver = { .driver = { .name = "tegra-dc", .of_match_table = tegra_dc_of_match, + .pm = &tegra_dc_pm_ops, }, .probe = tegra_dc_probe, .remove = tegra_dc_remove, diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index b59c3bf0df44..f753e239c55d 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -56,8 +56,8 @@ static void tegra_atomic_complete(struct tegra_drm *tegra, */ drm_atomic_helper_commit_modeset_disables(drm, state); - drm_atomic_helper_commit_planes(drm, state, false); drm_atomic_helper_commit_modeset_enables(drm, state); + drm_atomic_helper_commit_planes(drm, state, true); drm_atomic_helper_wait_for_vblanks(drm, state); -- cgit v1.2.3 From ef8187d752650fe79239c5de9efc906cb7f6b30d Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Aug 2015 09:29:54 +0200 Subject: drm/tegra: dsi: Implement runtime PM Use runtime PM to clock-(un)gate, (de)assert reset and control power to the DSI controller. This ties in nicely with atomic DPMS in that a runtime PM reference is taken before a pipe is enabled and dropped after it has been shut down. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 235 +++++++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 103 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index d1239ebc190f..7e75215a9de4 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -677,6 +678,45 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi) tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL); } +static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) +{ + u32 value; + + value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); + + return 0; +} + +static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) +{ + u32 value; + + /* + * XXX Is this still needed? The module reset is deasserted right + * before this function is called. + */ + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); + tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); + + /* start calibration */ + tegra_dsi_pad_enable(dsi); + + value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | + DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | + DSI_PAD_OUT_CLK(0x0); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2); + + value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | + DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); + tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3); + + return tegra_mipi_calibrate(dsi->mipi); +} + static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk, unsigned int vrefresh) { @@ -837,7 +877,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder) tegra_dsi_disable(dsi); - return; + pm_runtime_put(dsi->dev); } static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) @@ -848,6 +888,13 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) struct tegra_dsi *dsi = to_dsi(output); struct tegra_dsi_state *state; u32 value; + int err; + + pm_runtime_get_sync(dsi->dev); + + err = tegra_dsi_pad_calibrate(dsi); + if (err < 0) + dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); state = tegra_dsi_get_state(dsi); @@ -876,8 +923,6 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder) if (output->panel) drm_panel_enable(output->panel); - - return; } static int @@ -967,55 +1012,12 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = { .atomic_check = tegra_dsi_encoder_atomic_check, }; -static int tegra_dsi_pad_enable(struct tegra_dsi *dsi) -{ - u32 value; - - value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0); - tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0); - - return 0; -} - -static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi) -{ - u32 value; - - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3); - tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4); - - /* start calibration */ - tegra_dsi_pad_enable(dsi); - - value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) | - DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) | - DSI_PAD_OUT_CLK(0x0); - tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2); - - value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) | - DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3); - tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3); - - return tegra_mipi_calibrate(dsi->mipi); -} - static int tegra_dsi_init(struct host1x_client *client) { struct drm_device *drm = dev_get_drvdata(client->parent); struct tegra_dsi *dsi = host1x_client_to_dsi(client); int err; - reset_control_deassert(dsi->rst); - - err = tegra_dsi_pad_calibrate(dsi); - if (err < 0) { - dev_err(dsi->dev, "MIPI calibration failed: %d\n", err); - goto reset; - } - /* Gangsters must not register their own outputs. */ if (!dsi->master) { dsi->output.dev = client->dev; @@ -1038,12 +1040,9 @@ static int tegra_dsi_init(struct host1x_client *client) drm_connector_register(&dsi->output.connector); err = tegra_output_init(drm, &dsi->output); - if (err < 0) { - dev_err(client->dev, - "failed to initialize output: %d\n", + if (err < 0) + dev_err(dsi->dev, "failed to initialize output: %d\n", err); - goto reset; - } dsi->output.encoder.possible_crtcs = 0x3; } @@ -1055,10 +1054,6 @@ static int tegra_dsi_init(struct host1x_client *client) } return 0; - -reset: - reset_control_assert(dsi->rst); - return err; } static int tegra_dsi_exit(struct host1x_client *client) @@ -1070,7 +1065,7 @@ static int tegra_dsi_exit(struct host1x_client *client) if (IS_ENABLED(CONFIG_DEBUG_FS)) tegra_dsi_debugfs_exit(dsi); - reset_control_assert(dsi->rst); + regulator_disable(dsi->vdd); return 0; } @@ -1501,67 +1496,41 @@ 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"); - 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"); - goto reset; + return PTR_ERR(dsi->clk); } 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"); - 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"); - goto disable_clk; + return PTR_ERR(dsi->clk_lp); } dsi->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(dsi->clk_parent)) { dev_err(&pdev->dev, "cannot get parent clock\n"); - err = PTR_ERR(dsi->clk_parent); - goto disable_clk_lp; + return PTR_ERR(dsi->clk_parent); } dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi"); if (IS_ERR(dsi->vdd)) { dev_err(&pdev->dev, "cannot get VDD supply\n"); - 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"); - goto disable_clk_lp; + return PTR_ERR(dsi->vdd); } err = tegra_dsi_setup_clocks(dsi); if (err < 0) { dev_err(&pdev->dev, "cannot setup clocks\n"); - goto disable_vdd; + return err; } regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); dsi->regs = devm_ioremap_resource(&pdev->dev, regs); - if (IS_ERR(dsi->regs)) { - err = PTR_ERR(dsi->regs); - goto disable_vdd; - } + if (IS_ERR(dsi->regs)) + return PTR_ERR(dsi->regs); dsi->mipi = tegra_mipi_request(&pdev->dev); - if (IS_ERR(dsi->mipi)) { - err = PTR_ERR(dsi->mipi); - goto disable_vdd; - } + if (IS_ERR(dsi->mipi)) + return PTR_ERR(dsi->mipi); dsi->host.ops = &tegra_dsi_host_ops; dsi->host.dev = &pdev->dev; @@ -1572,6 +1541,9 @@ static int tegra_dsi_probe(struct platform_device *pdev) goto mipi_free; } + platform_set_drvdata(pdev, dsi); + pm_runtime_enable(&pdev->dev); + INIT_LIST_HEAD(&dsi->client.list); dsi->client.ops = &dsi_client_ops; dsi->client.dev = &pdev->dev; @@ -1583,22 +1555,12 @@ static int tegra_dsi_probe(struct platform_device *pdev) 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; } @@ -1607,6 +1569,8 @@ static int tegra_dsi_remove(struct platform_device *pdev) struct tegra_dsi *dsi = platform_get_drvdata(pdev); int err; + pm_runtime_disable(&pdev->dev); + err = host1x_client_unregister(&dsi->client); if (err < 0) { dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", @@ -1619,14 +1583,78 @@ static int tegra_dsi_remove(struct platform_device *pdev) mipi_dsi_host_unregister(&dsi->host); tegra_mipi_free(dsi->mipi); - regulator_disable(dsi->vdd); + return 0; +} + +#ifdef CONFIG_PM +static int tegra_dsi_suspend(struct device *dev) +{ + struct tegra_dsi *dsi = dev_get_drvdata(dev); + int err; + + err = reset_control_assert(dsi->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } + + usleep_range(1000, 2000); + clk_disable_unprepare(dsi->clk_lp); clk_disable_unprepare(dsi->clk); - reset_control_assert(dsi->rst); + + regulator_disable(dsi->vdd); return 0; } +static int tegra_dsi_resume(struct device *dev) +{ + struct tegra_dsi *dsi = dev_get_drvdata(dev); + int err; + + err = regulator_enable(dsi->vdd); + if (err < 0) { + dev_err(dsi->dev, "failed to enable VDD supply: %d\n", err); + return err; + } + + err = clk_prepare_enable(dsi->clk); + if (err < 0) { + dev_err(dev, "cannot enable DSI clock: %d\n", err); + goto disable_vdd; + } + + err = clk_prepare_enable(dsi->clk_lp); + if (err < 0) { + dev_err(dev, "cannot enable low-power clock: %d\n", err); + goto disable_clk; + } + + usleep_range(1000, 2000); + + err = reset_control_deassert(dsi->rst); + if (err < 0) { + dev_err(dev, "cannot assert reset: %d\n", err); + goto disable_clk_lp; + } + + return 0; + +disable_clk_lp: + clk_disable_unprepare(dsi->clk_lp); +disable_clk: + clk_disable_unprepare(dsi->clk); +disable_vdd: + regulator_disable(dsi->vdd); + return err; +} +#endif + +static const struct dev_pm_ops tegra_dsi_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_dsi_suspend, tegra_dsi_resume, NULL) +}; + static const struct of_device_id tegra_dsi_of_match[] = { { .compatible = "nvidia,tegra210-dsi", }, { .compatible = "nvidia,tegra132-dsi", }, @@ -1640,6 +1668,7 @@ struct platform_driver tegra_dsi_driver = { .driver = { .name = "tegra-dsi", .of_match_table = tegra_dsi_of_match, + .pm = &tegra_dsi_pm_ops, }, .probe = tegra_dsi_probe, .remove = tegra_dsi_remove, -- cgit v1.2.3 From 5234549b93aa2ada9ee3d628b0e06bf291d97577 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Aug 2015 16:00:43 +0200 Subject: drm/tegra: hdmi: Implement runtime PM Use runtime PM to clock-(un)gate and (de)assert reset to the HDMI controller. This ties in nicely with atomic DPMS in that a runtime PM reference is taken before a pipe is enabled and dropped after it has been shut down. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/hdmi.c | 135 ++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 47 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c index 529768d977b8..db90ec782f53 100644 --- a/drivers/gpu/drm/tegra/hdmi.c +++ b/drivers/gpu/drm/tegra/hdmi.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -641,6 +642,29 @@ static void tegra_hdmi_enable_audio(struct tegra_hdmi *hdmi) tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_HDMI_GENERIC_CTRL); } +static void tegra_hdmi_write_eld(struct tegra_hdmi *hdmi) +{ + size_t length = drm_eld_size(hdmi->output.connector.eld), i; + u32 value; + + for (i = 0; i < length; i++) + tegra_hdmi_writel(hdmi, i << 8 | hdmi->output.connector.eld[i], + HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); + + /* + * The HDA codec will always report an ELD buffer size of 96 bytes and + * the HDA codec driver will check that each byte read from the buffer + * is valid. Therefore every byte must be written, even if no 96 bytes + * were parsed from EDID. + */ + for (i = length; i < HDMI_ELD_BUFFER_SIZE; i++) + tegra_hdmi_writel(hdmi, i << 8 | 0, + HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); + + value = SOR_AUDIO_HDA_PRESENSE_VALID | SOR_AUDIO_HDA_PRESENSE_PRESENT; + tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); +} + static inline u32 tegra_hdmi_subpack(const u8 *ptr, size_t size) { u32 value = 0; @@ -945,29 +969,11 @@ static void tegra_hdmi_encoder_disable(struct drm_encoder *encoder) tegra_hdmi_disable_avi_infoframe(hdmi); tegra_hdmi_disable_audio(hdmi); } -} - -static void tegra_hdmi_write_eld(struct tegra_hdmi *hdmi) -{ - size_t length = drm_eld_size(hdmi->output.connector.eld), i; - u32 value; - for (i = 0; i < length; i++) - tegra_hdmi_writel(hdmi, i << 8 | hdmi->output.connector.eld[i], - HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); - - /* - * The HDA codec will always report an ELD buffer size of 96 bytes and - * the HDA codec driver will check that each byte read from the buffer - * is valid. Therefore every byte must be written, even if no 96 bytes - * were parsed from EDID. - */ - for (i = length; i < HDMI_ELD_BUFFER_SIZE; i++) - tegra_hdmi_writel(hdmi, i << 8 | 0, - HDMI_NV_PDISP_SOR_AUDIO_HDA_ELD_BUFWR); + tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE); + tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK); - value = SOR_AUDIO_HDA_PRESENSE_VALID | SOR_AUDIO_HDA_PRESENSE_PRESENT; - tegra_hdmi_writel(hdmi, value, HDMI_NV_PDISP_SOR_AUDIO_HDA_PRESENSE); + pm_runtime_put(hdmi->dev); } static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) @@ -982,6 +988,16 @@ static void tegra_hdmi_encoder_enable(struct drm_encoder *encoder) u32 value; int err; + pm_runtime_get_sync(hdmi->dev); + + /* + * Enable and unmask the HDA codec SCRATCH0 register interrupt. This + * is used for interoperability between the HDA codec driver and the + * HDMI driver. + */ + tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_ENABLE); + tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_MASK); + hdmi->pixel_clock = mode->clock * 1000; h_sync_width = mode->hsync_end - mode->hsync_start; h_back_porch = mode->htotal - mode->hsync_end; @@ -1507,22 +1523,6 @@ static int tegra_hdmi_init(struct host1x_client *client) return err; } - err = clk_prepare_enable(hdmi->clk); - if (err < 0) { - dev_err(hdmi->dev, "failed to enable clock: %d\n", err); - return err; - } - - reset_control_deassert(hdmi->rst); - - /* - * Enable and unmask the HDA codec SCRATCH0 register interrupt. This - * is used for interoperability between the HDA codec driver and the - * HDMI driver. - */ - tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_ENABLE); - tegra_hdmi_writel(hdmi, INT_CODEC_SCRATCH0, HDMI_NV_PDISP_INT_MASK); - return 0; } @@ -1530,14 +1530,8 @@ static int tegra_hdmi_exit(struct host1x_client *client) { struct tegra_hdmi *hdmi = host1x_client_to_hdmi(client); - tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_MASK); - tegra_hdmi_writel(hdmi, 0, HDMI_NV_PDISP_INT_ENABLE); - tegra_output_exit(&hdmi->output); - reset_control_assert(hdmi->rst); - clk_disable_unprepare(hdmi->clk); - regulator_disable(hdmi->vdd); regulator_disable(hdmi->pll); regulator_disable(hdmi->hdmi); @@ -1752,6 +1746,9 @@ static int tegra_hdmi_probe(struct platform_device *pdev) return err; } + platform_set_drvdata(pdev, hdmi); + pm_runtime_enable(&pdev->dev); + INIT_LIST_HEAD(&hdmi->client.list); hdmi->client.ops = &hdmi_client_ops; hdmi->client.dev = &pdev->dev; @@ -1763,8 +1760,6 @@ static int tegra_hdmi_probe(struct platform_device *pdev) return err; } - platform_set_drvdata(pdev, hdmi); - return 0; } @@ -1773,6 +1768,8 @@ static int tegra_hdmi_remove(struct platform_device *pdev) struct tegra_hdmi *hdmi = platform_get_drvdata(pdev); int err; + pm_runtime_disable(&pdev->dev); + err = host1x_client_unregister(&hdmi->client); if (err < 0) { dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", @@ -1782,17 +1779,61 @@ static int tegra_hdmi_remove(struct platform_device *pdev) tegra_output_remove(&hdmi->output); - clk_disable_unprepare(hdmi->clk_parent); + return 0; +} + +#ifdef CONFIG_PM +static int tegra_hdmi_suspend(struct device *dev) +{ + struct tegra_hdmi *hdmi = dev_get_drvdata(dev); + int err; + + err = reset_control_assert(hdmi->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } + + usleep_range(1000, 2000); + clk_disable_unprepare(hdmi->clk); return 0; } +static int tegra_hdmi_resume(struct device *dev) +{ + struct tegra_hdmi *hdmi = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(hdmi->clk); + if (err < 0) { + dev_err(dev, "failed to enable clock: %d\n", err); + return err; + } + + usleep_range(1000, 2000); + + err = reset_control_deassert(hdmi->rst); + if (err < 0) { + dev_err(dev, "failed to deassert reset: %d\n", err); + clk_disable_unprepare(hdmi->clk); + return err; + } + + return 0; +} +#endif + +static const struct dev_pm_ops tegra_hdmi_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_hdmi_suspend, tegra_hdmi_resume, NULL) +}; + struct platform_driver tegra_hdmi_driver = { .driver = { .name = "tegra-hdmi", - .owner = THIS_MODULE, .of_match_table = tegra_hdmi_of_match, + .pm = &tegra_hdmi_pm_ops, }, .probe = tegra_hdmi_probe, .remove = tegra_hdmi_remove, -- cgit v1.2.3 From aaff8bd2e824b6256e6cc1bd4eb3714de0683996 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 7 Aug 2015 16:04:54 +0200 Subject: drm/tegra: sor: Implement runtime PM Use runtime PM to clock-(un)gate and (de)assert reset to the SOR controller. This ties in nicely with atomic DPMS in that a runtime PM reference is taken before a pipe is enabled and dropped after it has been shut down. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 77 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 19 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 8c893b6c6a4c..1eb19ca9fea4 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -1331,8 +1332,7 @@ static void tegra_sor_edp_disable(struct drm_encoder *encoder) if (output->panel) drm_panel_unprepare(output->panel); - reset_control_assert(sor->rst); - clk_disable_unprepare(sor->clk); + pm_runtime_put(sor->dev); } #if 0 @@ -1393,11 +1393,7 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) state = to_sor_state(output->connector.state); - err = clk_prepare_enable(sor->clk); - if (err < 0) - dev_err(sor->dev, "failed to enable clock: %d\n", err); - - reset_control_deassert(sor->rst); + pm_runtime_get_sync(sor->dev); if (output->panel) drm_panel_prepare(output->panel); @@ -1874,9 +1870,7 @@ static void tegra_sor_hdmi_disable(struct drm_encoder *encoder) if (err < 0) dev_err(sor->dev, "failed to power off HDMI rail: %d\n", err); - reset_control_assert(sor->rst); - usleep_range(1000, 2000); - clk_disable_unprepare(sor->clk); + pm_runtime_put(sor->dev); } static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) @@ -1895,13 +1889,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) state = to_sor_state(output->connector.state); mode = &encoder->crtc->state->adjusted_mode; - err = clk_prepare_enable(sor->clk); - if (err < 0) - dev_err(sor->dev, "failed to enable clock: %d\n", err); - - usleep_range(1000, 2000); - - reset_control_deassert(sor->rst); + pm_runtime_get_sync(sor->dev); /* switch to safe parent clock */ err = tegra_sor_set_parent_clock(sor, sor->clk_safe); @@ -2531,6 +2519,9 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } + platform_set_drvdata(pdev, sor); + pm_runtime_enable(&pdev->dev); + INIT_LIST_HEAD(&sor->client.list); sor->client.ops = &sor_client_ops; sor->client.dev = &pdev->dev; @@ -2542,8 +2533,6 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } - platform_set_drvdata(pdev, sor); - return 0; remove: @@ -2559,6 +2548,8 @@ static int tegra_sor_remove(struct platform_device *pdev) struct tegra_sor *sor = platform_get_drvdata(pdev); int err; + pm_runtime_disable(&pdev->dev); + err = host1x_client_unregister(&sor->client); if (err < 0) { dev_err(&pdev->dev, "failed to unregister host1x client: %d\n", @@ -2577,10 +2568,58 @@ static int tegra_sor_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int tegra_sor_suspend(struct device *dev) +{ + struct tegra_sor *sor = dev_get_drvdata(dev); + int err; + + err = reset_control_assert(sor->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } + + usleep_range(1000, 2000); + + clk_disable_unprepare(sor->clk); + + return 0; +} + +static int tegra_sor_resume(struct device *dev) +{ + struct tegra_sor *sor = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(sor->clk); + if (err < 0) { + dev_err(dev, "failed to enable clock: %d\n", err); + return err; + } + + usleep_range(1000, 2000); + + err = reset_control_deassert(sor->rst); + if (err < 0) { + dev_err(dev, "failed to deassert reset: %d\n", err); + clk_disable_unprepare(sor->clk); + return err; + } + + return 0; +} +#endif + +static const struct dev_pm_ops tegra_sor_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_sor_suspend, tegra_sor_resume, NULL) +}; + struct platform_driver tegra_sor_driver = { .driver = { .name = "tegra-sor", .of_match_table = tegra_sor_of_match, + .pm = &tegra_sor_pm_ops, }, .probe = tegra_sor_probe, .remove = tegra_sor_remove, -- cgit v1.2.3 From b299221ca99683e50ea03ac8ca638f9f43d2a59c Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 1 Oct 2015 14:25:03 +0200 Subject: drm/tegra: sor: Implement sor1_brick clock sor1_brick is a clock that can be used as a source for the sor1 clock. The registers to control the clock output are part of the sor1 IP block and hence the sor driver is the best place to implement it. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 107 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 1eb19ca9fea4..896ba3bfbb2f 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -170,6 +171,7 @@ struct tegra_sor { struct reset_control *rst; struct clk *clk_parent; + struct clk *clk_brick; struct clk *clk_safe; struct clk *clk_dp; struct clk *clk; @@ -255,6 +257,101 @@ static int tegra_sor_set_parent_clock(struct tegra_sor *sor, struct clk *parent) return 0; } +struct tegra_clk_sor_brick { + struct clk_hw hw; + struct tegra_sor *sor; +}; + +static inline struct tegra_clk_sor_brick *to_brick(struct clk_hw *hw) +{ + return container_of(hw, struct tegra_clk_sor_brick, hw); +} + +static const char * const tegra_clk_sor_brick_parents[] = { + "pll_d2_out0", "pll_dp" +}; + +static int tegra_clk_sor_brick_set_parent(struct clk_hw *hw, u8 index) +{ + struct tegra_clk_sor_brick *brick = to_brick(hw); + struct tegra_sor *sor = brick->sor; + u32 value; + + value = tegra_sor_readl(sor, SOR_CLK_CNTRL); + value &= ~SOR_CLK_CNTRL_DP_CLK_SEL_MASK; + + switch (index) { + case 0: + value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK; + break; + + case 1: + value |= SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK; + break; + } + + tegra_sor_writel(sor, value, SOR_CLK_CNTRL); + + return 0; +} + +static u8 tegra_clk_sor_brick_get_parent(struct clk_hw *hw) +{ + struct tegra_clk_sor_brick *brick = to_brick(hw); + struct tegra_sor *sor = brick->sor; + u8 parent = U8_MAX; + u32 value; + + value = tegra_sor_readl(sor, SOR_CLK_CNTRL); + + switch (value & SOR_CLK_CNTRL_DP_CLK_SEL_MASK) { + case SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK: + case SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_PCLK: + parent = 0; + break; + + case SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_DPCLK: + case SOR_CLK_CNTRL_DP_CLK_SEL_DIFF_DPCLK: + parent = 1; + break; + } + + return parent; +} + +static const struct clk_ops tegra_clk_sor_brick_ops = { + .set_parent = tegra_clk_sor_brick_set_parent, + .get_parent = tegra_clk_sor_brick_get_parent, +}; + +static struct clk *tegra_clk_sor_brick_register(struct tegra_sor *sor, + const char *name) +{ + struct tegra_clk_sor_brick *brick; + struct clk_init_data init; + struct clk *clk; + + brick = devm_kzalloc(sor->dev, sizeof(*brick), GFP_KERNEL); + if (!brick) + return ERR_PTR(-ENOMEM); + + brick->sor = sor; + + init.name = name; + init.flags = 0; + init.parent_names = tegra_clk_sor_brick_parents; + init.num_parents = ARRAY_SIZE(tegra_clk_sor_brick_parents); + init.ops = &tegra_clk_sor_brick_ops; + + brick->hw.init = &init; + + clk = devm_clk_register(sor->dev, &brick->hw); + if (IS_ERR(clk)) + kfree(brick); + + return clk; +} + static int tegra_sor_dp_train_fast(struct tegra_sor *sor, struct drm_dp_link *link) { @@ -2522,6 +2619,16 @@ static int tegra_sor_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sor); pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + sor->clk_brick = tegra_clk_sor_brick_register(sor, "sor1_brick"); + pm_runtime_put(&pdev->dev); + + if (IS_ERR(sor->clk_brick)) { + err = PTR_ERR(sor->clk_brick); + dev_err(&pdev->dev, "failed to register SOR clock: %d\n", err); + goto remove; + } + INIT_LIST_HEAD(&sor->client.list); sor->client.ops = &sor_client_ops; sor->client.dev = &pdev->dev; -- cgit v1.2.3 From 618dee3941a43d8d4f762a7af99bcb59baba2bc5 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 9 Jun 2016 17:53:57 +0200 Subject: drm/tegra: sor: Use sor1_src clock to set parent for HDMI When running in HDMI mode, the sor1 IP block needs to use the sor1_src as parent clock, and in turn configure the sor1_src to use pll_d2_out0 as its parent. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 896ba3bfbb2f..26568ca3c43c 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -173,6 +173,7 @@ struct tegra_sor { struct clk *clk_parent; struct clk *clk_brick; struct clk *clk_safe; + struct clk *clk_src; struct clk *clk_dp; struct clk *clk; @@ -2101,7 +2102,11 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL); /* switch to parent clock */ - err = tegra_sor_set_parent_clock(sor, sor->clk_parent); + err = clk_set_parent(sor->clk_src, sor->clk_parent); + if (err < 0) + dev_err(sor->dev, "failed to set source clock: %d\n", err); + + err = tegra_sor_set_parent_clock(sor, sor->clk_src); if (err < 0) dev_err(sor->dev, "failed to set parent clock: %d\n", err); @@ -2595,6 +2600,16 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } + if (sor->soc->supports_hdmi || sor->soc->supports_dp) { + sor->clk_src = devm_clk_get(&pdev->dev, "source"); + if (IS_ERR(sor->clk_src)) { + err = PTR_ERR(sor->clk_src); + dev_err(sor->dev, "failed to get source clock: %d\n", + err); + goto remove; + } + } + sor->clk_parent = devm_clk_get(&pdev->dev, "parent"); if (IS_ERR(sor->clk_parent)) { err = PTR_ERR(sor->clk_parent); -- cgit v1.2.3 From 30b4943558b9c482c30fc09cb899d6b03f67fb11 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Mon, 3 Aug 2015 15:50:32 +0200 Subject: drm/tegra: sor: Make XBAR configurable per SoC Provide a per-SoC mapping of lanes which can be used to configure the XBAR. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index 26568ca3c43c..b652201eadad 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -151,6 +151,8 @@ struct tegra_sor_soc { const struct tegra_sor_hdmi_settings *settings; unsigned int num_settings; + + const u8 *xbar_cfg; }; struct tegra_sor; @@ -1613,6 +1615,14 @@ static void tegra_sor_edp_enable(struct drm_encoder *encoder) value &= ~SOR_PLL2_PORT_POWERDOWN; tegra_sor_writel(sor, value, SOR_PLL2); + /* XXX not in TRM */ + for (value = 0, i = 0; i < 5; i++) + value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->soc->xbar_cfg[i]) | + SOR_XBAR_CTRL_LINK1_XSEL(i, i); + + tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL); + tegra_sor_writel(sor, value, SOR_XBAR_CTRL); + /* switch to DP parent clock */ err = tegra_sor_set_parent_clock(sor, sor->clk_dp); if (err < 0) @@ -1980,7 +1990,7 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) struct tegra_sor *sor = to_sor(output); struct tegra_sor_state *state; struct drm_display_mode *mode; - unsigned int div; + unsigned int div, i; u32 value; int err; @@ -2086,20 +2096,13 @@ static void tegra_sor_hdmi_enable(struct drm_encoder *encoder) value = SOR_REFCLK_DIV_INT(div) | SOR_REFCLK_DIV_FRAC(div); tegra_sor_writel(sor, value, SOR_REFCLK); - /* XXX don't hardcode */ - value = SOR_XBAR_CTRL_LINK1_XSEL(4, 4) | - SOR_XBAR_CTRL_LINK1_XSEL(3, 3) | - SOR_XBAR_CTRL_LINK1_XSEL(2, 2) | - SOR_XBAR_CTRL_LINK1_XSEL(1, 1) | - SOR_XBAR_CTRL_LINK1_XSEL(0, 0) | - SOR_XBAR_CTRL_LINK0_XSEL(4, 4) | - SOR_XBAR_CTRL_LINK0_XSEL(3, 3) | - SOR_XBAR_CTRL_LINK0_XSEL(2, 0) | - SOR_XBAR_CTRL_LINK0_XSEL(1, 1) | - SOR_XBAR_CTRL_LINK0_XSEL(0, 2); - tegra_sor_writel(sor, value, SOR_XBAR_CTRL); + /* XXX not in TRM */ + for (value = 0, i = 0; i < 5; i++) + value |= SOR_XBAR_CTRL_LINK0_XSEL(i, sor->soc->xbar_cfg[i]) | + SOR_XBAR_CTRL_LINK1_XSEL(i, i); tegra_sor_writel(sor, 0x00000000, SOR_XBAR_POL); + tegra_sor_writel(sor, value, SOR_XBAR_CTRL); /* switch to parent clock */ err = clk_set_parent(sor->clk_src, sor->clk_parent); @@ -2475,11 +2478,16 @@ static const struct tegra_sor_ops tegra_sor_hdmi_ops = { .remove = tegra_sor_hdmi_remove, }; +static const u8 tegra124_sor_xbar_cfg[5] = { + 0, 1, 2, 3, 4 +}; + static const struct tegra_sor_soc tegra124_sor = { .supports_edp = true, .supports_lvds = true, .supports_hdmi = false, .supports_dp = false, + .xbar_cfg = tegra124_sor_xbar_cfg, }; static const struct tegra_sor_soc tegra210_sor = { @@ -2487,6 +2495,11 @@ static const struct tegra_sor_soc tegra210_sor = { .supports_lvds = false, .supports_hdmi = false, .supports_dp = false, + .xbar_cfg = tegra124_sor_xbar_cfg, +}; + +static const u8 tegra210_sor_xbar_cfg[5] = { + 2, 1, 0, 3, 4 }; static const struct tegra_sor_soc tegra210_sor1 = { @@ -2497,6 +2510,8 @@ static const struct tegra_sor_soc tegra210_sor1 = { .num_settings = ARRAY_SIZE(tegra210_sor_hdmi_defaults), .settings = tegra210_sor_hdmi_defaults, + + .xbar_cfg = tegra210_sor_xbar_cfg, }; static const struct of_device_id tegra_sor_of_match[] = { -- cgit v1.2.3 From 64230aa075864f7c8e270b583bca685304246b57 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 1 Jul 2016 14:21:37 +0100 Subject: drm/tegra: dsi: Prepare for generic PM domain support The DSI driver for Tegra requires the SOR power partition to be enabled. Now that Tegra supports the generic PM domain framework we manage the SOR power partition via this framework. However, the sequence for gating/ungating the SOR power partition requires that the DSI reset is asserted/de-asserted at the time the SOR power partition is gated/ungated, respectively. Now that the reset control core assumes that resets are exclusive, the Tegra generic PM domain code and the DSI driver cannot request the same reset unless we mark the reset as shared. Sharing resets will not work in this case because we cannot guarantee that the reset will be asserted/de-asserted at the appropriate time. Therefore, given that the Tegra generic PM domain code will handle the resets, do not request the reset in the DSI driver if the DSI device has a PM domain associated. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/dsi.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/dsi.c b/drivers/gpu/drm/tegra/dsi.c index 7e75215a9de4..972618e6acf6 100644 --- a/drivers/gpu/drm/tegra/dsi.c +++ b/drivers/gpu/drm/tegra/dsi.c @@ -1489,9 +1489,11 @@ static int tegra_dsi_probe(struct platform_device *pdev) dsi->format = MIPI_DSI_FMT_RGB888; dsi->lanes = 4; - dsi->rst = devm_reset_control_get(&pdev->dev, "dsi"); - if (IS_ERR(dsi->rst)) - return PTR_ERR(dsi->rst); + if (!pdev->dev.pm_domain) { + dsi->rst = devm_reset_control_get(&pdev->dev, "dsi"); + if (IS_ERR(dsi->rst)) + return PTR_ERR(dsi->rst); + } dsi->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(dsi->clk)) { @@ -1592,10 +1594,12 @@ static int tegra_dsi_suspend(struct device *dev) struct tegra_dsi *dsi = dev_get_drvdata(dev); int err; - err = reset_control_assert(dsi->rst); - if (err < 0) { - dev_err(dev, "failed to assert reset: %d\n", err); - return err; + if (dsi->rst) { + err = reset_control_assert(dsi->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } } usleep_range(1000, 2000); @@ -1633,10 +1637,12 @@ static int tegra_dsi_resume(struct device *dev) usleep_range(1000, 2000); - err = reset_control_deassert(dsi->rst); - if (err < 0) { - dev_err(dev, "cannot assert reset: %d\n", err); - goto disable_clk_lp; + if (dsi->rst) { + err = reset_control_deassert(dsi->rst); + if (err < 0) { + dev_err(dev, "cannot assert reset: %d\n", err); + goto disable_clk_lp; + } } return 0; -- cgit v1.2.3 From f8c79120aa1d9eedb36cdadbd23489056a9f7b90 Mon Sep 17 00:00:00 2001 From: Jon Hunter Date: Fri, 1 Jul 2016 14:21:38 +0100 Subject: drm/tegra: sor: Prepare for generic PM domain support The SOR driver for Tegra requires the SOR power partition to be enabled. Now that Tegra supports the generic PM domain framework we manage the SOR power partition via this framework. However, the sequence for gating/ungating the SOR power partition requires that the SOR reset is asserted/de-asserted at the time the SOR power partition is gated/ungated, respectively. Now that the reset control core assumes that resets are exclusive, the Tegra generic PM domain code and the SOR driver cannot request the same reset unless we mark the reset as shared. Sharing resets will not work in this case because we cannot guarantee that the reset will be asserted/de-asserted at the appropriate time. Therefore, given that the Tegra generic PM domain code will handle the resets, do not request the reset in the SOR driver if the SOR device has a PM domain associated. Signed-off-by: Jon Hunter Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 57 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index b652201eadad..cb7e06062174 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -2350,10 +2350,13 @@ static int tegra_sor_init(struct host1x_client *client) * XXX: Remove this reset once proper hand-over from firmware to * kernel is possible. */ - err = reset_control_assert(sor->rst); - if (err < 0) { - dev_err(sor->dev, "failed to assert SOR reset: %d\n", err); - return err; + if (sor->rst) { + err = reset_control_assert(sor->rst); + if (err < 0) { + dev_err(sor->dev, "failed to assert SOR reset: %d\n", + err); + return err; + } } err = clk_prepare_enable(sor->clk); @@ -2364,10 +2367,13 @@ static int tegra_sor_init(struct host1x_client *client) usleep_range(1000, 3000); - err = reset_control_deassert(sor->rst); - if (err < 0) { - dev_err(sor->dev, "failed to deassert SOR reset: %d\n", err); - return err; + if (sor->rst) { + err = reset_control_deassert(sor->rst); + if (err < 0) { + dev_err(sor->dev, "failed to deassert SOR reset: %d\n", + err); + return err; + } } err = clk_prepare_enable(sor->clk_safe); @@ -2601,11 +2607,14 @@ static int tegra_sor_probe(struct platform_device *pdev) goto remove; } - sor->rst = devm_reset_control_get(&pdev->dev, "sor"); - if (IS_ERR(sor->rst)) { - err = PTR_ERR(sor->rst); - dev_err(&pdev->dev, "failed to get reset control: %d\n", err); - goto remove; + if (!pdev->dev.pm_domain) { + sor->rst = devm_reset_control_get(&pdev->dev, "sor"); + if (IS_ERR(sor->rst)) { + err = PTR_ERR(sor->rst); + dev_err(&pdev->dev, "failed to get reset control: %d\n", + err); + goto remove; + } } sor->clk = devm_clk_get(&pdev->dev, NULL); @@ -2711,10 +2720,12 @@ static int tegra_sor_suspend(struct device *dev) struct tegra_sor *sor = dev_get_drvdata(dev); int err; - err = reset_control_assert(sor->rst); - if (err < 0) { - dev_err(dev, "failed to assert reset: %d\n", err); - return err; + if (sor->rst) { + err = reset_control_assert(sor->rst); + if (err < 0) { + dev_err(dev, "failed to assert reset: %d\n", err); + return err; + } } usleep_range(1000, 2000); @@ -2737,11 +2748,13 @@ static int tegra_sor_resume(struct device *dev) usleep_range(1000, 2000); - err = reset_control_deassert(sor->rst); - if (err < 0) { - dev_err(dev, "failed to deassert reset: %d\n", err); - clk_disable_unprepare(sor->clk); - return err; + if (sor->rst) { + err = reset_control_deassert(sor->rst); + if (err < 0) { + dev_err(dev, "failed to deassert reset: %d\n", err); + clk_disable_unprepare(sor->clk); + return err; + } } return 0; -- cgit v1.2.3 From 64ea25c3bc86c05c7da6c683b86663f4c90158d6 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Tue, 12 Jul 2016 16:52:22 +0200 Subject: drm/tegra: sor: Reject HDMI 2.0 modes Enabling HDMI 2.0 modes requires extra programming and will not work with the current driver, so reject all those modes. Signed-off-by: Thierry Reding --- drivers/gpu/drm/tegra/sor.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/gpu/drm/tegra/sor.c b/drivers/gpu/drm/tegra/sor.c index cb7e06062174..43e934e8fbec 100644 --- a/drivers/gpu/drm/tegra/sor.c +++ b/drivers/gpu/drm/tegra/sor.c @@ -1372,6 +1372,10 @@ static enum drm_mode_status tegra_sor_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) { + /* HDMI 2.0 modes are not yet supported */ + if (mode->clock > 340000) + return MODE_NOCLOCK; + return MODE_OK; } -- cgit v1.2.3