fbdev: sh_mobile_hdmi: implement locking
The SH-Mobile HDMI driver runs in several contexts: ISR, delayed work-queue, task context, when called from the sh_mobile_lcdc framebuffer driver. This creates ample race possibilities. Even though most these races are purely theoretical, it is better to close them. To trace fb_info validity we install a notification callback in the HDMI driver, and the only way for it to get to driver internal data is by using struct sh_mobile_lcdc_chan, therefore it had to be extracted into a separate common header. Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de> Signed-off-by: Paul Mundt <lethal@linux-sh.org>
This commit is contained in:
parent
89712699d7
commit
6de9edd5bd
|
@ -26,6 +26,8 @@
|
||||||
#include <video/sh_mobile_hdmi.h>
|
#include <video/sh_mobile_hdmi.h>
|
||||||
#include <video/sh_mobile_lcdc.h>
|
#include <video/sh_mobile_lcdc.h>
|
||||||
|
|
||||||
|
#include "sh_mobile_lcdcfb.h"
|
||||||
|
|
||||||
#define HDMI_SYSTEM_CTRL 0x00 /* System control */
|
#define HDMI_SYSTEM_CTRL 0x00 /* System control */
|
||||||
#define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control,
|
#define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control,
|
||||||
bits 19..16 of 20-bit N for Audio Clock Regeneration packet */
|
bits 19..16 of 20-bit N for Audio Clock Regeneration packet */
|
||||||
|
@ -209,6 +211,7 @@ struct sh_hdmi {
|
||||||
struct clk *hdmi_clk;
|
struct clk *hdmi_clk;
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
struct fb_info *info;
|
struct fb_info *info;
|
||||||
|
struct mutex mutex; /* Protect the info pointer */
|
||||||
struct delayed_work edid_work;
|
struct delayed_work edid_work;
|
||||||
struct fb_var_screeninfo var;
|
struct fb_var_screeninfo var;
|
||||||
};
|
};
|
||||||
|
@ -720,17 +723,21 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id)
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* locking: called with info->lock held, or before register_framebuffer() */
|
||||||
static void hdmi_display_on(void *arg, struct fb_info *info)
|
static void hdmi_display_on(void *arg, struct fb_info *info)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* info is guaranteed to be valid, when we are called, because our
|
||||||
|
* FB_EVENT_FB_UNBIND notify is also called with info->lock held
|
||||||
|
*/
|
||||||
struct sh_hdmi *hdmi = arg;
|
struct sh_hdmi *hdmi = arg;
|
||||||
struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
|
struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
|
||||||
|
|
||||||
pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state);
|
pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state);
|
||||||
/*
|
|
||||||
* FIXME: not a good place to store fb_info. And we cannot nullify it
|
/* No need to lock */
|
||||||
* even on monitor disconnect. What should the lifecycle be?
|
|
||||||
*/
|
|
||||||
hdmi->info = info;
|
hdmi->info = info;
|
||||||
|
|
||||||
switch (hdmi->hp_state) {
|
switch (hdmi->hp_state) {
|
||||||
case HDMI_HOTPLUG_EDID_DONE:
|
case HDMI_HOTPLUG_EDID_DONE:
|
||||||
/* PS mode d->e. All functions are active */
|
/* PS mode d->e. All functions are active */
|
||||||
|
@ -744,6 +751,7 @@ static void hdmi_display_on(void *arg, struct fb_info *info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* locking: called with info->lock held */
|
||||||
static void hdmi_display_off(void *arg)
|
static void hdmi_display_off(void *arg)
|
||||||
{
|
{
|
||||||
struct sh_hdmi *hdmi = arg;
|
struct sh_hdmi *hdmi = arg;
|
||||||
|
@ -766,6 +774,8 @@ static void edid_work_fn(struct work_struct *work)
|
||||||
if (!pdata->lcd_dev)
|
if (!pdata->lcd_dev)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
mutex_lock(&hdmi->mutex);
|
||||||
|
|
||||||
if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) {
|
if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) {
|
||||||
pm_runtime_get_sync(hdmi->dev);
|
pm_runtime_get_sync(hdmi->dev);
|
||||||
/* A device has been plugged in */
|
/* A device has been plugged in */
|
||||||
|
@ -776,21 +786,25 @@ static void edid_work_fn(struct work_struct *work)
|
||||||
msleep(10);
|
msleep(10);
|
||||||
|
|
||||||
if (!hdmi->info)
|
if (!hdmi->info)
|
||||||
return;
|
goto out;
|
||||||
|
|
||||||
acquire_console_sem();
|
acquire_console_sem();
|
||||||
|
|
||||||
/* HDMI plug in */
|
/* HDMI plug in */
|
||||||
hdmi->info->var = hdmi->var;
|
hdmi->info->var = hdmi->var;
|
||||||
if (hdmi->info->state != FBINFO_STATE_RUNNING)
|
if (hdmi->info->state != FBINFO_STATE_RUNNING) {
|
||||||
fb_set_suspend(hdmi->info, 0);
|
fb_set_suspend(hdmi->info, 0);
|
||||||
else
|
} else {
|
||||||
hdmi_display_on(hdmi, hdmi->info);
|
if (lock_fb_info(hdmi->info)) {
|
||||||
|
hdmi_display_on(hdmi, hdmi->info);
|
||||||
|
unlock_fb_info(hdmi->info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
release_console_sem();
|
release_console_sem();
|
||||||
} else {
|
} else {
|
||||||
if (!hdmi->info)
|
if (!hdmi->info)
|
||||||
return;
|
goto out;
|
||||||
|
|
||||||
acquire_console_sem();
|
acquire_console_sem();
|
||||||
|
|
||||||
|
@ -801,13 +815,61 @@ static void edid_work_fn(struct work_struct *work)
|
||||||
pm_runtime_put(hdmi->dev);
|
pm_runtime_put(hdmi->dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&hdmi->mutex);
|
||||||
|
|
||||||
pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev);
|
pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sh_hdmi_notify(struct notifier_block *nb,
|
||||||
|
unsigned long action, void *data);
|
||||||
|
|
||||||
|
static struct notifier_block sh_hdmi_notifier = {
|
||||||
|
.notifier_call = sh_hdmi_notify,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int sh_hdmi_notify(struct notifier_block *nb,
|
||||||
|
unsigned long action, void *data)
|
||||||
|
{
|
||||||
|
struct fb_event *event = data;
|
||||||
|
struct fb_info *info = event->info;
|
||||||
|
struct sh_mobile_lcdc_chan *ch = info->par;
|
||||||
|
struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
|
||||||
|
struct sh_hdmi *hdmi = board_cfg->board_data;
|
||||||
|
|
||||||
|
if (nb != &sh_hdmi_notifier || !hdmi || hdmi->info != info)
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
|
switch(action) {
|
||||||
|
case FB_EVENT_FB_REGISTERED:
|
||||||
|
/* Unneeded, activation taken care by hdmi_display_on() */
|
||||||
|
break;
|
||||||
|
case FB_EVENT_FB_UNREGISTERED:
|
||||||
|
/*
|
||||||
|
* We are called from unregister_framebuffer() with the
|
||||||
|
* info->lock held. This is bad for us, because we can race with
|
||||||
|
* the scheduled work, which has to call fb_set_suspend(), which
|
||||||
|
* takes info->lock internally, so, edid_work_fn() cannot take
|
||||||
|
* and hold info->lock for the whole function duration. Using an
|
||||||
|
* additional lock creates a classical AB-BA lock up. Therefore,
|
||||||
|
* we have to release the info->lock temporarily, synchronise
|
||||||
|
* with the work queue and re-acquire the info->lock.
|
||||||
|
*/
|
||||||
|
unlock_fb_info(hdmi->info);
|
||||||
|
mutex_lock(&hdmi->mutex);
|
||||||
|
hdmi->info = NULL;
|
||||||
|
mutex_unlock(&hdmi->mutex);
|
||||||
|
lock_fb_info(hdmi->info);
|
||||||
|
return NOTIFY_OK;
|
||||||
|
}
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
}
|
||||||
|
|
||||||
static int __init sh_hdmi_probe(struct platform_device *pdev)
|
static int __init sh_hdmi_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
|
struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
|
||||||
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
struct sh_mobile_lcdc_board_cfg *board_cfg;
|
||||||
int irq = platform_get_irq(pdev, 0), ret;
|
int irq = platform_get_irq(pdev, 0), ret;
|
||||||
struct sh_hdmi *hdmi;
|
struct sh_hdmi *hdmi;
|
||||||
long rate;
|
long rate;
|
||||||
|
@ -821,6 +883,7 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutex_init(&hdmi->mutex);
|
||||||
hdmi->dev = &pdev->dev;
|
hdmi->dev = &pdev->dev;
|
||||||
|
|
||||||
hdmi->hdmi_clk = clk_get(&pdev->dev, "ick");
|
hdmi->hdmi_clk = clk_get(&pdev->dev, "ick");
|
||||||
|
@ -878,9 +941,11 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Set up LCDC callbacks */
|
/* Set up LCDC callbacks */
|
||||||
pdata->lcd_chan->board_cfg.board_data = hdmi;
|
board_cfg = &pdata->lcd_chan->board_cfg;
|
||||||
pdata->lcd_chan->board_cfg.display_on = hdmi_display_on;
|
board_cfg->owner = THIS_MODULE;
|
||||||
pdata->lcd_chan->board_cfg.display_off = hdmi_display_off;
|
board_cfg->board_data = hdmi;
|
||||||
|
board_cfg->display_on = hdmi_display_on;
|
||||||
|
board_cfg->display_off = hdmi_display_off;
|
||||||
|
|
||||||
INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn);
|
INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn);
|
||||||
|
|
||||||
|
@ -907,6 +972,7 @@ eclkenable:
|
||||||
erate:
|
erate:
|
||||||
clk_put(hdmi->hdmi_clk);
|
clk_put(hdmi->hdmi_clk);
|
||||||
egetclk:
|
egetclk:
|
||||||
|
mutex_destroy(&hdmi->mutex);
|
||||||
kfree(hdmi);
|
kfree(hdmi);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -917,19 +983,24 @@ static int __exit sh_hdmi_remove(struct platform_device *pdev)
|
||||||
struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
|
struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
|
||||||
struct sh_hdmi *hdmi = platform_get_drvdata(pdev);
|
struct sh_hdmi *hdmi = platform_get_drvdata(pdev);
|
||||||
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
|
struct sh_mobile_lcdc_board_cfg *board_cfg = &pdata->lcd_chan->board_cfg;
|
||||||
int irq = platform_get_irq(pdev, 0);
|
int irq = platform_get_irq(pdev, 0);
|
||||||
|
|
||||||
pdata->lcd_chan->board_cfg.display_on = NULL;
|
board_cfg->display_on = NULL;
|
||||||
pdata->lcd_chan->board_cfg.display_off = NULL;
|
board_cfg->display_off = NULL;
|
||||||
pdata->lcd_chan->board_cfg.board_data = NULL;
|
board_cfg->board_data = NULL;
|
||||||
|
board_cfg->owner = NULL;
|
||||||
|
|
||||||
|
/* No new work will be scheduled, wait for running ISR */
|
||||||
free_irq(irq, hdmi);
|
free_irq(irq, hdmi);
|
||||||
pm_runtime_disable(&pdev->dev);
|
/* Wait for already scheduled work */
|
||||||
cancel_delayed_work_sync(&hdmi->edid_work);
|
cancel_delayed_work_sync(&hdmi->edid_work);
|
||||||
|
pm_runtime_disable(&pdev->dev);
|
||||||
clk_disable(hdmi->hdmi_clk);
|
clk_disable(hdmi->hdmi_clk);
|
||||||
clk_put(hdmi->hdmi_clk);
|
clk_put(hdmi->hdmi_clk);
|
||||||
iounmap(hdmi->base);
|
iounmap(hdmi->base);
|
||||||
release_mem_region(res->start, resource_size(res));
|
release_mem_region(res->start, resource_size(res));
|
||||||
|
mutex_destroy(&hdmi->mutex);
|
||||||
kfree(hdmi);
|
kfree(hdmi);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/fb.h>
|
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
|
@ -24,7 +23,8 @@
|
||||||
#include <video/sh_mobile_lcdc.h>
|
#include <video/sh_mobile_lcdc.h>
|
||||||
#include <asm/atomic.h>
|
#include <asm/atomic.h>
|
||||||
|
|
||||||
#define PALETTE_NR 16
|
#include "sh_mobile_lcdcfb.h"
|
||||||
|
|
||||||
#define SIDE_B_OFFSET 0x1000
|
#define SIDE_B_OFFSET 0x1000
|
||||||
#define MIRROR_OFFSET 0x2000
|
#define MIRROR_OFFSET 0x2000
|
||||||
|
|
||||||
|
@ -53,12 +53,6 @@ static int lcdc_shared_regs[] = {
|
||||||
};
|
};
|
||||||
#define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs)
|
#define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs)
|
||||||
|
|
||||||
/* per-channel registers */
|
|
||||||
enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
|
|
||||||
LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
|
|
||||||
LDHAJR,
|
|
||||||
NR_CH_REGS };
|
|
||||||
|
|
||||||
static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
|
static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
|
||||||
[LDDCKPAT1R] = 0x400,
|
[LDDCKPAT1R] = 0x400,
|
||||||
[LDDCKPAT2R] = 0x404,
|
[LDDCKPAT2R] = 0x404,
|
||||||
|
@ -112,25 +106,6 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
|
||||||
#define LDRCNTR_MRC 0x00000001
|
#define LDRCNTR_MRC 0x00000001
|
||||||
#define LDSR_MRS 0x00000100
|
#define LDSR_MRS 0x00000100
|
||||||
|
|
||||||
struct sh_mobile_lcdc_priv;
|
|
||||||
struct sh_mobile_lcdc_chan {
|
|
||||||
struct sh_mobile_lcdc_priv *lcdc;
|
|
||||||
unsigned long *reg_offs;
|
|
||||||
unsigned long ldmt1r_value;
|
|
||||||
unsigned long enabled; /* ME and SE in LDCNT2R */
|
|
||||||
struct sh_mobile_lcdc_chan_cfg cfg;
|
|
||||||
u32 pseudo_palette[PALETTE_NR];
|
|
||||||
unsigned long saved_ch_regs[NR_CH_REGS];
|
|
||||||
struct fb_info *info;
|
|
||||||
dma_addr_t dma_handle;
|
|
||||||
struct fb_deferred_io defio;
|
|
||||||
struct scatterlist *sglist;
|
|
||||||
unsigned long frame_end;
|
|
||||||
unsigned long pan_offset;
|
|
||||||
wait_queue_head_t frame_end_wait;
|
|
||||||
struct completion vsync_completion;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sh_mobile_lcdc_priv {
|
struct sh_mobile_lcdc_priv {
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
int irq;
|
int irq;
|
||||||
|
@ -589,8 +564,10 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
board_cfg = &ch->cfg.board_cfg;
|
board_cfg = &ch->cfg.board_cfg;
|
||||||
if (board_cfg->display_on)
|
if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
|
||||||
board_cfg->display_on(board_cfg->board_data, ch->info);
|
board_cfg->display_on(board_cfg->board_data, ch->info);
|
||||||
|
module_put(board_cfg->owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -622,8 +599,10 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
|
||||||
}
|
}
|
||||||
|
|
||||||
board_cfg = &ch->cfg.board_cfg;
|
board_cfg = &ch->cfg.board_cfg;
|
||||||
if (board_cfg->display_off)
|
if (try_module_get(board_cfg->owner) && board_cfg->display_off) {
|
||||||
board_cfg->display_off(board_cfg->board_data);
|
board_cfg->display_off(board_cfg->board_data);
|
||||||
|
module_put(board_cfg->owner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* stop the lcdc */
|
/* stop the lcdc */
|
||||||
|
@ -954,6 +933,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
|
||||||
.runtime_resume = sh_mobile_lcdc_runtime_resume,
|
.runtime_resume = sh_mobile_lcdc_runtime_resume,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* locking: called with info->lock held */
|
||||||
static int sh_mobile_lcdc_notify(struct notifier_block *nb,
|
static int sh_mobile_lcdc_notify(struct notifier_block *nb,
|
||||||
unsigned long action, void *data)
|
unsigned long action, void *data)
|
||||||
{
|
{
|
||||||
|
@ -971,16 +951,20 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb,
|
||||||
|
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case FB_EVENT_SUSPEND:
|
case FB_EVENT_SUSPEND:
|
||||||
if (board_cfg->display_off)
|
if (try_module_get(board_cfg->owner) && board_cfg->display_off) {
|
||||||
board_cfg->display_off(board_cfg->board_data);
|
board_cfg->display_off(board_cfg->board_data);
|
||||||
|
module_put(board_cfg->owner);
|
||||||
|
}
|
||||||
pm_runtime_put(info->device);
|
pm_runtime_put(info->device);
|
||||||
break;
|
break;
|
||||||
case FB_EVENT_RESUME:
|
case FB_EVENT_RESUME:
|
||||||
var = &info->var;
|
var = &info->var;
|
||||||
|
|
||||||
/* HDMI must be enabled before LCDC configuration */
|
/* HDMI must be enabled before LCDC configuration */
|
||||||
if (board_cfg->display_on)
|
if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
|
||||||
board_cfg->display_on(board_cfg->board_data, ch->info);
|
board_cfg->display_on(board_cfg->board_data, ch->info);
|
||||||
|
module_put(board_cfg->owner);
|
||||||
|
}
|
||||||
|
|
||||||
/* Check if the new display is not in our modelist */
|
/* Check if the new display is not in our modelist */
|
||||||
if (ch->info->modelist.next &&
|
if (ch->info->modelist.next &&
|
||||||
|
|
37
drivers/video/sh_mobile_lcdcfb.h
Normal file
37
drivers/video/sh_mobile_lcdcfb.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef SH_MOBILE_LCDCFB_H
|
||||||
|
#define SH_MOBILE_LCDCFB_H
|
||||||
|
|
||||||
|
#include <linux/completion.h>
|
||||||
|
#include <linux/fb.h>
|
||||||
|
#include <linux/wait.h>
|
||||||
|
|
||||||
|
/* per-channel registers */
|
||||||
|
enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
|
||||||
|
LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
|
||||||
|
LDHAJR,
|
||||||
|
NR_CH_REGS };
|
||||||
|
|
||||||
|
#define PALETTE_NR 16
|
||||||
|
|
||||||
|
struct sh_mobile_lcdc_priv;
|
||||||
|
struct fb_info;
|
||||||
|
|
||||||
|
struct sh_mobile_lcdc_chan {
|
||||||
|
struct sh_mobile_lcdc_priv *lcdc;
|
||||||
|
unsigned long *reg_offs;
|
||||||
|
unsigned long ldmt1r_value;
|
||||||
|
unsigned long enabled; /* ME and SE in LDCNT2R */
|
||||||
|
struct sh_mobile_lcdc_chan_cfg cfg;
|
||||||
|
u32 pseudo_palette[PALETTE_NR];
|
||||||
|
unsigned long saved_ch_regs[NR_CH_REGS];
|
||||||
|
struct fb_info *info;
|
||||||
|
dma_addr_t dma_handle;
|
||||||
|
struct fb_deferred_io defio;
|
||||||
|
struct scatterlist *sglist;
|
||||||
|
unsigned long frame_end;
|
||||||
|
unsigned long pan_offset;
|
||||||
|
wait_queue_head_t frame_end_wait;
|
||||||
|
struct completion vsync_completion;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -49,7 +49,9 @@ struct sh_mobile_lcdc_sys_bus_ops {
|
||||||
unsigned long (*read_data)(void *handle);
|
unsigned long (*read_data)(void *handle);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct module;
|
||||||
struct sh_mobile_lcdc_board_cfg {
|
struct sh_mobile_lcdc_board_cfg {
|
||||||
|
struct module *owner;
|
||||||
void *board_data;
|
void *board_data;
|
||||||
int (*setup_sys)(void *board_data, void *sys_ops_handle,
|
int (*setup_sys)(void *board_data, void *sys_ops_handle,
|
||||||
struct sh_mobile_lcdc_sys_bus_ops *sys_ops);
|
struct sh_mobile_lcdc_sys_bus_ops *sys_ops);
|
||||||
|
|
Loading…
Reference in a new issue