diff options
Diffstat (limited to 'drivers/video/fbdev/omap2/omapfb/dss/dispc-compat.c')
-rw-r--r-- | drivers/video/fbdev/omap2/omapfb/dss/dispc-compat.c | 657 |
1 files changed, 657 insertions, 0 deletions
diff --git a/drivers/video/fbdev/omap2/omapfb/dss/dispc-compat.c b/drivers/video/fbdev/omap2/omapfb/dss/dispc-compat.c new file mode 100644 index 000000000..341761831 --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/dss/dispc-compat.c @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Texas Instruments + * Author: Tomi Valkeinen <tomi.valkeinen@ti.com> + */ + +#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/omapfb_dss.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_FB_OMAP2_DSS_COLLECT_IRQ_STATS + spinlock_t irq_stats_lock; + struct dispc_irq_stats irq_stats; +#endif +} dispc_compat; + + +#ifdef CONFIG_FB_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_FB_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_FB_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)) + 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)) + 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)) + 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; + long time_left; + DECLARE_COMPLETION_ONSTACK(completion); + + r = omap_dispc_register_isr(dispc_irq_wait_handler, &completion, + irqmask); + + if (r) + return r; + + time_left = wait_for_completion_interruptible_timeout(&completion, + timeout); + + omap_dispc_unregister_isr(dispc_irq_wait_handler, &completion, irqmask); + + if (time_left == 0) + return -ETIMEDOUT; + + if (time_left == -ERESTARTSYS) + return -ERESTARTSYS; + + return 0; +} |