diff options
| author | Guennadi Liakhovetski <g.liakhovetski@gmx.de> | 2010-09-03 03:20:23 -0400 |
|---|---|---|
| committer | Paul Mundt <lethal@linux-sh.org> | 2010-09-14 04:23:21 -0400 |
| commit | 6de9edd5bde0cdfea12e9948690e53ec669c3018 (patch) | |
| tree | 638602a3d7726b27ae6ab85ef45f4f11c38c0283 | |
| parent | 89712699d7bc9cc93602407e0e9bc2490b771400 (diff) | |
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>
| -rw-r--r-- | drivers/video/sh_mobile_hdmi.c | 103 | ||||
| -rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 46 | ||||
| -rw-r--r-- | drivers/video/sh_mobile_lcdcfb.h | 37 | ||||
| -rw-r--r-- | include/video/sh_mobile_lcdc.h | 2 |
4 files changed, 141 insertions, 47 deletions
diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index a8cf9a510f30..56e44fd0a3af 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c | |||
| @@ -26,6 +26,8 @@ | |||
| 26 | #include <video/sh_mobile_hdmi.h> | 26 | #include <video/sh_mobile_hdmi.h> |
| 27 | #include <video/sh_mobile_lcdc.h> | 27 | #include <video/sh_mobile_lcdc.h> |
| 28 | 28 | ||
| 29 | #include "sh_mobile_lcdcfb.h" | ||
| 30 | |||
| 29 | #define HDMI_SYSTEM_CTRL 0x00 /* System control */ | 31 | #define HDMI_SYSTEM_CTRL 0x00 /* System control */ |
| 30 | #define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control, | 32 | #define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control, |
| 31 | bits 19..16 of 20-bit N for Audio Clock Regeneration packet */ | 33 | bits 19..16 of 20-bit N for Audio Clock Regeneration packet */ |
| @@ -209,6 +211,7 @@ struct sh_hdmi { | |||
| 209 | struct clk *hdmi_clk; | 211 | struct clk *hdmi_clk; |
| 210 | struct device *dev; | 212 | struct device *dev; |
| 211 | struct fb_info *info; | 213 | struct fb_info *info; |
| 214 | struct mutex mutex; /* Protect the info pointer */ | ||
| 212 | struct delayed_work edid_work; | 215 | struct delayed_work edid_work; |
| 213 | struct fb_var_screeninfo var; | 216 | struct fb_var_screeninfo var; |
| 214 | }; | 217 | }; |
| @@ -720,17 +723,21 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id) | |||
| 720 | return IRQ_HANDLED; | 723 | return IRQ_HANDLED; |
| 721 | } | 724 | } |
| 722 | 725 | ||
| 726 | /* locking: called with info->lock held, or before register_framebuffer() */ | ||
| 723 | static void hdmi_display_on(void *arg, struct fb_info *info) | 727 | static void hdmi_display_on(void *arg, struct fb_info *info) |
| 724 | { | 728 | { |
| 729 | /* | ||
| 730 | * info is guaranteed to be valid, when we are called, because our | ||
| 731 | * FB_EVENT_FB_UNBIND notify is also called with info->lock held | ||
| 732 | */ | ||
| 725 | struct sh_hdmi *hdmi = arg; | 733 | struct sh_hdmi *hdmi = arg; |
| 726 | struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; | 734 | struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; |
| 727 | 735 | ||
| 728 | pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state); | 736 | pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state); |
| 729 | /* | 737 | |
| 730 | * FIXME: not a good place to store fb_info. And we cannot nullify it | 738 | /* No need to lock */ |
| 731 | * even on monitor disconnect. What should the lifecycle be? | ||
| 732 | */ | ||
| 733 | hdmi->info = info; | 739 | hdmi->info = info; |
| 740 | |||
| 734 | switch (hdmi->hp_state) { | 741 | switch (hdmi->hp_state) { |
| 735 | case HDMI_HOTPLUG_EDID_DONE: | 742 | case HDMI_HOTPLUG_EDID_DONE: |
| 736 | /* PS mode d->e. All functions are active */ | 743 | /* PS mode d->e. All functions are active */ |
| @@ -744,6 +751,7 @@ static void hdmi_display_on(void *arg, struct fb_info *info) | |||
| 744 | } | 751 | } |
| 745 | } | 752 | } |
| 746 | 753 | ||
| 754 | /* locking: called with info->lock held */ | ||
| 747 | static void hdmi_display_off(void *arg) | 755 | static void hdmi_display_off(void *arg) |
| 748 | { | 756 | { |
| 749 | struct sh_hdmi *hdmi = arg; | 757 | struct sh_hdmi *hdmi = arg; |
| @@ -766,6 +774,8 @@ static void edid_work_fn(struct work_struct *work) | |||
| 766 | if (!pdata->lcd_dev) | 774 | if (!pdata->lcd_dev) |
| 767 | return; | 775 | return; |
| 768 | 776 | ||
| 777 | mutex_lock(&hdmi->mutex); | ||
| 778 | |||
| 769 | if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { | 779 | if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { |
| 770 | pm_runtime_get_sync(hdmi->dev); | 780 | pm_runtime_get_sync(hdmi->dev); |
| 771 | /* A device has been plugged in */ | 781 | /* A device has been plugged in */ |
| @@ -776,21 +786,25 @@ static void edid_work_fn(struct work_struct *work) | |||
| 776 | msleep(10); | 786 | msleep(10); |
| 777 | 787 | ||
| 778 | if (!hdmi->info) | 788 | if (!hdmi->info) |
| 779 | return; | 789 | goto out; |
| 780 | 790 | ||
| 781 | acquire_console_sem(); | 791 | acquire_console_sem(); |
| 782 | 792 | ||
| 783 | /* HDMI plug in */ | 793 | /* HDMI plug in */ |
| 784 | hdmi->info->var = hdmi->var; | 794 | hdmi->info->var = hdmi->var; |
| 785 | if (hdmi->info->state != FBINFO_STATE_RUNNING) | 795 | if (hdmi->info->state != FBINFO_STATE_RUNNING) { |
| 786 | fb_set_suspend(hdmi->info, 0); | 796 | fb_set_suspend(hdmi->info, 0); |
| 787 | else | 797 | } else { |
| 788 | hdmi_display_on(hdmi, hdmi->info); | 798 | if (lock_fb_info(hdmi->info)) { |
| 799 | hdmi_display_on(hdmi, hdmi->info); | ||
| 800 | unlock_fb_info(hdmi->info); | ||
| 801 | } | ||
| 802 | } | ||
| 789 | 803 | ||
| 790 | release_console_sem(); | 804 | release_console_sem(); |
| 791 | } else { | 805 | } else { |
| 792 | if (!hdmi->info) | 806 | if (!hdmi->info) |
| 793 | return; | 807 | goto out; |
| 794 | 808 | ||
| 795 | acquire_console_sem(); | 809 | acquire_console_sem(); |
| 796 | 810 | ||
| @@ -801,13 +815,61 @@ static void edid_work_fn(struct work_struct *work) | |||
| 801 | pm_runtime_put(hdmi->dev); | 815 | pm_runtime_put(hdmi->dev); |
| 802 | } | 816 | } |
| 803 | 817 | ||
| 818 | out: | ||
| 819 | mutex_unlock(&hdmi->mutex); | ||
| 820 | |||
| 804 | pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev); | 821 | pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev); |
| 805 | } | 822 | } |
| 806 | 823 | ||
| 824 | static int sh_hdmi_notify(struct notifier_block *nb, | ||
| 825 | unsigned long action, void *data); | ||
| 826 | |||
| 827 | static struct notifier_block sh_hdmi_notifier = { | ||
| 828 | .notifier_call = sh_hdmi_notify, | ||
| 829 | }; | ||
| 830 | |||
| 831 | static int sh_hdmi_notify(struct notifier_block *nb, | ||
| 832 | unsigned long action, void *data) | ||
| 833 | { | ||
| 834 | struct fb_event *event = data; | ||
| 835 | struct fb_info *info = event->info; | ||
| 836 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
| 837 | struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; | ||
| 838 | struct sh_hdmi *hdmi = board_cfg->board_data; | ||
| 839 | |||
| 840 | if (nb != &sh_hdmi_notifier || !hdmi || hdmi->info != info) | ||
| 841 | return NOTIFY_DONE; | ||
| 842 | |||
| 843 | switch(action) { | ||
| 844 | case FB_EVENT_FB_REGISTERED: | ||
| 845 | /* Unneeded, activation taken care by hdmi_display_on() */ | ||
| 846 | break; | ||
| 847 | case FB_EVENT_FB_UNREGISTERED: | ||
| 848 | /* | ||
| 849 | * We are called from unregister_framebuffer() with the | ||
| 850 | * info->lock held. This is bad for us, because we can race with | ||
| 851 | * the scheduled work, which has to call fb_set_suspend(), which | ||
| 852 | * takes info->lock internally, so, edid_work_fn() cannot take | ||
| 853 | * and hold info->lock for the whole function duration. Using an | ||
| 854 | * additional lock creates a classical AB-BA lock up. Therefore, | ||
| 855 | * we have to release the info->lock temporarily, synchronise | ||
| 856 | * with the work queue and re-acquire the info->lock. | ||
| 857 | */ | ||
| 858 | unlock_fb_info(hdmi->info); | ||
| 859 | mutex_lock(&hdmi->mutex); | ||
| 860 | hdmi->info = NULL; | ||
| 861 | mutex_unlock(&hdmi->mutex); | ||
| 862 | lock_fb_info(hdmi->info); | ||
| 863 | return NOTIFY_OK; | ||
| 864 | } | ||
| 865 | return NOTIFY_DONE; | ||
| 866 | } | ||
| 867 | |||
| 807 | static int __init sh_hdmi_probe(struct platform_device *pdev) | 868 | static int __init sh_hdmi_probe(struct platform_device *pdev) |
| 808 | { | 869 | { |
| 809 | struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; | 870 | struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; |
| 810 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 871 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| 872 | struct sh_mobile_lcdc_board_cfg *board_cfg; | ||
| 811 | int irq = platform_get_irq(pdev, 0), ret; | 873 | int irq = platform_get_irq(pdev, 0), ret; |
| 812 | struct sh_hdmi *hdmi; | 874 | struct sh_hdmi *hdmi; |
| 813 | long rate; | 875 | long rate; |
| @@ -821,6 +883,7 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) | |||
| 821 | return -ENOMEM; | 883 | return -ENOMEM; |
| 822 | } | 884 | } |
| 823 | 885 | ||
| 886 | mutex_init(&hdmi->mutex); | ||
| 824 | hdmi->dev = &pdev->dev; | 887 | hdmi->dev = &pdev->dev; |
| 825 | 888 | ||
| 826 | hdmi->hdmi_clk = clk_get(&pdev->dev, "ick"); | 889 | hdmi->hdmi_clk = clk_get(&pdev->dev, "ick"); |
| @@ -878,9 +941,11 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) | |||
| 878 | #endif | 941 | #endif |
| 879 | 942 | ||
| 880 | /* Set up LCDC callbacks */ | 943 | /* Set up LCDC callbacks */ |
| 881 | pdata->lcd_chan->board_cfg.board_data = hdmi; | 944 | board_cfg = &pdata->lcd_chan->board_cfg; |
| 882 | pdata->lcd_chan->board_cfg.display_on = hdmi_display_on; | 945 | board_cfg->owner = THIS_MODULE; |
| 883 | pdata->lcd_chan->board_cfg.display_off = hdmi_display_off; | 946 | board_cfg->board_data = hdmi; |
| 947 | board_cfg->display_on = hdmi_display_on; | ||
| 948 | board_cfg->display_off = hdmi_display_off; | ||
| 884 | 949 | ||
| 885 | INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn); | 950 | INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn); |
| 886 | 951 | ||
| @@ -907,6 +972,7 @@ eclkenable: | |||
| 907 | erate: | 972 | erate: |
| 908 | clk_put(hdmi->hdmi_clk); | 973 | clk_put(hdmi->hdmi_clk); |
| 909 | egetclk: | 974 | egetclk: |
| 975 | mutex_destroy(&hdmi->mutex); | ||
| 910 | kfree(hdmi); | 976 | kfree(hdmi); |
| 911 | 977 | ||
| 912 | return ret; | 978 | return ret; |
| @@ -917,19 +983,24 @@ static int __exit sh_hdmi_remove(struct platform_device *pdev) | |||
| 917 | struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; | 983 | struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; |
| 918 | struct sh_hdmi *hdmi = platform_get_drvdata(pdev); | 984 | struct sh_hdmi *hdmi = platform_get_drvdata(pdev); |
| 919 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 985 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| 986 | struct sh_mobile_lcdc_board_cfg *board_cfg = &pdata->lcd_chan->board_cfg; | ||
| 920 | int irq = platform_get_irq(pdev, 0); | 987 | int irq = platform_get_irq(pdev, 0); |
| 921 | 988 | ||
| 922 | pdata->lcd_chan->board_cfg.display_on = NULL; | 989 | board_cfg->display_on = NULL; |
| 923 | pdata->lcd_chan->board_cfg.display_off = NULL; | 990 | board_cfg->display_off = NULL; |
| 924 | pdata->lcd_chan->board_cfg.board_data = NULL; | 991 | board_cfg->board_data = NULL; |
| 992 | board_cfg->owner = NULL; | ||
| 925 | 993 | ||
| 994 | /* No new work will be scheduled, wait for running ISR */ | ||
| 926 | free_irq(irq, hdmi); | 995 | free_irq(irq, hdmi); |
| 927 | pm_runtime_disable(&pdev->dev); | 996 | /* Wait for already scheduled work */ |
| 928 | cancel_delayed_work_sync(&hdmi->edid_work); | 997 | cancel_delayed_work_sync(&hdmi->edid_work); |
| 998 | pm_runtime_disable(&pdev->dev); | ||
| 929 | clk_disable(hdmi->hdmi_clk); | 999 | clk_disable(hdmi->hdmi_clk); |
| 930 | clk_put(hdmi->hdmi_clk); | 1000 | clk_put(hdmi->hdmi_clk); |
| 931 | iounmap(hdmi->base); | 1001 | iounmap(hdmi->base); |
| 932 | release_mem_region(res->start, resource_size(res)); | 1002 | release_mem_region(res->start, resource_size(res)); |
| 1003 | mutex_destroy(&hdmi->mutex); | ||
| 933 | kfree(hdmi); | 1004 | kfree(hdmi); |
| 934 | 1005 | ||
| 935 | return 0; | 1006 | return 0; |
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index ddd6c4669bd7..29d7ce7e7a1c 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
| @@ -12,7 +12,6 @@ | |||
| 12 | #include <linux/init.h> | 12 | #include <linux/init.h> |
| 13 | #include <linux/delay.h> | 13 | #include <linux/delay.h> |
| 14 | #include <linux/mm.h> | 14 | #include <linux/mm.h> |
| 15 | #include <linux/fb.h> | ||
| 16 | #include <linux/clk.h> | 15 | #include <linux/clk.h> |
| 17 | #include <linux/pm_runtime.h> | 16 | #include <linux/pm_runtime.h> |
| 18 | #include <linux/platform_device.h> | 17 | #include <linux/platform_device.h> |
| @@ -24,7 +23,8 @@ | |||
| 24 | #include <video/sh_mobile_lcdc.h> | 23 | #include <video/sh_mobile_lcdc.h> |
| 25 | #include <asm/atomic.h> | 24 | #include <asm/atomic.h> |
| 26 | 25 | ||
| 27 | #define PALETTE_NR 16 | 26 | #include "sh_mobile_lcdcfb.h" |
| 27 | |||
| 28 | #define SIDE_B_OFFSET 0x1000 | 28 | #define SIDE_B_OFFSET 0x1000 |
| 29 | #define MIRROR_OFFSET 0x2000 | 29 | #define MIRROR_OFFSET 0x2000 |
| 30 | 30 | ||
| @@ -53,12 +53,6 @@ static int lcdc_shared_regs[] = { | |||
| 53 | }; | 53 | }; |
| 54 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) | 54 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) |
| 55 | 55 | ||
| 56 | /* per-channel registers */ | ||
| 57 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | ||
| 58 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, | ||
| 59 | LDHAJR, | ||
| 60 | NR_CH_REGS }; | ||
| 61 | |||
| 62 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | 56 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { |
| 63 | [LDDCKPAT1R] = 0x400, | 57 | [LDDCKPAT1R] = 0x400, |
| 64 | [LDDCKPAT2R] = 0x404, | 58 | [LDDCKPAT2R] = 0x404, |
| @@ -112,25 +106,6 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { | |||
| 112 | #define LDRCNTR_MRC 0x00000001 | 106 | #define LDRCNTR_MRC 0x00000001 |
| 113 | #define LDSR_MRS 0x00000100 | 107 | #define LDSR_MRS 0x00000100 |
| 114 | 108 | ||
| 115 | struct sh_mobile_lcdc_priv; | ||
| 116 | struct sh_mobile_lcdc_chan { | ||
| 117 | struct sh_mobile_lcdc_priv *lcdc; | ||
| 118 | unsigned long *reg_offs; | ||
| 119 | unsigned long ldmt1r_value; | ||
| 120 | unsigned long enabled; /* ME and SE in LDCNT2R */ | ||
| 121 | struct sh_mobile_lcdc_chan_cfg cfg; | ||
| 122 | u32 pseudo_palette[PALETTE_NR]; | ||
| 123 | unsigned long saved_ch_regs[NR_CH_REGS]; | ||
| 124 | struct fb_info *info; | ||
| 125 | dma_addr_t dma_handle; | ||
| 126 | struct fb_deferred_io defio; | ||
| 127 | struct scatterlist *sglist; | ||
| 128 | unsigned long frame_end; | ||
| 129 | unsigned long pan_offset; | ||
| 130 | wait_queue_head_t frame_end_wait; | ||
| 131 | struct completion vsync_completion; | ||
| 132 | }; | ||
| 133 | |||
| 134 | struct sh_mobile_lcdc_priv { | 109 | struct sh_mobile_lcdc_priv { |
| 135 | void __iomem *base; | 110 | void __iomem *base; |
| 136 | int irq; | 111 | int irq; |
| @@ -589,8 +564,10 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
| 589 | continue; | 564 | continue; |
| 590 | 565 | ||
| 591 | board_cfg = &ch->cfg.board_cfg; | 566 | board_cfg = &ch->cfg.board_cfg; |
| 592 | if (board_cfg->display_on) | 567 | if (try_module_get(board_cfg->owner) && board_cfg->display_on) { |
| 593 | board_cfg->display_on(board_cfg->board_data, ch->info); | 568 | board_cfg->display_on(board_cfg->board_data, ch->info); |
| 569 | module_put(board_cfg->owner); | ||
| 570 | } | ||
| 594 | } | 571 | } |
| 595 | 572 | ||
| 596 | return 0; | 573 | return 0; |
| @@ -622,8 +599,10 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
| 622 | } | 599 | } |
| 623 | 600 | ||
| 624 | board_cfg = &ch->cfg.board_cfg; | 601 | board_cfg = &ch->cfg.board_cfg; |
| 625 | if (board_cfg->display_off) | 602 | if (try_module_get(board_cfg->owner) && board_cfg->display_off) { |
| 626 | board_cfg->display_off(board_cfg->board_data); | 603 | board_cfg->display_off(board_cfg->board_data); |
| 604 | module_put(board_cfg->owner); | ||
| 605 | } | ||
| 627 | } | 606 | } |
| 628 | 607 | ||
| 629 | /* stop the lcdc */ | 608 | /* stop the lcdc */ |
| @@ -954,6 +933,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | |||
| 954 | .runtime_resume = sh_mobile_lcdc_runtime_resume, | 933 | .runtime_resume = sh_mobile_lcdc_runtime_resume, |
| 955 | }; | 934 | }; |
| 956 | 935 | ||
| 936 | /* locking: called with info->lock held */ | ||
| 957 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, | 937 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, |
| 958 | unsigned long action, void *data) | 938 | unsigned long action, void *data) |
| 959 | { | 939 | { |
| @@ -971,16 +951,20 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, | |||
| 971 | 951 | ||
| 972 | switch(action) { | 952 | switch(action) { |
| 973 | case FB_EVENT_SUSPEND: | 953 | case FB_EVENT_SUSPEND: |
| 974 | if (board_cfg->display_off) | 954 | if (try_module_get(board_cfg->owner) && board_cfg->display_off) { |
| 975 | board_cfg->display_off(board_cfg->board_data); | 955 | board_cfg->display_off(board_cfg->board_data); |
| 956 | module_put(board_cfg->owner); | ||
| 957 | } | ||
| 976 | pm_runtime_put(info->device); | 958 | pm_runtime_put(info->device); |
| 977 | break; | 959 | break; |
| 978 | case FB_EVENT_RESUME: | 960 | case FB_EVENT_RESUME: |
| 979 | var = &info->var; | 961 | var = &info->var; |
| 980 | 962 | ||
| 981 | /* HDMI must be enabled before LCDC configuration */ | 963 | /* HDMI must be enabled before LCDC configuration */ |
| 982 | if (board_cfg->display_on) | 964 | if (try_module_get(board_cfg->owner) && board_cfg->display_on) { |
| 983 | board_cfg->display_on(board_cfg->board_data, ch->info); | 965 | board_cfg->display_on(board_cfg->board_data, ch->info); |
| 966 | module_put(board_cfg->owner); | ||
| 967 | } | ||
| 984 | 968 | ||
| 985 | /* Check if the new display is not in our modelist */ | 969 | /* Check if the new display is not in our modelist */ |
| 986 | if (ch->info->modelist.next && | 970 | if (ch->info->modelist.next && |
diff --git a/drivers/video/sh_mobile_lcdcfb.h b/drivers/video/sh_mobile_lcdcfb.h new file mode 100644 index 000000000000..6fcfc0ffe3f8 --- /dev/null +++ b/drivers/video/sh_mobile_lcdcfb.h | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | #ifndef SH_MOBILE_LCDCFB_H | ||
| 2 | #define SH_MOBILE_LCDCFB_H | ||
| 3 | |||
| 4 | #include <linux/completion.h> | ||
| 5 | #include <linux/fb.h> | ||
| 6 | #include <linux/wait.h> | ||
| 7 | |||
| 8 | /* per-channel registers */ | ||
| 9 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | ||
| 10 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, | ||
| 11 | LDHAJR, | ||
| 12 | NR_CH_REGS }; | ||
| 13 | |||
| 14 | #define PALETTE_NR 16 | ||
| 15 | |||
| 16 | struct sh_mobile_lcdc_priv; | ||
| 17 | struct fb_info; | ||
| 18 | |||
| 19 | struct sh_mobile_lcdc_chan { | ||
| 20 | struct sh_mobile_lcdc_priv *lcdc; | ||
| 21 | unsigned long *reg_offs; | ||
| 22 | unsigned long ldmt1r_value; | ||
| 23 | unsigned long enabled; /* ME and SE in LDCNT2R */ | ||
| 24 | struct sh_mobile_lcdc_chan_cfg cfg; | ||
| 25 | u32 pseudo_palette[PALETTE_NR]; | ||
| 26 | unsigned long saved_ch_regs[NR_CH_REGS]; | ||
| 27 | struct fb_info *info; | ||
| 28 | dma_addr_t dma_handle; | ||
| 29 | struct fb_deferred_io defio; | ||
| 30 | struct scatterlist *sglist; | ||
| 31 | unsigned long frame_end; | ||
| 32 | unsigned long pan_offset; | ||
| 33 | wait_queue_head_t frame_end_wait; | ||
| 34 | struct completion vsync_completion; | ||
| 35 | }; | ||
| 36 | |||
| 37 | #endif | ||
diff --git a/include/video/sh_mobile_lcdc.h b/include/video/sh_mobile_lcdc.h index 19c69d788f3b..daabae5817c6 100644 --- a/include/video/sh_mobile_lcdc.h +++ b/include/video/sh_mobile_lcdc.h | |||
| @@ -49,7 +49,9 @@ struct sh_mobile_lcdc_sys_bus_ops { | |||
| 49 | unsigned long (*read_data)(void *handle); | 49 | unsigned long (*read_data)(void *handle); |
| 50 | }; | 50 | }; |
| 51 | 51 | ||
| 52 | struct module; | ||
| 52 | struct sh_mobile_lcdc_board_cfg { | 53 | struct sh_mobile_lcdc_board_cfg { |
| 54 | struct module *owner; | ||
| 53 | void *board_data; | 55 | void *board_data; |
| 54 | int (*setup_sys)(void *board_data, void *sys_ops_handle, | 56 | int (*setup_sys)(void *board_data, void *sys_ops_handle, |
| 55 | struct sh_mobile_lcdc_sys_bus_ops *sys_ops); | 57 | struct sh_mobile_lcdc_sys_bus_ops *sys_ops); |
