diff options
author | Andrea Bastoni <bastoni@cs.unc.edu> | 2010-05-30 19:16:45 -0400 |
---|---|---|
committer | Andrea Bastoni <bastoni@cs.unc.edu> | 2010-05-30 19:16:45 -0400 |
commit | ada47b5fe13d89735805b566185f4885f5a3f750 (patch) | |
tree | 644b88f8a71896307d71438e9b3af49126ffb22b /drivers/video/sh_mobile_lcdcfb.c | |
parent | 43e98717ad40a4ae64545b5ba047c7b86aa44f4f (diff) | |
parent | 3280f21d43ee541f97f8cda5792150d2dbec20d5 (diff) |
Merge branch 'wip-2.6.34' into old-private-masterarchived-private-master
Diffstat (limited to 'drivers/video/sh_mobile_lcdcfb.c')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 160 |
1 files changed, 110 insertions, 50 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 3ad5157f9899..e8c769944812 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -19,6 +19,8 @@ | |||
19 | #include <linux/dma-mapping.h> | 19 | #include <linux/dma-mapping.h> |
20 | #include <linux/interrupt.h> | 20 | #include <linux/interrupt.h> |
21 | #include <linux/vmalloc.h> | 21 | #include <linux/vmalloc.h> |
22 | #include <linux/ioctl.h> | ||
23 | #include <linux/slab.h> | ||
22 | #include <video/sh_mobile_lcdc.h> | 24 | #include <video/sh_mobile_lcdc.h> |
23 | #include <asm/atomic.h> | 25 | #include <asm/atomic.h> |
24 | 26 | ||
@@ -106,6 +108,7 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { | |||
106 | #define LDRCNTR_SRC 0x00010000 | 108 | #define LDRCNTR_SRC 0x00010000 |
107 | #define LDRCNTR_MRS 0x00000002 | 109 | #define LDRCNTR_MRS 0x00000002 |
108 | #define LDRCNTR_MRC 0x00000001 | 110 | #define LDRCNTR_MRC 0x00000001 |
111 | #define LDSR_MRS 0x00000100 | ||
109 | 112 | ||
110 | struct sh_mobile_lcdc_priv; | 113 | struct sh_mobile_lcdc_priv; |
111 | struct sh_mobile_lcdc_chan { | 114 | struct sh_mobile_lcdc_chan { |
@@ -122,8 +125,8 @@ struct sh_mobile_lcdc_chan { | |||
122 | struct scatterlist *sglist; | 125 | struct scatterlist *sglist; |
123 | unsigned long frame_end; | 126 | unsigned long frame_end; |
124 | unsigned long pan_offset; | 127 | unsigned long pan_offset; |
125 | unsigned long new_pan_offset; | ||
126 | wait_queue_head_t frame_end_wait; | 128 | wait_queue_head_t frame_end_wait; |
129 | struct completion vsync_completion; | ||
127 | }; | 130 | }; |
128 | 131 | ||
129 | struct sh_mobile_lcdc_priv { | 132 | struct sh_mobile_lcdc_priv { |
@@ -281,18 +284,42 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info, | |||
281 | struct list_head *pagelist) | 284 | struct list_head *pagelist) |
282 | { | 285 | { |
283 | struct sh_mobile_lcdc_chan *ch = info->par; | 286 | struct sh_mobile_lcdc_chan *ch = info->par; |
284 | unsigned int nr_pages; | 287 | struct sh_mobile_lcdc_board_cfg *bcfg = &ch->cfg.board_cfg; |
285 | 288 | ||
286 | /* enable clocks before accessing hardware */ | 289 | /* enable clocks before accessing hardware */ |
287 | sh_mobile_lcdc_clk_on(ch->lcdc); | 290 | sh_mobile_lcdc_clk_on(ch->lcdc); |
288 | 291 | ||
289 | nr_pages = sh_mobile_lcdc_sginit(info, pagelist); | 292 | /* |
290 | dma_map_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); | 293 | * It's possible to get here without anything on the pagelist via |
291 | 294 | * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync() | |
292 | /* trigger panel update */ | 295 | * invocation. In the former case, the acceleration routines are |
293 | lcdc_write_chan(ch, LDSM2R, 1); | 296 | * stepped in to when using the framebuffer console causing the |
294 | 297 | * workqueue to be scheduled without any dirty pages on the list. | |
295 | dma_unmap_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); | 298 | * |
299 | * Despite this, a panel update is still needed given that the | ||
300 | * acceleration routines have their own methods for writing in | ||
301 | * that still need to be updated. | ||
302 | * | ||
303 | * The fsync() and empty pagelist case could be optimized for, | ||
304 | * but we don't bother, as any application exhibiting such | ||
305 | * behaviour is fundamentally broken anyways. | ||
306 | */ | ||
307 | if (!list_empty(pagelist)) { | ||
308 | unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist); | ||
309 | |||
310 | /* trigger panel update */ | ||
311 | dma_map_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); | ||
312 | if (bcfg->start_transfer) | ||
313 | bcfg->start_transfer(bcfg->board_data, ch, | ||
314 | &sh_mobile_lcdc_sys_bus_ops); | ||
315 | lcdc_write_chan(ch, LDSM2R, 1); | ||
316 | dma_unmap_sg(info->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); | ||
317 | } else { | ||
318 | if (bcfg->start_transfer) | ||
319 | bcfg->start_transfer(bcfg->board_data, ch, | ||
320 | &sh_mobile_lcdc_sys_bus_ops); | ||
321 | lcdc_write_chan(ch, LDSM2R, 1); | ||
322 | } | ||
296 | } | 323 | } |
297 | 324 | ||
298 | static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) | 325 | static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) |
@@ -342,19 +369,8 @@ static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) | |||
342 | } | 369 | } |
343 | 370 | ||
344 | /* VSYNC End */ | 371 | /* VSYNC End */ |
345 | if (ldintr & LDINTR_VES) { | 372 | if (ldintr & LDINTR_VES) |
346 | unsigned long ldrcntr = lcdc_read(priv, _LDRCNTR); | 373 | complete(&ch->vsync_completion); |
347 | /* Set the source address for the next refresh */ | ||
348 | lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + | ||
349 | ch->new_pan_offset); | ||
350 | if (lcdc_chan_is_sublcd(ch)) | ||
351 | lcdc_write(ch->lcdc, _LDRCNTR, | ||
352 | ldrcntr ^ LDRCNTR_SRS); | ||
353 | else | ||
354 | lcdc_write(ch->lcdc, _LDRCNTR, | ||
355 | ldrcntr ^ LDRCNTR_MRS); | ||
356 | ch->pan_offset = ch->new_pan_offset; | ||
357 | } | ||
358 | } | 374 | } |
359 | 375 | ||
360 | return IRQ_HANDLED; | 376 | return IRQ_HANDLED; |
@@ -679,6 +695,7 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
679 | * 1) Enable Runtime PM | 695 | * 1) Enable Runtime PM |
680 | * 2) Force Runtime PM Resume since hardware is accessed from probe() | 696 | * 2) Force Runtime PM Resume since hardware is accessed from probe() |
681 | */ | 697 | */ |
698 | priv->dev = &pdev->dev; | ||
682 | pm_runtime_enable(priv->dev); | 699 | pm_runtime_enable(priv->dev); |
683 | pm_runtime_resume(priv->dev); | 700 | pm_runtime_resume(priv->dev); |
684 | return 0; | 701 | return 0; |
@@ -743,25 +760,69 @@ static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var, | |||
743 | struct fb_info *info) | 760 | struct fb_info *info) |
744 | { | 761 | { |
745 | struct sh_mobile_lcdc_chan *ch = info->par; | 762 | struct sh_mobile_lcdc_chan *ch = info->par; |
763 | struct sh_mobile_lcdc_priv *priv = ch->lcdc; | ||
764 | unsigned long ldrcntr; | ||
765 | unsigned long new_pan_offset; | ||
766 | |||
767 | new_pan_offset = (var->yoffset * info->fix.line_length) + | ||
768 | (var->xoffset * (info->var.bits_per_pixel / 8)); | ||
746 | 769 | ||
747 | if (info->var.xoffset == var->xoffset && | 770 | if (new_pan_offset == ch->pan_offset) |
748 | info->var.yoffset == var->yoffset) | ||
749 | return 0; /* No change, do nothing */ | 771 | return 0; /* No change, do nothing */ |
750 | 772 | ||
751 | ch->new_pan_offset = (var->yoffset * info->fix.line_length) + | 773 | ldrcntr = lcdc_read(priv, _LDRCNTR); |
752 | (var->xoffset * (info->var.bits_per_pixel / 8)); | ||
753 | 774 | ||
754 | if (ch->new_pan_offset != ch->pan_offset) { | 775 | /* Set the source address for the next refresh */ |
755 | unsigned long ldintr; | 776 | lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle + new_pan_offset); |
756 | ldintr = lcdc_read(ch->lcdc, _LDINTR); | 777 | if (lcdc_chan_is_sublcd(ch)) |
757 | ldintr |= LDINTR_VEE; | 778 | lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_SRS); |
758 | lcdc_write(ch->lcdc, _LDINTR, ldintr); | 779 | else |
759 | sh_mobile_lcdc_deferred_io_touch(info); | 780 | lcdc_write(ch->lcdc, _LDRCNTR, ldrcntr ^ LDRCNTR_MRS); |
760 | } | 781 | |
782 | ch->pan_offset = new_pan_offset; | ||
783 | |||
784 | sh_mobile_lcdc_deferred_io_touch(info); | ||
761 | 785 | ||
762 | return 0; | 786 | return 0; |
763 | } | 787 | } |
764 | 788 | ||
789 | static int sh_mobile_wait_for_vsync(struct fb_info *info) | ||
790 | { | ||
791 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
792 | unsigned long ldintr; | ||
793 | int ret; | ||
794 | |||
795 | /* Enable VSync End interrupt */ | ||
796 | ldintr = lcdc_read(ch->lcdc, _LDINTR); | ||
797 | ldintr |= LDINTR_VEE; | ||
798 | lcdc_write(ch->lcdc, _LDINTR, ldintr); | ||
799 | |||
800 | ret = wait_for_completion_interruptible_timeout(&ch->vsync_completion, | ||
801 | msecs_to_jiffies(100)); | ||
802 | if (!ret) | ||
803 | return -ETIMEDOUT; | ||
804 | |||
805 | return 0; | ||
806 | } | ||
807 | |||
808 | static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, | ||
809 | unsigned long arg) | ||
810 | { | ||
811 | int retval; | ||
812 | |||
813 | switch (cmd) { | ||
814 | case FBIO_WAITFORVSYNC: | ||
815 | retval = sh_mobile_wait_for_vsync(info); | ||
816 | break; | ||
817 | |||
818 | default: | ||
819 | retval = -ENOIOCTLCMD; | ||
820 | break; | ||
821 | } | ||
822 | return retval; | ||
823 | } | ||
824 | |||
825 | |||
765 | static struct fb_ops sh_mobile_lcdc_ops = { | 826 | static struct fb_ops sh_mobile_lcdc_ops = { |
766 | .owner = THIS_MODULE, | 827 | .owner = THIS_MODULE, |
767 | .fb_setcolreg = sh_mobile_lcdc_setcolreg, | 828 | .fb_setcolreg = sh_mobile_lcdc_setcolreg, |
@@ -771,6 +832,7 @@ static struct fb_ops sh_mobile_lcdc_ops = { | |||
771 | .fb_copyarea = sh_mobile_lcdc_copyarea, | 832 | .fb_copyarea = sh_mobile_lcdc_copyarea, |
772 | .fb_imageblit = sh_mobile_lcdc_imageblit, | 833 | .fb_imageblit = sh_mobile_lcdc_imageblit, |
773 | .fb_pan_display = sh_mobile_fb_pan_display, | 834 | .fb_pan_display = sh_mobile_fb_pan_display, |
835 | .fb_ioctl = sh_mobile_ioctl, | ||
774 | }; | 836 | }; |
775 | 837 | ||
776 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | 838 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) |
@@ -874,7 +936,7 @@ static int sh_mobile_lcdc_runtime_resume(struct device *dev) | |||
874 | return 0; | 936 | return 0; |
875 | } | 937 | } |
876 | 938 | ||
877 | static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | 939 | static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { |
878 | .suspend = sh_mobile_lcdc_suspend, | 940 | .suspend = sh_mobile_lcdc_suspend, |
879 | .resume = sh_mobile_lcdc_resume, | 941 | .resume = sh_mobile_lcdc_resume, |
880 | .runtime_suspend = sh_mobile_lcdc_runtime_suspend, | 942 | .runtime_suspend = sh_mobile_lcdc_runtime_suspend, |
@@ -883,7 +945,7 @@ static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | |||
883 | 945 | ||
884 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 946 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
885 | 947 | ||
886 | static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | 948 | static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) |
887 | { | 949 | { |
888 | struct fb_info *info; | 950 | struct fb_info *info; |
889 | struct sh_mobile_lcdc_priv *priv; | 951 | struct sh_mobile_lcdc_priv *priv; |
@@ -896,25 +958,24 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
896 | 958 | ||
897 | if (!pdev->dev.platform_data) { | 959 | if (!pdev->dev.platform_data) { |
898 | dev_err(&pdev->dev, "no platform data defined\n"); | 960 | dev_err(&pdev->dev, "no platform data defined\n"); |
899 | error = -EINVAL; | 961 | return -EINVAL; |
900 | goto err0; | ||
901 | } | 962 | } |
902 | 963 | ||
903 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 964 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
904 | i = platform_get_irq(pdev, 0); | 965 | i = platform_get_irq(pdev, 0); |
905 | if (!res || i < 0) { | 966 | if (!res || i < 0) { |
906 | dev_err(&pdev->dev, "cannot get platform resources\n"); | 967 | dev_err(&pdev->dev, "cannot get platform resources\n"); |
907 | error = -ENOENT; | 968 | return -ENOENT; |
908 | goto err0; | ||
909 | } | 969 | } |
910 | 970 | ||
911 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | 971 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
912 | if (!priv) { | 972 | if (!priv) { |
913 | dev_err(&pdev->dev, "cannot allocate device data\n"); | 973 | dev_err(&pdev->dev, "cannot allocate device data\n"); |
914 | error = -ENOMEM; | 974 | return -ENOMEM; |
915 | goto err0; | ||
916 | } | 975 | } |
917 | 976 | ||
977 | platform_set_drvdata(pdev, priv); | ||
978 | |||
918 | error = request_irq(i, sh_mobile_lcdc_irq, IRQF_DISABLED, | 979 | error = request_irq(i, sh_mobile_lcdc_irq, IRQF_DISABLED, |
919 | dev_name(&pdev->dev), priv); | 980 | dev_name(&pdev->dev), priv); |
920 | if (error) { | 981 | if (error) { |
@@ -923,8 +984,6 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
923 | } | 984 | } |
924 | 985 | ||
925 | priv->irq = i; | 986 | priv->irq = i; |
926 | priv->dev = &pdev->dev; | ||
927 | platform_set_drvdata(pdev, priv); | ||
928 | pdata = pdev->dev.platform_data; | 987 | pdata = pdev->dev.platform_data; |
929 | 988 | ||
930 | j = 0; | 989 | j = 0; |
@@ -938,8 +997,8 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
938 | goto err1; | 997 | goto err1; |
939 | } | 998 | } |
940 | init_waitqueue_head(&priv->ch[i].frame_end_wait); | 999 | init_waitqueue_head(&priv->ch[i].frame_end_wait); |
1000 | init_completion(&priv->ch[i].vsync_completion); | ||
941 | priv->ch[j].pan_offset = 0; | 1001 | priv->ch[j].pan_offset = 0; |
942 | priv->ch[j].new_pan_offset = 0; | ||
943 | 1002 | ||
944 | switch (pdata->ch[i].chan) { | 1003 | switch (pdata->ch[i].chan) { |
945 | case LCDC_CHAN_MAINLCD: | 1004 | case LCDC_CHAN_MAINLCD: |
@@ -1038,9 +1097,9 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1038 | info = ch->info; | 1097 | info = ch->info; |
1039 | 1098 | ||
1040 | if (info->fbdefio) { | 1099 | if (info->fbdefio) { |
1041 | priv->ch->sglist = vmalloc(sizeof(struct scatterlist) * | 1100 | ch->sglist = vmalloc(sizeof(struct scatterlist) * |
1042 | info->fix.smem_len >> PAGE_SHIFT); | 1101 | info->fix.smem_len >> PAGE_SHIFT); |
1043 | if (!priv->ch->sglist) { | 1102 | if (!ch->sglist) { |
1044 | dev_err(&pdev->dev, "cannot allocate sglist\n"); | 1103 | dev_err(&pdev->dev, "cannot allocate sglist\n"); |
1045 | goto err1; | 1104 | goto err1; |
1046 | } | 1105 | } |
@@ -1065,9 +1124,9 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1065 | } | 1124 | } |
1066 | 1125 | ||
1067 | return 0; | 1126 | return 0; |
1068 | err1: | 1127 | err1: |
1069 | sh_mobile_lcdc_remove(pdev); | 1128 | sh_mobile_lcdc_remove(pdev); |
1070 | err0: | 1129 | |
1071 | return error; | 1130 | return error; |
1072 | } | 1131 | } |
1073 | 1132 | ||
@@ -1078,7 +1137,7 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) | |||
1078 | int i; | 1137 | int i; |
1079 | 1138 | ||
1080 | for (i = 0; i < ARRAY_SIZE(priv->ch); i++) | 1139 | for (i = 0; i < ARRAY_SIZE(priv->ch); i++) |
1081 | if (priv->ch[i].info->dev) | 1140 | if (priv->ch[i].info && priv->ch[i].info->dev) |
1082 | unregister_framebuffer(priv->ch[i].info); | 1141 | unregister_framebuffer(priv->ch[i].info); |
1083 | 1142 | ||
1084 | sh_mobile_lcdc_stop(priv); | 1143 | sh_mobile_lcdc_stop(priv); |
@@ -1101,7 +1160,8 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) | |||
1101 | if (priv->dot_clk) | 1160 | if (priv->dot_clk) |
1102 | clk_put(priv->dot_clk); | 1161 | clk_put(priv->dot_clk); |
1103 | 1162 | ||
1104 | pm_runtime_disable(priv->dev); | 1163 | if (priv->dev) |
1164 | pm_runtime_disable(priv->dev); | ||
1105 | 1165 | ||
1106 | if (priv->base) | 1166 | if (priv->base) |
1107 | iounmap(priv->base); | 1167 | iounmap(priv->base); |