Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / video / fbdev / omap2 / dss / dispc-compat.c
diff --git a/kernel/drivers/video/fbdev/omap2/dss/dispc-compat.c b/kernel/drivers/video/fbdev/omap2/dss/dispc-compat.c
new file mode 100644 (file)
index 0000000..633c461
--- /dev/null
@@ -0,0 +1,667 @@
+/*
+ * 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 = &registered_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;
+}