aboutsummaryrefslogtreecommitdiffstats
path: root/Documentation/crc32.txt
blob: a08a7dd9d6255867e88b1ccc51ef820eb635286c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
A brief CRC tutorial.

A CRC is a long-division remainder.  You add the CRC to the message,
and the whole thing (message+CRC) is a multiple of the given
CRC polynomial.  To check the CRC, you can either check that the
CRC matches the recomputed value, *or* you can check that the
remainder computed on the message+CRC is 0.  This latter approach
is used by a lot of hardware implementations, and is why so many
protocols put the end-of-frame flag after the CRC.

It's actually the same long division you learned in school, except that
- We're working in binary, so the digits are only 0 and 1, and
- When dividing polynomials, there are no carries.  Rather than add and
  subtract, we just xor.  Thus, we tend to get a bit sloppy about
  the difference between adding and subtracting.

Like all division, the remainder is always smaller than the divisor.
To produce a 32-bit CRC, the divisor is actually a 33-bit CRC polynomial.
Since it's 33 bits long, bit 32 is always going to be set, so usually the
CRC is written in hex with the most significant bit omitted.  (If you're
familiar with the IEEE 754 floating-point format, it's the same idea.)

Note that a CRC is computed over a string of *bits*, so you have
to decide on the endianness of the bits within each byte.  To get
the best error-detecting properties, this should correspond to the
order they're actually sent.  For example, standard RS-232 serial is
little-endian; the most significant bit (sometimes used for parity)
is sent last.  And when appending a CRC word to a message, you should
do it in the right order, matching the endianness.

Just like with ordinary division, you proceed one digit (bit) at a time.
Each step of the division you take one more digit (bit) of the dividend
and append it to the current remainder.  Then you figure out the
appropriate multiple of the divisor to subtract to being the remainder
back into range.  In binary, this is easy - it has to be either 0 or 1,
and to make the XOR cancel, it's just a copy of bit 32 of the remainder.

When computing a CRC, we don't care about the quotient, so we can
throw the quotient bit away, but subtract the appropriate multiple of
the polynomial from the remainder and we're back to where we started,
ready to process the next bit.

A big-endian CRC written this way would be coded like:
for (i = 0; i < input_bits; i++) {
	multiple = remainder & 0x80000000 ? CRCPOLY : 0;
	remainder = (remainder << 1 | next_input_bit()) ^ multiple;
}

Notice how, to get at bit 32 of the shifted remainder, we look
at bit 31 of the remainder *before* shifting it.

But also notice how the next_input_bit() bits we're shifting into
the remainder don't actually affect any decision-making until
32 bits later.  Thus, the first 32 cycles of this are pretty boring.
Also, to add the CRC to a message, we need a 32-bit-long hole for it at
the end, so we have to add 32 extra cycles shifting in zeros at the
end of every message,

These details lead to a standard trick: rearrange merging in the
next_input_bit() until the moment it's needed.  Then the first 32 cycles
can be precomputed, and merging in the final 32 zero bits to make room
for the CRC can be skipped entirely.  This changes the code to:

for (i = 0; i < input_bits; i++) {
	remainder ^= next_input_bit() << 31;
	multiple = (remainder & 0x80000000) ? CRCPOLY : 0;
	remainder = (remainder << 1) ^ multiple;
}

With this optimization, the little-endian code is particularly simple:
for (i = 0; i < input_bits; i++) {
	remainder ^= next_input_bit();
	multiple = (remainder & 1) ? CRCPOLY : 0;
	remainder = (remainder >> 1) ^ multiple;
}

The most significant coefficient of the remainder polynomial is stored
in the least significant bit of the binary "remainder" variable.
The other details of endianness have been hidden in CRCPOLY (which must
be bit-reversed) and next_input_bit().

As long as next_input_bit is returning the bits in a sensible order, we don't
*have* to wait until the last possible moment to merge in additional bits.
We can do it 8 bits at a time rather than 1 bit at a time:
for (i = 0; i < input_bytes; i++) {
	remainder ^= next_input_byte() << 24;
	for (j = 0; j < 8; j++) {
		multiple = (remainder & 0x80000000) ? CRCPOLY : 0;
		remainder = (remainder << 1) ^ multiple;
	}
}

Or in little-endian:
for (i = 0; i < input_bytes; i++) {
	remainder ^= next_input_byte();
	for (j = 0; j < 8; j++) {
		multiple = (remainder & 1) ? CRCPOLY : 0;
		remainder = (remainder >> 1) ^ multiple;
	}
}

If the input is a multiple of 32 bits, you can even XOR in a 32-bit
word at a time and increase the inner loop count to 32.

You can also mix and match the two loop styles, for example doing the
bulk of a message byte-at-a-time and adding bit-at-a-time processing
for any fractional bytes at the end.

To reduce the number of conditional branches, software commonly uses
the byte-at-a-time table method, popularized by Dilip V. Sarwate,
"Computation of Cyclic Redundancy Checks via Table Look-Up", Comm. ACM
v.31 no.8 (August 1998) p. 1008-1013.

Here, rather than just shifting one bit of the remainder to decide
in the correct multiple to subtract, we can shift a byte at a time.
This produces a 40-bit (rather than a 33-bit) intermediate remainder,
and the correct multiple of the polynomial to subtract is found using
a 256-entry lookup table indexed by the high 8 bits.

(The table entries are simply the CRC-32 of the given one-byte messages.)

When space is more constrained, smaller tables can be used, e.g. two
4-bit shifts followed by a lookup in a 16-entry table.

It is not practical to process much more than 8 bits at a time using this
technique, because tables larger than 256 entries use too much memory and,
more importantly, too much of the L1 cache.

To get higher software performance, a "slicing" technique can be used.
See "High Octane CRC Generation with the Intel Slicing-by-8 Algorithm",
ftp://download.intel.com/technology/comms/perfnet/download/slicing-by-8.pdf

This does not change the number of table lookups, but does increase
the parallelism.  With the classic Sarwate algorithm, each table lookup
must be completed before the index of the next can be computed.

A "slicing by 2" technique would shift the remainder 16 bits at a time,
producing a 48-bit intermediate remainder.  Rather than doing a single
lookup in a 65536-entry table, the two high bytes are looked up in
two different 256-entry tables.  Each contains the remainder required
to cancel out the corresponding byte.  The tables are different because the
polynomials to cancel are different.  One has non-zero coefficients from
x^32 to x^39, while the other goes from x^40 to x^47.

Since modern processors can handle many parallel memory operations, this
takes barely longer than a single table look-up and thus performs almost
twice as fast as the basic Sarwate algorithm.

This can be extended to "slicing by 4" using 4 256-entry tables.
Each step, 32 bits of data is fetched, XORed with the CRC, and the result
broken into bytes and looked up in the tables.  Because the 32-bit shift
leaves the low-order bits of the intermediate remainder zero, the
final CRC is simply the XOR of the 4 table look-ups.

But this still enforces sequential execution: a second group of table
look-ups cannot begin until the previous groups 4 table look-ups have all
been completed.  Thus, the processor's load/store unit is sometimes idle.

To make maximum use of the processor, "slicing by 8" performs 8 look-ups
in parallel.  Each step, the 32-bit CRC is shifted 64 bits and XORed
with 64 bits of input data.  What is important to note is that 4 of
those 8 bytes are simply copies of the input data; they do not depend
on the previous CRC at all.  Thus, those 4 table look-ups may commence
immediately, without waiting for the previous loop iteration.

By always having 4 loads in flight, a modern superscalar processor can
be kept busy and make full use of its L1 cache.

Two more details about CRC implementation in the real world:

Normally, appending zero bits to a message which is already a multiple
of a polynomial produces a larger multiple of that polynomial.  Thus,
a basic CRC will not detect appended zero bits (or bytes).  To enable
a CRC to detect this condition, it's common to invert the CRC before
appending it.  This makes the remainder of the message+crc come out not
as zero, but some fixed non-zero value.  (The CRC of the inversion
pattern, 0xffffffff.)

The same problem applies to zero bits prepended to the message, and a
similar solution is used.  Instead of starting the CRC computation with
a remainder of 0, an initial remainder of all ones is used.  As long as
you start the same way on decoding, it doesn't make a difference.
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
/*
 * Copyright (c) 2016-2018, NVIDIA CORPORATION.  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, sublicense,
 * 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 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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS 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.
 */

#include <nvgpu/bios.h>
#include <nvgpu/gk20a.h>

#include "clk.h"
#include "clk_fll.h"
#include "clk_domain.h"
#include "clk_freq_controller.h"
#include "boardobj/boardobjgrp.h"
#include "boardobj/boardobjgrp_e32.h"
#include "ctrl/ctrlclk.h"
#include "ctrl/ctrlvolt.h"

static int clk_freq_controller_pmudatainit_super(struct gk20a *g,
	struct boardobj *board_obj_ptr,
	struct nv_pmu_boardobj *ppmudata)
{
	struct nv_pmu_clk_clk_freq_controller_boardobj_set *pfreq_cntlr_set;
	struct clk_freq_controller *pfreq_cntlr;
	int status = 0;

	status = boardobj_pmudatainit_super(g, board_obj_ptr, ppmudata);
	if (status) {
		return status;
	}

	pfreq_cntlr_set =
		(struct nv_pmu_clk_clk_freq_controller_boardobj_set *)ppmudata;
	pfreq_cntlr    = (struct clk_freq_controller *)board_obj_ptr;

	pfreq_cntlr_set->controller_id = pfreq_cntlr->controller_id;
	pfreq_cntlr_set->clk_domain = pfreq_cntlr->clk_domain;
	pfreq_cntlr_set->parts_freq_mode = pfreq_cntlr->parts_freq_mode;
	pfreq_cntlr_set->bdisable = pfreq_cntlr->bdisable;
	pfreq_cntlr_set->freq_cap_noise_unaware_vmin_above =
		pfreq_cntlr->freq_cap_noise_unaware_vmin_above;
	pfreq_cntlr_set->freq_cap_noise_unaware_vmin_below =
		pfreq_cntlr->freq_cap_noise_unaware_vmin_below;
	pfreq_cntlr_set->freq_hyst_pos_mhz = pfreq_cntlr->freq_hyst_pos_mhz;
	pfreq_cntlr_set->freq_hyst_neg_mhz = pfreq_cntlr->freq_hyst_neg_mhz;

	return status;
}

static int clk_freq_controller_pmudatainit_pi(struct gk20a *g,
	struct boardobj *board_obj_ptr,
	struct nv_pmu_boardobj *ppmudata)
{
	struct nv_pmu_clk_clk_freq_controller_pi_boardobj_set
		*pfreq_cntlr_pi_set;
	struct clk_freq_controller_pi *pfreq_cntlr_pi;
	int status = 0;

	status = clk_freq_controller_pmudatainit_super(g,
		board_obj_ptr, ppmudata);
	if (status) {
		return -1;
	}

	pfreq_cntlr_pi_set =
		(struct nv_pmu_clk_clk_freq_controller_pi_boardobj_set *)
		ppmudata;
	pfreq_cntlr_pi = (struct clk_freq_controller_pi *)board_obj_ptr;

	pfreq_cntlr_pi_set->prop_gain = pfreq_cntlr_pi->prop_gain;
	pfreq_cntlr_pi_set->integ_gain = pfreq_cntlr_pi->integ_gain;
	pfreq_cntlr_pi_set->integ_decay = pfreq_cntlr_pi->integ_decay;
	pfreq_cntlr_pi_set->volt_delta_min = pfreq_cntlr_pi->volt_delta_min;
	pfreq_cntlr_pi_set->volt_delta_max = pfreq_cntlr_pi->volt_delta_max;
	pfreq_cntlr_pi_set->slowdown_pct_min = pfreq_cntlr_pi->slowdown_pct_min;
	pfreq_cntlr_pi_set->bpoison = pfreq_cntlr_pi->bpoison;

	return status;
}

static int clk_freq_controller_construct_super(struct gk20a *g,
	struct boardobj **ppboardobj,
	u16 size, void *pargs)
{
	struct clk_freq_controller *pfreq_cntlr = NULL;
	struct clk_freq_controller *pfreq_cntlr_tmp = NULL;
	int status = 0;

	status = boardobj_construct_super(g, ppboardobj, size, pargs);
	if (status) {
		return -EINVAL;
	}

	pfreq_cntlr_tmp = (struct clk_freq_controller *)pargs;
	pfreq_cntlr = (struct clk_freq_controller *)*ppboardobj;

	pfreq_cntlr->super.pmudatainit = clk_freq_controller_pmudatainit_super;

	pfreq_cntlr->controller_id   = pfreq_cntlr_tmp->controller_id;
	pfreq_cntlr->clk_domain      = pfreq_cntlr_tmp->clk_domain;
	pfreq_cntlr->parts_freq_mode  = pfreq_cntlr_tmp->parts_freq_mode;
	pfreq_cntlr->freq_cap_noise_unaware_vmin_above  =
		pfreq_cntlr_tmp->freq_cap_noise_unaware_vmin_above;
	pfreq_cntlr->freq_cap_noise_unaware_vmin_below  =
		pfreq_cntlr_tmp->freq_cap_noise_unaware_vmin_below;
	pfreq_cntlr->freq_hyst_pos_mhz = pfreq_cntlr_tmp->freq_hyst_pos_mhz;
	pfreq_cntlr->freq_hyst_neg_mhz = pfreq_cntlr_tmp->freq_hyst_neg_mhz;

	return status;
}

static int clk_freq_controller_construct_pi(struct gk20a *g,
	struct boardobj **ppboardobj,
	u16 size, void *pargs)
{
	struct clk_freq_controller_pi *pfreq_cntlr_pi = NULL;
	struct clk_freq_controller_pi *pfreq_cntlr_pi_tmp = NULL;
	int status = 0;

	status = clk_freq_controller_construct_super(g, ppboardobj,
			size, pargs);
	if (status) {
		return -EINVAL;
	}

	pfreq_cntlr_pi = (struct clk_freq_controller_pi *)*ppboardobj;
	pfreq_cntlr_pi_tmp = (struct clk_freq_controller_pi *)pargs;

	pfreq_cntlr_pi->super.super.pmudatainit =
		clk_freq_controller_pmudatainit_pi;

	pfreq_cntlr_pi->prop_gain = pfreq_cntlr_pi_tmp->prop_gain;
	pfreq_cntlr_pi->integ_gain = pfreq_cntlr_pi_tmp->integ_gain;
	pfreq_cntlr_pi->integ_decay = pfreq_cntlr_pi_tmp->integ_decay;
	pfreq_cntlr_pi->volt_delta_min = pfreq_cntlr_pi_tmp->volt_delta_min;
	pfreq_cntlr_pi->volt_delta_max = pfreq_cntlr_pi_tmp->volt_delta_max;
	pfreq_cntlr_pi->slowdown_pct_min = pfreq_cntlr_pi_tmp->slowdown_pct_min;
	pfreq_cntlr_pi->bpoison = pfreq_cntlr_pi_tmp->bpoison;

	return status;
}

static struct clk_freq_controller *clk_clk_freq_controller_construct(
	struct gk20a *g,
	void *pargs)
{
	struct boardobj *board_obj_ptr = NULL;
	int status = 0;

	if (BOARDOBJ_GET_TYPE(pargs) != CTRL_CLK_CLK_FREQ_CONTROLLER_TYPE_PI) {
		return NULL;
	}

	status = clk_freq_controller_construct_pi(g, &board_obj_ptr,
				sizeof(struct clk_freq_controller_pi), pargs);
	if (status) {
		return NULL;
	}

	return (struct clk_freq_controller *)board_obj_ptr;
}


static int clk_get_freq_controller_table(struct gk20a *g,
		struct clk_freq_controllers *pclk_freq_controllers)
{
	int status = 0;
	u8 *pfreq_controller_table_ptr = NULL;
	struct vbios_fct_1x_header header = { 0 };
	struct vbios_fct_1x_entry entry  = { 0 };
	u8 entry_idx;
	u8 *entry_offset;
	struct clk_freq_controller *pclk_freq_cntr = NULL;
	struct clk_freq_controller *ptmp_freq_cntr = NULL;
	struct clk_freq_controller_pi *ptmp_freq_cntr_pi = NULL;
	struct clk_domain *pclk_domain;

	struct freq_controller_data_type {
		union {
			struct boardobj board_obj;
			struct clk_freq_controller freq_controller;
			struct clk_freq_controller_pi  freq_controller_pi;
		};
	} freq_controller_data;

	pfreq_controller_table_ptr =
		(u8 *)nvgpu_bios_get_perf_table_ptrs(g,
			g->bios.clock_token,
			FREQUENCY_CONTROLLER_TABLE);
	if (pfreq_controller_table_ptr == NULL) {
		status = -EINVAL;
		goto done;
	}

	memcpy(&header, pfreq_controller_table_ptr,
			sizeof(struct vbios_fct_1x_header));

	pclk_freq_controllers->sampling_period_ms = header.sampling_period_ms;
	pclk_freq_controllers->volt_policy_idx = 0;

	/* Read in the entries. */
	for (entry_idx = 0; entry_idx < header.entry_count; entry_idx++) {
		entry_offset = (pfreq_controller_table_ptr +
			header.header_size + (entry_idx * header.entry_size));

		memset(&freq_controller_data, 0x0,
				sizeof(struct freq_controller_data_type));
		ptmp_freq_cntr = &freq_controller_data.freq_controller;
		ptmp_freq_cntr_pi = &freq_controller_data.freq_controller_pi;

		memcpy(&entry, entry_offset,
			sizeof(struct vbios_fct_1x_entry));

		if (!BIOS_GET_FIELD(entry.flags0,
				NV_VBIOS_FCT_1X_ENTRY_FLAGS0_TYPE)) {
			continue;
		}

		freq_controller_data.board_obj.type = (u8)BIOS_GET_FIELD(
			entry.flags0, NV_VBIOS_FCT_1X_ENTRY_FLAGS0_TYPE);

		ptmp_freq_cntr->controller_id =
			(u8)BIOS_GET_FIELD(entry.param0,
				NV_VBIOS_FCT_1X_ENTRY_PARAM0_ID);

		pclk_domain = CLK_CLK_DOMAIN_GET((&g->clk_pmu),
				(u32)entry.clk_domain_idx);
		freq_controller_data.freq_controller.clk_domain =
			pclk_domain->api_domain;

		ptmp_freq_cntr->parts_freq_mode =
			(u8)BIOS_GET_FIELD(entry.param0,
				NV_VBIOS_FCT_1X_ENTRY_PARAM0_FREQ_MODE);

		/* Populate PI specific data */
		ptmp_freq_cntr_pi->slowdown_pct_min =
			(u8)BIOS_GET_FIELD(entry.param1,
				NV_VBIOS_FCT_1X_ENTRY_PARAM1_SLOWDOWN_PCT_MIN);

		ptmp_freq_cntr_pi->bpoison =
			BIOS_GET_FIELD(entry.param1,
				NV_VBIOS_FCT_1X_ENTRY_PARAM1_POISON);

		ptmp_freq_cntr_pi->prop_gain =
			(s32)BIOS_GET_FIELD(entry.param2,
				NV_VBIOS_FCT_1X_ENTRY_PARAM2_PROP_GAIN);

		ptmp_freq_cntr_pi->integ_gain =
			(s32)BIOS_GET_FIELD(entry.param3,
				NV_VBIOS_FCT_1X_ENTRY_PARAM3_INTEG_GAIN);

		ptmp_freq_cntr_pi->integ_decay =
			(s32)BIOS_GET_FIELD(entry.param4,
				NV_VBIOS_FCT_1X_ENTRY_PARAM4_INTEG_DECAY);

		ptmp_freq_cntr_pi->volt_delta_min =
		(s32)BIOS_GET_FIELD(entry.param5,
			NV_VBIOS_FCT_1X_ENTRY_PARAM5_VOLT_DELTA_MIN);

		ptmp_freq_cntr_pi->volt_delta_max =
		(s32)BIOS_GET_FIELD(entry.param6,
			NV_VBIOS_FCT_1X_ENTRY_PARAM6_VOLT_DELTA_MAX);

		ptmp_freq_cntr->freq_cap_noise_unaware_vmin_above =
			(s16)BIOS_GET_FIELD(entry.param7,
				NV_VBIOS_FCT_1X_ENTRY_PARAM7_FREQ_CAP_VF);

		ptmp_freq_cntr->freq_cap_noise_unaware_vmin_below =
		(s16)BIOS_GET_FIELD(entry.param7,
			NV_VBIOS_FCT_1X_ENTRY_PARAM7_FREQ_CAP_VMIN);

		ptmp_freq_cntr->freq_hyst_pos_mhz =
			(s16)BIOS_GET_FIELD(entry.param8,
				NV_VBIOS_FCT_1X_ENTRY_PARAM8_FREQ_HYST_POS);
		ptmp_freq_cntr->freq_hyst_neg_mhz =
			(s16)BIOS_GET_FIELD(entry.param8,
				NV_VBIOS_FCT_1X_ENTRY_PARAM8_FREQ_HYST_NEG);

		if (ptmp_freq_cntr_pi->volt_delta_max <
			ptmp_freq_cntr_pi->volt_delta_min) {
			goto done;
		}

		pclk_freq_cntr = clk_clk_freq_controller_construct(g,
						(void *)&freq_controller_data);

		if (pclk_freq_cntr == NULL) {
			nvgpu_err(g,
				"unable to construct clock freq cntlr boardobj for %d",
				entry_idx);
			status = -EINVAL;
			goto done;
		}

		status = boardobjgrp_objinsert(
				&pclk_freq_controllers->super.super,
				(struct boardobj *)pclk_freq_cntr, entry_idx);
		if (status) {
			nvgpu_err(g,
			"unable to insert clock freq cntlr boardobj for");
			status = -EINVAL;
			goto done;
		}

	}

done:
	return status;
}

int clk_freq_controller_pmu_setup(struct gk20a *g)
{
	int status;
	struct boardobjgrp *pboardobjgrp = NULL;

	nvgpu_log_info(g, " ");

	pboardobjgrp = &g->clk_pmu.clk_freq_controllers.super.super;

	if (!pboardobjgrp->bconstructed) {
		return -EINVAL;
	}

	status = pboardobjgrp->pmuinithandle(g, pboardobjgrp);

	nvgpu_log_info(g, "Done");
	return status;
}

static int _clk_freq_controller_devgrp_pmudata_instget(struct gk20a *g,
				   struct nv_pmu_boardobjgrp *pmuboardobjgrp,
				   struct nv_pmu_boardobj **ppboardobjpmudata,
					   u8 idx)
{
	struct nv_pmu_clk_clk_freq_controller_boardobj_grp_set *pgrp_set =
		(struct nv_pmu_clk_clk_freq_controller_boardobj_grp_set *)
		pmuboardobjgrp;

	nvgpu_log_info(g, " ");

	/*check whether pmuboardobjgrp has a valid boardobj in index*/
	if (((u32)BIT(idx) &
		pgrp_set->hdr.data.super.obj_mask.super.data[0]) == 0) {
		return -EINVAL;
	}

	*ppboardobjpmudata = (struct nv_pmu_boardobj *)
		&pgrp_set->objects[idx].data.board_obj;
	nvgpu_log_info(g, " Done");
	return 0;
}

static int _clk_freq_controllers_pmudatainit(struct gk20a *g,
			 struct boardobjgrp *pboardobjgrp,
			 struct nv_pmu_boardobjgrp_super *pboardobjgrppmu)
{
	struct nv_pmu_clk_clk_freq_controller_boardobjgrp_set_header *pset =
		(struct nv_pmu_clk_clk_freq_controller_boardobjgrp_set_header *)
		pboardobjgrppmu;
	struct clk_freq_controllers *pcntrs =
		(struct clk_freq_controllers *)pboardobjgrp;
	int status = 0;

	status = boardobjgrp_pmudatainit_e32(g, pboardobjgrp, pboardobjgrppmu);
	if (status) {
		nvgpu_err(g,
			"error updating pmu boardobjgrp for clk freq ctrs 0x%x",
			 status);
		goto done;
	}
	pset->sampling_period_ms = pcntrs->sampling_period_ms;
	pset->volt_policy_idx = pcntrs->volt_policy_idx;

done:
	return status;
}

int clk_freq_controller_sw_setup(struct gk20a *g)
{
	int status = 0;
	struct boardobjgrp *pboardobjgrp = NULL;
	struct clk_freq_controllers *pclk_freq_controllers;
	struct avfsfllobjs *pfllobjs = &(g->clk_pmu.avfs_fllobjs);
	struct fll_device *pfll;
	struct clk_freq_controller *pclkfreqctrl;
	u8 i;
	u8 j;

	nvgpu_log_info(g, " ");

	pclk_freq_controllers = &g->clk_pmu.clk_freq_controllers;
	status = boardobjgrpconstruct_e32(g, &pclk_freq_controllers->super);
	if (status) {
		nvgpu_err(g,
			"error creating boardobjgrp for clk FCT, status - 0x%x",
			status);
		goto done;
	}

	pboardobjgrp = &g->clk_pmu.clk_freq_controllers.super.super;

	pboardobjgrp->pmudatainit  = _clk_freq_controllers_pmudatainit;
	pboardobjgrp->pmudatainstget  =
			_clk_freq_controller_devgrp_pmudata_instget;
	pboardobjgrp->pmustatusinstget  = NULL;

	/* Initialize mask to zero.*/
	boardobjgrpmask_e32_init(&pclk_freq_controllers->freq_ctrl_load_mask,
		NULL);

	BOARDOBJGRP_PMU_CONSTRUCT(pboardobjgrp, CLK, CLK_FREQ_CONTROLLER);

	status = BOARDOBJGRP_PMU_CMD_GRP_SET_CONSTRUCT(g, pboardobjgrp,
			clk, CLK, clk_freq_controller, CLK_FREQ_CONTROLLER);
	if (status) {
		nvgpu_err(g,
			  "error constructing PMU_BOARDOBJ_CMD_GRP_SET interface - 0x%x",
			  status);
		goto done;
	}

	status = clk_get_freq_controller_table(g, pclk_freq_controllers);
	if (status) {
		nvgpu_err(g, "error reading freq controller table - 0x%x",
			status);
		goto done;
	}

	BOARDOBJGRP_FOR_EACH(&(pclk_freq_controllers->super.super),
			     struct clk_freq_controller *, pclkfreqctrl, i) {
		pfll = NULL;
		j = 0;
		BOARDOBJGRP_FOR_EACH(&(pfllobjs->super.super),
			     struct fll_device *, pfll, j) {
			if (pclkfreqctrl->controller_id == pfll->id) {
				pfll->freq_ctrl_idx = i;
				break;
			}
		}
		boardobjgrpmask_bitset(&pclk_freq_controllers->
			freq_ctrl_load_mask.super, i);
	}
done:
		nvgpu_log_info(g, " done status %x", status);
		return status;
}