summaryrefslogblamecommitdiff
path: root/drivers/video/amifb.c
blob: 4c9ec3f58c52e35c194d98cfebcd830075be6c61 (plain) (tree)
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802














































                                                                             

                        




                            
                          


























































                                                                                                   
                                                                                



















                                                                                
                                                                                


                                                                                
                                                                                


                                                                                
                                                                                






















































































































































































                                                                                
                                                                                






                                                                           
                                                                                  





























































































































































































































































                                                                                 

                                    



















































































































































































































                                                                                    
                                                                        





































































































































































































































































































































                                                                                              
                                                                                  













                                                    
                                                          
















                                                                 

                                                                                      


















                                                             
























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































                                                                                              

                                                           





                                             
                                               






                                                              
                                                            



                                                                            
                                                                                

                                         
                                                            


                                                                            
                                                                              

                                                                
                                                                                




                                                             
                                                              


                                                                              
                                                             




































































































































































































                                                                                       


                                                                                 
 









































                                                                               

                                            






































                                                        
                                                         


























































































































































































































































































































































































































                                                                                                            

























































































































































































































































































































































































































                                                                                                               
                                                                                     




















                                                
                                                 





                                              
                            












































                                                                                                   
                                                                                     

























                                             
                                                                  













                                                                
                            




                                                                        
                                              







































































































































































































































































































































































                                                                                                                 
/*
 * linux/drivers/video/amifb.c -- Amiga builtin chipset frame buffer device
 *
 *    Copyright (C) 1995-2003 Geert Uytterhoeven
 *
 *          with work by Roman Zippel
 *
 *
 * This file is based on the Atari frame buffer device (atafb.c):
 *
 *    Copyright (C) 1994 Martin Schaller
 *                       Roman Hodek
 *
 *          with work by Andreas Schwab
 *                       Guenther Kelleter
 *
 * and on the original Amiga console driver (amicon.c):
 *
 *    Copyright (C) 1993 Hamish Macdonald
 *                       Greg Harp
 *    Copyright (C) 1994 David Carter [carter@compsci.bristol.ac.uk]
 *
 *          with work by William Rucklidge (wjr@cs.cornell.edu)
 *                       Geert Uytterhoeven
 *                       Jes Sorensen (jds@kom.auc.dk)
 *
 *
 * History:
 *
 *   - 24 Jul 96: Copper generates now vblank interrupt and
 *                VESA Power Saving Protocol is fully implemented
 *   - 14 Jul 96: Rework and hopefully last ECS bugs fixed
 *   -  7 Mar 96: Hardware sprite support by Roman Zippel
 *   - 18 Feb 96: OCS and ECS support by Roman Zippel
 *                Hardware functions completely rewritten
 *   -  2 Dec 95: AGA version by Geert Uytterhoeven
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License. See the file COPYING in the main directory of this archive
 * for more details.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/ioport.h>

#include <linux/uaccess.h>
#include <asm/system.h>
#include <asm/irq.h>
#include <asm/amigahw.h>
#include <asm/amigaints.h>
#include <asm/setup.h>

#include "c2p.h"


#define DEBUG

#if !defined(CONFIG_FB_AMIGA_OCS) && !defined(CONFIG_FB_AMIGA_ECS) && !defined(CONFIG_FB_AMIGA_AGA)
#define CONFIG_FB_AMIGA_OCS   /* define at least one fb driver, this will change later */
#endif

#if !defined(CONFIG_FB_AMIGA_OCS)
#  define IS_OCS (0)
#elif defined(CONFIG_FB_AMIGA_ECS) || defined(CONFIG_FB_AMIGA_AGA)
#  define IS_OCS (chipset == TAG_OCS)
#else
#  define CONFIG_FB_AMIGA_OCS_ONLY
#  define IS_OCS (1)
#endif

#if !defined(CONFIG_FB_AMIGA_ECS)
#  define IS_ECS (0)
#elif defined(CONFIG_FB_AMIGA_OCS) || defined(CONFIG_FB_AMIGA_AGA)
#  define IS_ECS (chipset == TAG_ECS)
#else
#  define CONFIG_FB_AMIGA_ECS_ONLY
#  define IS_ECS (1)
#endif

#if !defined(CONFIG_FB_AMIGA_AGA)
#  define IS_AGA (0)
#elif defined(CONFIG_FB_AMIGA_OCS) || defined(CONFIG_FB_AMIGA_ECS)
#  define IS_AGA (chipset == TAG_AGA)
#else
#  define CONFIG_FB_AMIGA_AGA_ONLY
#  define IS_AGA (1)
#endif

#ifdef DEBUG
#  define DPRINTK(fmt, args...)	printk(KERN_DEBUG "%s: " fmt, __FUNCTION__ , ## args)
#else
#  define DPRINTK(fmt, args...)
#endif

/*******************************************************************************


   Generic video timings
   ---------------------

   Timings used by the frame buffer interface:

   +----------+---------------------------------------------+----------+-------+
   |          |                ^                            |          |       |
   |          |                |upper_margin                |          |       |
   |          |                v                            |          |       |
   +----------###############################################----------+-------+
   |          #                ^                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |   left   #                |                            #  right   | hsync |
   |  margin  #                |       xres                 #  margin  |  len  |
   |<-------->#<---------------+--------------------------->#<-------->|<----->|
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |yres                        #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                |                            #          |       |
   |          #                v                            #          |       |
   +----------###############################################----------+-------+
   |          |                ^                            |          |       |
   |          |                |lower_margin                |          |       |
   |          |                v                            |          |       |
   +----------+---------------------------------------------+----------+-------+
   |          |                ^                            |          |       |
   |          |                |vsync_len                   |          |       |
   |          |                v                            |          |       |
   +----------+---------------------------------------------+----------+-------+


   Amiga video timings
   -------------------

   The Amiga native chipsets uses another timing scheme:

      - hsstrt:   Start of horizontal synchronization pulse
      - hsstop:   End of horizontal synchronization pulse
      - htotal:   Last value on the line (i.e. line length = htotal+1)
      - vsstrt:   Start of vertical synchronization pulse
      - vsstop:   End of vertical synchronization pulse
      - vtotal:   Last line value (i.e. number of lines = vtotal+1)
      - hcenter:  Start of vertical retrace for interlace

   You can specify the blanking timings independently. Currently I just set
   them equal to the respective synchronization values:

      - hbstrt:   Start of horizontal blank
      - hbstop:   End of horizontal blank
      - vbstrt:   Start of vertical blank
      - vbstop:   End of vertical blank

   Horizontal values are in color clock cycles (280 ns), vertical values are in
   scanlines.

   (0, 0) is somewhere in the upper-left corner :-)


   Amiga visible window definitions
   --------------------------------

   Currently I only have values for AGA, SHRES (28 MHz dotclock). Feel free to
   make corrections and/or additions.

   Within the above synchronization specifications, the visible window is
   defined by the following parameters (actual register resolutions may be
   different; all horizontal values are normalized with respect to the pixel
   clock):

      - diwstrt_h:   Horizontal start of the visible window
      - diwstop_h:   Horizontal stop+1(*) of the visible window
      - diwstrt_v:   Vertical start of the visible window
      - diwstop_v:   Vertical stop of the visible window
      - ddfstrt:     Horizontal start of display DMA
      - ddfstop:     Horizontal stop of display DMA
      - hscroll:     Horizontal display output delay

   Sprite positioning:

      - sprstrt_h:   Horizontal start-4 of sprite
      - sprstrt_v:   Vertical start of sprite

   (*) Even Commodore did it wrong in the AGA monitor drivers by not adding 1.

   Horizontal values are in dotclock cycles (35 ns), vertical values are in
   scanlines.

   (0, 0) is somewhere in the upper-left corner :-)


   Dependencies (AGA, SHRES (35 ns dotclock))
   -------------------------------------------

   Since there are much more parameters for the Amiga display than for the
   frame buffer interface, there must be some dependencies among the Amiga
   display parameters. Here's what I found out:

      - ddfstrt and ddfstop are best aligned to 64 pixels.
      - the chipset needs 64+4 horizontal pixels after the DMA start before the
        first pixel is output, so diwstrt_h = ddfstrt+64+4 if you want to
        display the first pixel on the line too. Increase diwstrt_h for virtual
        screen panning.
      - the display DMA always fetches 64 pixels at a time (fmode = 3).
      - ddfstop is ddfstrt+#pixels-64.
      - diwstop_h = diwstrt_h+xres+1. Because of the additional 1 this can be 1
        more than htotal.
      - hscroll simply adds a delay to the display output. Smooth horizontal
        panning needs an extra 64 pixels on the left to prefetch the pixels that
        `fall off' on the left.
      - if ddfstrt < 192, the sprite DMA cycles are all stolen by the bitplane
        DMA, so it's best to make the DMA start as late as possible.
      - you really don't want to make ddfstrt < 128, since this will steal DMA
        cycles from the other DMA channels (audio, floppy and Chip RAM refresh).
      - I make diwstop_h and diwstop_v as large as possible.

   General dependencies
   --------------------

      - all values are SHRES pixel (35ns)

                  table 1:fetchstart  table 2:prefetch    table 3:fetchsize
                  ------------------  ----------------    -----------------
   Pixclock     # SHRES|HIRES|LORES # SHRES|HIRES|LORES # SHRES|HIRES|LORES
   -------------#------+-----+------#------+-----+------#------+-----+------
   Bus width 1x #   16 |  32 |  64  #   16 |  32 |  64  #   64 |  64 |  64
   Bus width 2x #   32 |  64 | 128  #   32 |  64 |  64  #   64 |  64 | 128
   Bus width 4x #   64 | 128 | 256  #   64 |  64 |  64  #   64 | 128 | 256

      - chipset needs 4 pixels before the first pixel is output
      - ddfstrt must be aligned to fetchstart (table 1)
      - chipset needs also prefetch (table 2) to get first pixel data, so
        ddfstrt = ((diwstrt_h-4) & -fetchstart) - prefetch
      - for horizontal panning decrease diwstrt_h
      - the length of a fetchline must be aligned to fetchsize (table 3)
      - if fetchstart is smaller than fetchsize, then ddfstrt can a little bit
        moved to optimize use of dma (useful for OCS/ECS overscan displays)
      - ddfstop is ddfstrt+ddfsize-fetchsize
      - If C= didn't change anything for AGA, then at following positions the
        dma bus is already used:
        ddfstrt <  48 -> memory refresh
                <  96 -> disk dma
                < 160 -> audio dma
                < 192 -> sprite 0 dma
                < 416 -> sprite dma (32 per sprite)
      - in accordance with the hardware reference manual a hardware stop is at
        192, but AGA (ECS?) can go below this.

   DMA priorities
   --------------

   Since there are limits on the earliest start value for display DMA and the
   display of sprites, I use the following policy on horizontal panning and
   the hardware cursor:

      - if you want to start display DMA too early, you lose the ability to
        do smooth horizontal panning (xpanstep 1 -> 64).
      - if you want to go even further, you lose the hardware cursor too.

   IMHO a hardware cursor is more important for X than horizontal scrolling,
   so that's my motivation.


   Implementation
   --------------

   ami_decode_var() converts the frame buffer values to the Amiga values. It's
   just a `straightforward' implementation of the above rules.


   Standard VGA timings
   --------------------

               xres  yres    left  right  upper  lower    hsync    vsync
               ----  ----    ----  -----  -----  -----    -----    -----
      80x25     720   400      27     45     35     12      108        2
      80x30     720   480      27     45     30      9      108        2

   These were taken from a XFree86 configuration file, recalculated for a 28 MHz
   dotclock (Amigas don't have a 25 MHz dotclock) and converted to frame buffer
   generic timings.

   As a comparison, graphics/monitor.h suggests the following:

               xres  yres    left  right  upper  lower    hsync    vsync
               ----  ----    ----  -----  -----  -----    -----    -----

      VGA       640   480      52    112     24     19    112 -      2 +
      VGA70     640   400      52    112     27     21    112 -      2 -


   Sync polarities
   ---------------

      VSYNC    HSYNC    Vertical size    Vertical total
      -----    -----    -------------    --------------
        +        +           Reserved          Reserved
        +        -                400               414
        -        +                350               362
        -        -                480               496

   Source: CL-GD542X Technical Reference Manual, Cirrus Logic, Oct 1992


   Broadcast video timings
   -----------------------

   According to the CCIR and RETMA specifications, we have the following values:

   CCIR -> PAL
   -----------

      - a scanline is 64 µs long, of which 52.48 µs are visible. This is about
        736 visible 70 ns pixels per line.
      - we have 625 scanlines, of which 575 are visible (interlaced); after
        rounding this becomes 576.

   RETMA -> NTSC
   -------------

      - a scanline is 63.5 µs long, of which 53.5 µs are visible.  This is about
        736 visible 70 ns pixels per line.
      - we have 525 scanlines, of which 485 are visible (interlaced); after
        rounding this becomes 484.

   Thus if you want a PAL compatible display, you have to do the following:

      - set the FB_SYNC_BROADCAST flag to indicate that standard broadcast
        timings are to be used.
      - make sure upper_margin+yres+lower_margin+vsync_len = 625 for an
        interlaced, 312 for a non-interlaced and 156 for a doublescanned
        display.
      - make sure left_margin+xres+right_margin+hsync_len = 1816 for a SHRES,
        908 for a HIRES and 454 for a LORES display.
      - the left visible part begins at 360 (SHRES; HIRES:180, LORES:90),
        left_margin+2*hsync_len must be greater or equal.
      - the upper visible part begins at 48 (interlaced; non-interlaced:24,
        doublescanned:12), upper_margin+2*vsync_len must be greater or equal.
      - ami_encode_var() calculates margins with a hsync of 5320 ns and a vsync
        of 4 scanlines

   The settings for a NTSC compatible display are straightforward.

   Note that in a strict sense the PAL and NTSC standards only define the
   encoding of the color part (chrominance) of the video signal and don't say
   anything about horizontal/vertical synchronization nor refresh rates.


                                                            -- Geert --

*******************************************************************************/


	/*
	 * Custom Chipset Definitions
	 */

#define CUSTOM_OFS(fld) ((long)&((struct CUSTOM*)0)->fld)

	/*
	 * BPLCON0 -- Bitplane Control Register 0
	 */

#define BPC0_HIRES	(0x8000)
#define BPC0_BPU2	(0x4000) /* Bit plane used count */
#define BPC0_BPU1	(0x2000)
#define BPC0_BPU0	(0x1000)
#define BPC0_HAM	(0x0800) /* HAM mode */
#define BPC0_DPF	(0x0400) /* Double playfield */
#define BPC0_COLOR	(0x0200) /* Enable colorburst */
#define BPC0_GAUD	(0x0100) /* Genlock audio enable */
#define BPC0_UHRES	(0x0080) /* Ultrahi res enable */
#define BPC0_SHRES	(0x0040) /* Super hi res mode */
#define BPC0_BYPASS	(0x0020) /* Bypass LUT - AGA */
#define BPC0_BPU3	(0x0010) /* AGA */
#define BPC0_LPEN	(0x0008) /* Light pen enable */
#define BPC0_LACE	(0x0004) /* Interlace */
#define BPC0_ERSY	(0x0002) /* External resync */
#define BPC0_ECSENA	(0x0001) /* ECS enable */

	/*
	 * BPLCON2 -- Bitplane Control Register 2
	 */

#define BPC2_ZDBPSEL2	(0x4000) /* Bitplane to be used for ZD - AGA */
#define BPC2_ZDBPSEL1	(0x2000)
#define BPC2_ZDBPSEL0	(0x1000)
#define BPC2_ZDBPEN	(0x0800) /* Enable ZD with ZDBPSELx - AGA */
#define BPC2_ZDCTEN	(0x0400) /* Enable ZD with palette bit #31 - AGA */
#define BPC2_KILLEHB	(0x0200) /* Kill EHB mode - AGA */
#define BPC2_RDRAM	(0x0100) /* Color table accesses read, not write - AGA */
#define BPC2_SOGEN	(0x0080) /* SOG output pin high - AGA */
#define BPC2_PF2PRI	(0x0040) /* PF2 priority over PF1 */
#define BPC2_PF2P2	(0x0020) /* PF2 priority wrt sprites */
#define BPC2_PF2P1	(0x0010)
#define BPC2_PF2P0	(0x0008)
#define BPC2_PF1P2	(0x0004) /* ditto PF1 */
#define BPC2_PF1P1	(0x0002)
#define BPC2_PF1P0	(0x0001)

	/*
	 * BPLCON3 -- Bitplane Control Register 3 (AGA)
	 */

#define BPC3_BANK2	(0x8000) /* Bits to select color register bank */
#define BPC3_BANK1	(0x4000)
#define BPC3_BANK0	(0x2000)
#define BPC3_PF2OF2	(0x1000) /* Bits for color table offset when PF2 */
#define BPC3_PF2OF1	(0x0800)
#define BPC3_PF2OF0	(0x0400)
#define BPC3_LOCT	(0x0200) /* Color register writes go to low bits */
#define BPC3_SPRES1	(0x0080) /* Sprite resolution bits */
#define BPC3_SPRES0	(0x0040)
#define BPC3_BRDRBLNK	(0x0020) /* Border blanked? */
#define BPC3_BRDRTRAN	(0x0010) /* Border transparent? */
#define BPC3_ZDCLKEN	(0x0004) /* ZD pin is 14 MHz (HIRES) clock output */
#define BPC3_BRDRSPRT	(0x0002) /* Sprites in border? */
#define BPC3_EXTBLKEN	(0x0001) /* BLANK programmable */

	/*
	 * BPLCON4 -- Bitplane Control Register 4 (AGA)
	 */

#define BPC4_BPLAM7	(0x8000) /* bitplane color XOR field */
#define BPC4_BPLAM6	(0x4000)
#define BPC4_BPLAM5	(0x2000)
#define BPC4_BPLAM4	(0x1000)
#define BPC4_BPLAM3	(0x0800)
#define BPC4_BPLAM2	(0x0400)
#define BPC4_BPLAM1	(0x0200)
#define BPC4_BPLAM0	(0x0100)
#define BPC4_ESPRM7	(0x0080) /* 4 high bits for even sprite colors */
#define BPC4_ESPRM6	(0x0040)
#define BPC4_ESPRM5	(0x0020)
#define BPC4_ESPRM4	(0x0010)
#define BPC4_OSPRM7	(0x0008) /* 4 high bits for odd sprite colors */
#define BPC4_OSPRM6	(0x0004)
#define BPC4_OSPRM5	(0x0002)
#define BPC4_OSPRM4	(0x0001)

	/*
	 * BEAMCON0 -- Beam Control Register
	 */

#define BMC0_HARDDIS	(0x4000) /* Disable hardware limits */
#define BMC0_LPENDIS	(0x2000) /* Disable light pen latch */
#define BMC0_VARVBEN	(0x1000) /* Enable variable vertical blank */
#define BMC0_LOLDIS	(0x0800) /* Disable long/short line toggle */
#define BMC0_CSCBEN	(0x0400) /* Composite sync/blank */
#define BMC0_VARVSYEN	(0x0200) /* Enable variable vertical sync */
#define BMC0_VARHSYEN	(0x0100) /* Enable variable horizontal sync */
#define BMC0_VARBEAMEN	(0x0080) /* Enable variable beam counters */
#define BMC0_DUAL	(0x0040) /* Enable alternate horizontal beam counter */
#define BMC0_PAL	(0x0020) /* Set decodes for PAL */
#define BMC0_VARCSYEN	(0x0010) /* Enable variable composite sync */
#define BMC0_BLANKEN	(0x0008) /* Blank enable (no longer used on AGA) */
#define BMC0_CSYTRUE	(0x0004) /* CSY polarity */
#define BMC0_VSYTRUE	(0x0002) /* VSY polarity */
#define BMC0_HSYTRUE	(0x0001) /* HSY polarity */


	/*
	 * FMODE -- Fetch Mode Control Register (AGA)
	 */

#define FMODE_SSCAN2	(0x8000) /* Sprite scan-doubling */
#define FMODE_BSCAN2	(0x4000) /* Use PF2 modulus every other line */
#define FMODE_SPAGEM	(0x0008) /* Sprite page mode */
#define FMODE_SPR32	(0x0004) /* Sprite 32 bit fetch */
#define FMODE_BPAGEM	(0x0002) /* Bitplane page mode */
#define FMODE_BPL32	(0x0001) /* Bitplane 32 bit fetch */

	/*
	 * Tags used to indicate a specific Pixel Clock
	 *
	 * clk_shift is the shift value to get the timings in 35 ns units
	 */

enum { TAG_SHRES, TAG_HIRES, TAG_LORES };

	/*
	 * Tags used to indicate the specific chipset
	 */

enum { TAG_OCS, TAG_ECS, TAG_AGA };

	/*
	 * Tags used to indicate the memory bandwidth
	 */

enum { TAG_FMODE_1, TAG_FMODE_2, TAG_FMODE_4 };


	/*
	 * Clock Definitions, Maximum Display Depth
	 *
	 * These depend on the E-Clock or the Chipset, so they are filled in
	 * dynamically
	 */

static u_long pixclock[3];	/* SHRES/HIRES/LORES: index = clk_shift */
static u_short maxdepth[3];	/* SHRES/HIRES/LORES: index = clk_shift */
static u_short maxfmode, chipset;


	/*
	 * Broadcast Video Timings
	 *
	 * Horizontal values are in 35 ns (SHRES) units
	 * Vertical values are in interlaced scanlines
	 */

#define PAL_DIWSTRT_H	(360)	/* PAL Window Limits */
#define PAL_DIWSTRT_V	(48)
#define PAL_HTOTAL	(1816)
#define PAL_VTOTAL	(625)

#define NTSC_DIWSTRT_H	(360)	/* NTSC Window Limits */
#define NTSC_DIWSTRT_V	(40)
#define NTSC_HTOTAL	(1816)
#define NTSC_VTOTAL	(525)


	/*
	 * Various macros
	 */

#define up2(v)		(((v)+1) & -2)
#define down2(v)	((v) & -2)
#define div2(v)		((v)>>1)
#define mod2(v)		((v) & 1)

#define up4(v)		(((v)+3) & -4)
#define down4(v)	((v) & -4)
#define mul4(v)		((v)<<2)
#define div4(v)		((v)>>2)
#define mod4(v)		((v) & 3)

#define up8(v)		(((v)+7) & -8)
#define down8(v)	((v) & -8)
#define div8(v)		((v)>>3)
#define mod8(v)		((v) & 7)

#define up16(v)		(((v)+15) & -16)
#define down16(v)	((v) & -16)
#define div16(v)	((v)>>4)
#define mod16(v)	((v) & 15)

#define up32(v)		(((v)+31) & -32)
#define down32(v)	((v) & -32)
#define div32(v)	((v)>>5)
#define mod32(v)	((v) & 31)

#define up64(v)		(((v)+63) & -64)
#define down64(v)	((v) & -64)
#define div64(v)	((v)>>6)
#define mod64(v)	((v) & 63)

#define upx(x,v)	(((v)+(x)-1) & -(x))
#define downx(x,v)	((v) & -(x))
#define modx(x,v)	((v) & ((x)-1))

/* if x1 is not a constant, this macro won't make real sense :-) */
#ifdef __mc68000__
#define DIVUL(x1, x2) ({int res; asm("divul %1,%2,%3": "=d" (res): \
	"d" (x2), "d" ((long)((x1)/0x100000000ULL)), "0" ((long)(x1))); res;})
#else
/* We know a bit about the numbers, so we can do it this way */
#define DIVUL(x1, x2) ((((long)((unsigned long long)x1 >> 8) / x2) << 8) + \
	((((long)((unsigned long long)x1 >> 8) % x2) << 8) / x2))
#endif

#define highw(x)	((u_long)(x)>>16 & 0xffff)
#define loww(x)		((u_long)(x) & 0xffff)

#define custom		amiga_custom

#define VBlankOn()	custom.intena = IF_SETCLR|IF_COPER
#define VBlankOff()	custom.intena = IF_COPER


	/*
	 * Chip RAM we reserve for the Frame Buffer
	 *
	 * This defines the Maximum Virtual Screen Size
	 * (Setable per kernel options?)
	 */

#define VIDEOMEMSIZE_AGA_2M	(1310720) /* AGA (2MB) : max 1280*1024*256  */
#define VIDEOMEMSIZE_AGA_1M	(786432)  /* AGA (1MB) : max 1024*768*256   */
#define VIDEOMEMSIZE_ECS_2M	(655360)  /* ECS (2MB) : max 1280*1024*16   */
#define VIDEOMEMSIZE_ECS_1M	(393216)  /* ECS (1MB) : max 1024*768*16    */
#define VIDEOMEMSIZE_OCS	(262144)  /* OCS       : max ca. 800*600*16 */

#define SPRITEMEMSIZE		(64*64/4) /* max 64*64*4 */
#define DUMMYSPRITEMEMSIZE	(8)
static u_long spritememory;

#define CHIPRAM_SAFETY_LIMIT	(16384)

static u_long videomemory;

	/*
	 * This is the earliest allowed start of fetching display data.
	 * Only if you really want no hardware cursor and audio,
	 * set this to 128, but let it better at 192
	 */

static u_long min_fstrt = 192;

#define assignchunk(name, type, ptr, size) \
{ \
	(name) = (type)(ptr); \
	ptr += size; \
}


	/*
	 * Copper Instructions
	 */

#define CMOVE(val, reg)		(CUSTOM_OFS(reg)<<16 | (val))
#define CMOVE2(val, reg)	((CUSTOM_OFS(reg)+2)<<16 | (val))
#define CWAIT(x, y)		(((y) & 0x1fe)<<23 | ((x) & 0x7f0)<<13 | 0x0001fffe)
#define CEND			(0xfffffffe)


typedef union {
	u_long l;
	u_short w[2];
} copins;

static struct copdisplay {
	copins *init;
	copins *wait;
	copins *list[2][2];
	copins *rebuild[2];
} copdisplay;

static u_short currentcop = 0;

	/*
	 * Hardware Cursor API Definitions
	 * These used to be in linux/fb.h, but were preliminary and used by
	 * amifb only anyway
	 */

#define FBIOGET_FCURSORINFO     0x4607
#define FBIOGET_VCURSORINFO     0x4608
#define FBIOPUT_VCURSORINFO     0x4609
#define FBIOGET_CURSORSTATE     0x460A
#define FBIOPUT_CURSORSTATE     0x460B


struct fb_fix_cursorinfo {
	__u16 crsr_width;		/* width and height of the cursor in */
	__u16 crsr_height;		/* pixels (zero if no cursor)	*/
	__u16 crsr_xsize;		/* cursor size in display pixels */
	__u16 crsr_ysize;
	__u16 crsr_color1;		/* colormap entry for cursor color1 */
	__u16 crsr_color2;		/* colormap entry for cursor color2 */
};

struct fb_var_cursorinfo {
	__u16 width;
	__u16 height;
	__u16 xspot;
	__u16 yspot;
	__u8 data[1];			/* field with [height][width]        */
};

struct fb_cursorstate {
	__s16 xoffset;
	__s16 yoffset;
	__u16 mode;
};

#define FB_CURSOR_OFF		0
#define FB_CURSOR_ON		1
#define FB_CURSOR_FLASH		2


	/*
	 * Hardware Cursor
	 */

static int cursorrate = 20;	/* Number of frames/flash toggle */
static u_short cursorstate = -1;
static u_short cursormode = FB_CURSOR_OFF;

static u_short *lofsprite, *shfsprite, *dummysprite;

	/*
	 * Current Video Mode
	 */

static struct amifb_par {

	/* General Values */

	int xres;		/* vmode */
	int yres;		/* vmode */
	int vxres;		/* vmode */
	int vyres;		/* vmode */
	int xoffset;		/* vmode */
	int yoffset;		/* vmode */
	u_short bpp;		/* vmode */
	u_short clk_shift;	/* vmode */
	u_short line_shift;	/* vmode */
	int vmode;		/* vmode */
	u_short diwstrt_h;	/* vmode */
	u_short diwstop_h;	/* vmode */
	u_short diwstrt_v;	/* vmode */
	u_short diwstop_v;	/* vmode */
	u_long next_line;	/* modulo for next line */
	u_long next_plane;	/* modulo for next plane */

	/* Cursor Values */

	struct {
		short crsr_x;	/* movecursor */
		short crsr_y;	/* movecursor */
		short spot_x;
		short spot_y;
		u_short height;
		u_short width;
		u_short fmode;
	} crsr;

	/* OCS Hardware Registers */

	u_long bplpt0;		/* vmode, pan (Note: physical address) */
	u_long bplpt0wrap;	/* vmode, pan (Note: physical address) */
	u_short ddfstrt;
	u_short ddfstop;
	u_short bpl1mod;
	u_short bpl2mod;
	u_short bplcon0;	/* vmode */
	u_short bplcon1;	/* vmode */
	u_short htotal;		/* vmode */
	u_short vtotal;		/* vmode */

	/* Additional ECS Hardware Registers */

	u_short bplcon3;	/* vmode */
	u_short beamcon0;	/* vmode */
	u_short hsstrt;		/* vmode */
	u_short hsstop;		/* vmode */
	u_short hbstrt;		/* vmode */
	u_short hbstop;		/* vmode */
	u_short vsstrt;		/* vmode */
	u_short vsstop;		/* vmode */
	u_short vbstrt;		/* vmode */
	u_short vbstop;		/* vmode */
	u_short hcenter;	/* vmode */

	/* Additional AGA Hardware Registers */

	u_short fmode;		/* vmode */
} currentpar;


static struct fb_info fb_info = {
    .fix = {
	.id		= "Amiga ",
	.visual		= FB_VISUAL_PSEUDOCOLOR,
	.accel		= FB_ACCEL_AMIGABLITT
    }
};


	/*
	 *  Saved color entry 0 so we can restore it when unblanking
	 */

static u_char red0, green0, blue0;


#if defined(CONFIG_FB_AMIGA_ECS)
static u_short ecs_palette[32];
#endif


	/*
	 * Latches for Display Changes during VBlank
	 */

static u_short do_vmode_full = 0;	/* Change the Video Mode */
static u_short do_vmode_pan = 0;	/* Update the Video Mode */
static short do_blank = 0;		/* (Un)Blank the Screen (±1) */
static u_short do_cursor = 0;		/* Move the Cursor */


	/*
	 * Various Flags
	 */

static u_short is_blanked = 0;		/* Screen is Blanked */
static u_short is_lace = 0;		/* Screen is laced */

	/*
	 * Predefined Video Modes
	 *
	 */

static struct fb_videomode ami_modedb[] __initdata = {

    /*
     *  AmigaOS Video Modes
     *
     *  If you change these, make sure to update DEFMODE_* as well!
     */

    {
	/* 640x200, 15 kHz, 60 Hz (NTSC) */
	"ntsc", 60, 640, 200, TAG_HIRES, 106, 86, 44, 16, 76, 2,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x400, 15 kHz, 60 Hz interlaced (NTSC) */
	"ntsc-lace", 60, 640, 400, TAG_HIRES, 106, 86, 88, 33, 76, 4,
	FB_SYNC_BROADCAST, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x256, 15 kHz, 50 Hz (PAL) */
	"pal", 50, 640, 256, TAG_HIRES, 106, 86, 40, 14, 76, 2,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x512, 15 kHz, 50 Hz interlaced (PAL) */
	"pal-lace", 50, 640, 512, TAG_HIRES, 106, 86, 80, 29, 76, 4,
	FB_SYNC_BROADCAST, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x480, 29 kHz, 57 Hz */
	"multiscan", 57, 640, 480, TAG_SHRES, 96, 112, 29, 8, 72, 8,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x960, 29 kHz, 57 Hz interlaced */
	"multiscan-lace", 57, 640, 960, TAG_SHRES, 96, 112, 58, 16, 72, 16,
	0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x200, 15 kHz, 72 Hz */
	"euro36", 72, 640, 200, TAG_HIRES, 92, 124, 6, 6, 52, 5,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x400, 15 kHz, 72 Hz interlaced */
	"euro36-lace", 72, 640, 400, TAG_HIRES, 92, 124, 12, 12, 52, 10,
	0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x400, 29 kHz, 68 Hz */
	"euro72", 68, 640, 400, TAG_SHRES, 164, 92, 9, 9, 80, 8,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x800, 29 kHz, 68 Hz interlaced */
	"euro72-lace", 68, 640, 800, TAG_SHRES, 164, 92, 18, 18, 80, 16,
	0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 800x300, 23 kHz, 70 Hz */
	"super72", 70, 800, 300, TAG_SHRES, 212, 140, 10, 11, 80, 7,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 800x600, 23 kHz, 70 Hz interlaced */
	"super72-lace", 70, 800, 600, TAG_SHRES, 212, 140, 20, 22, 80, 14,
	0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x200, 27 kHz, 57 Hz doublescan */
	"dblntsc", 57, 640, 200, TAG_SHRES, 196, 124, 18, 17, 80, 4,
	0, FB_VMODE_DOUBLE | FB_VMODE_YWRAP
    }, {
	/* 640x400, 27 kHz, 57 Hz */
	"dblntsc-ff", 57, 640, 400, TAG_SHRES, 196, 124, 36, 35, 80, 7,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x800, 27 kHz, 57 Hz interlaced */
	"dblntsc-lace", 57, 640, 800, TAG_SHRES, 196, 124, 72, 70, 80, 14,
	0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x256, 27 kHz, 47 Hz doublescan */
	"dblpal", 47, 640, 256, TAG_SHRES, 196, 124, 14, 13, 80, 4,
	0, FB_VMODE_DOUBLE | FB_VMODE_YWRAP
    }, {
	/* 640x512, 27 kHz, 47 Hz */
	"dblpal-ff", 47, 640, 512, TAG_SHRES, 196, 124, 28, 27, 80, 7,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x1024, 27 kHz, 47 Hz interlaced */
	"dblpal-lace", 47, 640, 1024, TAG_SHRES, 196, 124, 56, 54, 80, 14,
	0, FB_VMODE_INTERLACED | FB_VMODE_YWRAP
    },

    /*
     *  VGA Video Modes
     */

    {
	/* 640x480, 31 kHz, 60 Hz (VGA) */
	"vga", 60, 640, 480, TAG_SHRES, 64, 96, 30, 9, 112, 2,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 640x400, 31 kHz, 70 Hz (VGA) */
	"vga70", 70, 640, 400, TAG_SHRES, 64, 96, 35, 12, 112, 2,
	FB_SYNC_VERT_HIGH_ACT | FB_SYNC_COMP_HIGH_ACT, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    },

#if 0

    /*
     *  A2024 video modes
     *  These modes don't work yet because there's no A2024 driver.
     */

    {
	/* 1024x800, 10 Hz */
	"a2024-10", 10, 1024, 800, TAG_HIRES, 0, 0, 0, 0, 0, 0,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }, {
	/* 1024x800, 15 Hz */
	"a2024-15", 15, 1024, 800, TAG_HIRES, 0, 0, 0, 0, 0, 0,
	0, FB_VMODE_NONINTERLACED | FB_VMODE_YWRAP
    }
#endif
};

#define NUM_TOTAL_MODES  ARRAY_SIZE(ami_modedb)

static char *mode_option __initdata = NULL;
static int round_down_bpp = 1;	/* for mode probing */

	/*
	 * Some default modes
	 */


#define DEFMODE_PAL	    2	/* "pal" for PAL OCS/ECS */
#define DEFMODE_NTSC	    0	/* "ntsc" for NTSC OCS/ECS */
#define DEFMODE_AMBER_PAL   3	/* "pal-lace" for flicker fixed PAL (A3000) */
#define DEFMODE_AMBER_NTSC  1	/* "ntsc-lace" for flicker fixed NTSC (A3000) */
#define DEFMODE_AGA	    19	/* "vga70" for AGA */


static int amifb_ilbm = 0;	/* interleaved or normal bitplanes */
static int amifb_inverse = 0;


	/*
	 * Macros for the conversion from real world values to hardware register
	 * values
	 *
	 * This helps us to keep our attention on the real stuff...
	 *
	 * Hardware limits for AGA:
	 *
	 *	parameter  min    max  step
	 *	---------  ---   ----  ----
	 *	diwstrt_h    0   2047     1
	 *	diwstrt_v    0   2047     1
	 *	diwstop_h    0   4095     1
	 *	diwstop_v    0   4095     1
	 *
	 *	ddfstrt      0   2032    16
	 *	ddfstop      0   2032    16
	 *
	 *	htotal       8   2048     8
	 *	hsstrt       0   2040     8
	 *	hsstop       0   2040     8
	 *	vtotal       1   4096     1
	 *	vsstrt       0   4095     1
	 *	vsstop       0   4095     1
	 *	hcenter      0   2040     8
	 *
	 *	hbstrt       0   2047     1
	 *	hbstop       0   2047     1
	 *	vbstrt       0   4095     1
	 *	vbstop       0   4095     1
	 *
	 * Horizontal values are in 35 ns (SHRES) pixels
	 * Vertical values are in half scanlines
	 */

/* bplcon1 (smooth scrolling) */

#define hscroll2hw(hscroll) \
	(((hscroll)<<12 & 0x3000) | ((hscroll)<<8 & 0xc300) | \
	 ((hscroll)<<4 & 0x0c00) | ((hscroll)<<2 & 0x00f0) | ((hscroll)>>2 & 0x000f))

/* diwstrt/diwstop/diwhigh (visible display window) */

#define diwstrt2hw(diwstrt_h, diwstrt_v) \
	(((diwstrt_v)<<7 & 0xff00) | ((diwstrt_h)>>2 & 0x00ff))
#define diwstop2hw(diwstop_h, diwstop_v) \
	(((diwstop_v)<<7 & 0xff00) | ((diwstop_h)>>2 & 0x00ff))
#define diwhigh2hw(diwstrt_h, diwstrt_v, diwstop_h, diwstop_v) \
	(((diwstop_h)<<3 & 0x2000) | ((diwstop_h)<<11 & 0x1800) | \
	 ((diwstop_v)>>1 & 0x0700) | ((diwstrt_h)>>5 & 0x0020) | \
	 ((diwstrt_h)<<3 & 0x0018) | ((diwstrt_v)>>9 & 0x0007))

/* ddfstrt/ddfstop (display DMA) */

#define ddfstrt2hw(ddfstrt)	div8(ddfstrt)
#define ddfstop2hw(ddfstop)	div8(ddfstop)

/* hsstrt/hsstop/htotal/vsstrt/vsstop/vtotal/hcenter (sync timings) */

#define hsstrt2hw(hsstrt)	(div8(hsstrt))
#define hsstop2hw(hsstop)	(div8(hsstop))
#define htotal2hw(htotal)	(div8(htotal)-1)
#define vsstrt2hw(vsstrt)	(div2(vsstrt))
#define vsstop2hw(vsstop)	(div2(vsstop))
#define vtotal2hw(vtotal)	(div2(vtotal)-1)
#define hcenter2hw(htotal)	(div8(htotal))

/* hbstrt/hbstop/vbstrt/vbstop (blanking timings) */

#define hbstrt2hw(hbstrt)	(((hbstrt)<<8 & 0x0700) | ((hbstrt)>>3 & 0x00ff))
#define hbstop2hw(hbstop)	(((hbstop)<<8 & 0x0700) | ((hbstop)>>3 & 0x00ff))
#define vbstrt2hw(vbstrt)	(div2(vbstrt))
#define vbstop2hw(vbstop)	(div2(vbstop))

/* colour */

#define rgb2hw8_high(red, green, blue) \
	(((red & 0xf0)<<4) | (green & 0xf0) | ((blue & 0xf0)>>4))
#define rgb2hw8_low(red, green, blue) \
	(((red & 0x0f)<<8) | ((green & 0x0f)<<4) | (blue & 0x0f))
#define rgb2hw4(red, green, blue) \
	(((red & 0xf0)<<4) | (green & 0xf0) | ((blue & 0xf0)>>4))
#define rgb2hw2(red, green, blue) \
	(((red & 0xc0)<<4) | (green & 0xc0) | ((blue & 0xc0)>>4))

/* sprpos/sprctl (sprite positioning) */

#define spr2hw_pos(start_v, start_h) \
	(((start_v)<<7&0xff00) | ((start_h)>>3&0x00ff))
#define spr2hw_ctl(start_v, start_h, stop_v) \
	(((stop_v)<<7&0xff00) | ((start_v)>>4&0x0040) | ((stop_v)>>5&0x0020) | \
	 ((start_h)<<3&0x0018) | ((start_v)>>7&0x0004) | ((stop_v)>>8&0x0002) | \
	 ((start_h)>>2&0x0001))

/* get current vertical position of beam */
#define get_vbpos()	((u_short)((*(u_long volatile *)&custom.vposr >> 7) & 0xffe))

	/*
	 * Copper Initialisation List
	 */

#define COPINITSIZE (sizeof(copins)*40)

enum {
	cip_bplcon0
};

	/*
	 * Long Frame/Short Frame Copper List
	 * Don't change the order, build_copper()/rebuild_copper() rely on this
	 */

#define COPLISTSIZE (sizeof(copins)*64)

enum {
	cop_wait, cop_bplcon0,
	cop_spr0ptrh, cop_spr0ptrl,
	cop_diwstrt, cop_diwstop,
	cop_diwhigh,
};

	/*
	 * Pixel modes for Bitplanes and Sprites
	 */

static u_short bplpixmode[3] = {
	BPC0_SHRES,			/*  35 ns */
	BPC0_HIRES,			/*  70 ns */
	0				/* 140 ns */
};

static u_short sprpixmode[3] = {
	BPC3_SPRES1 | BPC3_SPRES0,	/*  35 ns */
	BPC3_SPRES1,			/*  70 ns */
	BPC3_SPRES0			/* 140 ns */
};

	/*
	 * Fetch modes for Bitplanes and Sprites
	 */

static u_short bplfetchmode[3] = {
	0,				/* 1x */
	FMODE_BPL32,			/* 2x */
	FMODE_BPAGEM | FMODE_BPL32	/* 4x */
};

static u_short sprfetchmode[3] = {
	0,				/* 1x */
	FMODE_SPR32,			/* 2x */
	FMODE_SPAGEM | FMODE_SPR32	/* 4x */
};


	/*
	 * Interface used by the world
	 */

int amifb_setup(char*);

static int amifb_check_var(struct fb_var_screeninfo *var,
			   struct fb_info *info);
static int amifb_set_par(struct fb_info *info);
static int amifb_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp,
			   struct fb_info *info);
static int amifb_blank(int blank, struct fb_info *info);
static int amifb_pan_display(struct fb_var_screeninfo *var,
			     struct fb_info *info);
static void amifb_fillrect(struct fb_info *info,
			   const struct fb_fillrect *rect);
static void amifb_copyarea(struct fb_info *info,
			   const struct fb_copyarea *region);
static void amifb_imageblit(struct fb_info *info,
			    const struct fb_image *image);
static int amifb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg);


	/*
	 * Interface to the low level console driver
	 */

int amifb_init(void);
static void amifb_deinit(void);

	/*
	 * Internal routines
	 */

static int flash_cursor(void);
static irqreturn_t amifb_interrupt(int irq, void *dev_id);
static u_long chipalloc(u_long size);
static void chipfree(void);

	/*
	 * Hardware routines
	 */

static int ami_decode_var(struct fb_var_screeninfo *var,
                          struct amifb_par *par);
static int ami_encode_var(struct fb_var_screeninfo *var,
                          struct amifb_par *par);
static void ami_pan_var(struct fb_var_screeninfo *var);
static int ami_update_par(void);
static void ami_update_display(void);
static void ami_init_display(void);
static void ami_do_blank(void);
static int ami_get_fix_cursorinfo(struct fb_fix_cursorinfo *fix);
static int ami_get_var_cursorinfo(struct fb_var_cursorinfo *var, u_char __user *data);
static int ami_set_var_cursorinfo(struct fb_var_cursorinfo *var, u_char __user *data);
static int ami_get_cursorstate(struct fb_cursorstate *state);
static int ami_set_cursorstate(struct fb_cursorstate *state);
static void ami_set_sprite(void);
static void ami_init_copper(void);
static void ami_reinit_copper(void);
static void ami_build_copper(void);
static void ami_rebuild_copper(void);


static struct fb_ops amifb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= amifb_check_var,
	.fb_set_par	= amifb_set_par,
	.fb_setcolreg	= amifb_setcolreg,
	.fb_blank	= amifb_blank,
	.fb_pan_display	= amifb_pan_display,
	.fb_fillrect	= amifb_fillrect,
	.fb_copyarea	= amifb_copyarea,
	.fb_imageblit	= amifb_imageblit,
	.fb_ioctl	= amifb_ioctl,
};

static void __init amifb_setup_mcap(char *spec)
{
	char *p;
	int vmin, vmax, hmin, hmax;

	/* Format for monitor capabilities is: <Vmin>;<Vmax>;<Hmin>;<Hmax>
	 * <V*> vertical freq. in Hz
	 * <H*> horizontal freq. in kHz
	 */

	if (!(p = strsep(&spec, ";")) || !*p)
		return;
	vmin = simple_strtoul(p, NULL, 10);
	if (vmin <= 0)
		return;
	if (!(p = strsep(&spec, ";")) || !*p)
		return;
	vmax = simple_strtoul(p, NULL, 10);
	if (vmax <= 0 || vmax <= vmin)
		return;
	if (!(p = strsep(&spec, ";")) || !*p)
		return;
	hmin = 1000 * simple_strtoul(p, NULL, 10);
	if (hmin <= 0)
		return;
	if (!(p = strsep(&spec, "")) || !*p)
		return;
	hmax = 1000 * simple_strtoul(p, NULL, 10);
	if (hmax <= 0 || hmax <= hmin)
		return;

	fb_info.monspecs.vfmin = vmin;
	fb_info.monspecs.vfmax = vmax;
	fb_info.monspecs.hfmin = hmin;
	fb_info.monspecs.hfmax = hmax;
}

int __init amifb_setup(char *options)
{
	char *this_opt;

	if (!options || !*options)
		return 0;

	while ((this_opt = strsep(&options, ",")) != NULL) {
		if (!*this_opt)
			continue;
		if (!strcmp(this_opt, "inverse")) {
			amifb_inverse = 1;
			fb_invert_cmaps();
		} else if (!strcmp(this_opt, "ilbm"))
			amifb_ilbm = 1;
		else if (!strncmp(this_opt, "monitorcap:", 11))
			amifb_setup_mcap(this_opt+11);
		else if (!strncmp(this_opt, "fstart:", 7))
			min_fstrt = simple_strtoul(this_opt+7, NULL, 0);
		else
			mode_option = this_opt;
	}

	if (min_fstrt < 48)
		min_fstrt = 48;

	return 0;
}


static int amifb_check_var(struct fb_var_screeninfo *var,
			   struct fb_info *info)
{
	int err;
	struct amifb_par par;

	/* Validate wanted screen parameters */
	if ((err = ami_decode_var(var, &par)))
		return err;

	/* Encode (possibly rounded) screen parameters */
	ami_encode_var(var, &par);
	return 0;
}


static int amifb_set_par(struct fb_info *info)
{
	struct amifb_par *par = (struct amifb_par *)info->par;

	do_vmode_pan = 0;
	do_vmode_full = 0;

	/* Decode wanted screen parameters */
	ami_decode_var(&info->var, par);

	/* Set new videomode */
	ami_build_copper();

	/* Set VBlank trigger */
	do_vmode_full = 1;

	/* Update fix for new screen parameters */
	if (par->bpp == 1) {
		info->fix.type = FB_TYPE_PACKED_PIXELS;
		info->fix.type_aux = 0;
	} else if (amifb_ilbm) {
		info->fix.type = FB_TYPE_INTERLEAVED_PLANES;
		info->fix.type_aux = par->next_line;
	} else {
		info->fix.type = FB_TYPE_PLANES;
		info->fix.type_aux = 0;
	}
	info->fix.line_length = div8(upx(16<<maxfmode, par->vxres));

	if (par->vmode & FB_VMODE_YWRAP) {
		info->fix.ywrapstep = 1;
		info->fix.xpanstep = 0;
		info->fix.ypanstep = 0;
		info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YWRAP |
		    FBINFO_READS_FAST; /* override SCROLL_REDRAW */
	} else {
		info->fix.ywrapstep = 0;
		if (par->vmode & FB_VMODE_SMOOTH_XPAN)
			info->fix.xpanstep = 1;
		else
			info->fix.xpanstep = 16<<maxfmode;
		info->fix.ypanstep = 1;
		info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN;
	}
	return 0;
}


	/*
	 * Pan or Wrap the Display
	 *
	 * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
	 */

static int amifb_pan_display(struct fb_var_screeninfo *var,
			     struct fb_info *info)
{
	if (var->vmode & FB_VMODE_YWRAP) {
		if (var->yoffset < 0 ||
		    var->yoffset >= info->var.yres_virtual || var->xoffset)
			return -EINVAL;
	} else {
		/*
		 * TODO: There will be problems when xpan!=1, so some columns
		 * on the right side will never be seen
		 */
		if (var->xoffset+info->var.xres > upx(16<<maxfmode, info->var.xres_virtual) ||
		    var->yoffset+info->var.yres > info->var.yres_virtual)
			return -EINVAL;
	}
	ami_pan_var(var);
	info->var.xoffset = var->xoffset;
	info->var.yoffset = var->yoffset;
	if (var->vmode & FB_VMODE_YWRAP)
		info->var.vmode |= FB_VMODE_YWRAP;
	else
		info->var.vmode &= ~FB_VMODE_YWRAP;
	return 0;
}


#if BITS_PER_LONG == 32
#define BYTES_PER_LONG	4
#define SHIFT_PER_LONG	5
#elif BITS_PER_LONG == 64
#define BYTES_PER_LONG	8
#define SHIFT_PER_LONG	6
#else
#define Please update me
#endif


    /*
     *  Compose two values, using a bitmask as decision value
     *  This is equivalent to (a & mask) | (b & ~mask)
     */

static inline unsigned long comp(unsigned long a, unsigned long b,
				 unsigned long mask)
{
	return ((a ^ b) & mask) ^ b;
}


static inline unsigned long xor(unsigned long a, unsigned long b,
				unsigned long mask)
{
	return (a & mask) ^ b;
}


    /*
     *  Unaligned forward bit copy using 32-bit or 64-bit memory accesses
     */

static void bitcpy(unsigned long *dst, int dst_idx, const unsigned long *src,
		   int src_idx, u32 n)
{
	unsigned long first, last;
	int shift = dst_idx-src_idx, left, right;
	unsigned long d0, d1;
	int m;

	if (!n)
		return;

	shift = dst_idx-src_idx;
	first = ~0UL >> dst_idx;
	last = ~(~0UL >> ((dst_idx+n) % BITS_PER_LONG));

	if (!shift) {
		// Same alignment for source and dest

		if (dst_idx+n <= BITS_PER_LONG) {
			// Single word
			if (last)
				first &= last;
			*dst = comp(*src, *dst, first);
		} else {
			// Multiple destination words
			// Leading bits
			if (first) {
				*dst = comp(*src, *dst, first);
				dst++;
				src++;
				n -= BITS_PER_LONG-dst_idx;
			}

			// Main chunk
			n /= BITS_PER_LONG;
			while (n >= 8) {
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				n -= 8;
			}
			while (n--)
				*dst++ = *src++;

			// Trailing bits
			if (last)
				*dst = comp(*src, *dst, last);
		}
	} else {
		// Different alignment for source and dest

		right = shift & (BITS_PER_LONG-1);
		left = -shift & (BITS_PER_LONG-1);

		if (dst_idx+n <= BITS_PER_LONG) {
			// Single destination word
			if (last)
				first &= last;
			if (shift > 0) {
				// Single source word
				*dst = comp(*src >> right, *dst, first);
			} else if (src_idx+n <= BITS_PER_LONG) {
				// Single source word
				*dst = comp(*src << left, *dst, first);
			} else {
				// 2 source words
				d0 = *src++;
				d1 = *src;
				*dst = comp(d0 << left | d1 >> right, *dst,
					    first);
			}
		} else {
			// Multiple destination words
			d0 = *src++;
			// Leading bits
			if (shift > 0) {
				// Single source word
				*dst = comp(d0 >> right, *dst, first);
				dst++;
				n -= BITS_PER_LONG-dst_idx;
			} else {
				// 2 source words
				d1 = *src++;
				*dst = comp(d0 << left | d1 >> right, *dst,
					    first);
				d0 = d1;
				dst++;
				n -= BITS_PER_LONG-dst_idx;
			}

			// Main chunk
			m = n % BITS_PER_LONG;
			n /= BITS_PER_LONG;
			while (n >= 4) {
				d1 = *src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				d1 = *src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				d1 = *src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				d1 = *src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				n -= 4;
			}
			while (n--) {
				d1 = *src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
			}

			// Trailing bits
			if (last) {
				if (m <= right) {
					// Single source word
					*dst = comp(d0 << left, *dst, last);
				} else {
					// 2 source words
					d1 = *src;
					*dst = comp(d0 << left | d1 >> right,
						    *dst, last);
				}
			}
		}
	}
}


    /*
     *  Unaligned reverse bit copy using 32-bit or 64-bit memory accesses
     */

static void bitcpy_rev(unsigned long *dst, int dst_idx,
		       const unsigned long *src, int src_idx, u32 n)
{
	unsigned long first, last;
	int shift = dst_idx-src_idx, left, right;
	unsigned long d0, d1;
	int m;

	if (!n)
		return;

	dst += (n-1)/BITS_PER_LONG;
	src += (n-1)/BITS_PER_LONG;
	if ((n-1) % BITS_PER_LONG) {
		dst_idx += (n-1) % BITS_PER_LONG;
		dst += dst_idx >> SHIFT_PER_LONG;
		dst_idx &= BITS_PER_LONG-1;
		src_idx += (n-1) % BITS_PER_LONG;
		src += src_idx >> SHIFT_PER_LONG;
		src_idx &= BITS_PER_LONG-1;
	}

	shift = dst_idx-src_idx;
	first = ~0UL << (BITS_PER_LONG-1-dst_idx);
	last = ~(~0UL << (BITS_PER_LONG-1-((dst_idx-n) % BITS_PER_LONG)));

	if (!shift) {
		// Same alignment for source and dest

		if ((unsigned long)dst_idx+1 >= n) {
			// Single word
			if (last)
				first &= last;
			*dst = comp(*src, *dst, first);
		} else {
			// Multiple destination words
			// Leading bits
			if (first) {
				*dst = comp(*src, *dst, first);
				dst--;
				src--;
				n -= dst_idx+1;
			}

			// Main chunk
			n /= BITS_PER_LONG;
			while (n >= 8) {
				*dst-- = *src--;
				*dst-- = *src--;
				*dst-- = *src--;
				*dst-- = *src--;
				*dst-- = *src--;
				*dst-- = *src--;
				*dst-- = *src--;
				*dst-- = *src--;
				n -= 8;
			}
			while (n--)
				*dst-- = *src--;

			// Trailing bits
			if (last)
				*dst = comp(*src, *dst, last);
		}
	} else {
		// Different alignment for source and dest

		right = shift & (BITS_PER_LONG-1);
		left = -shift & (BITS_PER_LONG-1);

		if ((unsigned long)dst_idx+1 >= n) {
			// Single destination word
			if (last)
				first &= last;
			if (shift < 0) {
				// Single source word
				*dst = comp(*src << left, *dst, first);
			} else if (1+(unsigned long)src_idx >= n) {
				// Single source word
				*dst = comp(*src >> right, *dst, first);
			} else {
				// 2 source words
				d0 = *src--;
				d1 = *src;
				*dst = comp(d0 >> right | d1 << left, *dst,
					    first);
			}
		} else {
			// Multiple destination words
			d0 = *src--;
			// Leading bits
			if (shift < 0) {
				// Single source word
				*dst = comp(d0 << left, *dst, first);
				dst--;
				n -= dst_idx+1;
			} else {
				// 2 source words
				d1 = *src--;
				*dst = comp(d0 >> right | d1 << left, *dst,
					    first);
				d0 = d1;
				dst--;
				n -= dst_idx+1;
			}

			// Main chunk
			m = n % BITS_PER_LONG;
			n /= BITS_PER_LONG;
			while (n >= 4) {
				d1 = *src--;
				*dst-- = d0 >> right | d1 << left;
				d0 = d1;
				d1 = *src--;
				*dst-- = d0 >> right | d1 << left;
				d0 = d1;
				d1 = *src--;
				*dst-- = d0 >> right | d1 << left;
				d0 = d1;
				d1 = *src--;
				*dst-- = d0 >> right | d1 << left;
				d0 = d1;
				n -= 4;
			}
			while (n--) {
				d1 = *src--;
				*dst-- = d0 >> right | d1 << left;
				d0 = d1;
			}

			// Trailing bits
			if (last) {
				if (m <= left) {
					// Single source word
					*dst = comp(d0 >> right, *dst, last);
				} else {
					// 2 source words
					d1 = *src;
					*dst = comp(d0 >> right | d1 << left,
						    *dst, last);
				}
			}
		}
	}
}


    /*
     *  Unaligned forward inverting bit copy using 32-bit or 64-bit memory
     *  accesses
     */

static void bitcpy_not(unsigned long *dst, int dst_idx,
		       const unsigned long *src, int src_idx, u32 n)
{
	unsigned long first, last;
	int shift = dst_idx-src_idx, left, right;
	unsigned long d0, d1;
	int m;

	if (!n)
		return;

	shift = dst_idx-src_idx;
	first = ~0UL >> dst_idx;
	last = ~(~0UL >> ((dst_idx+n) % BITS_PER_LONG));

	if (!shift) {
		// Same alignment for source and dest

		if (dst_idx+n <= BITS_PER_LONG) {
			// Single word
			if (last)
				first &= last;
			*dst = comp(~*src, *dst, first);
		} else {
			// Multiple destination words
			// Leading bits
			if (first) {
				*dst = comp(~*src, *dst, first);
				dst++;
				src++;
				n -= BITS_PER_LONG-dst_idx;
			}

			// Main chunk
			n /= BITS_PER_LONG;
			while (n >= 8) {
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				*dst++ = ~*src++;
				n -= 8;
			}
			while (n--)
				*dst++ = ~*src++;

			// Trailing bits
			if (last)
				*dst = comp(~*src, *dst, last);
		}
	} else {
		// Different alignment for source and dest

		right = shift & (BITS_PER_LONG-1);
		left = -shift & (BITS_PER_LONG-1);

		if (dst_idx+n <= BITS_PER_LONG) {
			// Single destination word
			if (last)
				first &= last;
			if (shift > 0) {
				// Single source word
				*dst = comp(~*src >> right, *dst, first);
			} else if (src_idx+n <= BITS_PER_LONG) {
				// Single source word
				*dst = comp(~*src << left, *dst, first);
			} else {
				// 2 source words
				d0 = ~*src++;
				d1 = ~*src;
				*dst = comp(d0 << left | d1 >> right, *dst,
					    first);
			}
		} else {
			// Multiple destination words
			d0 = ~*src++;
			// Leading bits
			if (shift > 0) {
				// Single source word
				*dst = comp(d0 >> right, *dst, first);
				dst++;
				n -= BITS_PER_LONG-dst_idx;
			} else {
				// 2 source words
				d1 = ~*src++;
				*dst = comp(d0 << left | d1 >> right, *dst,
					    first);
				d0 = d1;
				dst++;
				n -= BITS_PER_LONG-dst_idx;
			}

			// Main chunk
			m = n % BITS_PER_LONG;
			n /= BITS_PER_LONG;
			while (n >= 4) {
				d1 = ~*src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				d1 = ~*src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				d1 = ~*src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				d1 = ~*src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
				n -= 4;
			}
			while (n--) {
				d1 = ~*src++;
				*dst++ = d0 << left | d1 >> right;
				d0 = d1;
			}

			// Trailing bits
			if (last) {
				if (m <= right) {
					// Single source word
					*dst = comp(d0 << left, *dst, last);
				} else {
					// 2 source words
					d1 = ~*src;
					*dst = comp(d0 << left | d1 >> right,
						    *dst, last);
				}
			}
		}
	}
}


    /*
     *  Unaligned 32-bit pattern fill using 32/64-bit memory accesses
     */

static void bitfill32(unsigned long *dst, int dst_idx, u32 pat, u32 n)
{
	unsigned long val = pat;
	unsigned long first, last;

	if (!n)
		return;

#if BITS_PER_LONG == 64
	val |= val << 32;
#endif

	first = ~0UL >> dst_idx;
	last = ~(~0UL >> ((dst_idx+n) % BITS_PER_LONG));

	if (dst_idx+n <= BITS_PER_LONG) {
		// Single word
		if (last)
			first &= last;
		*dst = comp(val, *dst, first);
	} else {
		// Multiple destination words
		// Leading bits
		if (first) {
			*dst = comp(val, *dst, first);
			dst++;
			n -= BITS_PER_LONG-dst_idx;
		}

		// Main chunk
		n /= BITS_PER_LONG;
		while (n >= 8) {
			*dst++ = val;
			*dst++ = val;
			*dst++ = val;
			*dst++ = val;
			*dst++ = val;
			*dst++ = val;
			*dst++ = val;
			*dst++ = val;
			n -= 8;
		}
		while (n--)
			*dst++ = val;

		// Trailing bits
		if (last)
			*dst = comp(val, *dst, last);
	}
}


    /*
     *  Unaligned 32-bit pattern xor using 32/64-bit memory accesses
     */

static void bitxor32(unsigned long *dst, int dst_idx, u32 pat, u32 n)
{
	unsigned long val = pat;
	unsigned long first, last;

	if (!n)
		return;

#if BITS_PER_LONG == 64
	val |= val << 32;
#endif

	first = ~0UL >> dst_idx;
	last = ~(~0UL >> ((dst_idx+n) % BITS_PER_LONG));

	if (dst_idx+n <= BITS_PER_LONG) {
		// Single word
		if (last)
			first &= last;
		*dst = xor(val, *dst, first);
	} else {
		// Multiple destination words
		// Leading bits
		if (first) {
			*dst = xor(val, *dst, first);
			dst++;
			n -= BITS_PER_LONG-dst_idx;
		}

		// Main chunk
		n /= BITS_PER_LONG;
		while (n >= 4) {
			*dst++ ^= val;
			*dst++ ^= val;
			*dst++ ^= val;
			*dst++ ^= val;
			n -= 4;
		}
		while (n--)
			*dst++ ^= val;

		// Trailing bits
		if (last)
			*dst = xor(val, *dst, last);
	}
}

static inline void fill_one_line(int bpp, unsigned long next_plane,
				 unsigned long *dst, int dst_idx, u32 n,
				 u32 color)
{
	while (1) {
		dst += dst_idx >> SHIFT_PER_LONG;
		dst_idx &= (BITS_PER_LONG-1);
		bitfill32(dst, dst_idx, color & 1 ? ~0 : 0, n);
		if (!--bpp)
			break;
		color >>= 1;
		dst_idx += next_plane*8;
	}
}

static inline void xor_one_line(int bpp, unsigned long next_plane,
				unsigned long *dst, int dst_idx, u32 n,
				u32 color)
{
	while (color) {
		dst += dst_idx >> SHIFT_PER_LONG;
		dst_idx &= (BITS_PER_LONG-1);
		bitxor32(dst, dst_idx, color & 1 ? ~0 : 0, n);
		if (!--bpp)
			break;
		color >>= 1;
		dst_idx += next_plane*8;
	}
}


static void amifb_fillrect(struct fb_info *info,
			   const struct fb_fillrect *rect)
{
	struct amifb_par *par = (struct amifb_par *)info->par;
	int dst_idx, x2, y2;
	unsigned long *dst;
	u32 width, height;

	if (!rect->width || !rect->height)
		return;

	/*
	 * We could use hardware clipping but on many cards you get around
	 * hardware clipping by writing to framebuffer directly.
	 * */
	x2 = rect->dx + rect->width;
	y2 = rect->dy + rect->height;
	x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual;
	y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual;
	width = x2 - rect->dx;
	height = y2 - rect->dy;

	dst = (unsigned long *)
		((unsigned long)info->screen_base & ~(BYTES_PER_LONG-1));
	dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG-1))*8;
	dst_idx += rect->dy*par->next_line*8+rect->dx;
	while (height--) {
		switch (rect->rop) {
		    case ROP_COPY:
			fill_one_line(info->var.bits_per_pixel,
				      par->next_plane, dst, dst_idx, width,
				      rect->color);
			break;

		    case ROP_XOR:
			xor_one_line(info->var.bits_per_pixel, par->next_plane,
				     dst, dst_idx, width, rect->color);
			break;
		}
		dst_idx += par->next_line*8;
	}
}

static inline void copy_one_line(int bpp, unsigned long next_plane,
				 unsigned long *dst, int dst_idx,
				 unsigned long *src, int src_idx, u32 n)
{
	while (1) {
		dst += dst_idx >> SHIFT_PER_LONG;
		dst_idx &= (BITS_PER_LONG-1);
		src += src_idx >> SHIFT_PER_LONG;
		src_idx &= (BITS_PER_LONG-1);
		bitcpy(dst, dst_idx, src, src_idx, n);
		if (!--bpp)
			break;
		dst_idx += next_plane*8;
		src_idx += next_plane*8;
	}
}

static inline void copy_one_line_rev(int bpp, unsigned long next_plane,
				     unsigned long *dst, int dst_idx,
				     unsigned long *src, int src_idx, u32 n)
{
	while (1) {
		dst += dst_idx >> SHIFT_PER_LONG;
		dst_idx &= (BITS_PER_LONG-1);
		src += src_idx >> SHIFT_PER_LONG;
		src_idx &= (BITS_PER_LONG-1);
		bitcpy_rev(dst, dst_idx, src, src_idx, n);
		if (!--bpp)
			break;
		dst_idx += next_plane*8;
		src_idx += next_plane*8;
	}
}


static void amifb_copyarea(struct fb_info *info,
			   const struct fb_copyarea *area)
{
	struct amifb_par *par = (struct amifb_par *)info->par;
	int x2, y2;
	u32 dx, dy, sx, sy, width, height;
	unsigned long *dst, *src;
	int dst_idx, src_idx;
	int rev_copy = 0;

	/* clip the destination */
	x2 = area->dx + area->width;
	y2 = area->dy + area->height;
	dx = area->dx > 0 ? area->dx : 0;
	dy = area->dy > 0 ? area->dy : 0;
	x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual;
	y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual;
	width = x2 - dx;
	height = y2 - dy;

	/* update sx,sy */
	sx = area->sx + (dx - area->dx);
	sy = area->sy + (dy - area->dy);

	/* the source must be completely inside the virtual screen */
	if (sx < 0 || sy < 0 || (sx + width) > info->var.xres_virtual ||
	    (sy + height) > info->var.yres_virtual)
		return;

	if (dy > sy || (dy == sy && dx > sx)) {
		dy += height;
		sy += height;
		rev_copy = 1;
	}
	dst = (unsigned long *)
		((unsigned long)info->screen_base & ~(BYTES_PER_LONG-1));
	src = dst;
	dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG-1))*8;
	src_idx = dst_idx;
	dst_idx += dy*par->next_line*8+dx;
	src_idx += sy*par->next_line*8+sx;
	if (rev_copy) {
		while (height--) {
			dst_idx -= par->next_line*8;
			src_idx -= par->next_line*8;
			copy_one_line_rev(info->var.bits_per_pixel,
					  par->next_plane, dst, dst_idx, src,
					  src_idx, width);
		}
	} else {
		while (height--) {
			copy_one_line(info->var.bits_per_pixel,
				      par->next_plane, dst, dst_idx, src,
				      src_idx, width);
			dst_idx += par->next_line*8;
			src_idx += par->next_line*8;
		}
	}
}


static inline void expand_one_line(int bpp, unsigned long next_plane,
				   unsigned long *dst, int dst_idx, u32 n,
				   const u8 *data, u32 bgcolor, u32 fgcolor)
{
    const unsigned long *src;
    int src_idx;

    while (1) {
	dst += dst_idx >> SHIFT_PER_LONG;
	dst_idx &= (BITS_PER_LONG-1);
	if ((bgcolor ^ fgcolor) & 1) {
	    src = (unsigned long *)((unsigned long)data & ~(BYTES_PER_LONG-1));
	    src_idx = ((unsigned long)data & (BYTES_PER_LONG-1))*8;
	    if (fgcolor & 1)
		bitcpy(dst, dst_idx, src, src_idx, n);
	    else
		bitcpy_not(dst, dst_idx, src, src_idx, n);
	    /* set or clear */
	} else
	    bitfill32(dst, dst_idx, fgcolor & 1 ? ~0 : 0, n);
	if (!--bpp)
	    break;
	bgcolor >>= 1;
	fgcolor >>= 1;
	dst_idx += next_plane*8;
    }
}


static void amifb_imageblit(struct fb_info *info, const struct fb_image *image)
{
	struct amifb_par *par = (struct amifb_par *)info->par;
	int x2, y2;
	unsigned long *dst;
	int dst_idx;
	const char *src;
	u32 dx, dy, width, height, pitch;

	/*
	 * We could use hardware clipping but on many cards you get around
	 * hardware clipping by writing to framebuffer directly like we are
	 * doing here.
	 */
	x2 = image->dx + image->width;
	y2 = image->dy + image->height;
	dx = image->dx;
	dy = image->dy;
	x2 = x2 < info->var.xres_virtual ? x2 : info->var.xres_virtual;
	y2 = y2 < info->var.yres_virtual ? y2 : info->var.yres_virtual;
	width  = x2 - dx;
	height = y2 - dy;

	if (image->depth == 1) {
		dst = (unsigned long *)
			((unsigned long)info->screen_base & ~(BYTES_PER_LONG-1));
		dst_idx = ((unsigned long)info->screen_base & (BYTES_PER_LONG-1))*8;
		dst_idx += dy*par->next_line*8+dx;
		src = image->data;
		pitch = (image->width+7)/8;
		while (height--) {
			expand_one_line(info->var.bits_per_pixel,
					par->next_plane, dst, dst_idx, width,
					src, image->bg_color,
					image->fg_color);
			dst_idx += par->next_line*8;
			src += pitch;
		}
	} else {
		c2p(info->screen_base, image->data, dx, dy, width, height,
		    par->next_line, par->next_plane, image->width,
		    info->var.bits_per_pixel);
	}
}


	/*
	 * Amiga Frame Buffer Specific ioctls
	 */

static int amifb_ioctl(struct fb_info *info,
		       unsigned int cmd, unsigned long arg)
{
	union {
		struct fb_fix_cursorinfo fix;
		struct fb_var_cursorinfo var;
		struct fb_cursorstate state;
	} crsr;
	void __user *argp = (void __user *)arg;
	int i;

	switch (cmd) {
		case FBIOGET_FCURSORINFO:
			i = ami_get_fix_cursorinfo(&crsr.fix);
			if (i)
				return i;
			return copy_to_user(argp, &crsr.fix,
					    sizeof(crsr.fix)) ? -EFAULT : 0;

		case FBIOGET_VCURSORINFO:
			i = ami_get_var_cursorinfo(&crsr.var,
				((struct fb_var_cursorinfo __user *)arg)->data);
			if (i)
				return i;
			return copy_to_user(argp, &crsr.var,
					    sizeof(crsr.var)) ? -EFAULT : 0;

		case FBIOPUT_VCURSORINFO:
			if (copy_from_user(&crsr.var, argp, sizeof(crsr.var)))
				return -EFAULT;
			return ami_set_var_cursorinfo(&crsr.var,
				((struct fb_var_cursorinfo __user *)arg)->data);

		case FBIOGET_CURSORSTATE:
			i = ami_get_cursorstate(&crsr.state);
			if (i)
				return i;
			return copy_to_user(argp, &crsr.state,
					    sizeof(crsr.state)) ? -EFAULT : 0;

		case FBIOPUT_CURSORSTATE:
			if (copy_from_user(&crsr.state, argp,
					   sizeof(crsr.state)))
				return -EFAULT;
			return ami_set_cursorstate(&crsr.state);
	}
	return -EINVAL;
}


	/*
	 * Allocate, Clear and Align a Block of Chip Memory
	 */

static u_long unaligned_chipptr = 0;

static inline u_long __init chipalloc(u_long size)
{
	size += PAGE_SIZE-1;
	if (!(unaligned_chipptr = (u_long)amiga_chip_alloc(size,
							   "amifb [RAM]")))
		panic("No Chip RAM for frame buffer");
	memset((void *)unaligned_chipptr, 0, size);
	return PAGE_ALIGN(unaligned_chipptr);
}

static inline void chipfree(void)
{
	if (unaligned_chipptr)
		amiga_chip_free((void *)unaligned_chipptr);
}


	/*
	 * Initialisation
	 */

int __init amifb_init(void)
{
	int tag, i, err = 0;
	u_long chipptr;
	u_int defmode;

#ifndef MODULE
	char *option = NULL;

	if (fb_get_options("amifb", &option)) {
		amifb_video_off();
		return -ENODEV;
	}
	amifb_setup(option);
#endif
	if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(AMI_VIDEO))
		return -ENXIO;

	/*
	 * We request all registers starting from bplpt[0]
	 */
	if (!request_mem_region(CUSTOM_PHYSADDR+0xe0, 0x120,
				"amifb [Denise/Lisa]"))
		return -EBUSY;

	custom.dmacon = DMAF_ALL | DMAF_MASTER;

	switch (amiga_chipset) {
#ifdef CONFIG_FB_AMIGA_OCS
		case CS_OCS:
			strcat(fb_info.fix.id, "OCS");
default_chipset:
			chipset = TAG_OCS;
			maxdepth[TAG_SHRES] = 0;	/* OCS means no SHRES */
			maxdepth[TAG_HIRES] = 4;
			maxdepth[TAG_LORES] = 6;
			maxfmode = TAG_FMODE_1;
			defmode = amiga_vblank == 50 ? DEFMODE_PAL
						     : DEFMODE_NTSC;
			fb_info.fix.smem_len = VIDEOMEMSIZE_OCS;
			break;
#endif /* CONFIG_FB_AMIGA_OCS */

#ifdef CONFIG_FB_AMIGA_ECS
		case CS_ECS:
			strcat(fb_info.fix.id, "ECS");
			chipset = TAG_ECS;
			maxdepth[TAG_SHRES] = 2;
			maxdepth[TAG_HIRES] = 4;
			maxdepth[TAG_LORES] = 6;
			maxfmode = TAG_FMODE_1;
			if (AMIGAHW_PRESENT(AMBER_FF))
			    defmode = amiga_vblank == 50 ? DEFMODE_AMBER_PAL
							 : DEFMODE_AMBER_NTSC;
			else
			    defmode = amiga_vblank == 50 ? DEFMODE_PAL
							 : DEFMODE_NTSC;
			if (amiga_chip_avail()-CHIPRAM_SAFETY_LIMIT >
			    VIDEOMEMSIZE_ECS_1M)
				fb_info.fix.smem_len = VIDEOMEMSIZE_ECS_2M;
			else
				fb_info.fix.smem_len = VIDEOMEMSIZE_ECS_1M;
			break;
#endif /* CONFIG_FB_AMIGA_ECS */

#ifdef CONFIG_FB_AMIGA_AGA
		case CS_AGA:
			strcat(fb_info.fix.id, "AGA");
			chipset = TAG_AGA;
			maxdepth[TAG_SHRES] = 8;
			maxdepth[TAG_HIRES] = 8;
			maxdepth[TAG_LORES] = 8;
			maxfmode = TAG_FMODE_4;
			defmode = DEFMODE_AGA;
			if (amiga_chip_avail()-CHIPRAM_SAFETY_LIMIT >
			    VIDEOMEMSIZE_AGA_1M)
				fb_info.fix.smem_len = VIDEOMEMSIZE_AGA_2M;
			else
				fb_info.fix.smem_len = VIDEOMEMSIZE_AGA_1M;
			break;
#endif /* CONFIG_FB_AMIGA_AGA */

		default:
#ifdef CONFIG_FB_AMIGA_OCS
			printk("Unknown graphics chipset, defaulting to OCS\n");
			strcat(fb_info.fix.id, "Unknown");
			goto default_chipset;
#else /* CONFIG_FB_AMIGA_OCS */
			err = -ENXIO;
			goto amifb_error;
#endif /* CONFIG_FB_AMIGA_OCS */
			break;
	}

	/*
	 * Calculate the Pixel Clock Values for this Machine
	 */

	{
	u_long tmp = DIVUL(200000000000ULL, amiga_eclock);

	pixclock[TAG_SHRES] = (tmp + 4) / 8;	/* SHRES:  35 ns / 28 MHz */
	pixclock[TAG_HIRES] = (tmp + 2) / 4;	/* HIRES:  70 ns / 14 MHz */
	pixclock[TAG_LORES] = (tmp + 1) / 2;	/* LORES: 140 ns /  7 MHz */
	}

	/*
	 * Replace the Tag Values with the Real Pixel Clock Values
	 */

	for (i = 0; i < NUM_TOTAL_MODES; i++) {
		struct fb_videomode *mode = &ami_modedb[i];
		tag = mode->pixclock;
		if (tag == TAG_SHRES || tag == TAG_HIRES || tag == TAG_LORES) {
			mode->pixclock = pixclock[tag];
		}
	}

	/*
	 *  These monitor specs are for a typical Amiga monitor (e.g. A1960)
	 */
	if (fb_info.monspecs.hfmin == 0) {
	    fb_info.monspecs.hfmin = 15000;
	    fb_info.monspecs.hfmax = 38000;
	    fb_info.monspecs.vfmin = 49;
	    fb_info.monspecs.vfmax = 90;
	}

	fb_info.fbops = &amifb_ops;
	fb_info.par = &currentpar;
	fb_info.flags = FBINFO_DEFAULT;

	if (!fb_find_mode(&fb_info.var, &fb_info, mode_option, ami_modedb,
			  NUM_TOTAL_MODES, &ami_modedb[defmode], 4)) {
		err = -EINVAL;
		goto amifb_error;
	}

	round_down_bpp = 0;
	chipptr = chipalloc(fb_info.fix.smem_len+
	                    SPRITEMEMSIZE+
	                    DUMMYSPRITEMEMSIZE+
	                    COPINITSIZE+
	                    4*COPLISTSIZE);

	assignchunk(videomemory, u_long, chipptr, fb_info.fix.smem_len);
	assignchunk(spritememory, u_long, chipptr, SPRITEMEMSIZE);
	assignchunk(dummysprite, u_short *, chipptr, DUMMYSPRITEMEMSIZE);
	assignchunk(copdisplay.init, copins *, chipptr, COPINITSIZE);
	assignchunk(copdisplay.list[0][0], copins *, chipptr, COPLISTSIZE);
	assignchunk(copdisplay.list[0][1], copins *, chipptr, COPLISTSIZE);
	assignchunk(copdisplay.list[1][0], copins *, chipptr, COPLISTSIZE);
	assignchunk(copdisplay.list[1][1], copins *, chipptr, COPLISTSIZE);

	/*
	 * access the videomem with writethrough cache
	 */
	fb_info.fix.smem_start = (u_long)ZTWO_PADDR(videomemory);
	videomemory = (u_long)ioremap_writethrough(fb_info.fix.smem_start,
						   fb_info.fix.smem_len);
	if (!videomemory) {
		printk("amifb: WARNING! unable to map videomem cached writethrough\n");
		fb_info.screen_base = (char *)ZTWO_VADDR(fb_info.fix.smem_start);
	} else
		fb_info.screen_base = (char *)videomemory;

	memset(dummysprite, 0, DUMMYSPRITEMEMSIZE);

	/*
	 * Enable Display DMA
	 */

	custom.dmacon = DMAF_SETCLR | DMAF_MASTER | DMAF_RASTER | DMAF_COPPER |
	                DMAF_BLITTER | DMAF_SPRITE;

	/*
	 * Make sure the Copper has something to do
	 */

	ami_init_copper();

	if (request_irq(IRQ_AMIGA_COPPER, amifb_interrupt, 0,
	                "fb vertb handler", &currentpar)) {
		err = -EBUSY;
		goto amifb_error;
	}

	fb_alloc_cmap(&fb_info.cmap, 1<<fb_info.var.bits_per_pixel, 0);

	if (register_framebuffer(&fb_info) < 0) {
		err = -EINVAL;
		goto amifb_error;
	}

	printk("fb%d: %s frame buffer device, using %dK of video memory\n",
	       fb_info.node, fb_info.fix.id, fb_info.fix.smem_len>>10);

	return 0;

amifb_error:
	amifb_deinit();
	return err;
}

static void amifb_deinit(void)
{
	fb_dealloc_cmap(&fb_info.cmap);
	chipfree();
	if (videomemory)
		iounmap((void*)videomemory);
	release_mem_region(CUSTOM_PHYSADDR+0xe0, 0x120);
	custom.dmacon = DMAF_ALL | DMAF_MASTER;
}


	/*
	 * Blank the display.
	 */

static int amifb_blank(int blank, struct fb_info *info)
{
	do_blank = blank ? blank : -1;

	return 0;
}

	/*
	 * Flash the cursor (called by VBlank interrupt)
	 */

static int flash_cursor(void)
{
	static int cursorcount = 1;

	if (cursormode == FB_CURSOR_FLASH) {
		if (!--cursorcount) {
			cursorstate = -cursorstate;
			cursorcount = cursorrate;
			if (!is_blanked)
				return 1;
		}
	}
	return 0;
}

	/*
	 * VBlank Display Interrupt
	 */

static irqreturn_t amifb_interrupt(int irq, void *dev_id)
{
	if (do_vmode_pan || do_vmode_full)
		ami_update_display();

	if (do_vmode_full)
		ami_init_display();

	if (do_vmode_pan) {
		flash_cursor();
		ami_rebuild_copper();
		do_cursor = do_vmode_pan = 0;
	} else if (do_cursor) {
		flash_cursor();
		ami_set_sprite();
		do_cursor = 0;
	} else {
		if (flash_cursor())
			ami_set_sprite();
	}

	if (do_blank) {
		ami_do_blank();
		do_blank = 0;
	}

	if (do_vmode_full) {
		ami_reinit_copper();
		do_vmode_full = 0;
	}
	return IRQ_HANDLED;
}

/* --------------------------- Hardware routines --------------------------- */

	/*
	 * Get the video params out of `var'. If a value doesn't fit, round
	 * it up, if it's too big, return -EINVAL.
	 */

static int ami_decode_var(struct fb_var_screeninfo *var,
                          struct amifb_par *par)
{
	u_short clk_shift, line_shift;
	u_long maxfetchstop, fstrt, fsize, fconst, xres_n, yres_n;
	u_int htotal, vtotal;

	/*
	 * Find a matching Pixel Clock
	 */

	for (clk_shift = TAG_SHRES; clk_shift <= TAG_LORES; clk_shift++)
		if (var->pixclock <= pixclock[clk_shift])
			break;
	if (clk_shift > TAG_LORES) {
		DPRINTK("pixclock too high\n");
		return -EINVAL;
	}
	par->clk_shift = clk_shift;

	/*
	 * Check the Geometry Values
	 */

	if ((par->xres = var->xres) < 64)
		par->xres = 64;
	if ((par->yres = var->yres) < 64)
		par->yres = 64;
	if ((par->vxres = var->xres_virtual) < par->xres)
		par->vxres = par->xres;
	if ((par->vyres = var->yres_virtual) < par->yres)
		par->vyres = par->yres;

	par->bpp = var->bits_per_pixel;
	if (!var->nonstd) {
		if (par->bpp < 1)
			par->bpp = 1;
		if (par->bpp > maxdepth[clk_shift]) {
			if (round_down_bpp && maxdepth[clk_shift])
				par->bpp = maxdepth[clk_shift];
			else {
				DPRINTK("invalid bpp\n");
				return -EINVAL;
			}
		}
	} else if (var->nonstd == FB_NONSTD_HAM) {
		if (par->bpp < 6)
			par->bpp = 6;
		if (par->bpp != 6) {
			if (par->bpp < 8)
				par->bpp = 8;
			if (par->bpp != 8 || !IS_AGA) {
				DPRINTK("invalid bpp for ham mode\n");
				return -EINVAL;
			}
		}
	} else {
		DPRINTK("unknown nonstd mode\n");
		return -EINVAL;
	}

	/*
	 * FB_VMODE_SMOOTH_XPAN will be cleared, if one of the folloing
	 * checks failed and smooth scrolling is not possible
	 */

	par->vmode = var->vmode | FB_VMODE_SMOOTH_XPAN;
	switch (par->vmode & FB_VMODE_MASK) {
		case FB_VMODE_INTERLACED:
			line_shift = 0;
			break;
		case FB_VMODE_NONINTERLACED:
			line_shift = 1;
			break;
		case FB_VMODE_DOUBLE:
			if (!IS_AGA) {
				DPRINTK("double mode only possible with aga\n");
				return -EINVAL;
			}
			line_shift = 2;
			break;
		default:
			DPRINTK("unknown video mode\n");
			return -EINVAL;
			break;
	}
	par->line_shift = line_shift;

	/*
	 * Vertical and Horizontal Timings
	 */

	xres_n = par->xres<<clk_shift;
	yres_n = par->yres<<line_shift;
	par->htotal = down8((var->left_margin+par->xres+var->right_margin+var->hsync_len)<<clk_shift);
	par->vtotal = down2(((var->upper_margin+par->yres+var->lower_margin+var->vsync_len)<<line_shift)+1);

	if (IS_AGA)
		par->bplcon3 = sprpixmode[clk_shift];
	else
		par->bplcon3 = 0;
	if (var->sync & FB_SYNC_BROADCAST) {
		par->diwstop_h = par->htotal-((var->right_margin-var->hsync_len)<<clk_shift);
		if (IS_AGA)
			par->diwstop_h += mod4(var->hsync_len);
		else
			par->diwstop_h = down4(par->diwstop_h);

		par->diwstrt_h = par->diwstop_h - xres_n;
		par->diwstop_v = par->vtotal-((var->lower_margin-var->vsync_len)<<line_shift);
		par->diwstrt_v = par->diwstop_v - yres_n;
		if (par->diwstop_h >= par->htotal+8) {
			DPRINTK("invalid diwstop_h\n");
			return -EINVAL;
		}
		if (par->diwstop_v > par->vtotal) {
			DPRINTK("invalid diwstop_v\n");
			return -EINVAL;
		}

		if (!IS_OCS) {
			/* Initialize sync with some reasonable values for pwrsave */
			par->hsstrt = 160;
			par->hsstop = 320;
			par->vsstrt = 30;
			par->vsstop = 34;
		} else {
			par->hsstrt = 0;
			par->hsstop = 0;
			par->vsstrt = 0;
			par->vsstop = 0;
		}
		if (par->vtotal > (PAL_VTOTAL+NTSC_VTOTAL)/2) {
			/* PAL video mode */
			if (par->htotal != PAL_HTOTAL) {
				DPRINTK("htotal invalid for pal\n");
				return -EINVAL;
			}
			if (par->diwstrt_h < PAL_DIWSTRT_H) {
				DPRINTK("diwstrt_h too low for pal\n");
				return -EINVAL;
			}
			if (par->diwstrt_v < PAL_DIWSTRT_V) {
				DPRINTK("diwstrt_v too low for pal\n");
				return -EINVAL;
			}
			htotal = PAL_HTOTAL>>clk_shift;
			vtotal = PAL_VTOTAL>>1;
			if (!IS_OCS) {
				par->beamcon0 = BMC0_PAL;
				par->bplcon3 |= BPC3_BRDRBLNK;
			} else if (AMIGAHW_PRESENT(AGNUS_HR_PAL) ||
			           AMIGAHW_PRESENT(AGNUS_HR_NTSC)) {
				par->beamcon0 = BMC0_PAL;
				par->hsstop = 1;
			} else if (amiga_vblank != 50) {
				DPRINTK("pal not supported by this chipset\n");
				return -EINVAL;
			}
		} else {
			/* NTSC video mode
			 * In the AGA chipset seems to be hardware bug with BPC3_BRDRBLNK
			 * and NTSC activated, so than better let diwstop_h <= 1812
			 */
			if (par->htotal != NTSC_HTOTAL) {
				DPRINTK("htotal invalid for ntsc\n");
				return -EINVAL;
			}
			if (par->diwstrt_h < NTSC_DIWSTRT_H) {
				DPRINTK("diwstrt_h too low for ntsc\n");
				return -EINVAL;
			}
			if (par->diwstrt_v < NTSC_DIWSTRT_V) {
				DPRINTK("diwstrt_v too low for ntsc\n");
				return -EINVAL;
			}
			htotal = NTSC_HTOTAL>>clk_shift;
			vtotal = NTSC_VTOTAL>>1;
			if (!IS_OCS) {
				par->beamcon0 = 0;
				par->bplcon3 |= BPC3_BRDRBLNK;
			} else if (AMIGAHW_PRESENT(AGNUS_HR_PAL) ||
			           AMIGAHW_PRESENT(AGNUS_HR_NTSC)) {
				par->beamcon0 = 0;
				par->hsstop = 1;
			} else if (amiga_vblank != 60) {
				DPRINTK("ntsc not supported by this chipset\n");
				return -EINVAL;
			}
		}
		if (IS_OCS) {
			if (par->diwstrt_h >= 1024 || par->diwstop_h < 1024 ||
			    par->diwstrt_v >=  512 || par->diwstop_v <  256) {
				DPRINTK("invalid position for display on ocs\n");
				return -EINVAL;
			}
		}
	} else if (!IS_OCS) {
		/* Programmable video mode */
		par->hsstrt = var->right_margin<<clk_shift;
		par->hsstop = (var->right_margin+var->hsync_len)<<clk_shift;
		par->diwstop_h = par->htotal - mod8(par->hsstrt) + 8 - (1 << clk_shift);
		if (!IS_AGA)
			par->diwstop_h = down4(par->diwstop_h) - 16;
		par->diwstrt_h = par->diwstop_h - xres_n;
		par->hbstop = par->diwstrt_h + 4;
		par->hbstrt = par->diwstop_h + 4;
		if (par->hbstrt >= par->htotal + 8)
			par->hbstrt -= par->htotal;
		par->hcenter = par->hsstrt + (par->htotal >> 1);
		par->vsstrt = var->lower_margin<<line_shift;
		par->vsstop = (var->lower_margin+var->vsync_len)<<line_shift;
		par->diwstop_v = par->vtotal;
		if ((par->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED)
			par->diwstop_v -= 2;
		par->diwstrt_v = par->diwstop_v - yres_n;
		par->vbstop = par->diwstrt_v - 2;
		par->vbstrt = par->diwstop_v - 2;
		if (par->vtotal > 2048) {
			DPRINTK("vtotal too high\n");
			return -EINVAL;
		}
		if (par->htotal > 2048) {
			DPRINTK("htotal too high\n");
			return -EINVAL;
		}
		par->bplcon3 |= BPC3_EXTBLKEN;
		par->beamcon0 = BMC0_HARDDIS | BMC0_VARVBEN | BMC0_LOLDIS |
		                BMC0_VARVSYEN | BMC0_VARHSYEN | BMC0_VARBEAMEN |
		                BMC0_PAL | BMC0_VARCSYEN;
		if (var->sync & FB_SYNC_HOR_HIGH_ACT)
			par->beamcon0 |= BMC0_HSYTRUE;
		if (var->sync & FB_SYNC_VERT_HIGH_ACT)
			par->beamcon0 |= BMC0_VSYTRUE;
		if (var->sync & FB_SYNC_COMP_HIGH_ACT)
			par->beamcon0 |= BMC0_CSYTRUE;
		htotal = par->htotal>>clk_shift;
		vtotal = par->vtotal>>1;
	} else {
		DPRINTK("only broadcast modes possible for ocs\n");
		return -EINVAL;
	}

	/*
	 * Checking the DMA timing
	 */

	fconst = 16<<maxfmode<<clk_shift;

	/*
	 * smallest window start value without turn off other dma cycles
	 * than sprite1-7, unless you change min_fstrt
	 */


	fsize = ((maxfmode+clk_shift <= 1) ? fconst : 64);
	fstrt = downx(fconst, par->diwstrt_h-4) - fsize;
	if (fstrt < min_fstrt) {
		DPRINTK("fetch start too low\n");
		return -EINVAL;
	}

	/*
	 * smallest window start value where smooth scrolling is possible
	 */

	fstrt = downx(fconst, par->diwstrt_h-fconst+(1<<clk_shift)-4) - fsize;
	if (fstrt < min_fstrt)
		par->vmode &= ~FB_VMODE_SMOOTH_XPAN;

	maxfetchstop = down16(par->htotal - 80);

	fstrt = downx(fconst, par->diwstrt_h-4) - 64 - fconst;
	fsize = upx(fconst, xres_n + modx(fconst, downx(1<<clk_shift, par->diwstrt_h-4)));
	if (fstrt + fsize > maxfetchstop)
		par->vmode &= ~FB_VMODE_SMOOTH_XPAN;

	fsize = upx(fconst, xres_n);
	if (fstrt + fsize > maxfetchstop) {
		DPRINTK("fetch stop too high\n");
		return -EINVAL;
	}

	if (maxfmode + clk_shift <= 1) {
		fsize = up64(xres_n + fconst - 1);
		if (min_fstrt + fsize - 64 > maxfetchstop)
			par->vmode &= ~FB_VMODE_SMOOTH_XPAN;

		fsize = up64(xres_n);
		if (min_fstrt + fsize - 64 > maxfetchstop) {
			DPRINTK("fetch size too high\n");
			return -EINVAL;
		}

		fsize -= 64;
	} else
		fsize -= fconst;

	/*
	 * Check if there is enough time to update the bitplane pointers for ywrap
	 */

	if (par->htotal-fsize-64 < par->bpp*64)
		par->vmode &= ~FB_VMODE_YWRAP;

	/*
	 * Bitplane calculations and check the Memory Requirements
	 */

	if (amifb_ilbm) {
		par->next_plane = div8(upx(16<<maxfmode, par->vxres));
		par->next_line = par->bpp*par->next_plane;
		if (par->next_line * par->vyres > fb_info.fix.smem_len) {
			DPRINTK("too few video mem\n");
			return -EINVAL;
		}
	} else {
		par->next_line = div8(upx(16<<maxfmode, par->vxres));
		par->next_plane = par->vyres*par->next_line;
		if (par->next_plane * par->bpp > fb_info.fix.smem_len) {
			DPRINTK("too few video mem\n");
			return -EINVAL;
		}
	}

	/*
	 * Hardware Register Values
	 */

	par->bplcon0 = BPC0_COLOR | bplpixmode[clk_shift];
	if (!IS_OCS)
		par->bplcon0 |= BPC0_ECSENA;
	if (par->bpp == 8)
		par->bplcon0 |= BPC0_BPU3;
	else
		par->bplcon0 |= par->bpp<<12;
	if (var->nonstd == FB_NONSTD_HAM)
		par->bplcon0 |= BPC0_HAM;
	if (var->sync & FB_SYNC_EXT)
		par->bplcon0 |= BPC0_ERSY;

	if (IS_AGA)
		par->fmode = bplfetchmode[maxfmode];

	switch (par->vmode & FB_VMODE_MASK) {
		case FB_VMODE_INTERLACED:
			par->bplcon0 |= BPC0_LACE;
			break;
		case FB_VMODE_DOUBLE:
			if (IS_AGA)
				par->fmode |= FMODE_SSCAN2 | FMODE_BSCAN2;
			break;
	}

	if (!((par->vmode ^ var->vmode) & FB_VMODE_YWRAP)) {
		par->xoffset = var->xoffset;
		par->yoffset = var->yoffset;
		if (par->vmode & FB_VMODE_YWRAP) {
			if (par->xoffset || par->yoffset < 0 || par->yoffset >= par->vyres)
				par->xoffset = par->yoffset = 0;
		} else {
			if (par->xoffset < 0 || par->xoffset > upx(16<<maxfmode, par->vxres-par->xres) ||
			    par->yoffset < 0 || par->yoffset > par->vyres-par->yres)
				par->xoffset = par->yoffset = 0;
		}
	} else
		par->xoffset = par->yoffset = 0;

	par->crsr.crsr_x = par->crsr.crsr_y = 0;
	par->crsr.spot_x = par->crsr.spot_y = 0;
	par->crsr.height = par->crsr.width = 0;

	return 0;
}

	/*
	 * Fill the `var' structure based on the values in `par' and maybe
	 * other values read out of the hardware.
	 */

static int ami_encode_var(struct fb_var_screeninfo *var,
                          struct amifb_par *par)
{
	u_short clk_shift, line_shift;

	memset(var, 0, sizeof(struct fb_var_screeninfo));

	clk_shift = par->clk_shift;
	line_shift = par->line_shift;

	var->xres = par->xres;
	var->yres = par->yres;
	var->xres_virtual = par->vxres;
	var->yres_virtual = par->vyres;
	var->xoffset = par->xoffset;
	var->yoffset = par->yoffset;

	var->bits_per_pixel = par->bpp;
	var->grayscale = 0;

	var->red.offset = 0;
	var->red.msb_right = 0;
	var->red.length = par->bpp;
	if (par->bplcon0 & BPC0_HAM)
	    var->red.length -= 2;
	var->blue = var->green = var->red;
	var->transp.offset = 0;
	var->transp.length = 0;
	var->transp.msb_right = 0;

	if (par->bplcon0 & BPC0_HAM)
		var->nonstd = FB_NONSTD_HAM;
	else
		var->nonstd = 0;
	var->activate = 0;

	var->height = -1;
	var->width = -1;

	var->pixclock = pixclock[clk_shift];

	if (IS_AGA && par->fmode & FMODE_BSCAN2)
		var->vmode = FB_VMODE_DOUBLE;
	else if (par->bplcon0 & BPC0_LACE)
		var->vmode = FB_VMODE_INTERLACED;
	else
		var->vmode = FB_VMODE_NONINTERLACED;

	if (!IS_OCS && par->beamcon0 & BMC0_VARBEAMEN) {
		var->hsync_len = (par->hsstop-par->hsstrt)>>clk_shift;
		var->right_margin = par->hsstrt>>clk_shift;
		var->left_margin = (par->htotal>>clk_shift) - var->xres - var->right_margin - var->hsync_len;
		var->vsync_len = (par->vsstop-par->vsstrt)>>line_shift;
		var->lower_margin = par->vsstrt>>line_shift;
		var->upper_margin = (par->vtotal>>line_shift) - var->yres - var->lower_margin - var->vsync_len;
		var->sync = 0;
		if (par->beamcon0 & BMC0_HSYTRUE)
			var->sync |= FB_SYNC_HOR_HIGH_ACT;
		if (par->beamcon0 & BMC0_VSYTRUE)
			var->sync |= FB_SYNC_VERT_HIGH_ACT;
		if (par->beamcon0 & BMC0_CSYTRUE)
			var->sync |= FB_SYNC_COMP_HIGH_ACT;
	} else {
		var->sync = FB_SYNC_BROADCAST;
		var->hsync_len = (152>>clk_shift) + mod4(par->diwstop_h);
		var->right_margin = ((par->htotal - down4(par->diwstop_h))>>clk_shift) + var->hsync_len;
		var->left_margin = (par->htotal>>clk_shift) - var->xres - var->right_margin - var->hsync_len;
		var->vsync_len = 4>>line_shift;
		var->lower_margin = ((par->vtotal - par->diwstop_v)>>line_shift) + var->vsync_len;
		var->upper_margin = (((par->vtotal - 2)>>line_shift) + 1) - var->yres -
		                    var->lower_margin - var->vsync_len;
	}

	if (par->bplcon0 & BPC0_ERSY)
		var->sync |= FB_SYNC_EXT;
	if (par->vmode & FB_VMODE_YWRAP)
		var->vmode |= FB_VMODE_YWRAP;

	return 0;
}


	/*
	 * Pan or Wrap the Display
	 *
	 * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
	 * in `var'.
	 */

static void ami_pan_var(struct fb_var_screeninfo *var)
{
	struct amifb_par *par = &currentpar;

	par->xoffset = var->xoffset;
	par->yoffset = var->yoffset;
	if (var->vmode & FB_VMODE_YWRAP)
		par->vmode |= FB_VMODE_YWRAP;
	else
		par->vmode &= ~FB_VMODE_YWRAP;

	do_vmode_pan = 0;
	ami_update_par();
	do_vmode_pan = 1;
}

	/*
	 * Update hardware
	 */

static int ami_update_par(void)
{
	struct amifb_par *par = &currentpar;
	short clk_shift, vshift, fstrt, fsize, fstop, fconst,  shift, move, mod;

	clk_shift = par->clk_shift;

	if (!(par->vmode & FB_VMODE_SMOOTH_XPAN))
		par->xoffset = upx(16<<maxfmode, par->xoffset);

	fconst = 16<<maxfmode<<clk_shift;
	vshift = modx(16<<maxfmode, par->xoffset);
	fstrt = par->diwstrt_h - (vshift<<clk_shift) - 4;
	fsize = (par->xres+vshift)<<clk_shift;
	shift = modx(fconst, fstrt);
	move = downx(2<<maxfmode, div8(par->xoffset));
	if (maxfmode + clk_shift > 1) {
		fstrt = downx(fconst, fstrt) - 64;
		fsize = upx(fconst, fsize);
		fstop = fstrt + fsize - fconst;
	} else {
		mod = fstrt = downx(fconst, fstrt) - fconst;
		fstop = fstrt + upx(fconst, fsize) - 64;
		fsize = up64(fsize);
		fstrt = fstop - fsize + 64;
		if (fstrt < min_fstrt) {
			fstop += min_fstrt - fstrt;
			fstrt = min_fstrt;
		}
		move = move - div8((mod-fstrt)>>clk_shift);
	}
	mod = par->next_line - div8(fsize>>clk_shift);
	par->ddfstrt = fstrt;
	par->ddfstop = fstop;
	par->bplcon1 = hscroll2hw(shift);
	par->bpl2mod = mod;
	if (par->bplcon0 & BPC0_LACE)
		par->bpl2mod += par->next_line;
	if (IS_AGA && (par->fmode & FMODE_BSCAN2))
		par->bpl1mod = -div8(fsize>>clk_shift);
	else
		par->bpl1mod = par->bpl2mod;

	if (par->yoffset) {
		par->bplpt0 = fb_info.fix.smem_start + par->next_line*par->yoffset + move;
		if (par->vmode & FB_VMODE_YWRAP) {
			if (par->yoffset > par->vyres-par->yres) {
				par->bplpt0wrap = fb_info.fix.smem_start + move;
				if (par->bplcon0 & BPC0_LACE && mod2(par->diwstrt_v+par->vyres-par->yoffset))
					par->bplpt0wrap += par->next_line;
			}
		}
	} else
		par->bplpt0 = fb_info.fix.smem_start + move;

	if (par->bplcon0 & BPC0_LACE && mod2(par->diwstrt_v))
		par->bplpt0 += par->next_line;

	return 0;
}


	/*
	 * Set a single color register. The values supplied are already
	 * rounded down to the hardware's capabilities (according to the
	 * entries in the var structure). Return != 0 for invalid regno.
	 */

static int amifb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
                           u_int transp, struct fb_info *info)
{
	if (IS_AGA) {
		if (regno > 255)
			return 1;
	} else if (currentpar.bplcon0 & BPC0_SHRES) {
		if (regno > 3)
			return 1;
	} else {
		if (regno > 31)
			return 1;
	}
	red >>= 8;
	green >>= 8;
	blue >>= 8;
	if (!regno) {
		red0 = red;
		green0 = green;
		blue0 = blue;
	}

	/*
	 * Update the corresponding Hardware Color Register, unless it's Color
	 * Register 0 and the screen is blanked.
	 *
	 * VBlank is switched off to protect bplcon3 or ecs_palette[] from
	 * being changed by ami_do_blank() during the VBlank.
	 */

	if (regno || !is_blanked) {
#if defined(CONFIG_FB_AMIGA_AGA)
		if (IS_AGA) {
			u_short bplcon3 = currentpar.bplcon3;
			VBlankOff();
			custom.bplcon3 = bplcon3 | (regno<<8 & 0xe000);
			custom.color[regno&31] = rgb2hw8_high(red, green, blue);
			custom.bplcon3 = bplcon3 | (regno<<8 & 0xe000) | BPC3_LOCT;
			custom.color[regno&31] = rgb2hw8_low(red, green, blue);
			custom.bplcon3 = bplcon3;
			VBlankOn();
		} else
#endif
#if defined(CONFIG_FB_AMIGA_ECS)
		if (currentpar.bplcon0 & BPC0_SHRES) {
			u_short color, mask;
			int i;

			mask = 0x3333;
			color = rgb2hw2(red, green, blue);
			VBlankOff();
			for (i = regno+12; i >= (int)regno; i -= 4)
				custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color;
			mask <<=2; color >>= 2;
			regno = down16(regno)+mul4(mod4(regno));
			for (i = regno+3; i >= (int)regno; i--)
				custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color;
			VBlankOn();
		} else
#endif
			custom.color[regno] = rgb2hw4(red, green, blue);
	}
	return 0;
}

static void ami_update_display(void)
{
	struct amifb_par *par = &currentpar;

	custom.bplcon1 = par->bplcon1;
	custom.bpl1mod = par->bpl1mod;
	custom.bpl2mod = par->bpl2mod;
	custom.ddfstrt = ddfstrt2hw(par->ddfstrt);
	custom.ddfstop = ddfstop2hw(par->ddfstop);
}

	/*
	 * Change the video mode (called by VBlank interrupt)
	 */

static void ami_init_display(void)
{
	struct amifb_par *par = &currentpar;
	int i;

	custom.bplcon0 = par->bplcon0 & ~BPC0_LACE;
	custom.bplcon2 = (IS_OCS ? 0 : BPC2_KILLEHB) | BPC2_PF2P2 | BPC2_PF1P2;
	if (!IS_OCS) {
		custom.bplcon3 = par->bplcon3;
		if (IS_AGA)
			custom.bplcon4 = BPC4_ESPRM4 | BPC4_OSPRM4;
		if (par->beamcon0 & BMC0_VARBEAMEN) {
			custom.htotal = htotal2hw(par->htotal);
			custom.hbstrt = hbstrt2hw(par->hbstrt);
			custom.hbstop = hbstop2hw(par->hbstop);
			custom.hsstrt = hsstrt2hw(par->hsstrt);
			custom.hsstop = hsstop2hw(par->hsstop);
			custom.hcenter = hcenter2hw(par->hcenter);
			custom.vtotal = vtotal2hw(par->vtotal);
			custom.vbstrt = vbstrt2hw(par->vbstrt);
			custom.vbstop = vbstop2hw(par->vbstop);
			custom.vsstrt = vsstrt2hw(par->vsstrt);
			custom.vsstop = vsstop2hw(par->vsstop);
		}
	}
	if (!IS_OCS || par->hsstop)
		custom.beamcon0 = par->beamcon0;
	if (IS_AGA)
		custom.fmode = par->fmode;

	/*
	 * The minimum period for audio depends on htotal
	 */

	amiga_audio_min_period = div16(par->htotal);

	is_lace = par->bplcon0 & BPC0_LACE ? 1 : 0;
#if 1
	if (is_lace) {
		i = custom.vposr >> 15;
	} else {
		custom.vposw = custom.vposr | 0x8000;
		i = 1;
	}
#else
	i = 1;
	custom.vposw = custom.vposr | 0x8000;
#endif
	custom.cop2lc = (u_short *)ZTWO_PADDR(copdisplay.list[currentcop][i]);
}

	/*
	 * (Un)Blank the screen (called by VBlank interrupt)
	 */

static void ami_do_blank(void)
{
	struct amifb_par *par = &currentpar;
#if defined(CONFIG_FB_AMIGA_AGA)
	u_short bplcon3 = par->bplcon3;
#endif
	u_char red, green, blue;

	if (do_blank > 0) {
		custom.dmacon = DMAF_RASTER | DMAF_SPRITE;
		red = green = blue = 0;
		if (!IS_OCS && do_blank > 1) {
			switch (do_blank) {
				case FB_BLANK_VSYNC_SUSPEND:
					custom.hsstrt = hsstrt2hw(par->hsstrt);
					custom.hsstop = hsstop2hw(par->hsstop);
					custom.vsstrt = vsstrt2hw(par->vtotal+4);
					custom.vsstop = vsstop2hw(par->vtotal+4);
					break;
				case FB_BLANK_HSYNC_SUSPEND:
					custom.hsstrt = hsstrt2hw(par->htotal+16);
					custom.hsstop = hsstop2hw(par->htotal+16);
					custom.vsstrt = vsstrt2hw(par->vsstrt);
					custom.vsstop = vsstrt2hw(par->vsstop);
					break;
				case FB_BLANK_POWERDOWN:
					custom.hsstrt = hsstrt2hw(par->htotal+16);
					custom.hsstop = hsstop2hw(par->htotal+16);
					custom.vsstrt = vsstrt2hw(par->vtotal+4);
					custom.vsstop = vsstop2hw(par->vtotal+4);
					break;
			}
			if (!(par->beamcon0 & BMC0_VARBEAMEN)) {
				custom.htotal = htotal2hw(par->htotal);
				custom.vtotal = vtotal2hw(par->vtotal);
				custom.beamcon0 = BMC0_HARDDIS | BMC0_VARBEAMEN |
				                  BMC0_VARVSYEN | BMC0_VARHSYEN | BMC0_VARCSYEN;
			}
		}
	} else {
		custom.dmacon = DMAF_SETCLR | DMAF_RASTER | DMAF_SPRITE;
		red = red0;
		green = green0;
		blue = blue0;
		if (!IS_OCS) {
			custom.hsstrt = hsstrt2hw(par->hsstrt);
			custom.hsstop = hsstop2hw(par->hsstop);
			custom.vsstrt = vsstrt2hw(par->vsstrt);
			custom.vsstop = vsstop2hw(par->vsstop);
			custom.beamcon0 = par->beamcon0;
		}
	}
#if defined(CONFIG_FB_AMIGA_AGA)
	if (IS_AGA) {
		custom.bplcon3 = bplcon3;
		custom.color[0] = rgb2hw8_high(red, green, blue);
		custom.bplcon3 = bplcon3 | BPC3_LOCT;
		custom.color[0] = rgb2hw8_low(red, green, blue);
		custom.bplcon3 = bplcon3;
	} else
#endif
#if defined(CONFIG_FB_AMIGA_ECS)
	if (par->bplcon0 & BPC0_SHRES) {
		u_short color, mask;
		int i;

		mask = 0x3333;
		color = rgb2hw2(red, green, blue);
		for (i = 12; i >= 0; i -= 4)
			custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color;
		mask <<=2; color >>= 2;
		for (i = 3; i >= 0; i--)
			custom.color[i] = ecs_palette[i] = (ecs_palette[i] & mask) | color;
	} else
#endif
		custom.color[0] = rgb2hw4(red, green, blue);
	is_blanked = do_blank > 0 ? do_blank : 0;
}

static int ami_get_fix_cursorinfo(struct fb_fix_cursorinfo *fix)
{
	struct amifb_par *par = &currentpar;

	fix->crsr_width = fix->crsr_xsize = par->crsr.width;
	fix->crsr_height = fix->crsr_ysize = par->crsr.height;
	fix->crsr_color1 = 17;
	fix->crsr_color2 = 18;
	return 0;
}

static int ami_get_var_cursorinfo(struct fb_var_cursorinfo *var, u_char __user *data)
{
	struct amifb_par *par = &currentpar;
	register u_short *lspr, *sspr;
#ifdef __mc68000__
	register u_long datawords asm ("d2");
#else
	register u_long datawords;
#endif
	register short delta;
	register u_char color;
	short height, width, bits, words;
	int size, alloc;

	size = par->crsr.height*par->crsr.width;
	alloc = var->height*var->width;
	var->height = par->crsr.height;
	var->width = par->crsr.width;
	var->xspot = par->crsr.spot_x;
	var->yspot = par->crsr.spot_y;
	if (size > var->height*var->width)
		return -ENAMETOOLONG;
	if (!access_ok(VERIFY_WRITE, data, size))
		return -EFAULT;
	delta = 1<<par->crsr.fmode;
	lspr = lofsprite + (delta<<1);
	if (par->bplcon0 & BPC0_LACE)
		sspr = shfsprite + (delta<<1);
	else
		sspr = NULL;
	for (height = (short)var->height-1; height >= 0; height--) {
		bits = 0; words = delta; datawords = 0;
		for (width = (short)var->width-1; width >= 0; width--) {
			if (bits == 0) {
				bits = 16; --words;
#ifdef __mc68000__
				asm volatile ("movew %1@(%3:w:2),%0 ; swap %0 ; movew %1@+,%0"
					: "=d" (datawords), "=a" (lspr) : "1" (lspr), "d" (delta));
#else
				datawords = (*(lspr+delta) << 16) | (*lspr++);
#endif
			}
			--bits;
#ifdef __mc68000__
			asm volatile (
				"clrb %0 ; swap %1 ; lslw #1,%1 ; roxlb #1,%0 ; "
				"swap %1 ; lslw #1,%1 ; roxlb #1,%0"
				: "=d" (color), "=d" (datawords) : "1" (datawords));
#else
			color = (((datawords >> 30) & 2)
				 | ((datawords >> 15) & 1));
			datawords <<= 1;
#endif
			put_user(color, data++);
		}
		if (bits > 0) {
			--words; ++lspr;
		}
		while (--words >= 0)
			++lspr;
#ifdef __mc68000__
		asm volatile ("lea %0@(%4:w:2),%0 ; tstl %1 ; jeq 1f ; exg %0,%1\n1:"
			: "=a" (lspr), "=a" (sspr) : "0" (lspr), "1" (sspr), "d" (delta));
#else
		lspr += delta;
		if (sspr) {
			u_short *tmp = lspr;
			lspr = sspr;
			sspr = tmp;
		}
#endif
	}
	return 0;
}

static int ami_set_var_cursorinfo(struct fb_var_cursorinfo *var, u_char __user *data)
{
	struct amifb_par *par = &currentpar;
	register u_short *lspr, *sspr;
#ifdef __mc68000__
	register u_long datawords asm ("d2");
#else
	register u_long datawords;
#endif
	register short delta;
	u_short fmode;
	short height, width, bits, words;

	if (!var->width)
		return -EINVAL;
	else if (var->width <= 16)
		fmode = TAG_FMODE_1;
	else if (var->width <= 32)
		fmode = TAG_FMODE_2;
	else if (var->width <= 64)
		fmode = TAG_FMODE_4;
	else
		return -EINVAL;
	if (fmode > maxfmode)
		return -EINVAL;
	if (!var->height)
		return -EINVAL;
	if (!access_ok(VERIFY_READ, data, var->width*var->height))
		return -EFAULT;
	delta = 1<<fmode;
	lofsprite = shfsprite = (u_short *)spritememory;
	lspr = lofsprite + (delta<<1);
	if (par->bplcon0 & BPC0_LACE) {
		if (((var->height+4)<<fmode<<2) > SPRITEMEMSIZE)
			return -EINVAL;
		memset(lspr, 0, (var->height+4)<<fmode<<2);
		shfsprite += ((var->height+5)&-2)<<fmode;
		sspr = shfsprite + (delta<<1);
	} else {
		if (((var->height+2)<<fmode<<2) > SPRITEMEMSIZE)
			return -EINVAL;
		memset(lspr, 0, (var->height+2)<<fmode<<2);
		sspr = NULL;
	}
	for (height = (short)var->height-1; height >= 0; height--) {
		bits = 16; words = delta; datawords = 0;
		for (width = (short)var->width-1; width >= 0; width--) {
			unsigned long tdata = 0;
			get_user(tdata, data);
			data++;
#ifdef __mc68000__
			asm volatile (
				"lsrb #1,%2 ; roxlw #1,%0 ; swap %0 ; "
				"lsrb #1,%2 ; roxlw #1,%0 ; swap %0"
				: "=d" (datawords)
				: "0" (datawords), "d" (tdata));
#else
			datawords = ((datawords << 1) & 0xfffefffe);
			datawords |= tdata & 1;
			datawords |= (tdata & 2) << (16-1);
#endif
			if (--bits == 0) {
				bits = 16; --words;
#ifdef __mc68000__
				asm volatile ("swap %2 ; movew %2,%0@(%3:w:2) ; swap %2 ; movew %2,%0@+"
					: "=a" (lspr) : "0" (lspr), "d" (datawords), "d" (delta));
#else
				*(lspr+delta) = (u_short) (datawords >> 16);
				*lspr++ = (u_short) (datawords & 0xffff);
#endif
			}
		}
		if (bits < 16) {
			--words;
#ifdef __mc68000__
			asm volatile (
				"swap %2 ; lslw %4,%2 ; movew %2,%0@(%3:w:2) ; "
				"swap %2 ; lslw %4,%2 ; movew %2,%0@+"
				: "=a" (lspr) : "0" (lspr), "d" (datawords), "d" (delta), "d" (bits));
#else
			*(lspr+delta) = (u_short) (datawords >> (16+bits));
			*lspr++ = (u_short) ((datawords & 0x0000ffff) >> bits);
#endif
		}
		while (--words >= 0) {
#ifdef __mc68000__
			asm volatile ("moveql #0,%%d0 ; movew %%d0,%0@(%2:w:2) ; movew %%d0,%0@+"
				: "=a" (lspr) : "0" (lspr), "d" (delta) : "d0");
#else
			*(lspr+delta) = 0;
			*lspr++ = 0;
#endif
		}
#ifdef __mc68000__
		asm volatile ("lea %0@(%4:w:2),%0 ; tstl %1 ; jeq 1f ; exg %0,%1\n1:"
			: "=a" (lspr), "=a" (sspr) : "0" (lspr), "1" (sspr), "d" (delta));
#else
		lspr += delta;
		if (sspr) {
			u_short *tmp = lspr;
			lspr = sspr;
			sspr = tmp;
		}
#endif
	}
	par->crsr.height = var->height;
	par->crsr.width = var->width;
	par->crsr.spot_x = var->xspot;
	par->crsr.spot_y = var->yspot;
	par->crsr.fmode = fmode;
	if (IS_AGA) {
		par->fmode &= ~(FMODE_SPAGEM | FMODE_SPR32);
		par->fmode |= sprfetchmode[fmode];
		custom.fmode = par->fmode;
	}
	return 0;
}

static int ami_get_cursorstate(struct fb_cursorstate *state)
{
	struct amifb_par *par = &currentpar;

	state->xoffset = par->crsr.crsr_x;
	state->yoffset = par->crsr.crsr_y;
	state->mode = cursormode;
	return 0;
}

static int ami_set_cursorstate(struct fb_cursorstate *state)
{
	struct amifb_par *par = &currentpar;

	par->crsr.crsr_x = state->xoffset;
	par->crsr.crsr_y = state->yoffset;
	if ((cursormode = state->mode) == FB_CURSOR_OFF)
		cursorstate = -1;
	do_cursor = 1;
	return 0;
}

static void ami_set_sprite(void)
{
	struct amifb_par *par = &currentpar;
	copins *copl, *cops;
	u_short hs, vs, ve;
	u_long pl, ps, pt;
	short mx, my;

	cops = copdisplay.list[currentcop][0];
	copl = copdisplay.list[currentcop][1];
	ps = pl = ZTWO_PADDR(dummysprite);
	mx = par->crsr.crsr_x-par->crsr.spot_x;
	my = par->crsr.crsr_y-par->crsr.spot_y;
	if (!(par->vmode & FB_VMODE_YWRAP)) {
		mx -= par->xoffset;
		my -= par->yoffset;
	}
	if (!is_blanked && cursorstate > 0 && par->crsr.height > 0 &&
	    mx > -(short)par->crsr.width && mx < par->xres &&
	    my > -(short)par->crsr.height && my < par->yres) {
		pl = ZTWO_PADDR(lofsprite);
		hs = par->diwstrt_h + (mx<<par->clk_shift) - 4;
		vs = par->diwstrt_v + (my<<par->line_shift);
		ve = vs + (par->crsr.height<<par->line_shift);
		if (par->bplcon0 & BPC0_LACE) {
			ps = ZTWO_PADDR(shfsprite);
			lofsprite[0] = spr2hw_pos(vs, hs);
			shfsprite[0] = spr2hw_pos(vs+1, hs);
			if (mod2(vs)) {
				lofsprite[1<<par->crsr.fmode] = spr2hw_ctl(vs, hs, ve);
				shfsprite[1<<par->crsr.fmode] = spr2hw_ctl(vs+1, hs, ve+1);
				pt = pl; pl = ps; ps = pt;
			} else {
				lofsprite[1<<par->crsr.fmode] = spr2hw_ctl(vs, hs, ve+1);
				shfsprite[1<<par->crsr.fmode] = spr2hw_ctl(vs+1, hs, ve);
			}
		} else {
			lofsprite[0] = spr2hw_pos(vs, hs) | (IS_AGA && (par->fmode & FMODE_BSCAN2) ? 0x80 : 0);
			lofsprite[1<<par->crsr.fmode] = spr2hw_ctl(vs, hs, ve);
		}
	}
	copl[cop_spr0ptrh].w[1] = highw(pl);
	copl[cop_spr0ptrl].w[1] = loww(pl);
	if (par->bplcon0 & BPC0_LACE) {
		cops[cop_spr0ptrh].w[1] = highw(ps);
		cops[cop_spr0ptrl].w[1] = loww(ps);
	}
}


	/*
	 * Initialise the Copper Initialisation List
	 */

static void __init ami_init_copper(void)
{
	copins *cop = copdisplay.init;
	u_long p;
	int i;

	if (!IS_OCS) {
		(cop++)->l = CMOVE(BPC0_COLOR | BPC0_SHRES | BPC0_ECSENA, bplcon0);
		(cop++)->l = CMOVE(0x0181, diwstrt);
		(cop++)->l = CMOVE(0x0281, diwstop);
		(cop++)->l = CMOVE(0x0000, diwhigh);
	} else
		(cop++)->l = CMOVE(BPC0_COLOR, bplcon0);
	p = ZTWO_PADDR(dummysprite);
	for (i = 0; i < 8; i++) {
		(cop++)->l = CMOVE(0, spr[i].pos);
		(cop++)->l = CMOVE(highw(p), sprpt[i]);
		(cop++)->l = CMOVE2(loww(p), sprpt[i]);
	}

	(cop++)->l = CMOVE(IF_SETCLR | IF_COPER, intreq);
	copdisplay.wait = cop;
	(cop++)->l = CEND;
	(cop++)->l = CMOVE(0, copjmp2);
	cop->l = CEND;

	custom.cop1lc = (u_short *)ZTWO_PADDR(copdisplay.init);
	custom.copjmp1 = 0;
}

static void ami_reinit_copper(void)
{
	struct amifb_par *par = &currentpar;

	copdisplay.init[cip_bplcon0].w[1] = ~(BPC0_BPU3 | BPC0_BPU2 | BPC0_BPU1 | BPC0_BPU0) & par->bplcon0;
	copdisplay.wait->l = CWAIT(32, par->diwstrt_v-4);
}

	/*
	 * Build the Copper List
	 */

static void ami_build_copper(void)
{
	struct amifb_par *par = &currentpar;
	copins *copl, *cops;
	u_long p;

	currentcop = 1 - currentcop;

	copl = copdisplay.list[currentcop][1];

	(copl++)->l = CWAIT(0, 10);
	(copl++)->l = CMOVE(par->bplcon0, bplcon0);
	(copl++)->l = CMOVE(0, sprpt[0]);
	(copl++)->l = CMOVE2(0, sprpt[0]);

	if (par->bplcon0 & BPC0_LACE) {
		cops = copdisplay.list[currentcop][0];

		(cops++)->l = CWAIT(0, 10);
		(cops++)->l = CMOVE(par->bplcon0, bplcon0);
		(cops++)->l = CMOVE(0, sprpt[0]);
		(cops++)->l = CMOVE2(0, sprpt[0]);

		(copl++)->l = CMOVE(diwstrt2hw(par->diwstrt_h, par->diwstrt_v+1), diwstrt);
		(copl++)->l = CMOVE(diwstop2hw(par->diwstop_h, par->diwstop_v+1), diwstop);
		(cops++)->l = CMOVE(diwstrt2hw(par->diwstrt_h, par->diwstrt_v), diwstrt);
		(cops++)->l = CMOVE(diwstop2hw(par->diwstop_h, par->diwstop_v), diwstop);
		if (!IS_OCS) {
			(copl++)->l = CMOVE(diwhigh2hw(par->diwstrt_h, par->diwstrt_v+1,
			                    par->diwstop_h, par->diwstop_v+1), diwhigh);
			(cops++)->l = CMOVE(diwhigh2hw(par->diwstrt_h, par->diwstrt_v,
			                    par->diwstop_h, par->diwstop_v), diwhigh);
#if 0
			if (par->beamcon0 & BMC0_VARBEAMEN) {
				(copl++)->l = CMOVE(vtotal2hw(par->vtotal), vtotal);
				(copl++)->l = CMOVE(vbstrt2hw(par->vbstrt+1), vbstrt);
				(copl++)->l = CMOVE(vbstop2hw(par->vbstop+1), vbstop);
				(cops++)->l = CMOVE(vtotal2hw(par->vtotal), vtotal);
				(cops++)->l = CMOVE(vbstrt2hw(par->vbstrt), vbstrt);
				(cops++)->l = CMOVE(vbstop2hw(par->vbstop), vbstop);
			}
#endif
		}
		p = ZTWO_PADDR(copdisplay.list[currentcop][0]);
		(copl++)->l = CMOVE(highw(p), cop2lc);
		(copl++)->l = CMOVE2(loww(p), cop2lc);
		p = ZTWO_PADDR(copdisplay.list[currentcop][1]);
		(cops++)->l = CMOVE(highw(p), cop2lc);
		(cops++)->l = CMOVE2(loww(p), cop2lc);
		copdisplay.rebuild[0] = cops;
	} else {
		(copl++)->l = CMOVE(diwstrt2hw(par->diwstrt_h, par->diwstrt_v), diwstrt);
		(copl++)->l = CMOVE(diwstop2hw(par->diwstop_h, par->diwstop_v), diwstop);
		if (!IS_OCS) {
			(copl++)->l = CMOVE(diwhigh2hw(par->diwstrt_h, par->diwstrt_v,
			                    par->diwstop_h, par->diwstop_v), diwhigh);
#if 0
			if (par->beamcon0 & BMC0_VARBEAMEN) {
				(copl++)->l = CMOVE(vtotal2hw(par->vtotal), vtotal);
				(copl++)->l = CMOVE(vbstrt2hw(par->vbstrt), vbstrt);
				(copl++)->l = CMOVE(vbstop2hw(par->vbstop), vbstop);
			}
#endif
		}
	}
	copdisplay.rebuild[1] = copl;

	ami_update_par();
	ami_rebuild_copper();
}

	/*
	 * Rebuild the Copper List
	 *
	 * We only change the things that are not static
	 */

static void ami_rebuild_copper(void)
{
	struct amifb_par *par = &currentpar;
	copins *copl, *cops;
	u_short line, h_end1, h_end2;
	short i;
	u_long p;

	if (IS_AGA && maxfmode + par->clk_shift == 0)
		h_end1 = par->diwstrt_h-64;
	else
		h_end1 = par->htotal-32;
	h_end2 = par->ddfstop+64;

	ami_set_sprite();

	copl = copdisplay.rebuild[1];
	p = par->bplpt0;
	if (par->vmode & FB_VMODE_YWRAP) {
		if ((par->vyres-par->yoffset) != 1 || !mod2(par->diwstrt_v)) {
			if (par->yoffset > par->vyres-par->yres) {
				for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) {
					(copl++)->l = CMOVE(highw(p), bplpt[i]);
					(copl++)->l = CMOVE2(loww(p), bplpt[i]);
				}
				line = par->diwstrt_v + ((par->vyres-par->yoffset)<<par->line_shift) - 1;
				while (line >= 512) {
					(copl++)->l = CWAIT(h_end1, 510);
					line -= 512;
				}
				if (line >= 510 && IS_AGA && maxfmode + par->clk_shift == 0)
					(copl++)->l = CWAIT(h_end1, line);
				else
					(copl++)->l = CWAIT(h_end2, line);
				p = par->bplpt0wrap;
			}
		} else p = par->bplpt0wrap;
	}
	for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) {
		(copl++)->l = CMOVE(highw(p), bplpt[i]);
		(copl++)->l = CMOVE2(loww(p), bplpt[i]);
	}
	copl->l = CEND;

	if (par->bplcon0 & BPC0_LACE) {
		cops = copdisplay.rebuild[0];
		p = par->bplpt0;
		if (mod2(par->diwstrt_v))
			p -= par->next_line;
		else
			p += par->next_line;
		if (par->vmode & FB_VMODE_YWRAP) {
			if ((par->vyres-par->yoffset) != 1 || mod2(par->diwstrt_v)) {
				if (par->yoffset > par->vyres-par->yres+1) {
					for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) {
						(cops++)->l = CMOVE(highw(p), bplpt[i]);
						(cops++)->l = CMOVE2(loww(p), bplpt[i]);
					}
					line = par->diwstrt_v + ((par->vyres-par->yoffset)<<par->line_shift) - 2;
					while (line >= 512) {
						(cops++)->l = CWAIT(h_end1, 510);
						line -= 512;
					}
					if (line > 510 && IS_AGA && maxfmode + par->clk_shift == 0)
						(cops++)->l = CWAIT(h_end1, line);
					else
						(cops++)->l = CWAIT(h_end2, line);
					p = par->bplpt0wrap;
					if (mod2(par->diwstrt_v+par->vyres-par->yoffset))
						p -= par->next_line;
					else
						p += par->next_line;
				}
			} else p = par->bplpt0wrap - par->next_line;
		}
		for (i = 0; i < (short)par->bpp; i++, p += par->next_plane) {
			(cops++)->l = CMOVE(highw(p), bplpt[i]);
			(cops++)->l = CMOVE2(loww(p), bplpt[i]);
		}
		cops->l = CEND;
	}
}


module_init(amifb_init);

#ifdef MODULE
MODULE_LICENSE("GPL");

void cleanup_module(void)
{
	unregister_framebuffer(&fb_info);
	amifb_deinit();
	amifb_video_off();
}
#endif /* MODULE */