123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- /*
- * Copyright (C) 2012 Texas Instruments
- * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 as published by
- * the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #define DSS_SUBSYS_NAME "APPLY"
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/spinlock.h>
- #include <linux/jiffies.h>
- #include <linux/delay.h>
- #include <linux/interrupt.h>
- #include <linux/seq_file.h>
- #include <video/omapdss.h>
- #include "dss.h"
- #include "dss_features.h"
- #include "dispc-compat.h"
- #define DISPC_IRQ_MASK_ERROR (DISPC_IRQ_GFX_FIFO_UNDERFLOW | \
- DISPC_IRQ_OCP_ERR | \
- DISPC_IRQ_VID1_FIFO_UNDERFLOW | \
- DISPC_IRQ_VID2_FIFO_UNDERFLOW | \
- DISPC_IRQ_SYNC_LOST | \
- DISPC_IRQ_SYNC_LOST_DIGIT)
- #define DISPC_MAX_NR_ISRS 8
- struct omap_dispc_isr_data {
- omap_dispc_isr_t isr;
- void *arg;
- u32 mask;
- };
- struct dispc_irq_stats {
- unsigned long last_reset;
- unsigned irq_count;
- unsigned irqs[32];
- };
- static struct {
- spinlock_t irq_lock;
- u32 irq_error_mask;
- struct omap_dispc_isr_data registered_isr[DISPC_MAX_NR_ISRS];
- u32 error_irqs;
- struct work_struct error_work;
- #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
- spinlock_t irq_stats_lock;
- struct dispc_irq_stats irq_stats;
- #endif
- } dispc_compat;
- #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
- static void dispc_dump_irqs(struct seq_file *s)
- {
- unsigned long flags;
- struct dispc_irq_stats stats;
- spin_lock_irqsave(&dispc_compat.irq_stats_lock, flags);
- stats = dispc_compat.irq_stats;
- memset(&dispc_compat.irq_stats, 0, sizeof(dispc_compat.irq_stats));
- dispc_compat.irq_stats.last_reset = jiffies;
- spin_unlock_irqrestore(&dispc_compat.irq_stats_lock, flags);
- seq_printf(s, "period %u ms\n",
- jiffies_to_msecs(jiffies - stats.last_reset));
- seq_printf(s, "irqs %d\n", stats.irq_count);
- #define PIS(x) \
- seq_printf(s, "%-20s %10d\n", #x, stats.irqs[ffs(DISPC_IRQ_##x)-1]);
- PIS(FRAMEDONE);
- PIS(VSYNC);
- PIS(EVSYNC_EVEN);
- PIS(EVSYNC_ODD);
- PIS(ACBIAS_COUNT_STAT);
- PIS(PROG_LINE_NUM);
- PIS(GFX_FIFO_UNDERFLOW);
- PIS(GFX_END_WIN);
- PIS(PAL_GAMMA_MASK);
- PIS(OCP_ERR);
- PIS(VID1_FIFO_UNDERFLOW);
- PIS(VID1_END_WIN);
- PIS(VID2_FIFO_UNDERFLOW);
- PIS(VID2_END_WIN);
- if (dss_feat_get_num_ovls() > 3) {
- PIS(VID3_FIFO_UNDERFLOW);
- PIS(VID3_END_WIN);
- }
- PIS(SYNC_LOST);
- PIS(SYNC_LOST_DIGIT);
- PIS(WAKEUP);
- if (dss_has_feature(FEAT_MGR_LCD2)) {
- PIS(FRAMEDONE2);
- PIS(VSYNC2);
- PIS(ACBIAS_COUNT_STAT2);
- PIS(SYNC_LOST2);
- }
- if (dss_has_feature(FEAT_MGR_LCD3)) {
- PIS(FRAMEDONE3);
- PIS(VSYNC3);
- PIS(ACBIAS_COUNT_STAT3);
- PIS(SYNC_LOST3);
- }
- #undef PIS
- }
- #endif
- /* dispc.irq_lock has to be locked by the caller */
- static void _omap_dispc_set_irqs(void)
- {
- u32 mask;
- int i;
- struct omap_dispc_isr_data *isr_data;
- mask = dispc_compat.irq_error_mask;
- for (i = 0; i < DISPC_MAX_NR_ISRS; i++) {
- isr_data = &dispc_compat.registered_isr[i];
- if (isr_data->isr == NULL)
- continue;
- mask |= isr_data->mask;
- }
- dispc_write_irqenable(mask);
- }
- int omap_dispc_register_isr(omap_dispc_isr_t isr, void *arg, u32 mask)
- {
- int i;
- int ret;
- unsigned long flags;
- struct omap_dispc_isr_data *isr_data;
- if (isr == NULL)
- return -EINVAL;
- spin_lock_irqsave(&dispc_compat.irq_lock, flags);
- /* check for duplicate entry */
- for (i = 0; i < DISPC_MAX_NR_ISRS; i++) {
- isr_data = &dispc_compat.registered_isr[i];
- if (isr_data->isr == isr && isr_data->arg == arg &&
- isr_data->mask == mask) {
- ret = -EINVAL;
- goto err;
- }
- }
- isr_data = NULL;
- ret = -EBUSY;
- for (i = 0; i < DISPC_MAX_NR_ISRS; i++) {
- isr_data = &dispc_compat.registered_isr[i];
- if (isr_data->isr != NULL)
- continue;
- isr_data->isr = isr;
- isr_data->arg = arg;
- isr_data->mask = mask;
- ret = 0;
- break;
- }
- if (ret)
- goto err;
- _omap_dispc_set_irqs();
- spin_unlock_irqrestore(&dispc_compat.irq_lock, flags);
- return 0;
- err:
- spin_unlock_irqrestore(&dispc_compat.irq_lock, flags);
- return ret;
- }
- EXPORT_SYMBOL(omap_dispc_register_isr);
- int omap_dispc_unregister_isr(omap_dispc_isr_t isr, void *arg, u32 mask)
- {
- int i;
- unsigned long flags;
- int ret = -EINVAL;
- struct omap_dispc_isr_data *isr_data;
- spin_lock_irqsave(&dispc_compat.irq_lock, flags);
- for (i = 0; i < DISPC_MAX_NR_ISRS; i++) {
- isr_data = &dispc_compat.registered_isr[i];
- if (isr_data->isr != isr || isr_data->arg != arg ||
- isr_data->mask != mask)
- continue;
- /* found the correct isr */
- isr_data->isr = NULL;
- isr_data->arg = NULL;
- isr_data->mask = 0;
- ret = 0;
- break;
- }
- if (ret == 0)
- _omap_dispc_set_irqs();
- spin_unlock_irqrestore(&dispc_compat.irq_lock, flags);
- return ret;
- }
- EXPORT_SYMBOL(omap_dispc_unregister_isr);
- static void print_irq_status(u32 status)
- {
- if ((status & dispc_compat.irq_error_mask) == 0)
- return;
- #define PIS(x) (status & DISPC_IRQ_##x) ? (#x " ") : ""
- pr_debug("DISPC IRQ: 0x%x: %s%s%s%s%s%s%s%s%s\n",
- status,
- PIS(OCP_ERR),
- PIS(GFX_FIFO_UNDERFLOW),
- PIS(VID1_FIFO_UNDERFLOW),
- PIS(VID2_FIFO_UNDERFLOW),
- dss_feat_get_num_ovls() > 3 ? PIS(VID3_FIFO_UNDERFLOW) : "",
- PIS(SYNC_LOST),
- PIS(SYNC_LOST_DIGIT),
- dss_has_feature(FEAT_MGR_LCD2) ? PIS(SYNC_LOST2) : "",
- dss_has_feature(FEAT_MGR_LCD3) ? PIS(SYNC_LOST3) : "");
- #undef PIS
- }
- /* Called from dss.c. Note that we don't touch clocks here,
- * but we presume they are on because we got an IRQ. However,
- * an irq handler may turn the clocks off, so we may not have
- * clock later in the function. */
- static irqreturn_t omap_dispc_irq_handler(int irq, void *arg)
- {
- int i;
- u32 irqstatus, irqenable;
- u32 handledirqs = 0;
- u32 unhandled_errors;
- struct omap_dispc_isr_data *isr_data;
- struct omap_dispc_isr_data registered_isr[DISPC_MAX_NR_ISRS];
- spin_lock(&dispc_compat.irq_lock);
- irqstatus = dispc_read_irqstatus();
- irqenable = dispc_read_irqenable();
- /* IRQ is not for us */
- if (!(irqstatus & irqenable)) {
- spin_unlock(&dispc_compat.irq_lock);
- return IRQ_NONE;
- }
- #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
- spin_lock(&dispc_compat.irq_stats_lock);
- dispc_compat.irq_stats.irq_count++;
- dss_collect_irq_stats(irqstatus, dispc_compat.irq_stats.irqs);
- spin_unlock(&dispc_compat.irq_stats_lock);
- #endif
- print_irq_status(irqstatus);
- /* Ack the interrupt. Do it here before clocks are possibly turned
- * off */
- dispc_clear_irqstatus(irqstatus);
- /* flush posted write */
- dispc_read_irqstatus();
- /* make a copy and unlock, so that isrs can unregister
- * themselves */
- memcpy(registered_isr, dispc_compat.registered_isr,
- sizeof(registered_isr));
- spin_unlock(&dispc_compat.irq_lock);
- for (i = 0; i < DISPC_MAX_NR_ISRS; i++) {
- isr_data = ®istered_isr[i];
- if (!isr_data->isr)
- continue;
- if (isr_data->mask & irqstatus) {
- isr_data->isr(isr_data->arg, irqstatus);
- handledirqs |= isr_data->mask;
- }
- }
- spin_lock(&dispc_compat.irq_lock);
- unhandled_errors = irqstatus & ~handledirqs & dispc_compat.irq_error_mask;
- if (unhandled_errors) {
- dispc_compat.error_irqs |= unhandled_errors;
- dispc_compat.irq_error_mask &= ~unhandled_errors;
- _omap_dispc_set_irqs();
- schedule_work(&dispc_compat.error_work);
- }
- spin_unlock(&dispc_compat.irq_lock);
- return IRQ_HANDLED;
- }
- static void dispc_error_worker(struct work_struct *work)
- {
- int i;
- u32 errors;
- unsigned long flags;
- static const unsigned fifo_underflow_bits[] = {
- DISPC_IRQ_GFX_FIFO_UNDERFLOW,
- DISPC_IRQ_VID1_FIFO_UNDERFLOW,
- DISPC_IRQ_VID2_FIFO_UNDERFLOW,
- DISPC_IRQ_VID3_FIFO_UNDERFLOW,
- };
- spin_lock_irqsave(&dispc_compat.irq_lock, flags);
- errors = dispc_compat.error_irqs;
- dispc_compat.error_irqs = 0;
- spin_unlock_irqrestore(&dispc_compat.irq_lock, flags);
- dispc_runtime_get();
- for (i = 0; i < omap_dss_get_num_overlays(); ++i) {
- struct omap_overlay *ovl;
- unsigned bit;
- ovl = omap_dss_get_overlay(i);
- bit = fifo_underflow_bits[i];
- if (bit & errors) {
- DSSERR("FIFO UNDERFLOW on %s, disabling the overlay\n",
- ovl->name);
- ovl->disable(ovl);
- msleep(50);
- }
- }
- for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) {
- struct omap_overlay_manager *mgr;
- unsigned bit;
- mgr = omap_dss_get_overlay_manager(i);
- bit = dispc_mgr_get_sync_lost_irq(i);
- if (bit & errors) {
- int j;
- DSSERR("SYNC_LOST on channel %s, restarting the output "
- "with video overlays disabled\n",
- mgr->name);
- dss_mgr_disable(mgr);
- for (j = 0; j < omap_dss_get_num_overlays(); ++j) {
- struct omap_overlay *ovl;
- ovl = omap_dss_get_overlay(j);
- if (ovl->id != OMAP_DSS_GFX &&
- ovl->manager == mgr)
- ovl->disable(ovl);
- }
- dss_mgr_enable(mgr);
- }
- }
- if (errors & DISPC_IRQ_OCP_ERR) {
- DSSERR("OCP_ERR\n");
- for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) {
- struct omap_overlay_manager *mgr;
- mgr = omap_dss_get_overlay_manager(i);
- dss_mgr_disable(mgr);
- }
- }
- spin_lock_irqsave(&dispc_compat.irq_lock, flags);
- dispc_compat.irq_error_mask |= errors;
- _omap_dispc_set_irqs();
- spin_unlock_irqrestore(&dispc_compat.irq_lock, flags);
- dispc_runtime_put();
- }
- int dss_dispc_initialize_irq(void)
- {
- int r;
- #ifdef CONFIG_OMAP2_DSS_COLLECT_IRQ_STATS
- spin_lock_init(&dispc_compat.irq_stats_lock);
- dispc_compat.irq_stats.last_reset = jiffies;
- dss_debugfs_create_file("dispc_irq", dispc_dump_irqs);
- #endif
- spin_lock_init(&dispc_compat.irq_lock);
- memset(dispc_compat.registered_isr, 0,
- sizeof(dispc_compat.registered_isr));
- dispc_compat.irq_error_mask = DISPC_IRQ_MASK_ERROR;
- if (dss_has_feature(FEAT_MGR_LCD2))
- dispc_compat.irq_error_mask |= DISPC_IRQ_SYNC_LOST2;
- if (dss_has_feature(FEAT_MGR_LCD3))
- dispc_compat.irq_error_mask |= DISPC_IRQ_SYNC_LOST3;
- if (dss_feat_get_num_ovls() > 3)
- dispc_compat.irq_error_mask |= DISPC_IRQ_VID3_FIFO_UNDERFLOW;
- /*
- * there's SYNC_LOST_DIGIT waiting after enabling the DSS,
- * so clear it
- */
- dispc_clear_irqstatus(dispc_read_irqstatus());
- INIT_WORK(&dispc_compat.error_work, dispc_error_worker);
- _omap_dispc_set_irqs();
- r = dispc_request_irq(omap_dispc_irq_handler, &dispc_compat);
- if (r) {
- DSSERR("dispc_request_irq failed\n");
- return r;
- }
- return 0;
- }
- void dss_dispc_uninitialize_irq(void)
- {
- dispc_free_irq(&dispc_compat);
- }
- static void dispc_mgr_disable_isr(void *data, u32 mask)
- {
- struct completion *compl = data;
- complete(compl);
- }
- static void dispc_mgr_enable_lcd_out(enum omap_channel channel)
- {
- dispc_mgr_enable(channel, true);
- }
- static void dispc_mgr_disable_lcd_out(enum omap_channel channel)
- {
- DECLARE_COMPLETION_ONSTACK(framedone_compl);
- int r;
- u32 irq;
- if (dispc_mgr_is_enabled(channel) == false)
- return;
- /*
- * When we disable LCD output, we need to wait for FRAMEDONE to know
- * that DISPC has finished with the LCD output.
- */
- irq = dispc_mgr_get_framedone_irq(channel);
- r = omap_dispc_register_isr(dispc_mgr_disable_isr, &framedone_compl,
- irq);
- if (r)
- DSSERR("failed to register FRAMEDONE isr\n");
- dispc_mgr_enable(channel, false);
- /* if we couldn't register for framedone, just sleep and exit */
- if (r) {
- msleep(100);
- return;
- }
- if (!wait_for_completion_timeout(&framedone_compl,
- msecs_to_jiffies(100)))
- DSSERR("timeout waiting for FRAME DONE\n");
- r = omap_dispc_unregister_isr(dispc_mgr_disable_isr, &framedone_compl,
- irq);
- if (r)
- DSSERR("failed to unregister FRAMEDONE isr\n");
- }
- static void dispc_digit_out_enable_isr(void *data, u32 mask)
- {
- struct completion *compl = data;
- /* ignore any sync lost interrupts */
- if (mask & (DISPC_IRQ_EVSYNC_EVEN | DISPC_IRQ_EVSYNC_ODD))
- complete(compl);
- }
- static void dispc_mgr_enable_digit_out(void)
- {
- DECLARE_COMPLETION_ONSTACK(vsync_compl);
- int r;
- u32 irq_mask;
- if (dispc_mgr_is_enabled(OMAP_DSS_CHANNEL_DIGIT) == true)
- return;
- /*
- * Digit output produces some sync lost interrupts during the first
- * frame when enabling. Those need to be ignored, so we register for the
- * sync lost irq to prevent the error handler from triggering.
- */
- irq_mask = dispc_mgr_get_vsync_irq(OMAP_DSS_CHANNEL_DIGIT) |
- dispc_mgr_get_sync_lost_irq(OMAP_DSS_CHANNEL_DIGIT);
- r = omap_dispc_register_isr(dispc_digit_out_enable_isr, &vsync_compl,
- irq_mask);
- if (r) {
- DSSERR("failed to register %x isr\n", irq_mask);
- return;
- }
- dispc_mgr_enable(OMAP_DSS_CHANNEL_DIGIT, true);
- /* wait for the first evsync */
- if (!wait_for_completion_timeout(&vsync_compl, msecs_to_jiffies(100)))
- DSSERR("timeout waiting for digit out to start\n");
- r = omap_dispc_unregister_isr(dispc_digit_out_enable_isr, &vsync_compl,
- irq_mask);
- if (r)
- DSSERR("failed to unregister %x isr\n", irq_mask);
- }
- static void dispc_mgr_disable_digit_out(void)
- {
- DECLARE_COMPLETION_ONSTACK(framedone_compl);
- int r, i;
- u32 irq_mask;
- int num_irqs;
- if (dispc_mgr_is_enabled(OMAP_DSS_CHANNEL_DIGIT) == false)
- return;
- /*
- * When we disable the digit output, we need to wait for FRAMEDONE to
- * know that DISPC has finished with the output.
- */
- irq_mask = dispc_mgr_get_framedone_irq(OMAP_DSS_CHANNEL_DIGIT);
- num_irqs = 1;
- if (!irq_mask) {
- /*
- * omap 2/3 don't have framedone irq for TV, so we need to use
- * vsyncs for this.
- */
- irq_mask = dispc_mgr_get_vsync_irq(OMAP_DSS_CHANNEL_DIGIT);
- /*
- * We need to wait for both even and odd vsyncs. Note that this
- * is not totally reliable, as we could get a vsync interrupt
- * before we disable the output, which leads to timeout in the
- * wait_for_completion.
- */
- num_irqs = 2;
- }
- r = omap_dispc_register_isr(dispc_mgr_disable_isr, &framedone_compl,
- irq_mask);
- if (r)
- DSSERR("failed to register %x isr\n", irq_mask);
- dispc_mgr_enable(OMAP_DSS_CHANNEL_DIGIT, false);
- /* if we couldn't register the irq, just sleep and exit */
- if (r) {
- msleep(100);
- return;
- }
- for (i = 0; i < num_irqs; ++i) {
- if (!wait_for_completion_timeout(&framedone_compl,
- msecs_to_jiffies(100)))
- DSSERR("timeout waiting for digit out to stop\n");
- }
- r = omap_dispc_unregister_isr(dispc_mgr_disable_isr, &framedone_compl,
- irq_mask);
- if (r)
- DSSERR("failed to unregister %x isr\n", irq_mask);
- }
- void dispc_mgr_enable_sync(enum omap_channel channel)
- {
- if (dss_mgr_is_lcd(channel))
- dispc_mgr_enable_lcd_out(channel);
- else if (channel == OMAP_DSS_CHANNEL_DIGIT)
- dispc_mgr_enable_digit_out();
- else
- WARN_ON(1);
- }
- void dispc_mgr_disable_sync(enum omap_channel channel)
- {
- if (dss_mgr_is_lcd(channel))
- dispc_mgr_disable_lcd_out(channel);
- else if (channel == OMAP_DSS_CHANNEL_DIGIT)
- dispc_mgr_disable_digit_out();
- else
- WARN_ON(1);
- }
- static inline void dispc_irq_wait_handler(void *data, u32 mask)
- {
- complete((struct completion *)data);
- }
- int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask,
- unsigned long timeout)
- {
- int r;
- DECLARE_COMPLETION_ONSTACK(completion);
- r = omap_dispc_register_isr(dispc_irq_wait_handler, &completion,
- irqmask);
- if (r)
- return r;
- timeout = wait_for_completion_interruptible_timeout(&completion,
- timeout);
- omap_dispc_unregister_isr(dispc_irq_wait_handler, &completion, irqmask);
- if (timeout == 0)
- return -ETIMEDOUT;
- if (timeout == -ERESTARTSYS)
- return -ERESTARTSYS;
- return 0;
- }
|