aboutsummaryrefslogblamecommitdiffstats
blob: 8a1fe7df50257bc6efe61a3ee6ffe71dd6413c93 (plain) (tree)
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
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
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
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226



























                                                                          

                    









































                                                                              
               


                  
               
                


                 
                
                 









                                                                         


                                                
                     



                                                        

                                                             























































                                                                                     

                                                               


                                                                          





                                                                            

                                                                        

                                                                       


                   


                                                          
 


                                                                       
 
                                                                                          







                                                                              
                          







                                                                 




                                                  
                            
 













                                         


                                        









                                          


                                        









                                            


                                            

















                                                                           
                                                                

               


                                                          



                                                       
                                                                                       

                                                            
                     























                                                                                  
                                                                                       

                                                            
                     


















                                                                                  


                                                          






                                                                      
                                                                                       

                                                            
                     



















                                                                            


                                                          



                                              
                                                 





                                                               
                                                                                       




                                                                 
                     











                                                                   





                                                                  


                                                                           
                                                                    












                                                                      

                                                                    



                                                                                                  




                                                         






















































                                                                              
                                                  

                                       
                            














                                        


                                        












                                          


                                        









                                            


                                            












                                             


                                          




                                                       

                                                                  








                                             





                                                                            

                                                                        

                                                                       


                   


                                                          

                            
                                          
                              
                                                       
 


                                                                
 
                                                                                          



                                                                      

                                                                 






                                                                    
                                                                            


                                 
                                            
                                                    
     
 
                       





                                                                      



                                                       

                                                                           

                                                            
                     


                                                       




                                                                      

                                                                 






                                                                    


                             
                                                 

                         
                                           
                                      








                                                                                
                       





                                                                 



                                                       



                                                                                        


                                                            
                     


                                                       


















                                                                               

                                                                    



                                                                                                  




                                                         
                          










                                      

                           


































                                                                                  

                           




             












































                                                                                    





                                                                   





                                                                  


                                                                           
                                                                    











                                                                      





                                                
                             
                           
 








                                                                 
                                                        

                             
                                                        

































                                                                           
























                                                                                   
























                                                                            

                                        





                                                                  








                                                                        
                                            







































                                                                                   

                          





















































































































































































































































































































































































                                                                                                                 
                                                               

                                                
                                                       












                                                       
 
                                                       



                                                                             
 




                                                                               

                                                        

                                             


                                                                                                   
            


                                                                                
     
 

                                                          

                                                                           
       

                                                        
                                                                           
                                                       
                                                                
                                                        
                                                                     
 

                                                          
 
                               
                                                                      
                                                                                           
 
                                                                                          

 







                                                                                




                                                                               












                                                                                                   

                                                                        



















                                                                                          

                                                                                  
                                                             
















                                                                                
                                                                                                       

                                                            
                     

                                                       


                                                            






                                                                      
                                                               


                                                                    
                                                                  










                                                    

                                                                 

                     
                                              






                                                                                           

                                                            
                     
                                              
         








                                                            



             
                                                                               

                                                                                      
 
          
 

                                                                                    
                 
     
 















                                                                                                            
                                     










                                                 
                                           










                                                              
                           


                                                                 

                                                              





                                                       
                                                                                                      












                                                          
                                                          






                                                                                        
                                                
                     
                                                                       

































                                                                                                                                                            



             


                                                                   
                                                
























                                                                           
                                                                                




                             
                           






                                                         
                                                                            

                             
                                                                                      

                             
                                                                                      






                       





















                                                                                   
                                                                              














                                                                    

                                                           







































































                                                                             



                                                         































                                                            
                         




















































































































































































































































































































                                                                                                                            
                                           







































































































































































                                                                                                      























                                                                            
                                                


                                        
                                                  
                                                  
                                                 




                                                  





                                                   

























                                                                             
                                                       



















































































                                                                
/*
 * Copyright © 2011 Red Hat All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS, AUTHORS
 * AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 */
/*
 * Authors:
 *      Jérôme Glisse <jglisse@redhat.com>
 */
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "drm.h"
#include "xf86drm.h"
#include "radeon_drm.h"
#include "radeon_surface.h"

#define ALIGN(value, alignment) (((value) + alignment - 1) & ~(alignment - 1))
#define MAX2(A, B)              ((A) > (B) ? (A) : (B))
#define MIN2(A, B)              ((A) < (B) ? (A) : (B))

/* keep this private */
enum radeon_family {
    CHIP_UNKNOWN,
    CHIP_R600,
    CHIP_RV610,
    CHIP_RV630,
    CHIP_RV670,
    CHIP_RV620,
    CHIP_RV635,
    CHIP_RS780,
    CHIP_RS880,
    CHIP_RV770,
    CHIP_RV730,
    CHIP_RV710,
    CHIP_RV740,
    CHIP_CEDAR,
    CHIP_REDWOOD,
    CHIP_JUNIPER,
    CHIP_CYPRESS,
    CHIP_HEMLOCK,
    CHIP_PALM,
    CHIP_SUMO,
    CHIP_SUMO2,
    CHIP_BARTS,
    CHIP_TURKS,
    CHIP_CAICOS,
    CHIP_CAYMAN,
    CHIP_ARUBA,
    CHIP_TAHITI,
    CHIP_PITCAIRN,
    CHIP_VERDE,
    CHIP_OLAND,
    CHIP_HAINAN,
    CHIP_BONAIRE,
    CHIP_KAVERI,
    CHIP_KABINI,
    CHIP_HAWAII,
    CHIP_MULLINS,
    CHIP_LAST,
};

typedef int (*hw_init_surface_t)(struct radeon_surface_manager *surf_man,
                                 struct radeon_surface *surf);
typedef int (*hw_best_surface_t)(struct radeon_surface_manager *surf_man,
                                 struct radeon_surface *surf);

struct radeon_hw_info {
    /* apply to r6, eg */
    uint32_t                        group_bytes;
    uint32_t                        num_banks;
    uint32_t                        num_pipes;
    /* apply to eg */
    uint32_t                        row_size;
    unsigned                        allow_2d;
    /* apply to si */
    uint32_t                        tile_mode_array[32];
    /* apply to cik */
    uint32_t                        macrotile_mode_array[16];
};

struct radeon_surface_manager {
    int                         fd;
    uint32_t                    device_id;
    struct radeon_hw_info       hw_info;
    unsigned                    family;
    hw_init_surface_t           surface_init;
    hw_best_surface_t           surface_best;
};

/* helper */
static int radeon_get_value(int fd, unsigned req, uint32_t *value)
{
    struct drm_radeon_info info = {};
    int r;

    *value = 0;
    info.request = req;
    info.value = (uintptr_t)value;
    r = drmCommandWriteRead(fd, DRM_RADEON_INFO, &info,
                            sizeof(struct drm_radeon_info));
    return r;
}

static int radeon_get_family(struct radeon_surface_manager *surf_man)
{
    switch (surf_man->device_id) {
#define CHIPSET(pci_id, name, fam) case pci_id: surf_man->family = CHIP_##fam; break;
#include "r600_pci_ids.h"
#undef CHIPSET
    default:
        return -EINVAL;
    }
    return 0;
}

static unsigned next_power_of_two(unsigned x)
{
   if (x <= 1)
       return 1;

   return (1 << ((sizeof(unsigned) * 8) - __builtin_clz(x - 1)));
}

static unsigned mip_minify(unsigned size, unsigned level)
{
    unsigned val;

    val = MAX2(1, size >> level);
    if (level > 0)
        val = next_power_of_two(val);
    return val;
}

static void surf_minify(struct radeon_surface *surf,
                        struct radeon_surface_level *surflevel,
                        unsigned bpe, unsigned level,
                        uint32_t xalign, uint32_t yalign, uint32_t zalign,
                        unsigned offset)
{
    surflevel->npix_x = mip_minify(surf->npix_x, level);
    surflevel->npix_y = mip_minify(surf->npix_y, level);
    surflevel->npix_z = mip_minify(surf->npix_z, level);
    surflevel->nblk_x = (surflevel->npix_x + surf->blk_w - 1) / surf->blk_w;
    surflevel->nblk_y = (surflevel->npix_y + surf->blk_h - 1) / surf->blk_h;
    surflevel->nblk_z = (surflevel->npix_z + surf->blk_d - 1) / surf->blk_d;
    if (surf->nsamples == 1 && surflevel->mode == RADEON_SURF_MODE_2D &&
        !(surf->flags & RADEON_SURF_FMASK)) {
        if (surflevel->nblk_x < xalign || surflevel->nblk_y < yalign) {
            surflevel->mode = RADEON_SURF_MODE_1D;
            return;
        }
    }
    surflevel->nblk_x  = ALIGN(surflevel->nblk_x, xalign);
    surflevel->nblk_y  = ALIGN(surflevel->nblk_y, yalign);
    surflevel->nblk_z  = ALIGN(surflevel->nblk_z, zalign);

    surflevel->offset = offset;
    surflevel->pitch_bytes = surflevel->nblk_x * bpe * surf->nsamples;
    surflevel->slice_size = surflevel->pitch_bytes * surflevel->nblk_y;

    surf->bo_size = offset + surflevel->slice_size * surflevel->nblk_z * surf->array_size;
}

/* ===========================================================================
 * r600/r700 family
 */
static int r6_init_hw_info(struct radeon_surface_manager *surf_man)
{
    uint32_t tiling_config;
    drmVersionPtr version;
    int r;

    r = radeon_get_value(surf_man->fd, RADEON_INFO_TILING_CONFIG,
                         &tiling_config);
    if (r) {
        return r;
    }

    surf_man->hw_info.allow_2d = 0;
    version = drmGetVersion(surf_man->fd);
    if (version && version->version_minor >= 14) {
        surf_man->hw_info.allow_2d = 1;
    }
    drmFreeVersion(version);

    switch ((tiling_config & 0xe) >> 1) {
    case 0:
        surf_man->hw_info.num_pipes = 1;
        break;
    case 1:
        surf_man->hw_info.num_pipes = 2;
        break;
    case 2:
        surf_man->hw_info.num_pipes = 4;
        break;
    case 3:
        surf_man->hw_info.num_pipes = 8;
        break;
    default:
        surf_man->hw_info.num_pipes = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0x30) >> 4) {
    case 0:
        surf_man->hw_info.num_banks = 4;
        break;
    case 1:
        surf_man->hw_info.num_banks = 8;
        break;
    default:
        surf_man->hw_info.num_banks = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xc0) >> 6) {
    case 0:
        surf_man->hw_info.group_bytes = 256;
        break;
    case 1:
        surf_man->hw_info.group_bytes = 512;
        break;
    default:
        surf_man->hw_info.group_bytes = 256;
        surf_man->hw_info.allow_2d = 0;
        break;
    }
    return 0;
}

static int r6_surface_init_linear(struct radeon_surface_manager *surf_man,
                                  struct radeon_surface *surf,
                                  uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign;
    unsigned i;

    /* compute alignment */
    if (!start_level) {
        surf->bo_alignment = MAX2(256, surf_man->hw_info.group_bytes);
    }
    /* the 32 alignment is for scanout, cb or db but to allow texture to be
     * easily bound as such we force this alignment to all surface
     */
    xalign = MAX2(1, surf_man->hw_info.group_bytes / surf->bpe);
    yalign = 1;
    zalign = 1;
    if (surf->flags & RADEON_SURF_SCANOUT) {
        xalign = MAX2((surf->bpe == 1) ? 64 : 32, xalign);
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        surf->level[i].mode = RADEON_SURF_MODE_LINEAR;
        surf_minify(surf, surf->level+i, surf->bpe, i, xalign, yalign, zalign, offset);
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
    }
    return 0;
}

static int r6_surface_init_linear_aligned(struct radeon_surface_manager *surf_man,
                                          struct radeon_surface *surf,
                                          uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign;
    unsigned i;

    /* compute alignment */
    if (!start_level) {
        surf->bo_alignment = MAX2(256, surf_man->hw_info.group_bytes);
    }
    xalign = MAX2(64, surf_man->hw_info.group_bytes / surf->bpe);
    yalign = 1;
    zalign = 1;

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        surf->level[i].mode = RADEON_SURF_MODE_LINEAR_ALIGNED;
        surf_minify(surf, surf->level+i, surf->bpe, i, xalign, yalign, zalign, offset);
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
    }
    return 0;
}

static int r6_surface_init_1d(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign, tilew;
    unsigned i;

    /* compute alignment */
    tilew = 8;
    xalign = surf_man->hw_info.group_bytes / (tilew * surf->bpe * surf->nsamples);
    xalign = MAX2(tilew, xalign);
    yalign = tilew;
    zalign = 1;
    if (surf->flags & RADEON_SURF_SCANOUT) {
        xalign = MAX2((surf->bpe == 1) ? 64 : 32, xalign);
    }
    if (!start_level) {
        surf->bo_alignment = MAX2(256, surf_man->hw_info.group_bytes);
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        surf->level[i].mode = RADEON_SURF_MODE_1D;
        surf_minify(surf, surf->level+i, surf->bpe, i, xalign, yalign, zalign, offset);
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
    }
    return 0;
}

static int r6_surface_init_2d(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign, tilew;
    unsigned i;

    /* compute alignment */
    tilew = 8;
    zalign = 1;
    xalign = (surf_man->hw_info.group_bytes * surf_man->hw_info.num_banks) /
             (tilew * surf->bpe * surf->nsamples);
    xalign = MAX2(tilew * surf_man->hw_info.num_banks, xalign);
    yalign = tilew * surf_man->hw_info.num_pipes;
    if (surf->flags & RADEON_SURF_SCANOUT) {
        xalign = MAX2((surf->bpe == 1) ? 64 : 32, xalign);
    }
    if (!start_level) {
        surf->bo_alignment =
            MAX2(surf_man->hw_info.num_pipes *
                 surf_man->hw_info.num_banks *
                 surf->nsamples * surf->bpe * 64,
                 xalign * yalign * surf->nsamples * surf->bpe);
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        surf->level[i].mode = RADEON_SURF_MODE_2D;
        surf_minify(surf, surf->level+i, surf->bpe, i, xalign, yalign, zalign, offset);
        if (surf->level[i].mode == RADEON_SURF_MODE_1D) {
            return r6_surface_init_1d(surf_man, surf, offset, i);
        }
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
    }
    return 0;
}

static int r6_surface_init(struct radeon_surface_manager *surf_man,
                           struct radeon_surface *surf)
{
    unsigned mode;
    int r;

    /* MSAA surfaces support the 2D mode only. */
    if (surf->nsamples > 1) {
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_2D, MODE);
    }

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER)) {
        /* zbuffer only support 1D or 2D tiled surface */
        switch (mode) {
        case RADEON_SURF_MODE_1D:
        case RADEON_SURF_MODE_2D:
            break;
        default:
            mode = RADEON_SURF_MODE_1D;
            surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
            surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_1D, MODE);
            break;
        }
    }

    /* force 1d on kernel that can't do 2d */
    if (!surf_man->hw_info.allow_2d && mode > RADEON_SURF_MODE_1D) {
        if (surf->nsamples > 1) {
            fprintf(stderr, "radeon: Cannot use 2D tiling for an MSAA surface (%i).\n", __LINE__);
            return -EFAULT;
        }
        mode = RADEON_SURF_MODE_1D;
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(mode, MODE);
    }

    /* check surface dimension */
    if (surf->npix_x > 8192 || surf->npix_y > 8192 || surf->npix_z > 8192) {
        return -EINVAL;
    }

    /* check mipmap last_level */
    if (surf->last_level > 14) {
        return -EINVAL;
    }

    /* check tiling mode */
    switch (mode) {
    case RADEON_SURF_MODE_LINEAR:
        r = r6_surface_init_linear(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_LINEAR_ALIGNED:
        r = r6_surface_init_linear_aligned(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_1D:
        r = r6_surface_init_1d(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_2D:
        r = r6_surface_init_2d(surf_man, surf, 0, 0);
        break;
    default:
        return -EINVAL;
    }
    return r;
}

static int r6_surface_best(struct radeon_surface_manager *surf_man,
                           struct radeon_surface *surf)
{
    /* no value to optimize for r6xx/r7xx */
    return 0;
}


/* ===========================================================================
 * evergreen family
 */
static int eg_init_hw_info(struct radeon_surface_manager *surf_man)
{
    uint32_t tiling_config;
    drmVersionPtr version;
    int r;

    r = radeon_get_value(surf_man->fd, RADEON_INFO_TILING_CONFIG,
                         &tiling_config);
    if (r) {
        return r;
    }

    surf_man->hw_info.allow_2d = 0;
    version = drmGetVersion(surf_man->fd);
    if (version && version->version_minor >= 16) {
        surf_man->hw_info.allow_2d = 1;
    }
    drmFreeVersion(version);

    switch (tiling_config & 0xf) {
    case 0:
        surf_man->hw_info.num_pipes = 1;
        break;
    case 1:
        surf_man->hw_info.num_pipes = 2;
        break;
    case 2:
        surf_man->hw_info.num_pipes = 4;
        break;
    case 3:
        surf_man->hw_info.num_pipes = 8;
        break;
    default:
        surf_man->hw_info.num_pipes = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf0) >> 4) {
    case 0:
        surf_man->hw_info.num_banks = 4;
        break;
    case 1:
        surf_man->hw_info.num_banks = 8;
        break;
    case 2:
        surf_man->hw_info.num_banks = 16;
        break;
    default:
        surf_man->hw_info.num_banks = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf00) >> 8) {
    case 0:
        surf_man->hw_info.group_bytes = 256;
        break;
    case 1:
        surf_man->hw_info.group_bytes = 512;
        break;
    default:
        surf_man->hw_info.group_bytes = 256;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf000) >> 12) {
    case 0:
        surf_man->hw_info.row_size = 1024;
        break;
    case 1:
        surf_man->hw_info.row_size = 2048;
        break;
    case 2:
        surf_man->hw_info.row_size = 4096;
        break;
    default:
        surf_man->hw_info.row_size = 4096;
        surf_man->hw_info.allow_2d = 0;
        break;
    }
    return 0;
}

static void eg_surf_minify(struct radeon_surface *surf,
                           struct radeon_surface_level *surflevel,
                           unsigned bpe,
                           unsigned level,
                           unsigned slice_pt,
                           unsigned mtilew,
                           unsigned mtileh,
                           unsigned mtileb,
                           unsigned offset)
{
    unsigned mtile_pr, mtile_ps;

    surflevel->npix_x = mip_minify(surf->npix_x, level);
    surflevel->npix_y = mip_minify(surf->npix_y, level);
    surflevel->npix_z = mip_minify(surf->npix_z, level);
    surflevel->nblk_x = (surflevel->npix_x + surf->blk_w - 1) / surf->blk_w;
    surflevel->nblk_y = (surflevel->npix_y + surf->blk_h - 1) / surf->blk_h;
    surflevel->nblk_z = (surflevel->npix_z + surf->blk_d - 1) / surf->blk_d;
    if (surf->nsamples == 1 && surflevel->mode == RADEON_SURF_MODE_2D &&
        !(surf->flags & RADEON_SURF_FMASK)) {
        if (surflevel->nblk_x < mtilew || surflevel->nblk_y < mtileh) {
            surflevel->mode = RADEON_SURF_MODE_1D;
            return;
        }
    }
    surflevel->nblk_x  = ALIGN(surflevel->nblk_x, mtilew);
    surflevel->nblk_y  = ALIGN(surflevel->nblk_y, mtileh);
    surflevel->nblk_z  = ALIGN(surflevel->nblk_z, 1);

    /* macro tile per row */
    mtile_pr = surflevel->nblk_x / mtilew;
    /* macro tile per slice */
    mtile_ps = (mtile_pr * surflevel->nblk_y) / mtileh;

    surflevel->offset = offset;
    surflevel->pitch_bytes = surflevel->nblk_x * bpe * slice_pt;
    surflevel->slice_size = mtile_ps * mtileb * slice_pt;

    surf->bo_size = offset + surflevel->slice_size * surflevel->nblk_z * surf->array_size;
}

static int eg_surface_init_1d(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              struct radeon_surface_level *level,
                              unsigned bpe,
                              uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign, tilew;
    unsigned i;

    /* compute alignment */
    tilew = 8;
    xalign = surf_man->hw_info.group_bytes / (tilew * bpe * surf->nsamples);
    xalign = MAX2(tilew, xalign);
    yalign = tilew;
    zalign = 1;
    if (surf->flags & RADEON_SURF_SCANOUT) {
        xalign = MAX2((bpe == 1) ? 64 : 32, xalign);
    }

    if (!start_level) {
        unsigned alignment = MAX2(256, surf_man->hw_info.group_bytes);
        surf->bo_alignment = MAX2(surf->bo_alignment, alignment);

        if (offset) {
            offset = ALIGN(offset, alignment);
        }
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        level[i].mode = RADEON_SURF_MODE_1D;
        surf_minify(surf, level+i, bpe, i, xalign, yalign, zalign, offset);
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
    }
    return 0;
}

static int eg_surface_init_2d(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              struct radeon_surface_level *level,
                              unsigned bpe, unsigned tile_split,
                              uint64_t offset, unsigned start_level)
{
    unsigned tilew, tileh, tileb;
    unsigned mtilew, mtileh, mtileb;
    unsigned slice_pt;
    unsigned i;

    /* compute tile values */
    tilew = 8;
    tileh = 8;
    tileb = tilew * tileh * bpe * surf->nsamples;
    /* slices per tile */
    slice_pt = 1;
    if (tileb > tile_split && tile_split) {
        slice_pt = tileb / tile_split;
    }
    tileb = tileb / slice_pt;

    /* macro tile width & height */
    mtilew = (tilew * surf->bankw * surf_man->hw_info.num_pipes) * surf->mtilea;
    mtileh = (tileh * surf->bankh * surf_man->hw_info.num_banks) / surf->mtilea;
    /* macro tile bytes */
    mtileb = (mtilew / tilew) * (mtileh / tileh) * tileb;

    if (!start_level) {
        unsigned alignment = MAX2(256, mtileb);
        surf->bo_alignment = MAX2(surf->bo_alignment, alignment);

        if (offset) {
            offset = ALIGN(offset, alignment);
        }
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        level[i].mode = RADEON_SURF_MODE_2D;
        eg_surf_minify(surf, level+i, bpe, i, slice_pt, mtilew, mtileh, mtileb, offset);
        if (level[i].mode == RADEON_SURF_MODE_1D) {
            return eg_surface_init_1d(surf_man, surf, level, bpe, offset, i);
        }
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
    }
    return 0;
}

static int eg_surface_sanity(struct radeon_surface_manager *surf_man,
                             struct radeon_surface *surf,
                             unsigned mode)
{
    unsigned tileb;

    /* check surface dimension */
    if (surf->npix_x > 16384 || surf->npix_y > 16384 || surf->npix_z > 16384) {
        return -EINVAL;
    }

    /* check mipmap last_level */
    if (surf->last_level > 15) {
        return -EINVAL;
    }

    /* force 1d on kernel that can't do 2d */
    if (!surf_man->hw_info.allow_2d && mode > RADEON_SURF_MODE_1D) {
        if (surf->nsamples > 1) {
            fprintf(stderr, "radeon: Cannot use 2D tiling for an MSAA surface (%i).\n", __LINE__);
            return -EFAULT;
        }
        mode = RADEON_SURF_MODE_1D;
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(mode, MODE);
    }

    /* check tile split */
    if (mode == RADEON_SURF_MODE_2D) {
        switch (surf->tile_split) {
        case 64:
        case 128:
        case 256:
        case 512:
        case 1024:
        case 2048:
        case 4096:
            break;
        default:
            return -EINVAL;
        }
        switch (surf->mtilea) {
        case 1:
        case 2:
        case 4:
        case 8:
            break;
        default:
            return -EINVAL;
        }
        /* check aspect ratio */
        if (surf_man->hw_info.num_banks < surf->mtilea) {
            return -EINVAL;
        }
        /* check bank width */
        switch (surf->bankw) {
        case 1:
        case 2:
        case 4:
        case 8:
            break;
        default:
            return -EINVAL;
        }
        /* check bank height */
        switch (surf->bankh) {
        case 1:
        case 2:
        case 4:
        case 8:
            break;
        default:
            return -EINVAL;
        }
        tileb = MIN2(surf->tile_split, 64 * surf->bpe * surf->nsamples);
        if ((tileb * surf->bankh * surf->bankw) < surf_man->hw_info.group_bytes) {
            return -EINVAL;
        }
    }

    return 0;
}

static int eg_surface_init_1d_miptrees(struct radeon_surface_manager *surf_man,
                                       struct radeon_surface *surf)
{
    unsigned zs_flags = RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER;
    int r, is_depth_stencil = (surf->flags & zs_flags) == zs_flags;
    /* Old libdrm headers didn't have stencil_level in it. This prevents crashes. */
    struct radeon_surface_level tmp[RADEON_SURF_MAX_LEVEL];
    struct radeon_surface_level *stencil_level =
        (surf->flags & RADEON_SURF_HAS_SBUFFER_MIPTREE) ? surf->stencil_level : tmp;

    r = eg_surface_init_1d(surf_man, surf, surf->level, surf->bpe, 0, 0);
    if (r)
        return r;

    if (is_depth_stencil) {
        r = eg_surface_init_1d(surf_man, surf, stencil_level, 1,
                               surf->bo_size, 0);
        surf->stencil_offset = stencil_level[0].offset;
    }
    return r;
}

static int eg_surface_init_2d_miptrees(struct radeon_surface_manager *surf_man,
                                       struct radeon_surface *surf)
{
    unsigned zs_flags = RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER;
    int r, is_depth_stencil = (surf->flags & zs_flags) == zs_flags;
    /* Old libdrm headers didn't have stencil_level in it. This prevents crashes. */
    struct radeon_surface_level tmp[RADEON_SURF_MAX_LEVEL];
    struct radeon_surface_level *stencil_level =
        (surf->flags & RADEON_SURF_HAS_SBUFFER_MIPTREE) ? surf->stencil_level : tmp;

    r = eg_surface_init_2d(surf_man, surf, surf->level, surf->bpe,
                           surf->tile_split, 0, 0);
    if (r)
        return r;

    if (is_depth_stencil) {
        r = eg_surface_init_2d(surf_man, surf, stencil_level, 1,
                               surf->stencil_tile_split, surf->bo_size, 0);
        surf->stencil_offset = stencil_level[0].offset;
    }
    return r;
}

static int eg_surface_init(struct radeon_surface_manager *surf_man,
                           struct radeon_surface *surf)
{
    unsigned mode;
    int r;

    /* MSAA surfaces support the 2D mode only. */
    if (surf->nsamples > 1) {
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_2D, MODE);
    }

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER)) {
        /* zbuffer only support 1D or 2D tiled surface */
        switch (mode) {
        case RADEON_SURF_MODE_1D:
        case RADEON_SURF_MODE_2D:
            break;
        default:
            mode = RADEON_SURF_MODE_1D;
            surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
            surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_1D, MODE);
            break;
        }
    }

    r = eg_surface_sanity(surf_man, surf, mode);
    if (r) {
        return r;
    }

    surf->stencil_offset = 0;
    surf->bo_alignment = 0;

    /* check tiling mode */
    switch (mode) {
    case RADEON_SURF_MODE_LINEAR:
        r = r6_surface_init_linear(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_LINEAR_ALIGNED:
        r = r6_surface_init_linear_aligned(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_1D:
        r = eg_surface_init_1d_miptrees(surf_man, surf);
        break;
    case RADEON_SURF_MODE_2D:
        r = eg_surface_init_2d_miptrees(surf_man, surf);
        break;
    default:
        return -EINVAL;
    }
    return r;
}

static unsigned log2_int(unsigned x)
{
    unsigned l;

    if (x < 2) {
        return 0;
    }
    for (l = 2; ; l++) {
        if ((unsigned)(1 << l) > x) {
            return l - 1;
        }
    }
    return 0;
}

/* compute best tile_split, bankw, bankh, mtilea
 * depending on surface
 */
static int eg_surface_best(struct radeon_surface_manager *surf_man,
                           struct radeon_surface *surf)
{
    unsigned mode, tileb, h_over_w;
    int r;

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    /* set some default value to avoid sanity check choking on them */
    surf->tile_split = 1024;
    surf->bankw = 1;
    surf->bankh = 1;
    surf->mtilea = surf_man->hw_info.num_banks;
    tileb = MIN2(surf->tile_split, 64 * surf->bpe * surf->nsamples);
    for (; surf->bankh <= 8; surf->bankh *= 2) {
        if ((tileb * surf->bankh * surf->bankw) >= surf_man->hw_info.group_bytes) {
            break;
        }
    }
    if (surf->mtilea > 8) {
        surf->mtilea = 8;
    }

    r = eg_surface_sanity(surf_man, surf, mode);
    if (r) {
        return r;
    }

    if (mode != RADEON_SURF_MODE_2D) {
        /* nothing to do for non 2D tiled surface */
        return 0;
    }

    /* Tweak TILE_SPLIT for performance here. */
    if (surf->nsamples > 1) {
        if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER)) {
            switch (surf->nsamples) {
            case 2:
                surf->tile_split = 128;
                break;
            case 4:
                surf->tile_split = 128;
                break;
            case 8:
                surf->tile_split = 256;
                break;
            case 16: /* cayman only */
                surf->tile_split = 512;
                break;
            default:
                fprintf(stderr, "radeon: Wrong number of samples %i (%i)\n",
                        surf->nsamples, __LINE__);
                return -EINVAL;
            }
            surf->stencil_tile_split = 64;
        } else {
            /* tile split must be >= 256 for colorbuffer surfaces */
            surf->tile_split = MAX2(surf->nsamples * surf->bpe * 64, 256);
            if (surf->tile_split > 4096)
                surf->tile_split = 4096;
        }
    } else {
        /* set tile split to row size */
        surf->tile_split = surf_man->hw_info.row_size;
        surf->stencil_tile_split = surf_man->hw_info.row_size / 2;
    }

    /* bankw or bankh greater than 1 increase alignment requirement, not
     * sure if it's worth using smaller bankw & bankh to stick with 2D
     * tiling on small surface rather than falling back to 1D tiling.
     * Use recommanded value based on tile size for now.
     *
     * fmask buffer has different optimal value figure them out once we
     * use it.
     */
    if (surf->flags & RADEON_SURF_SBUFFER) {
        /* assume 1 bytes for stencil, we optimize for stencil as stencil
         * and depth shares surface values
         */
        tileb = MIN2(surf->tile_split, 64 * surf->nsamples);
    } else {
        tileb = MIN2(surf->tile_split, 64 * surf->bpe * surf->nsamples);
    }

    /* use bankw of 1 to minimize width alignment, might be interesting to
     * increase it for large surface
     */
    surf->bankw = 1;
    switch (tileb) {
    case 64:
        surf->bankh = 4;
        break;
    case 128:
    case 256:
        surf->bankh = 2;
        break;
    default:
        surf->bankh = 1;
        break;
    }
    /* double check the constraint */
    for (; surf->bankh <= 8; surf->bankh *= 2) {
        if ((tileb * surf->bankh * surf->bankw) >= surf_man->hw_info.group_bytes) {
            break;
        }
    }

    h_over_w = (((surf->bankh * surf_man->hw_info.num_banks) << 16) /
                (surf->bankw * surf_man->hw_info.num_pipes)) >> 16;
    surf->mtilea = 1 << (log2_int(h_over_w) >> 1);

    return 0;
}


/* ===========================================================================
 * Southern Islands family
 */
#define SI__GB_TILE_MODE__PIPE_CONFIG(x)        (((x) >> 6) & 0x1f)
#define     SI__PIPE_CONFIG__ADDR_SURF_P2               0
#define     SI__PIPE_CONFIG__ADDR_SURF_P4_8x16          4
#define     SI__PIPE_CONFIG__ADDR_SURF_P4_16x16         5
#define     SI__PIPE_CONFIG__ADDR_SURF_P4_16x32         6
#define     SI__PIPE_CONFIG__ADDR_SURF_P4_32x32         7
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_16x16_8x16    8
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_16x32_8x16    9
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_32x32_8x16    10
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_16x32_16x16   11
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x16   12
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x32   13
#define     SI__PIPE_CONFIG__ADDR_SURF_P8_32x64_32x32   14
#define SI__GB_TILE_MODE__TILE_SPLIT(x)         (((x) >> 11) & 0x7)
#define     SI__TILE_SPLIT__64B                         0
#define     SI__TILE_SPLIT__128B                        1
#define     SI__TILE_SPLIT__256B                        2
#define     SI__TILE_SPLIT__512B                        3
#define     SI__TILE_SPLIT__1024B                       4
#define     SI__TILE_SPLIT__2048B                       5
#define     SI__TILE_SPLIT__4096B                       6
#define SI__GB_TILE_MODE__BANK_WIDTH(x)         (((x) >> 14) & 0x3)
#define     SI__BANK_WIDTH__1                           0
#define     SI__BANK_WIDTH__2                           1
#define     SI__BANK_WIDTH__4                           2
#define     SI__BANK_WIDTH__8                           3
#define SI__GB_TILE_MODE__BANK_HEIGHT(x)        (((x) >> 16) & 0x3)
#define     SI__BANK_HEIGHT__1                          0
#define     SI__BANK_HEIGHT__2                          1
#define     SI__BANK_HEIGHT__4                          2
#define     SI__BANK_HEIGHT__8                          3
#define SI__GB_TILE_MODE__MACRO_TILE_ASPECT(x)  (((x) >> 18) & 0x3)
#define     SI__MACRO_TILE_ASPECT__1                    0
#define     SI__MACRO_TILE_ASPECT__2                    1
#define     SI__MACRO_TILE_ASPECT__4                    2
#define     SI__MACRO_TILE_ASPECT__8                    3
#define SI__GB_TILE_MODE__NUM_BANKS(x)          (((x) >> 20) & 0x3)
#define     SI__NUM_BANKS__2_BANK                       0
#define     SI__NUM_BANKS__4_BANK                       1
#define     SI__NUM_BANKS__8_BANK                       2
#define     SI__NUM_BANKS__16_BANK                      3


static void si_gb_tile_mode(uint32_t gb_tile_mode,
                            unsigned *num_pipes,
                            unsigned *num_banks,
                            uint32_t *macro_tile_aspect,
                            uint32_t *bank_w,
                            uint32_t *bank_h,
                            uint32_t *tile_split)
{
    if (num_pipes) {
        switch (SI__GB_TILE_MODE__PIPE_CONFIG(gb_tile_mode)) {
        case SI__PIPE_CONFIG__ADDR_SURF_P2:
        default:
            *num_pipes = 2;
            break;
        case SI__PIPE_CONFIG__ADDR_SURF_P4_8x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P4_16x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P4_16x32:
        case SI__PIPE_CONFIG__ADDR_SURF_P4_32x32:
            *num_pipes = 4;
            break;
        case SI__PIPE_CONFIG__ADDR_SURF_P8_16x16_8x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P8_16x32_8x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P8_32x32_8x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P8_16x32_16x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x16:
        case SI__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x32:
        case SI__PIPE_CONFIG__ADDR_SURF_P8_32x64_32x32:
            *num_pipes = 8;
            break;
        }
    }
    if (num_banks) {
        switch (SI__GB_TILE_MODE__NUM_BANKS(gb_tile_mode)) {
        default:
        case SI__NUM_BANKS__2_BANK:
            *num_banks = 2;
            break;
        case SI__NUM_BANKS__4_BANK:
            *num_banks = 4;
            break;
        case SI__NUM_BANKS__8_BANK:
            *num_banks = 8;
            break;
        case SI__NUM_BANKS__16_BANK:
            *num_banks = 16;
            break;
        }
    }
    if (macro_tile_aspect) {
        switch (SI__GB_TILE_MODE__MACRO_TILE_ASPECT(gb_tile_mode)) {
        default:
        case SI__MACRO_TILE_ASPECT__1:
            *macro_tile_aspect = 1;
            break;
        case SI__MACRO_TILE_ASPECT__2:
            *macro_tile_aspect = 2;
            break;
        case SI__MACRO_TILE_ASPECT__4:
            *macro_tile_aspect = 4;
            break;
        case SI__MACRO_TILE_ASPECT__8:
            *macro_tile_aspect = 8;
            break;
        }
    }
    if (bank_w) {
        switch (SI__GB_TILE_MODE__BANK_WIDTH(gb_tile_mode)) {
        default:
        case SI__BANK_WIDTH__1:
            *bank_w = 1;
            break;
        case SI__BANK_WIDTH__2:
            *bank_w = 2;
            break;
        case SI__BANK_WIDTH__4:
            *bank_w = 4;
            break;
        case SI__BANK_WIDTH__8:
            *bank_w = 8;
            break;
        }
    }
    if (bank_h) {
        switch (SI__GB_TILE_MODE__BANK_HEIGHT(gb_tile_mode)) {
        default:
        case SI__BANK_HEIGHT__1:
            *bank_h = 1;
            break;
        case SI__BANK_HEIGHT__2:
            *bank_h = 2;
            break;
        case SI__BANK_HEIGHT__4:
            *bank_h = 4;
            break;
        case SI__BANK_HEIGHT__8:
            *bank_h = 8;
            break;
        }
    }
    if (tile_split) {
        switch (SI__GB_TILE_MODE__TILE_SPLIT(gb_tile_mode)) {
        default:
        case SI__TILE_SPLIT__64B:
            *tile_split = 64;
            break;
        case SI__TILE_SPLIT__128B:
            *tile_split = 128;
            break;
        case SI__TILE_SPLIT__256B:
            *tile_split = 256;
            break;
        case SI__TILE_SPLIT__512B:
            *tile_split = 512;
            break;
        case SI__TILE_SPLIT__1024B:
            *tile_split = 1024;
            break;
        case SI__TILE_SPLIT__2048B:
            *tile_split = 2048;
            break;
        case SI__TILE_SPLIT__4096B:
            *tile_split = 4096;
            break;
        }
    }
}

static int si_init_hw_info(struct radeon_surface_manager *surf_man)
{
    uint32_t tiling_config;
    drmVersionPtr version;
    int r;

    r = radeon_get_value(surf_man->fd, RADEON_INFO_TILING_CONFIG,
                         &tiling_config);
    if (r) {
        return r;
    }

    surf_man->hw_info.allow_2d = 0;
    version = drmGetVersion(surf_man->fd);
    if (version && version->version_minor >= 33) {
        if (!radeon_get_value(surf_man->fd, RADEON_INFO_SI_TILE_MODE_ARRAY, surf_man->hw_info.tile_mode_array)) {
            surf_man->hw_info.allow_2d = 1;
        }
    }
    drmFreeVersion(version);

    switch (tiling_config & 0xf) {
    case 0:
        surf_man->hw_info.num_pipes = 1;
        break;
    case 1:
        surf_man->hw_info.num_pipes = 2;
        break;
    case 2:
        surf_man->hw_info.num_pipes = 4;
        break;
    case 3:
        surf_man->hw_info.num_pipes = 8;
        break;
    default:
        surf_man->hw_info.num_pipes = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf0) >> 4) {
    case 0:
        surf_man->hw_info.num_banks = 4;
        break;
    case 1:
        surf_man->hw_info.num_banks = 8;
        break;
    case 2:
        surf_man->hw_info.num_banks = 16;
        break;
    default:
        surf_man->hw_info.num_banks = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf00) >> 8) {
    case 0:
        surf_man->hw_info.group_bytes = 256;
        break;
    case 1:
        surf_man->hw_info.group_bytes = 512;
        break;
    default:
        surf_man->hw_info.group_bytes = 256;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf000) >> 12) {
    case 0:
        surf_man->hw_info.row_size = 1024;
        break;
    case 1:
        surf_man->hw_info.row_size = 2048;
        break;
    case 2:
        surf_man->hw_info.row_size = 4096;
        break;
    default:
        surf_man->hw_info.row_size = 4096;
        surf_man->hw_info.allow_2d = 0;
        break;
    }
    return 0;
}

static int si_surface_sanity(struct radeon_surface_manager *surf_man,
                             struct radeon_surface *surf,
                             unsigned mode, unsigned *tile_mode, unsigned *stencil_tile_mode)
{
    uint32_t gb_tile_mode;

    /* check surface dimension */
    if (surf->npix_x > 16384 || surf->npix_y > 16384 || surf->npix_z > 16384) {
        return -EINVAL;
    }

    /* check mipmap last_level */
    if (surf->last_level > 15) {
        return -EINVAL;
    }

    /* force 1d on kernel that can't do 2d */
    if (mode > RADEON_SURF_MODE_1D &&
        (!surf_man->hw_info.allow_2d || !(surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX))) {
        if (surf->nsamples > 1) {
            fprintf(stderr, "radeon: Cannot use 1D tiling for an MSAA surface (%i).\n", __LINE__);
            return -EFAULT;
        }
        mode = RADEON_SURF_MODE_1D;
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(mode, MODE);
    }

    if (surf->nsamples > 1 && mode != RADEON_SURF_MODE_2D) {
        return -EINVAL;
    }

    if (!surf->tile_split) {
        /* default value */
        surf->mtilea = 1;
        surf->bankw = 1;
        surf->bankw = 1;
        surf->tile_split = 64;
        surf->stencil_tile_split = 64;
    }

    switch (mode) {
    case RADEON_SURF_MODE_2D:
        if (surf->flags & RADEON_SURF_SBUFFER) {
            switch (surf->nsamples) {
            case 1:
                *stencil_tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D;
                break;
            case 2:
                *stencil_tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D_2AA;
                break;
            case 4:
                *stencil_tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D_4AA;
                break;
            case 8:
                *stencil_tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D_8AA;
                break;
            default:
                return -EINVAL;
            }
            /* retrieve tiling mode value */
            gb_tile_mode = surf_man->hw_info.tile_mode_array[*stencil_tile_mode];
            si_gb_tile_mode(gb_tile_mode, NULL, NULL, NULL, NULL, NULL, &surf->stencil_tile_split);
        }
        if (surf->flags & RADEON_SURF_ZBUFFER) {
            switch (surf->nsamples) {
            case 1:
                *tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D;
                break;
            case 2:
                *tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D_2AA;
                break;
            case 4:
                *tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D_4AA;
                break;
            case 8:
                *tile_mode = SI_TILE_MODE_DEPTH_STENCIL_2D_8AA;
                break;
            default:
                return -EINVAL;
            }
        } else if (surf->flags & RADEON_SURF_SCANOUT) {
            switch (surf->bpe) {
            case 2:
                *tile_mode = SI_TILE_MODE_COLOR_2D_SCANOUT_16BPP;
                break;
            case 4:
                *tile_mode = SI_TILE_MODE_COLOR_2D_SCANOUT_32BPP;
                break;
            default:
                return -EINVAL;
            }
        } else {
            switch (surf->bpe) {
            case 1:
                *tile_mode = SI_TILE_MODE_COLOR_2D_8BPP;
                break;
            case 2:
                *tile_mode = SI_TILE_MODE_COLOR_2D_16BPP;
                break;
            case 4:
                *tile_mode = SI_TILE_MODE_COLOR_2D_32BPP;
                break;
            case 8:
            case 16:
                *tile_mode = SI_TILE_MODE_COLOR_2D_64BPP;
                break;
            default:
                return -EINVAL;
            }
        }
        /* retrieve tiling mode value */
        gb_tile_mode = surf_man->hw_info.tile_mode_array[*tile_mode];
        si_gb_tile_mode(gb_tile_mode, NULL, NULL, &surf->mtilea, &surf->bankw, &surf->bankh, &surf->tile_split);
        break;
    case RADEON_SURF_MODE_1D:
        if (surf->flags & RADEON_SURF_SBUFFER) {
            *stencil_tile_mode = SI_TILE_MODE_DEPTH_STENCIL_1D;
        }
        if (surf->flags & RADEON_SURF_ZBUFFER) {
            *tile_mode = SI_TILE_MODE_DEPTH_STENCIL_1D;
        } else if (surf->flags & RADEON_SURF_SCANOUT) {
            *tile_mode = SI_TILE_MODE_COLOR_1D_SCANOUT;
        } else {
            *tile_mode = SI_TILE_MODE_COLOR_1D;
        }
        break;
    case RADEON_SURF_MODE_LINEAR_ALIGNED:
    default:
        *tile_mode = SI_TILE_MODE_COLOR_LINEAR_ALIGNED;
    }

    return 0;
}

static void si_surf_minify(struct radeon_surface *surf,
                           struct radeon_surface_level *surflevel,
                           unsigned bpe, unsigned level,
                           uint32_t xalign, uint32_t yalign, uint32_t zalign,
                           uint32_t slice_align, unsigned offset)
{
    if (level == 0) {
        surflevel->npix_x = surf->npix_x;
    } else {
        surflevel->npix_x = mip_minify(next_power_of_two(surf->npix_x), level);
    }
    surflevel->npix_y = mip_minify(surf->npix_y, level);
    surflevel->npix_z = mip_minify(surf->npix_z, level);

    if (level == 0 && surf->last_level > 0) {
        surflevel->nblk_x = (next_power_of_two(surflevel->npix_x) + surf->blk_w - 1) / surf->blk_w;
        surflevel->nblk_y = (next_power_of_two(surflevel->npix_y) + surf->blk_h - 1) / surf->blk_h;
        surflevel->nblk_z = (next_power_of_two(surflevel->npix_z) + surf->blk_d - 1) / surf->blk_d;
    } else {
        surflevel->nblk_x = (surflevel->npix_x + surf->blk_w - 1) / surf->blk_w;
        surflevel->nblk_y = (surflevel->npix_y + surf->blk_h - 1) / surf->blk_h;
        surflevel->nblk_z = (surflevel->npix_z + surf->blk_d - 1) / surf->blk_d;
    }

    surflevel->nblk_y  = ALIGN(surflevel->nblk_y, yalign);

    /* XXX: Texture sampling uses unexpectedly large pitches in some cases,
     * these are just guesses for the rules behind those
     */
    if (level == 0 && surf->last_level == 0)
        /* Non-mipmap pitch padded to slice alignment */
        /* Using just bpe here breaks stencil blitting; surf->bpe works. */
        xalign = MAX2(xalign, slice_align / surf->bpe);
    else if (surflevel->mode == RADEON_SURF_MODE_LINEAR_ALIGNED)
        /* Small rows evenly distributed across slice */
        xalign = MAX2(xalign, slice_align / bpe / surflevel->nblk_y);

    surflevel->nblk_x  = ALIGN(surflevel->nblk_x, xalign);
    surflevel->nblk_z  = ALIGN(surflevel->nblk_z, zalign);

    surflevel->offset = offset;
    surflevel->pitch_bytes = surflevel->nblk_x * bpe * surf->nsamples;
    surflevel->slice_size = ALIGN(surflevel->pitch_bytes * surflevel->nblk_y, slice_align);

    surf->bo_size = offset + surflevel->slice_size * surflevel->nblk_z * surf->array_size;
}

static void si_surf_minify_2d(struct radeon_surface *surf,
                              struct radeon_surface_level *surflevel,
                              unsigned bpe, unsigned level, unsigned slice_pt,
                              uint32_t xalign, uint32_t yalign, uint32_t zalign,
                              unsigned mtileb, unsigned offset)
{
    unsigned mtile_pr, mtile_ps;

    if (level == 0) {
        surflevel->npix_x = surf->npix_x;
    } else {
        surflevel->npix_x = mip_minify(next_power_of_two(surf->npix_x), level);
    }
    surflevel->npix_y = mip_minify(surf->npix_y, level);
    surflevel->npix_z = mip_minify(surf->npix_z, level);

    if (level == 0 && surf->last_level > 0) {
        surflevel->nblk_x = (next_power_of_two(surflevel->npix_x) + surf->blk_w - 1) / surf->blk_w;
        surflevel->nblk_y = (next_power_of_two(surflevel->npix_y) + surf->blk_h - 1) / surf->blk_h;
        surflevel->nblk_z = (next_power_of_two(surflevel->npix_z) + surf->blk_d - 1) / surf->blk_d;
    } else {
        surflevel->nblk_x = (surflevel->npix_x + surf->blk_w - 1) / surf->blk_w;
        surflevel->nblk_y = (surflevel->npix_y + surf->blk_h - 1) / surf->blk_h;
        surflevel->nblk_z = (surflevel->npix_z + surf->blk_d - 1) / surf->blk_d;
    }

    if (surf->nsamples == 1 && surflevel->mode == RADEON_SURF_MODE_2D &&
        !(surf->flags & RADEON_SURF_FMASK)) {
        if (surflevel->nblk_x < xalign || surflevel->nblk_y < yalign) {
            surflevel->mode = RADEON_SURF_MODE_1D;
            return;
        }
    }
    surflevel->nblk_x  = ALIGN(surflevel->nblk_x, xalign);
    surflevel->nblk_y  = ALIGN(surflevel->nblk_y, yalign);
    surflevel->nblk_z  = ALIGN(surflevel->nblk_z, zalign);

    /* macro tile per row */
    mtile_pr = surflevel->nblk_x / xalign;
    /* macro tile per slice */
    mtile_ps = (mtile_pr * surflevel->nblk_y) / yalign;
    surflevel->offset = offset;
    surflevel->pitch_bytes = surflevel->nblk_x * bpe * slice_pt;
    surflevel->slice_size = mtile_ps * mtileb * slice_pt;

    surf->bo_size = offset + surflevel->slice_size * surflevel->nblk_z * surf->array_size;
}

static int si_surface_init_linear_aligned(struct radeon_surface_manager *surf_man,
                                          struct radeon_surface *surf,
                                          unsigned tile_mode,
                                          uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign, slice_align;
    unsigned i;

    /* compute alignment */
    if (!start_level) {
        surf->bo_alignment = MAX2(256, surf_man->hw_info.group_bytes);
    }
    xalign = MAX2(8, 64 / surf->bpe);
    yalign = 1;
    zalign = 1;
    slice_align = MAX2(64 * surf->bpe, surf_man->hw_info.group_bytes);

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        surf->level[i].mode = RADEON_SURF_MODE_LINEAR_ALIGNED;
        si_surf_minify(surf, surf->level+i, surf->bpe, i, xalign, yalign, zalign, slice_align, offset);
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, surf->bo_alignment);
        }
        if (surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX) {
            surf->tiling_index[i] = tile_mode;
        }
    }
    return 0;
}

static int si_surface_init_1d(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              struct radeon_surface_level *level,
                              unsigned bpe, unsigned tile_mode,
                              uint64_t offset, unsigned start_level)
{
    uint32_t xalign, yalign, zalign, slice_align;
    unsigned alignment = MAX2(256, surf_man->hw_info.group_bytes);
    unsigned i;

    /* compute alignment */
    xalign = 8;
    yalign = 8;
    zalign = 1;
    slice_align = surf_man->hw_info.group_bytes;
    if (surf->flags & RADEON_SURF_SCANOUT) {
        xalign = MAX2((bpe == 1) ? 64 : 32, xalign);
    }

    if (start_level <= 1) {
        surf->bo_alignment = MAX2(surf->bo_alignment, alignment);

        if (offset) {
            offset = ALIGN(offset, alignment);
        }
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        level[i].mode = RADEON_SURF_MODE_1D;
        si_surf_minify(surf, level+i, bpe, i, xalign, yalign, zalign, slice_align, offset);
        /* level0 and first mipmap need to have alignment */
        offset = surf->bo_size;
        if (i == 0) {
            offset = ALIGN(offset, alignment);
        }
        if (surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX) {
            if (surf->level == level) {
                surf->tiling_index[i] = tile_mode;
                /* it's ok because stencil is done after */
                surf->stencil_tiling_index[i] = tile_mode;
            } else {
                surf->stencil_tiling_index[i] = tile_mode;
            }
        }
    }
    return 0;
}

static int si_surface_init_1d_miptrees(struct radeon_surface_manager *surf_man,
                                       struct radeon_surface *surf,
                                       unsigned tile_mode, unsigned stencil_tile_mode)
{
    int r;

    r = si_surface_init_1d(surf_man, surf, surf->level, surf->bpe, tile_mode, 0, 0);
    if (r) {
        return r;
    }

    if (surf->flags & RADEON_SURF_SBUFFER) {
        r = si_surface_init_1d(surf_man, surf, surf->stencil_level, 1, stencil_tile_mode, surf->bo_size, 0);
        surf->stencil_offset = surf->stencil_level[0].offset;
    }
    return r;
}

static int si_surface_init_2d(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              struct radeon_surface_level *level,
                              unsigned bpe, unsigned tile_mode,
                              unsigned num_pipes, unsigned num_banks,
                              unsigned tile_split,
                              uint64_t offset,
                              unsigned start_level)
{
    uint64_t aligned_offset = offset;
    unsigned tilew, tileh, tileb;
    unsigned mtilew, mtileh, mtileb;
    unsigned slice_pt;
    unsigned i;

    /* compute tile values */
    tilew = 8;
    tileh = 8;
    tileb = tilew * tileh * bpe * surf->nsamples;
    /* slices per tile */
    slice_pt = 1;
    if (tileb > tile_split && tile_split) {
        slice_pt = tileb / tile_split;
    }
    tileb = tileb / slice_pt;

    /* macro tile width & height */
    mtilew = (tilew * surf->bankw * num_pipes) * surf->mtilea;
    mtileh = (tileh * surf->bankh * num_banks) / surf->mtilea;

    /* macro tile bytes */
    mtileb = (mtilew / tilew) * (mtileh / tileh) * tileb;

    if (start_level <= 1) {
        unsigned alignment = MAX2(256, mtileb);
        surf->bo_alignment = MAX2(surf->bo_alignment, alignment);

        if (aligned_offset) {
            aligned_offset = ALIGN(aligned_offset, alignment);
        }
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        level[i].mode = RADEON_SURF_MODE_2D;
        si_surf_minify_2d(surf, level+i, bpe, i, slice_pt, mtilew, mtileh, 1, mtileb, aligned_offset);
        if (level[i].mode == RADEON_SURF_MODE_1D) {
            switch (tile_mode) {
            case SI_TILE_MODE_COLOR_2D_8BPP:
            case SI_TILE_MODE_COLOR_2D_16BPP:
            case SI_TILE_MODE_COLOR_2D_32BPP:
            case SI_TILE_MODE_COLOR_2D_64BPP:
                tile_mode = SI_TILE_MODE_COLOR_1D;
                break;
            case SI_TILE_MODE_COLOR_2D_SCANOUT_16BPP:
            case SI_TILE_MODE_COLOR_2D_SCANOUT_32BPP:
                tile_mode = SI_TILE_MODE_COLOR_1D_SCANOUT;
                break;
            case SI_TILE_MODE_DEPTH_STENCIL_2D:
                tile_mode = SI_TILE_MODE_DEPTH_STENCIL_1D;
                break;
            default:
                return -EINVAL;
            }
            return si_surface_init_1d(surf_man, surf, level, bpe, tile_mode, offset, i);
        }
        /* level0 and first mipmap need to have alignment */
        aligned_offset = offset = surf->bo_size;
        if (i == 0) {
            aligned_offset = ALIGN(aligned_offset, surf->bo_alignment);
        }
        if (surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX) {
            if (surf->level == level) {
                surf->tiling_index[i] = tile_mode;
                /* it's ok because stencil is done after */
                surf->stencil_tiling_index[i] = tile_mode;
            } else {
                surf->stencil_tiling_index[i] = tile_mode;
            }
        }
    }
    return 0;
}

static int si_surface_init_2d_miptrees(struct radeon_surface_manager *surf_man,
                                       struct radeon_surface *surf,
                                       unsigned tile_mode, unsigned stencil_tile_mode)
{
    unsigned num_pipes, num_banks;
    uint32_t gb_tile_mode;
    int r;

    /* retrieve tiling mode value */
    gb_tile_mode = surf_man->hw_info.tile_mode_array[tile_mode];
    si_gb_tile_mode(gb_tile_mode, &num_pipes, &num_banks, NULL, NULL, NULL, NULL);

    r = si_surface_init_2d(surf_man, surf, surf->level, surf->bpe, tile_mode, num_pipes, num_banks, surf->tile_split, 0, 0);
    if (r) {
        return r;
    }

    if (surf->flags & RADEON_SURF_SBUFFER) {
        r = si_surface_init_2d(surf_man, surf, surf->stencil_level, 1, stencil_tile_mode, num_pipes, num_banks, surf->stencil_tile_split, surf->bo_size, 0);
        surf->stencil_offset = surf->stencil_level[0].offset;
    }
    return r;
}

static int si_surface_init(struct radeon_surface_manager *surf_man,
                           struct radeon_surface *surf)
{
    unsigned mode, tile_mode, stencil_tile_mode;
    int r;

    /* MSAA surfaces support the 2D mode only. */
    if (surf->nsamples > 1) {
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_2D, MODE);
    }

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER)) {
        /* zbuffer only support 1D or 2D tiled surface */
        switch (mode) {
        case RADEON_SURF_MODE_1D:
        case RADEON_SURF_MODE_2D:
            break;
        default:
            mode = RADEON_SURF_MODE_1D;
            surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
            surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_1D, MODE);
            break;
        }
    }

    r = si_surface_sanity(surf_man, surf, mode, &tile_mode, &stencil_tile_mode);
    if (r) {
        return r;
    }

    surf->stencil_offset = 0;
    surf->bo_alignment = 0;

    /* check tiling mode */
    switch (mode) {
    case RADEON_SURF_MODE_LINEAR:
        r = r6_surface_init_linear(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_LINEAR_ALIGNED:
        r = si_surface_init_linear_aligned(surf_man, surf, tile_mode, 0, 0);
        break;
    case RADEON_SURF_MODE_1D:
        r = si_surface_init_1d_miptrees(surf_man, surf, tile_mode, stencil_tile_mode);
        break;
    case RADEON_SURF_MODE_2D:
        r = si_surface_init_2d_miptrees(surf_man, surf, tile_mode, stencil_tile_mode);
        break;
    default:
        return -EINVAL;
    }
    return r;
}

/*
 * depending on surface
 */
static int si_surface_best(struct radeon_surface_manager *surf_man,
                           struct radeon_surface *surf)
{
    unsigned mode, tile_mode, stencil_tile_mode;

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER) &&
        !(surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX)) {
        /* depth/stencil force 1d tiling for old mesa */
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_1D, MODE);
    }

    return si_surface_sanity(surf_man, surf, mode, &tile_mode, &stencil_tile_mode);
}


/* ===========================================================================
 * Sea Islands family
 */
#define CIK__GB_TILE_MODE__PIPE_CONFIG(x)        (((x) >> 6) & 0x1f)
#define     CIK__PIPE_CONFIG__ADDR_SURF_P2               0
#define     CIK__PIPE_CONFIG__ADDR_SURF_P4_8x16          4
#define     CIK__PIPE_CONFIG__ADDR_SURF_P4_16x16         5
#define     CIK__PIPE_CONFIG__ADDR_SURF_P4_16x32         6
#define     CIK__PIPE_CONFIG__ADDR_SURF_P4_32x32         7
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_16x16_8x16    8
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_16x32_8x16    9
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_32x32_8x16    10
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_16x32_16x16   11
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x16   12
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x32   13
#define     CIK__PIPE_CONFIG__ADDR_SURF_P8_32x64_32x32   14
#define     CIK__PIPE_CONFIG__ADDR_SURF_P16_32X32_8X16   16
#define     CIK__PIPE_CONFIG__ADDR_SURF_P16_32X32_16X16  17
#define CIK__GB_TILE_MODE__TILE_SPLIT(x)         (((x) >> 11) & 0x7)
#define     CIK__TILE_SPLIT__64B                         0
#define     CIK__TILE_SPLIT__128B                        1
#define     CIK__TILE_SPLIT__256B                        2
#define     CIK__TILE_SPLIT__512B                        3
#define     CIK__TILE_SPLIT__1024B                       4
#define     CIK__TILE_SPLIT__2048B                       5
#define     CIK__TILE_SPLIT__4096B                       6
#define CIK__GB_TILE_MODE__SAMPLE_SPLIT(x)         (((x) >> 25) & 0x3)
#define     CIK__SAMPLE_SPLIT__1                         0
#define     CIK__SAMPLE_SPLIT__2                         1
#define     CIK__SAMPLE_SPLIT__4                         2
#define     CIK__SAMPLE_SPLIT__8                         3
#define CIK__GB_MACROTILE_MODE__BANK_WIDTH(x)        ((x) & 0x3)
#define     CIK__BANK_WIDTH__1                           0
#define     CIK__BANK_WIDTH__2                           1
#define     CIK__BANK_WIDTH__4                           2
#define     CIK__BANK_WIDTH__8                           3
#define CIK__GB_MACROTILE_MODE__BANK_HEIGHT(x)       (((x) >> 2) & 0x3)
#define     CIK__BANK_HEIGHT__1                          0
#define     CIK__BANK_HEIGHT__2                          1
#define     CIK__BANK_HEIGHT__4                          2
#define     CIK__BANK_HEIGHT__8                          3
#define CIK__GB_MACROTILE_MODE__MACRO_TILE_ASPECT(x) (((x) >> 4) & 0x3)
#define     CIK__MACRO_TILE_ASPECT__1                    0
#define     CIK__MACRO_TILE_ASPECT__2                    1
#define     CIK__MACRO_TILE_ASPECT__4                    2
#define     CIK__MACRO_TILE_ASPECT__8                    3
#define CIK__GB_MACROTILE_MODE__NUM_BANKS(x)         (((x) >> 6) & 0x3)
#define     CIK__NUM_BANKS__2_BANK                       0
#define     CIK__NUM_BANKS__4_BANK                       1
#define     CIK__NUM_BANKS__8_BANK                       2
#define     CIK__NUM_BANKS__16_BANK                      3


static void cik_get_2d_params(struct radeon_surface_manager *surf_man,
                              unsigned bpe, unsigned nsamples, bool is_color,
                              unsigned tile_mode,
                              uint32_t *num_pipes,
                              uint32_t *tile_split_ptr,
                              uint32_t *num_banks,
                              uint32_t *macro_tile_aspect,
                              uint32_t *bank_w,
                              uint32_t *bank_h)
{
    uint32_t gb_tile_mode = surf_man->hw_info.tile_mode_array[tile_mode];
    unsigned tileb_1x, tileb;
    unsigned gb_macrotile_mode;
    unsigned macrotile_index;
    unsigned tile_split, sample_split;

    if (num_pipes) {
        switch (CIK__GB_TILE_MODE__PIPE_CONFIG(gb_tile_mode)) {
        case CIK__PIPE_CONFIG__ADDR_SURF_P2:
        default:
            *num_pipes = 2;
            break;
        case CIK__PIPE_CONFIG__ADDR_SURF_P4_8x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P4_16x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P4_16x32:
        case CIK__PIPE_CONFIG__ADDR_SURF_P4_32x32:
            *num_pipes = 4;
            break;
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_16x16_8x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_16x32_8x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_32x32_8x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_16x32_16x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_32x32_16x32:
        case CIK__PIPE_CONFIG__ADDR_SURF_P8_32x64_32x32:
            *num_pipes = 8;
            break;
        case CIK__PIPE_CONFIG__ADDR_SURF_P16_32X32_8X16:
        case CIK__PIPE_CONFIG__ADDR_SURF_P16_32X32_16X16:
            *num_pipes = 16;
            break;
        }
    }
    switch (CIK__GB_TILE_MODE__TILE_SPLIT(gb_tile_mode)) {
    default:
    case CIK__TILE_SPLIT__64B:
        tile_split = 64;
        break;
    case CIK__TILE_SPLIT__128B:
        tile_split = 128;
        break;
    case CIK__TILE_SPLIT__256B:
        tile_split = 256;
        break;
    case CIK__TILE_SPLIT__512B:
        tile_split = 512;
        break;
    case CIK__TILE_SPLIT__1024B:
        tile_split = 1024;
        break;
    case CIK__TILE_SPLIT__2048B:
        tile_split = 2048;
        break;
    case CIK__TILE_SPLIT__4096B:
        tile_split = 4096;
        break;
    }
    switch (CIK__GB_TILE_MODE__SAMPLE_SPLIT(gb_tile_mode)) {
    default:
    case CIK__SAMPLE_SPLIT__1:
        sample_split = 1;
        break;
    case CIK__SAMPLE_SPLIT__2:
        sample_split = 2;
        break;
    case CIK__SAMPLE_SPLIT__4:
        sample_split = 4;
        break;
    case CIK__SAMPLE_SPLIT__8:
        sample_split = 8;
        break;
    }

    /* Adjust the tile split. */
    tileb_1x = 8 * 8 * bpe;
    if (is_color) {
        tile_split = MAX2(256, sample_split * tileb_1x);
    }
    tile_split = MIN2(surf_man->hw_info.row_size, tile_split);

    /* Determine the macrotile index. */
    tileb = MIN2(tile_split, nsamples * tileb_1x);

    for (macrotile_index = 0; tileb > 64; macrotile_index++) {
        tileb >>= 1;
    }
    gb_macrotile_mode = surf_man->hw_info.macrotile_mode_array[macrotile_index];

    if (tile_split_ptr) {
        *tile_split_ptr = tile_split;
    }
    if (num_banks) {
        switch (CIK__GB_MACROTILE_MODE__NUM_BANKS(gb_macrotile_mode)) {
        default:
        case CIK__NUM_BANKS__2_BANK:
            *num_banks = 2;
            break;
        case CIK__NUM_BANKS__4_BANK:
            *num_banks = 4;
            break;
        case CIK__NUM_BANKS__8_BANK:
            *num_banks = 8;
            break;
        case CIK__NUM_BANKS__16_BANK:
            *num_banks = 16;
            break;
        }
    }
    if (macro_tile_aspect) {
        switch (CIK__GB_MACROTILE_MODE__MACRO_TILE_ASPECT(gb_macrotile_mode)) {
        default:
        case CIK__MACRO_TILE_ASPECT__1:
            *macro_tile_aspect = 1;
            break;
        case CIK__MACRO_TILE_ASPECT__2:
            *macro_tile_aspect = 2;
            break;
        case CIK__MACRO_TILE_ASPECT__4:
            *macro_tile_aspect = 4;
            break;
        case CIK__MACRO_TILE_ASPECT__8:
            *macro_tile_aspect = 8;
            break;
        }
    }
    if (bank_w) {
        switch (CIK__GB_MACROTILE_MODE__BANK_WIDTH(gb_macrotile_mode)) {
        default:
        case CIK__BANK_WIDTH__1:
            *bank_w = 1;
            break;
        case CIK__BANK_WIDTH__2:
            *bank_w = 2;
            break;
        case CIK__BANK_WIDTH__4:
            *bank_w = 4;
            break;
        case CIK__BANK_WIDTH__8:
            *bank_w = 8;
            break;
        }
    }
    if (bank_h) {
        switch (CIK__GB_MACROTILE_MODE__BANK_HEIGHT(gb_macrotile_mode)) {
        default:
        case CIK__BANK_HEIGHT__1:
            *bank_h = 1;
            break;
        case CIK__BANK_HEIGHT__2:
            *bank_h = 2;
            break;
        case CIK__BANK_HEIGHT__4:
            *bank_h = 4;
            break;
        case CIK__BANK_HEIGHT__8:
            *bank_h = 8;
            break;
        }
    }
}

static int cik_init_hw_info(struct radeon_surface_manager *surf_man)
{
    uint32_t tiling_config;
    drmVersionPtr version;
    int r;

    r = radeon_get_value(surf_man->fd, RADEON_INFO_TILING_CONFIG,
                         &tiling_config);
    if (r) {
        return r;
    }

    surf_man->hw_info.allow_2d = 0;
    version = drmGetVersion(surf_man->fd);
    if (version && version->version_minor >= 35) {
        if (!radeon_get_value(surf_man->fd, RADEON_INFO_SI_TILE_MODE_ARRAY, surf_man->hw_info.tile_mode_array) &&
	    !radeon_get_value(surf_man->fd, RADEON_INFO_CIK_MACROTILE_MODE_ARRAY, surf_man->hw_info.macrotile_mode_array)) {
            surf_man->hw_info.allow_2d = 1;
        }
    }
    drmFreeVersion(version);

    switch (tiling_config & 0xf) {
    case 0:
        surf_man->hw_info.num_pipes = 1;
        break;
    case 1:
        surf_man->hw_info.num_pipes = 2;
        break;
    case 2:
        surf_man->hw_info.num_pipes = 4;
        break;
    case 3:
        surf_man->hw_info.num_pipes = 8;
        break;
    default:
        surf_man->hw_info.num_pipes = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf0) >> 4) {
    case 0:
        surf_man->hw_info.num_banks = 4;
        break;
    case 1:
        surf_man->hw_info.num_banks = 8;
        break;
    case 2:
        surf_man->hw_info.num_banks = 16;
        break;
    default:
        surf_man->hw_info.num_banks = 8;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf00) >> 8) {
    case 0:
        surf_man->hw_info.group_bytes = 256;
        break;
    case 1:
        surf_man->hw_info.group_bytes = 512;
        break;
    default:
        surf_man->hw_info.group_bytes = 256;
        surf_man->hw_info.allow_2d = 0;
        break;
    }

    switch ((tiling_config & 0xf000) >> 12) {
    case 0:
        surf_man->hw_info.row_size = 1024;
        break;
    case 1:
        surf_man->hw_info.row_size = 2048;
        break;
    case 2:
        surf_man->hw_info.row_size = 4096;
        break;
    default:
        surf_man->hw_info.row_size = 4096;
        surf_man->hw_info.allow_2d = 0;
        break;
    }
    return 0;
}

static int cik_surface_sanity(struct radeon_surface_manager *surf_man,
                              struct radeon_surface *surf,
                              unsigned mode, unsigned *tile_mode, unsigned *stencil_tile_mode)
{
    /* check surface dimension */
    if (surf->npix_x > 16384 || surf->npix_y > 16384 || surf->npix_z > 16384) {
        return -EINVAL;
    }

    /* check mipmap last_level */
    if (surf->last_level > 15) {
        return -EINVAL;
    }

    /* force 1d on kernel that can't do 2d */
    if (mode > RADEON_SURF_MODE_1D &&
        (!surf_man->hw_info.allow_2d || !(surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX))) {
        if (surf->nsamples > 1) {
            fprintf(stderr, "radeon: Cannot use 1D tiling for an MSAA surface (%i).\n", __LINE__);
            return -EFAULT;
        }
        mode = RADEON_SURF_MODE_1D;
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(mode, MODE);
    }

    if (surf->nsamples > 1 && mode != RADEON_SURF_MODE_2D) {
        return -EINVAL;
    }

    if (!surf->tile_split) {
        /* default value */
        surf->mtilea = 1;
        surf->bankw = 1;
        surf->bankw = 1;
        surf->tile_split = 64;
        surf->stencil_tile_split = 64;
    }

    switch (mode) {
    case RADEON_SURF_MODE_2D: {
        if (surf->flags & RADEON_SURF_Z_OR_SBUFFER) {
            switch (surf->nsamples) {
            case 1:
                *tile_mode = CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_64;
                break;
            case 2:
            case 4:
                *tile_mode = CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_128;
                break;
            case 8:
                *tile_mode = CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_256;
                break;
            default:
                return -EINVAL;
            }

            if (surf->flags & RADEON_SURF_SBUFFER) {
                *stencil_tile_mode = *tile_mode;

                cik_get_2d_params(surf_man, 1, surf->nsamples, false,
                                  *stencil_tile_mode, NULL,
                                  &surf->stencil_tile_split,
                                  NULL, NULL, NULL, NULL);
            }
        } else if (surf->flags & RADEON_SURF_SCANOUT) {
            *tile_mode = CIK_TILE_MODE_COLOR_2D_SCANOUT;
        } else {
            *tile_mode = CIK_TILE_MODE_COLOR_2D;
        }

        /* retrieve tiling mode values */
        cik_get_2d_params(surf_man, surf->bpe, surf->nsamples,
                          !(surf->flags & RADEON_SURF_Z_OR_SBUFFER), *tile_mode,
                          NULL, &surf->tile_split, NULL, &surf->mtilea,
                          &surf->bankw, &surf->bankh);
        break;
    }
    case RADEON_SURF_MODE_1D:
        if (surf->flags & RADEON_SURF_SBUFFER) {
            *stencil_tile_mode = CIK_TILE_MODE_DEPTH_STENCIL_1D;
        }
        if (surf->flags & RADEON_SURF_ZBUFFER) {
            *tile_mode = CIK_TILE_MODE_DEPTH_STENCIL_1D;
        } else if (surf->flags & RADEON_SURF_SCANOUT) {
            *tile_mode = SI_TILE_MODE_COLOR_1D_SCANOUT;
        } else {
            *tile_mode = SI_TILE_MODE_COLOR_1D;
        }
        break;
    case RADEON_SURF_MODE_LINEAR_ALIGNED:
    default:
        *tile_mode = SI_TILE_MODE_COLOR_LINEAR_ALIGNED;
    }

    return 0;
}

static int cik_surface_init_2d(struct radeon_surface_manager *surf_man,
                               struct radeon_surface *surf,
                               struct radeon_surface_level *level,
                               unsigned bpe, unsigned tile_mode,
                               unsigned tile_split,
                               unsigned num_pipes, unsigned num_banks,
                               uint64_t offset,
                               unsigned start_level)
{
    uint64_t aligned_offset = offset;
    unsigned tilew, tileh, tileb_1x, tileb;
    unsigned mtilew, mtileh, mtileb;
    unsigned slice_pt;
    unsigned i;

    /* compute tile values */
    tilew = 8;
    tileh = 8;
    tileb_1x = tilew * tileh * bpe;

    tile_split = MIN2(surf_man->hw_info.row_size, tile_split);

    tileb = surf->nsamples * tileb_1x;

    /* slices per tile */
    slice_pt = 1;
    if (tileb > tile_split && tile_split) {
        slice_pt = tileb / tile_split;
        tileb = tileb / slice_pt;
    }

    /* macro tile width & height */
    mtilew = (tilew * surf->bankw * num_pipes) * surf->mtilea;
    mtileh = (tileh * surf->bankh * num_banks) / surf->mtilea;

    /* macro tile bytes */
    mtileb = (mtilew / tilew) * (mtileh / tileh) * tileb;

    if (start_level <= 1) {
        unsigned alignment = MAX2(256, mtileb);
        surf->bo_alignment = MAX2(surf->bo_alignment, alignment);

        if (aligned_offset) {
            aligned_offset = ALIGN(aligned_offset, alignment);
        }
    }

    /* build mipmap tree */
    for (i = start_level; i <= surf->last_level; i++) {
        level[i].mode = RADEON_SURF_MODE_2D;
        si_surf_minify_2d(surf, level+i, bpe, i, slice_pt, mtilew, mtileh, 1, mtileb, aligned_offset);
        if (level[i].mode == RADEON_SURF_MODE_1D) {
            switch (tile_mode) {
            case CIK_TILE_MODE_COLOR_2D:
                tile_mode = SI_TILE_MODE_COLOR_1D;
                break;
            case CIK_TILE_MODE_COLOR_2D_SCANOUT:
                tile_mode = SI_TILE_MODE_COLOR_1D_SCANOUT;
                break;
            case CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_64:
            case CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_128:
            case CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_256:
            case CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_512:
            case CIK_TILE_MODE_DEPTH_STENCIL_2D_TILESPLIT_ROW_SIZE:
                tile_mode = CIK_TILE_MODE_DEPTH_STENCIL_1D;
                break;
            default:
                return -EINVAL;
            }
            return si_surface_init_1d(surf_man, surf, level, bpe, tile_mode, offset, i);
        }
        /* level0 and first mipmap need to have alignment */
        aligned_offset = offset = surf->bo_size;
        if (i == 0) {
            aligned_offset = ALIGN(aligned_offset, surf->bo_alignment);
        }
        if (surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX) {
            if (surf->level == level) {
                surf->tiling_index[i] = tile_mode;
                /* it's ok because stencil is done after */
                surf->stencil_tiling_index[i] = tile_mode;
            } else {
                surf->stencil_tiling_index[i] = tile_mode;
            }
        }
    }
    return 0;
}

static int cik_surface_init_2d_miptrees(struct radeon_surface_manager *surf_man,
                                        struct radeon_surface *surf,
                                        unsigned tile_mode, unsigned stencil_tile_mode)
{
    int r;
    uint32_t num_pipes, num_banks;

    cik_get_2d_params(surf_man, surf->bpe, surf->nsamples,
                        !(surf->flags & RADEON_SURF_Z_OR_SBUFFER), tile_mode,
                        &num_pipes, NULL, &num_banks, NULL, NULL, NULL);

    r = cik_surface_init_2d(surf_man, surf, surf->level, surf->bpe, tile_mode,
                            surf->tile_split, num_pipes, num_banks, 0, 0);
    if (r) {
        return r;
    }

    if (surf->flags & RADEON_SURF_SBUFFER) {
        r = cik_surface_init_2d(surf_man, surf, surf->stencil_level, 1, stencil_tile_mode,
                                surf->stencil_tile_split, num_pipes, num_banks,
                                surf->bo_size, 0);
        surf->stencil_offset = surf->stencil_level[0].offset;
    }
    return r;
}

static int cik_surface_init(struct radeon_surface_manager *surf_man,
                            struct radeon_surface *surf)
{
    unsigned mode, tile_mode, stencil_tile_mode;
    int r;

    /* MSAA surfaces support the 2D mode only. */
    if (surf->nsamples > 1) {
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_2D, MODE);
    }

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER)) {
        /* zbuffer only support 1D or 2D tiled surface */
        switch (mode) {
        case RADEON_SURF_MODE_1D:
        case RADEON_SURF_MODE_2D:
            break;
        default:
            mode = RADEON_SURF_MODE_1D;
            surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
            surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_1D, MODE);
            break;
        }
    }

    r = cik_surface_sanity(surf_man, surf, mode, &tile_mode, &stencil_tile_mode);
    if (r) {
        return r;
    }

    surf->stencil_offset = 0;
    surf->bo_alignment = 0;

    /* check tiling mode */
    switch (mode) {
    case RADEON_SURF_MODE_LINEAR:
        r = r6_surface_init_linear(surf_man, surf, 0, 0);
        break;
    case RADEON_SURF_MODE_LINEAR_ALIGNED:
        r = si_surface_init_linear_aligned(surf_man, surf, tile_mode, 0, 0);
        break;
    case RADEON_SURF_MODE_1D:
        r = si_surface_init_1d_miptrees(surf_man, surf, tile_mode, stencil_tile_mode);
        break;
    case RADEON_SURF_MODE_2D:
        r = cik_surface_init_2d_miptrees(surf_man, surf, tile_mode, stencil_tile_mode);
        break;
    default:
        return -EINVAL;
    }
    return r;
}

/*
 * depending on surface
 */
static int cik_surface_best(struct radeon_surface_manager *surf_man,
                            struct radeon_surface *surf)
{
    unsigned mode, tile_mode, stencil_tile_mode;

    /* tiling mode */
    mode = (surf->flags >> RADEON_SURF_MODE_SHIFT) & RADEON_SURF_MODE_MASK;

    if (surf->flags & (RADEON_SURF_ZBUFFER | RADEON_SURF_SBUFFER) &&
        !(surf->flags & RADEON_SURF_HAS_TILE_MODE_INDEX)) {
        /* depth/stencil force 1d tiling for old mesa */
        surf->flags = RADEON_SURF_CLR(surf->flags, MODE);
        surf->flags |= RADEON_SURF_SET(RADEON_SURF_MODE_1D, MODE);
    }

    return cik_surface_sanity(surf_man, surf, mode, &tile_mode, &stencil_tile_mode);
}


/* ===========================================================================
 * public API
 */
struct radeon_surface_manager *radeon_surface_manager_new(int fd)
{
    struct radeon_surface_manager *surf_man;

    surf_man = calloc(1, sizeof(struct radeon_surface_manager));
    if (surf_man == NULL) {
        return NULL;
    }
    surf_man->fd = fd;
    if (radeon_get_value(fd, RADEON_INFO_DEVICE_ID, &surf_man->device_id)) {
        goto out_err;
    }
    if (radeon_get_family(surf_man)) {
        goto out_err;
    }

    if (surf_man->family <= CHIP_RV740) {
        if (r6_init_hw_info(surf_man)) {
            goto out_err;
        }
        surf_man->surface_init = &r6_surface_init;
        surf_man->surface_best = &r6_surface_best;
    } else if (surf_man->family <= CHIP_ARUBA) {
        if (eg_init_hw_info(surf_man)) {
            goto out_err;
        }
        surf_man->surface_init = &eg_surface_init;
        surf_man->surface_best = &eg_surface_best;
    } else if (surf_man->family < CHIP_BONAIRE) {
        if (si_init_hw_info(surf_man)) {
            goto out_err;
        }
        surf_man->surface_init = &si_surface_init;
        surf_man->surface_best = &si_surface_best;
    } else {
        if (cik_init_hw_info(surf_man)) {
            goto out_err;
        }
        surf_man->surface_init = &cik_surface_init;
        surf_man->surface_best = &cik_surface_best;
    }

    return surf_man;
out_err:
    free(surf_man);
    return NULL;
}

void radeon_surface_manager_free(struct radeon_surface_manager *surf_man)
{
    free(surf_man);
}

static int radeon_surface_sanity(struct radeon_surface_manager *surf_man,
                                 struct radeon_surface *surf,
                                 unsigned type,
                                 unsigned mode)
{
    if (surf_man == NULL || surf_man->surface_init == NULL || surf == NULL) {
        return -EINVAL;
    }

    /* all dimension must be at least 1 ! */
    if (!surf->npix_x || !surf->npix_y || !surf->npix_z) {
        return -EINVAL;
    }
    if (!surf->blk_w || !surf->blk_h || !surf->blk_d) {
        return -EINVAL;
    }
    if (!surf->array_size) {
        return -EINVAL;
    }
    /* array size must be a power of 2 */
    surf->array_size = next_power_of_two(surf->array_size);

    switch (surf->nsamples) {
    case 1:
    case 2:
    case 4:
    case 8:
        break;
    default:
        return -EINVAL;
    }
    /* check type */
    switch (type) {
    case RADEON_SURF_TYPE_1D:
        if (surf->npix_y > 1) {
            return -EINVAL;
        }
    case RADEON_SURF_TYPE_2D:
        if (surf->npix_z > 1) {
            return -EINVAL;
        }
        break;
    case RADEON_SURF_TYPE_CUBEMAP:
        if (surf->npix_z > 1) {
            return -EINVAL;
        }
        /* deal with cubemap as they were texture array */
        if (surf_man->family >= CHIP_RV770) {
            surf->array_size = 8;
        } else {
            surf->array_size = 6;
        }
        break;
    case RADEON_SURF_TYPE_3D:
        break;
    case RADEON_SURF_TYPE_1D_ARRAY:
        if (surf->npix_y > 1) {
            return -EINVAL;
        }
    case RADEON_SURF_TYPE_2D_ARRAY:
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

int radeon_surface_init(struct radeon_surface_manager *surf_man,
                        struct radeon_surface *surf)
{
    unsigned mode, type;
    int r;

    type = RADEON_SURF_GET(surf->flags, TYPE);
    mode = RADEON_SURF_GET(surf->flags, MODE);

    r = radeon_surface_sanity(surf_man, surf, type, mode);
    if (r) {
        return r;
    }
    return surf_man->surface_init(surf_man, surf);
}

int radeon_surface_best(struct radeon_surface_manager *surf_man,
                        struct radeon_surface *surf)
{
    unsigned mode, type;
    int r;

    type = RADEON_SURF_GET(surf->flags, TYPE);
    mode = RADEON_SURF_GET(surf->flags, MODE);

    r = radeon_surface_sanity(surf_man, surf, type, mode);
    if (r) {
        return r;
    }
    return surf_man->surface_best(surf_man, surf);
}