/*
|
* Copyright 2013 Vadim Girlin <vadimgirlin@gmail.com>
|
*
|
* 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
|
* on 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 above copyright notice and this permission notice (including the next
|
* paragraph) shall be included in all copies or substantial portions of the
|
* Software.
|
*
|
* 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 AUTHOR(S) AND/OR THEIR 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.
|
*
|
* Authors:
|
* Vadim Girlin
|
*/
|
|
#define RA_DEBUG 0
|
|
#if RA_DEBUG
|
#define RA_DUMP(q) do { q } while (0)
|
#else
|
#define RA_DUMP(q)
|
#endif
|
|
#include <cstring>
|
|
#include "sb_bc.h"
|
#include "sb_shader.h"
|
#include "sb_pass.h"
|
|
namespace r600_sb {
|
|
class regbits {
|
typedef uint32_t basetype;
|
static const unsigned bt_bytes = sizeof(basetype);
|
static const unsigned bt_index_shift = 5;
|
static const unsigned bt_index_mask = (1u << bt_index_shift) - 1;
|
static const unsigned bt_bits = bt_bytes << 3;
|
static const unsigned size = MAX_GPR * 4 / bt_bits;
|
|
basetype dta[size];
|
|
unsigned num_temps;
|
|
public:
|
|
regbits(unsigned num_temps) : dta(), num_temps(num_temps) {}
|
regbits(unsigned num_temps, unsigned value) : num_temps(num_temps)
|
{ set_all(value); }
|
|
regbits(shader &sh, val_set &vs) : num_temps(sh.get_ctx().alu_temp_gprs)
|
{ set_all(1); from_val_set(sh, vs); }
|
|
void set_all(unsigned val);
|
void from_val_set(shader &sh, val_set &vs);
|
|
void set(unsigned index);
|
void clear(unsigned index);
|
bool get(unsigned index);
|
|
void set(unsigned index, unsigned val);
|
|
sel_chan find_free_bit();
|
sel_chan find_free_chans(unsigned mask);
|
sel_chan find_free_chan_by_mask(unsigned mask);
|
sel_chan find_free_array(unsigned size, unsigned mask);
|
|
void dump();
|
};
|
|
// =======================================
|
|
void regbits::dump() {
|
for (unsigned i = 0; i < size * bt_bits; ++i) {
|
|
if (!(i & 31))
|
sblog << "\n";
|
|
if (!(i & 3)) {
|
sblog.print_w(i / 4, 7);
|
sblog << " ";
|
}
|
|
sblog << (get(i) ? 1 : 0);
|
}
|
}
|
|
|
void regbits::set_all(unsigned v) {
|
memset(&dta, v ? 0xFF : 0x00, size * bt_bytes);
|
}
|
|
void regbits::from_val_set(shader &sh, val_set& vs) {
|
val_set &s = vs;
|
unsigned g;
|
for (val_set::iterator I = s.begin(sh), E = s.end(sh); I != E; ++I) {
|
value *v = *I;
|
if (v->is_any_gpr()) {
|
g = v->get_final_gpr();
|
if (!g)
|
continue;
|
} else
|
continue;
|
|
assert(g);
|
--g;
|
assert(g < 512);
|
clear(g);
|
}
|
}
|
|
void regbits::set(unsigned index) {
|
unsigned ih = index >> bt_index_shift;
|
unsigned il = index & bt_index_mask;
|
dta[ih] |= ((basetype)1u << il);
|
}
|
|
void regbits::clear(unsigned index) {
|
unsigned ih = index >> bt_index_shift;
|
unsigned il = index & bt_index_mask;
|
assert(ih < size);
|
dta[ih] &= ~((basetype)1u << il);
|
}
|
|
bool regbits::get(unsigned index) {
|
unsigned ih = index >> bt_index_shift;
|
unsigned il = index & bt_index_mask;
|
return dta[ih] & ((basetype)1u << il);
|
}
|
|
void regbits::set(unsigned index, unsigned val) {
|
unsigned ih = index >> bt_index_shift;
|
unsigned il = index & bt_index_mask;
|
basetype bm = 1u << il;
|
dta[ih] = (dta[ih] & ~bm) | (val << il);
|
}
|
|
// free register for ra means the bit is set
|
sel_chan regbits::find_free_bit() {
|
unsigned elt = 0;
|
unsigned bit = 0;
|
|
while (elt < size && !dta[elt])
|
++elt;
|
|
if (elt >= size)
|
return 0;
|
|
bit = __builtin_ctz(dta[elt]) + (elt << bt_index_shift);
|
|
assert(bit < ((MAX_GPR - num_temps) << 2));
|
|
return bit + 1;
|
}
|
|
// find free gpr component to use as indirectly addressable array
|
sel_chan regbits::find_free_array(unsigned length, unsigned mask) {
|
unsigned cc[4] = {};
|
|
// FIXME optimize this. though hopefully we won't have a lot of arrays
|
for (unsigned a = 0; a < MAX_GPR - num_temps; ++a) {
|
for(unsigned c = 0; c < MAX_CHAN; ++c) {
|
if (mask & (1 << c)) {
|
if (get((a << 2) | c)) {
|
if (++cc[c] == length)
|
return sel_chan(a - length + 1, c);
|
} else {
|
cc[c] = 0;
|
}
|
}
|
}
|
}
|
return 0;
|
}
|
|
sel_chan regbits::find_free_chans(unsigned mask) {
|
unsigned elt = 0;
|
unsigned bit = 0;
|
|
assert (!(mask & ~0xF));
|
basetype cd = dta[elt];
|
|
do {
|
if (!cd) {
|
if (++elt < size) {
|
cd = dta[elt];
|
bit = 0;
|
continue;
|
} else
|
return 0;
|
}
|
|
unsigned p = __builtin_ctz(cd) & ~(basetype)3u;
|
|
assert (p <= bt_bits - bit);
|
bit += p;
|
cd >>= p;
|
|
if ((cd & mask) == mask) {
|
return ((elt << bt_index_shift) | bit) + 1;
|
}
|
|
bit += 4;
|
cd >>= 4;
|
|
} while (1);
|
|
return 0;
|
}
|
|
sel_chan regbits::find_free_chan_by_mask(unsigned mask) {
|
unsigned elt = 0;
|
unsigned bit = 0;
|
|
assert (!(mask & ~0xF));
|
basetype cd = dta[elt];
|
|
do {
|
if (!cd) {
|
if (++elt < size) {
|
cd = dta[elt];
|
bit = 0;
|
continue;
|
} else
|
return 0;
|
}
|
|
unsigned p = __builtin_ctz(cd) & ~(basetype)3u;
|
|
assert (p <= bt_bits - bit);
|
bit += p;
|
cd >>= p;
|
|
if (cd & mask) {
|
unsigned nb = __builtin_ctz(cd & mask);
|
unsigned ofs = ((elt << bt_index_shift) | bit);
|
return nb + ofs + 1;
|
}
|
|
bit += 4;
|
cd >>= 4;
|
|
} while (1);
|
|
return 0;
|
}
|
|
// ================================
|
|
void ra_init::alloc_arrays() {
|
|
gpr_array_vec &ga = sh.arrays();
|
|
for(gpr_array_vec::iterator I = ga.begin(), E = ga.end(); I != E; ++I) {
|
gpr_array *a = *I;
|
|
RA_DUMP(
|
sblog << "array [" << a->array_size << "] at " << a->base_gpr << "\n";
|
sblog << "\n";
|
);
|
|
// skip preallocated arrays (e.g. with preloaded inputs)
|
if (a->gpr) {
|
RA_DUMP( sblog << " FIXED at " << a->gpr << "\n"; );
|
continue;
|
}
|
|
bool dead = a->is_dead();
|
|
if (dead) {
|
RA_DUMP( sblog << " DEAD\n"; );
|
continue;
|
}
|
|
val_set &s = a->interferences;
|
|
|
for (val_set::iterator I = s.begin(sh), E = s.end(sh); I != E; ++I) {
|
value *v = *I;
|
if (v->array == a)
|
s.remove_val(v);
|
}
|
|
RA_DUMP(
|
sblog << " interf: ";
|
dump::dump_set(sh, s);
|
sblog << "\n";
|
);
|
|
regbits rb(sh, s);
|
|
sel_chan base = rb.find_free_array(a->array_size,
|
(1 << a->base_gpr.chan()));
|
|
RA_DUMP( sblog << " found base: " << base << "\n"; );
|
|
a->gpr = base;
|
}
|
}
|
|
|
int ra_init::run() {
|
|
alloc_arrays();
|
|
ra_node(sh.root);
|
return 0;
|
}
|
|
void ra_init::ra_node(container_node* c) {
|
|
for (node_iterator I = c->begin(), E = c->end(); I != E; ++I) {
|
node *n = *I;
|
if (n->type == NT_OP) {
|
process_op(n);
|
}
|
if (n->is_container() && !n->is_alu_packed()) {
|
ra_node(static_cast<container_node*>(n));
|
}
|
}
|
}
|
|
void ra_init::process_op(node* n) {
|
|
bool copy = n->is_copy_mov();
|
|
RA_DUMP(
|
sblog << "ra_init: process_op : ";
|
dump::dump_op(n);
|
sblog << "\n";
|
);
|
|
if (n->is_alu_packed()) {
|
for (vvec::iterator I = n->src.begin(), E = n->src.end(); I != E; ++I) {
|
value *v = *I;
|
if (v && v->is_sgpr() && v->constraint &&
|
v->constraint->kind == CK_PACKED_BS) {
|
color_bs_constraint(v->constraint);
|
break;
|
}
|
}
|
}
|
|
if (n->is_fetch_inst() || n->is_cf_inst()) {
|
for (vvec::iterator I = n->src.begin(), E = n->src.end(); I != E; ++I) {
|
value *v = *I;
|
if (v && v->is_sgpr())
|
color(v);
|
}
|
}
|
|
for (vvec::iterator I = n->dst.begin(), E = n->dst.end(); I != E; ++I) {
|
value *v = *I;
|
if (!v)
|
continue;
|
if (v->is_sgpr()) {
|
if (!v->gpr) {
|
if (copy && !v->constraint) {
|
value *s = *(n->src.begin() + (I - n->dst.begin()));
|
assert(s);
|
if (s->is_sgpr()) {
|
assign_color(v, s->gpr);
|
}
|
} else
|
color(v);
|
}
|
}
|
}
|
}
|
|
void ra_init::color_bs_constraint(ra_constraint* c) {
|
vvec &vv = c->values;
|
assert(vv.size() <= 8);
|
|
RA_DUMP(
|
sblog << "color_bs_constraint: ";
|
dump::dump_vec(vv);
|
sblog << "\n";
|
);
|
|
regbits rb(ctx.alu_temp_gprs);
|
|
unsigned chan_count[4] = {};
|
unsigned allowed_chans = 0x0F;
|
|
for (vvec::iterator I = vv.begin(), E = vv.end(); I != E; ++I) {
|
value *v = *I;
|
|
if (!v || v->is_dead())
|
continue;
|
|
sel_chan gpr = v->get_final_gpr();
|
|
val_set interf;
|
|
if (v->chunk)
|
sh.coal.get_chunk_interferences(v->chunk, interf);
|
else
|
interf = v->interferences;
|
|
RA_DUMP(
|
sblog << " processing " << *v << " interferences : ";
|
dump::dump_set(sh, interf);
|
sblog << "\n";
|
);
|
|
if (gpr) {
|
unsigned chan = gpr.chan();
|
if (chan_count[chan] < 3) {
|
++chan_count[chan];
|
continue;
|
} else {
|
v->flags &= ~VLF_FIXED;
|
allowed_chans &= ~(1 << chan);
|
assert(allowed_chans);
|
}
|
}
|
|
v->gpr = 0;
|
|
gpr = 1;
|
rb.set_all(1);
|
|
|
rb.from_val_set(sh, interf);
|
|
RA_DUMP(
|
sblog << " regbits : ";
|
rb.dump();
|
sblog << "\n";
|
);
|
|
while (allowed_chans && gpr.sel() < sh.num_nontemp_gpr()) {
|
|
while (rb.get(gpr - 1) == 0)
|
gpr = gpr + 1;
|
|
RA_DUMP(
|
sblog << " trying " << gpr << "\n";
|
);
|
|
unsigned chan = gpr.chan();
|
if (chan_count[chan] < 3) {
|
++chan_count[chan];
|
|
if (v->chunk) {
|
vvec::iterator F = std::find(v->chunk->values.begin(),
|
v->chunk->values.end(),
|
v);
|
v->chunk->values.erase(F);
|
v->chunk = NULL;
|
}
|
|
assign_color(v, gpr);
|
break;
|
} else {
|
allowed_chans &= ~(1 << chan);
|
}
|
gpr = gpr + 1;
|
}
|
|
if (!gpr) {
|
sblog << "color_bs_constraint: failed...\n";
|
assert(!"coloring failed");
|
}
|
}
|
}
|
|
void ra_init::color(value* v) {
|
|
if (v->constraint && v->constraint->kind == CK_PACKED_BS) {
|
color_bs_constraint(v->constraint);
|
return;
|
}
|
|
if (v->chunk && v->chunk->is_fixed())
|
return;
|
|
RA_DUMP(
|
sblog << "coloring ";
|
dump::dump_val(v);
|
sblog << " interferences ";
|
dump::dump_set(sh, v->interferences);
|
sblog << "\n";
|
);
|
|
if (v->is_reg_pinned()) {
|
assert(v->is_chan_pinned());
|
assign_color(v, v->pin_gpr);
|
return;
|
}
|
|
regbits rb(sh, v->interferences);
|
sel_chan c;
|
|
if (v->is_chan_pinned()) {
|
RA_DUMP( sblog << "chan_pinned = " << v->pin_gpr.chan() << " "; );
|
unsigned mask = 1 << v->pin_gpr.chan();
|
c = rb.find_free_chans(mask) + v->pin_gpr.chan();
|
} else {
|
unsigned cm = get_preferable_chan_mask();
|
RA_DUMP( sblog << "pref chan mask: " << cm << "\n"; );
|
c = rb.find_free_chan_by_mask(cm);
|
}
|
|
assert(c && c.sel() < 128 - ctx.alu_temp_gprs && "color failed");
|
assign_color(v, c);
|
}
|
|
void ra_init::assign_color(value* v, sel_chan c) {
|
add_prev_chan(c.chan());
|
v->gpr = c;
|
RA_DUMP(
|
sblog << "colored ";
|
dump::dump_val(v);
|
sblog << " to " << c << "\n";
|
);
|
}
|
|
// ===================================================
|
|
int ra_split::run() {
|
split(sh.root);
|
return 0;
|
}
|
|
void ra_split::split_phi_src(container_node *loc, container_node *c,
|
unsigned id, bool loop) {
|
for (node_iterator I = c->begin(), E = c->end(); I != E; ++I) {
|
node *p = *I;
|
value* &v = p->src[id], *d = p->dst[0];
|
assert(v);
|
|
if (!d->is_sgpr() || v->is_undef())
|
continue;
|
|
value *t = sh.create_temp_value();
|
if (loop && id == 0)
|
loc->insert_before(sh.create_copy_mov(t, v));
|
else
|
loc->push_back(sh.create_copy_mov(t, v));
|
v = t;
|
|
sh.coal.add_edge(v, d, coalescer::phi_cost);
|
}
|
}
|
|
void ra_split::split_phi_dst(node* loc, container_node *c, bool loop) {
|
for (node_iterator I = c->begin(), E = c->end(); I != E; ++I) {
|
node *p = *I;
|
value* &v = p->dst[0];
|
assert(v);
|
|
if (!v->is_sgpr())
|
continue;
|
|
value *t = sh.create_temp_value();
|
node *cp = sh.create_copy_mov(v, t);
|
if (loop)
|
static_cast<container_node*>(loc)->push_front(cp);
|
else
|
loc->insert_after(cp);
|
v = t;
|
}
|
}
|
|
|
void ra_split::init_phi_constraints(container_node *c) {
|
for (node_iterator I = c->begin(), E = c->end(); I != E; ++I) {
|
node *p = *I;
|
ra_constraint *cc = sh.coal.create_constraint(CK_PHI);
|
cc->values.push_back(p->dst[0]);
|
|
for (vvec::iterator I = p->src.begin(), E = p->src.end(); I != E; ++I) {
|
value *v = *I;
|
if (v->is_sgpr())
|
cc->values.push_back(v);
|
}
|
|
cc->update_values();
|
}
|
}
|
|
void ra_split::split(container_node* n) {
|
|
if (n->type == NT_DEPART) {
|
depart_node *d = static_cast<depart_node*>(n);
|
if (d->target->phi)
|
split_phi_src(d, d->target->phi, d->dep_id, false);
|
} else if (n->type == NT_REPEAT) {
|
repeat_node *r = static_cast<repeat_node*>(n);
|
if (r->target->loop_phi)
|
split_phi_src(r, r->target->loop_phi, r->rep_id, true);
|
} else if (n->type == NT_REGION) {
|
region_node *r = static_cast<region_node*>(n);
|
if (r->phi) {
|
split_phi_dst(r, r->phi, false);
|
}
|
if (r->loop_phi) {
|
split_phi_dst(r->get_entry_code_location(), r->loop_phi,
|
true);
|
split_phi_src(r, r->loop_phi, 0, true);
|
}
|
}
|
|
for (node_riterator N, I = n->rbegin(), E = n->rend(); I != E; I = N) {
|
N = I;
|
++N;
|
node *o = *I;
|
if (o->type == NT_OP) {
|
split_op(o);
|
} else if (o->is_container()) {
|
split(static_cast<container_node*>(o));
|
}
|
}
|
|
if (n->type == NT_REGION) {
|
region_node *r = static_cast<region_node*>(n);
|
if (r->phi)
|
init_phi_constraints(r->phi);
|
if (r->loop_phi)
|
init_phi_constraints(r->loop_phi);
|
}
|
}
|
|
void ra_split::split_op(node* n) {
|
switch(n->subtype) {
|
case NST_ALU_PACKED_INST:
|
split_alu_packed(static_cast<alu_packed_node*>(n));
|
break;
|
case NST_FETCH_INST:
|
case NST_CF_INST:
|
split_vector_inst(n);
|
default:
|
break;
|
}
|
}
|
|
void ra_split::split_packed_ins(alu_packed_node *n) {
|
vvec vv = n->src;
|
vvec sv, dv;
|
|
for (vvec::iterator I = vv.begin(), E = vv.end(); I != E; ++I) {
|
|
value *&v = *I;
|
|
if (v && v->is_any_gpr() && !v->is_undef()) {
|
|
vvec::iterator F = std::find(sv.begin(), sv.end(), v);
|
value *t;
|
|
if (F != sv.end()) {
|
t = *(dv.begin() + (F - sv.begin()));
|
} else {
|
t = sh.create_temp_value();
|
sv.push_back(v);
|
dv.push_back(t);
|
}
|
v = t;
|
}
|
}
|
|
unsigned cnt = sv.size();
|
|
if (cnt > 0) {
|
n->src = vv;
|
for (vvec::iterator SI = sv.begin(), DI = dv.begin(), SE = sv.end();
|
SI != SE; ++SI, ++DI) {
|
n->insert_before(sh.create_copy_mov(*DI, *SI));
|
}
|
|
ra_constraint *c = sh.coal.create_constraint(CK_PACKED_BS);
|
c->values = dv;
|
c->update_values();
|
}
|
}
|
|
// TODO handle other packed ops for cayman
|
void ra_split::split_alu_packed(alu_packed_node* n) {
|
switch (n->op()) {
|
case ALU_OP2_DOT4:
|
case ALU_OP2_DOT4_IEEE:
|
case ALU_OP2_CUBE:
|
split_packed_ins(n);
|
break;
|
default:
|
break;
|
}
|
}
|
|
void ra_split::split_vec(vvec &vv, vvec &v1, vvec &v2, bool allow_swz) {
|
unsigned ch = 0;
|
for (vvec::iterator I = vv.begin(), E = vv.end(); I != E; ++I, ++ch) {
|
|
value* &o = *I;
|
|
if (o) {
|
|
assert(!o->is_dead());
|
|
if (o->is_undef() || o->is_geometry_emit())
|
continue;
|
|
if (allow_swz && o->is_float_0_or_1())
|
continue;
|
|
value *t;
|
vvec::iterator F =
|
allow_swz ? std::find(v2.begin(), v2.end(), o) : v2.end();
|
|
if (F != v2.end()) {
|
t = *(v1.begin() + (F - v2.begin()));
|
} else {
|
t = sh.create_temp_value();
|
|
if (!allow_swz) {
|
t->flags |= VLF_PIN_CHAN;
|
t->pin_gpr = sel_chan(0, ch);
|
}
|
|
v2.push_back(o);
|
v1.push_back(t);
|
}
|
o = t;
|
}
|
}
|
}
|
|
void ra_split::split_vector_inst(node* n) {
|
ra_constraint *c;
|
|
bool call_fs = n->is_cf_op(CF_OP_CALL_FS);
|
bool no_src_swizzle = n->is_cf_inst() && (n->cf_op_flags() & CF_MEM);
|
|
no_src_swizzle |= n->is_fetch_op(FETCH_OP_VFETCH) ||
|
n->is_fetch_op(FETCH_OP_SEMFETCH);
|
|
no_src_swizzle |= n->is_fetch_inst() && (n->fetch_op_flags() & FF_GDS);
|
|
if (!n->src.empty() && !call_fs) {
|
|
// we may have more than one source vector -
|
// fetch instructions with FF_USEGRAD have gradient values in
|
// src vectors 1 (src[4-7] and 2 (src[8-11])
|
|
unsigned nvec = n->src.size() >> 2;
|
assert(nvec << 2 <= n->src.size());
|
|
for (unsigned nv = 0; nv < nvec; ++nv) {
|
vvec sv, tv, nsrc(4);
|
unsigned arg_start = nv << 2;
|
|
std::copy(n->src.begin() + arg_start,
|
n->src.begin() + arg_start + 4,
|
nsrc.begin());
|
|
split_vec(nsrc, tv, sv, !no_src_swizzle);
|
|
unsigned cnt = sv.size();
|
|
if (no_src_swizzle || cnt) {
|
|
std::copy(nsrc.begin(), nsrc.end(), n->src.begin() + arg_start);
|
|
for(unsigned i = 0, s = tv.size(); i < s; ++i) {
|
n->insert_before(sh.create_copy_mov(tv[i], sv[i]));
|
}
|
|
c = sh.coal.create_constraint(CK_SAME_REG);
|
c->values = tv;
|
c->update_values();
|
}
|
}
|
}
|
|
if (!n->dst.empty()) {
|
vvec sv, tv, ndst = n->dst;
|
|
split_vec(ndst, tv, sv, true);
|
|
if (sv.size()) {
|
n->dst = ndst;
|
|
node *lp = n;
|
for(unsigned i = 0, s = tv.size(); i < s; ++i) {
|
lp->insert_after(sh.create_copy_mov(sv[i], tv[i]));
|
lp = lp->next;
|
}
|
|
if (call_fs) {
|
for (unsigned i = 0, cnt = tv.size(); i < cnt; ++i) {
|
value *v = tv[i];
|
value *s = sv[i];
|
if (!v)
|
continue;
|
|
v->flags |= VLF_PIN_REG | VLF_PIN_CHAN;
|
s->flags &= ~(VLF_PIN_REG | VLF_PIN_CHAN);
|
sel_chan sel;
|
|
if (s->is_rel()) {
|
assert(s->rel->is_const());
|
sel = sel_chan(s->select.sel() +
|
s->rel->get_const_value().u,
|
s->select.chan());
|
} else
|
sel = s->select;
|
|
v->gpr = v->pin_gpr = sel;
|
v->fix();
|
}
|
} else {
|
c = sh.coal.create_constraint(CK_SAME_REG);
|
c->values = tv;
|
c->update_values();
|
}
|
}
|
}
|
}
|
|
void ra_init::add_prev_chan(unsigned chan) {
|
prev_chans = (prev_chans << 4) | (1 << chan);
|
}
|
|
unsigned ra_init::get_preferable_chan_mask() {
|
unsigned i, used_chans = 0;
|
unsigned chans = prev_chans;
|
|
for (i = 0; i < ra_tune; ++i) {
|
used_chans |= chans;
|
chans >>= 4;
|
}
|
|
return (~used_chans) & 0xF;
|
}
|
|
} // namespace r600_sb
|