/* * 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); dispc_ovl_enable(ovl->id, false); dispc_mgr_go(ovl->manager->id); 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); } int omap_dispc_wait_for_irq_interruptible_timeout(u32 irqmask, unsigned long timeout) { void dispc_irq_wait_handler(void *data, u32 mask) { complete((struct completion *)data); } 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; }