summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Nold <dannynold@freescale.com>2011-04-21 16:54:08 -0500
committerDanny Nold <dannynold@freescale.com>2011-04-22 12:20:08 -0500
commit0d74818ce4b45f8f0a3fa4174359a1fac4badeb4 (patch)
tree727d972388720b40cc89088661ecbeb273600316
parent3c07d833ef6a12dc81de5b878131b3ba862b946c (diff)
ENGR00142459 - EPDC fb: TCE underrun workaround
- Add new LUT selection method to always prefer a higher order LUT than those currently active. - Handle TCE underrun interrupts. Continue with normal operation and prevent them from bringing down the EPDC driver. - Add option to prevent TCE underruns - tce_prevent - Add method to synchronize submission of updates when potentially vulnerable to TCE underrun. Signed-off-by: Danny Nold <dannynold@freescale.com>
-rw-r--r--drivers/video/mxc/mxc_epdc_fb.c142
1 files changed, 132 insertions, 10 deletions
diff --git a/drivers/video/mxc/mxc_epdc_fb.c b/drivers/video/mxc/mxc_epdc_fb.c
index d0a30ba44235..3366eeee8d51 100644
--- a/drivers/video/mxc/mxc_epdc_fb.c
+++ b/drivers/video/mxc/mxc_epdc_fb.c
@@ -47,8 +47,7 @@
#include <linux/gpio.h>
#include <linux/regulator/driver.h>
#include <linux/fsl_devices.h>
-
-#include <linux/time.h>
+#include <linux/bitops.h>
#include "epdc_regs.h"
@@ -171,10 +170,15 @@ struct mxc_epdc_fb_data {
struct work_struct epdc_submit_work;
bool waiting_for_wb;
bool waiting_for_lut;
+ bool waiting_for_lut15;
struct completion update_res_free;
+ struct completion lut15_free;
+ struct completion eof_event;
+ int eof_sync_period;
struct mutex power_mutex;
bool powering_down;
int pwrdown_delay;
+ unsigned long tce_prevent;
/* FB elements related to PxP DMA */
struct completion pxp_tx_cmpl;
@@ -480,6 +484,25 @@ static inline void epdc_clear_working_buf_irq(void)
EPDC_IRQ_CLEAR);
}
+static inline void epdc_eof_intr(bool enable)
+{
+ if (enable)
+ __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_SET);
+ else
+ __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_CLEAR);
+}
+
+static inline void epdc_clear_eof_irq(void)
+{
+ __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_CLEAR);
+}
+
+static inline bool epdc_signal_eof(void)
+{
+ return (__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ)
+ & EPDC_IRQ_FRAME_END_IRQ) ? true : false;
+}
+
static inline void epdc_set_temp(u32 temp)
{
__raw_writel(temp, EPDC_TEMP);
@@ -579,6 +602,21 @@ static inline int epdc_get_next_lut(void)
return val;
}
+static int epdc_choose_next_lut(int *next_lut)
+{
+ u32 luts_status = __raw_readl(EPDC_STATUS_LUTS);
+
+ *next_lut = fls(luts_status & 0xFFFF);
+
+ if (*next_lut > 15)
+ *next_lut = epdc_get_next_lut();
+
+ if (luts_status & 0x8000)
+ return 1;
+ else
+ return 0;
+}
+
static inline bool epdc_is_working_buffer_busy(void)
{
u32 val = __raw_readl(EPDC_STATUS);
@@ -1224,6 +1262,18 @@ static int mxc_epdc_fb_set_par(struct fb_info *info)
}
}
+ /*
+ * EOF sync delay (in us) should be equal to the vscan holdoff time
+ * VSCAN_HOLDOFF time = (VSCAN_HOLDOFF value + 1) * Vertical lines
+ * Add 25us for additional margin
+ */
+ fb_data->eof_sync_period = (fb_data->cur_mode->vscan_holdoff + 1) *
+ 1000000/(fb_data->cur_mode->vmode->refresh *
+ (fb_data->cur_mode->vmode->upper_margin +
+ fb_data->cur_mode->vmode->yres +
+ fb_data->cur_mode->vmode->lower_margin +
+ fb_data->cur_mode->vmode->vsync_len)) + 25;
+
mxc_epdc_fb_set_fix(info);
return 0;
@@ -1883,6 +1933,7 @@ static void epdc_submit_work_func(struct work_struct *work)
struct update_data_list *upd_data_list = NULL;
struct mxcfb_rect adj_update_region;
bool end_merge = false;
+ int ret;
/* Protect access to buffer queues and to update HW */
spin_lock_irqsave(&fb_data->queue_lock, flags);
@@ -2079,10 +2130,43 @@ static void epdc_submit_work_func(struct work_struct *work)
spin_lock_irqsave(&fb_data->queue_lock, flags);
}
+ ret = epdc_choose_next_lut(&upd_data_list->lut_num);
+ /*
+ * If LUT15 is in use:
+ * - Wait for LUT15 to complete is if TCE underrun prevent is enabled
+ * - If we go ahead with update, sync update submission with EOF
+ */
+ if (ret && fb_data->tce_prevent) {
+ dev_dbg(fb_data->dev, "Waiting for LUT15\n");
+
+ /* Initialize event signalling that lut15 is free */
+ init_completion(&fb_data->lut15_free);
+
+ fb_data->waiting_for_lut15 = true;
+
+ /* Leave spinlock while waiting for LUT to free up */
+ spin_unlock_irqrestore(&fb_data->queue_lock, flags);
+ wait_for_completion(&fb_data->lut15_free);
+ spin_lock_irqsave(&fb_data->queue_lock, flags);
+
+ epdc_choose_next_lut(&upd_data_list->lut_num);
+ } else if (ret) {
+ /* Synchronize update submission time to reduce
+ chances of TCE underrun */
+ init_completion(&fb_data->eof_event);
+
+ epdc_eof_intr(true);
+
+ /* Leave spinlock while waiting for EOF event */
+ spin_unlock_irqrestore(&fb_data->queue_lock, flags);
+ wait_for_completion(&fb_data->eof_event);
+ udelay(fb_data->eof_sync_period);
+ spin_lock_irqsave(&fb_data->queue_lock, flags);
+
+ }
/* LUTs are available, so we get one here */
fb_data->cur_update = upd_data_list;
- upd_data_list->lut_num = epdc_get_next_lut();
/* Reset mask for LUTS that have completed during WB processing */
fb_data->luts_complete_wb = 0;
@@ -2317,15 +2401,24 @@ int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data,
return 0;
}
+ /* LUTs are available, so we get one here */
+ ret = epdc_choose_next_lut(&upd_data_list->lut_num);
+ if (ret && fb_data->tce_prevent) {
+ dev_dbg(fb_data->dev, "Must wait for LUT15\n");
+ /* Add processed Y buffer to update list */
+ list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue);
+
+ /* Return and allow the update to be submitted by the ISR. */
+ spin_unlock_irqrestore(&fb_data->queue_lock, flags);
+ return 0;
+ }
+
/* Save current update */
fb_data->cur_update = upd_data_list;
/* Reset mask for LUTS that have completed during WB processing */
fb_data->luts_complete_wb = 0;
- /* LUTs are available, so we get one here */
- upd_data_list->lut_num = epdc_get_next_lut();
-
/* Associate LUT with update marker */
list_for_each_entry_safe(next_marker, temp_marker,
&upd_data_list->update_desc->upd_marker_list, upd_list)
@@ -2759,6 +2852,7 @@ static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
int i;
bool wb_lut_done = false;
bool free_update = true;
+ int ret, next_lut;
/*
* If we just completed one-time panel init, bypass
@@ -2785,8 +2879,16 @@ static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) {
- dev_err(fb_data->dev, "TCE underrun! Panel may lock up.\n");
- return IRQ_HANDLED;
+ dev_err(fb_data->dev,
+ "TCE underrun! Will continue to update panel\n");
+ /* Clear TCE underrun IRQ */
+ __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_CLEAR);
+ }
+
+ /* Check if we are waiting on EOF to sync a new update submission */
+ if (epdc_signal_eof()) {
+ epdc_clear_eof_irq();
+ complete(&fb_data->eof_event);
}
/* Protect access to buffer queues and to update HW */
@@ -2825,6 +2927,12 @@ static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
fb_data->waiting_for_lut = false;
}
+ /* Signal completion if LUT15 free and is needed */
+ if (fb_data->waiting_for_lut15 && (i == 15)) {
+ complete(&fb_data->lut15_free);
+ fb_data->waiting_for_lut15 = false;
+ }
+
/* Detect race condition where WB and its LUT complete
(i.e. full update completes) in one swoop */
if (fb_data->cur_update &&
@@ -3010,6 +3118,14 @@ static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+ /* Check to see if there is a valid LUT to use */
+ ret = epdc_choose_next_lut(&next_lut);
+ if (ret && fb_data->tce_prevent) {
+ dev_dbg(fb_data->dev, "Must wait for LUT15\n");
+ spin_unlock_irqrestore(&fb_data->queue_lock, flags);
+ return IRQ_HANDLED;
+ }
+
/*
* Are any of our collision updates able to go now?
* Go through all updates in the collision list and check to see
@@ -3054,8 +3170,8 @@ static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
}
}
- /* LUTs are available, so we get one here */
- fb_data->cur_update->lut_num = epdc_get_next_lut();
+ /* Use LUT selected above */
+ fb_data->cur_update->lut_num = next_lut;
/* Associate LUT with update markers */
list_for_each_entry_safe(next_marker, temp,
@@ -3361,6 +3477,8 @@ int __devinit mxc_epdc_fb_probe(struct platform_device *pdev)
goto out_fbdata;
}
+ fb_data->tce_prevent = 0;
+
if (options)
while ((opt = strsep(&options, ",")) != NULL) {
if (!*opt)
@@ -3371,6 +3489,8 @@ int __devinit mxc_epdc_fb_probe(struct platform_device *pdev)
simple_strtoul(opt + 4, NULL, 0);
else if (!strncmp(opt, "x_mem=", 6))
x_mem_size = memparse(opt + 6, NULL);
+ else if (!strncmp(opt, "tce_prevent", 11))
+ fb_data->tce_prevent = 1;
else
panel_str = opt;
}
@@ -3558,6 +3678,7 @@ int __devinit mxc_epdc_fb_probe(struct platform_device *pdev)
/* Initialize our internal copy of the screeninfo */
fb_data->epdc_fb_var = *var_info;
fb_data->fb_offset = 0;
+ fb_data->eof_sync_period = 0;
/*
* Initialize lists for pending updates,
@@ -3803,6 +3924,7 @@ int __devinit mxc_epdc_fb_probe(struct platform_device *pdev)
fb_data->order_cnt = 0;
fb_data->waiting_for_wb = false;
fb_data->waiting_for_lut = false;
+ fb_data->waiting_for_lut15 = false;
fb_data->waiting_for_idle = false;
fb_data->blank = FB_BLANK_UNBLANK;
fb_data->power_state = POWER_STATE_OFF;