1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <lib/zx/vmo.h>
6 #include <float.h>
7 #include <math.h>
8 
9 #include "display-device.h"
10 #include "intel-i915.h"
11 #include "macros.h"
12 #include "pipe.h"
13 #include "registers.h"
14 #include "registers-dpll.h"
15 #include "registers-transcoder.h"
16 #include "tiling.h"
17 
18 namespace {
19 
float_to_i915_csc_offset(float f)20 uint32_t float_to_i915_csc_offset(float f) {
21     ZX_DEBUG_ASSERT(0 <= f && f < 1.0f); // Controller::CheckConfiguration validates this
22 
23     // f is in [0, 1). Multiply by 2^12 to convert to a 12-bit fixed-point fraction.
24     return static_cast<uint32_t>(f * pow(FLT_RADIX, 12));
25 }
26 
float_to_i915_csc_coefficient(float f)27 uint32_t float_to_i915_csc_coefficient(float f) {
28     registers::CscCoeffFormat res;
29     if (f < 0) {
30         f *= -1;
31         res.set_sign(1);
32     }
33 
34     if (f < .125) {
35         res.set_exponent(res.kExponent0125);
36         f /= .125f;
37     } else if (f < .25) {
38         res.set_exponent(res.kExponent025);
39         f /= .25f;
40     } else if (f < .5) {
41         res.set_exponent(res.kExponent05);
42         f /= .5f;
43     } else if (f < 1) {
44         res.set_exponent(res.kExponent1);
45     } else if (f < 2) {
46         res.set_exponent(res.kExponent2);
47         f /= 2.0f;
48     } else {
49         res.set_exponent(res.kExponent4);
50         f /= 4.0f;
51     }
52     f = (f * 512) + .5f;
53 
54     if (f >= 512) {
55         res.set_mantissa(0x1ff);
56     } else {
57         res.set_mantissa(static_cast<uint16_t>(f));
58     }
59 
60     return res.reg_value();
61 }
62 
encode_pipe_color_component(uint8_t component)63 uint32_t encode_pipe_color_component(uint8_t component) {
64     // Convert to unsigned .10 fixed point format
65     return component << 2;
66 }
67 
68 } // namespace
69 
70 namespace i915 {
71 
Pipe(Controller * controller,registers::Pipe pipe)72 Pipe::Pipe(Controller* controller, registers::Pipe pipe)
73         : controller_(controller), pipe_(pipe) {}
74 
mmio_space() const75 ddk::MmioBuffer* Pipe::mmio_space() const {
76     return controller_->mmio_space();
77 }
78 
Init()79 void Pipe::Init() {
80     pipe_power_ = controller_->power()->GetPipePowerWellRef(pipe_);
81     controller_->interrupts()->EnablePipeVsync(pipe_, true);
82 }
83 
Resume()84 void Pipe::Resume() {
85     controller_->interrupts()->EnablePipeVsync(pipe_, true);
86 }
87 
Reset()88 void Pipe::Reset() {
89     controller_->ResetPipe(pipe_);
90     controller_->ResetTrans(transcoder());
91 }
92 
Detach()93 void Pipe::Detach() {
94     attached_display_ = INVALID_DISPLAY_ID;
95 }
96 
AttachToDisplay(uint64_t id,bool is_edp)97 void Pipe::AttachToDisplay(uint64_t id, bool is_edp) {
98     attached_display_ = id;
99     attached_edp_ = is_edp;
100 }
101 
ApplyModeConfig(const display_mode_t & mode)102 void Pipe::ApplyModeConfig(const display_mode_t& mode) {
103     registers::TranscoderRegs trans_regs(transcoder());
104 
105     // Configure the rest of the transcoder
106     uint32_t h_active = mode.h_addressable - 1;
107     uint32_t h_sync_start = h_active + mode.h_front_porch;
108     uint32_t h_sync_end = h_sync_start + mode.h_sync_pulse;
109     uint32_t h_total = h_active + mode.h_blanking;
110 
111     uint32_t v_active = mode.v_addressable - 1;
112     uint32_t v_sync_start = v_active + mode.v_front_porch;
113     uint32_t v_sync_end = v_sync_start + mode.v_sync_pulse;
114     uint32_t v_total = v_active + mode.v_blanking;
115 
116     auto h_total_reg = trans_regs.HTotal().FromValue(0);
117     h_total_reg.set_count_total(h_total);
118     h_total_reg.set_count_active(h_active);
119     h_total_reg.WriteTo(mmio_space());
120     auto v_total_reg = trans_regs.VTotal().FromValue(0);
121     v_total_reg.set_count_total(v_total);
122     v_total_reg.set_count_active(v_active);
123     v_total_reg.WriteTo(mmio_space());
124 
125     auto h_sync_reg = trans_regs.HSync().FromValue(0);
126     h_sync_reg.set_sync_start(h_sync_start);
127     h_sync_reg.set_sync_end(h_sync_end);
128     h_sync_reg.WriteTo(mmio_space());
129     auto v_sync_reg = trans_regs.VSync().FromValue(0);
130     v_sync_reg.set_sync_start(v_sync_start);
131     v_sync_reg.set_sync_end(v_sync_end);
132     v_sync_reg.WriteTo(mmio_space());
133 
134     // The Intel docs say that H/VBlank should be programmed with the same H/VTotal
135     trans_regs.HBlank().FromValue(h_total_reg.reg_value()).WriteTo(mmio_space());
136     trans_regs.VBlank().FromValue(v_total_reg.reg_value()).WriteTo(mmio_space());
137 
138     registers::PipeRegs pipe_regs(pipe());
139     auto pipe_size = pipe_regs.PipeSourceSize().FromValue(0);
140     pipe_size.set_horizontal_source_size(mode.h_addressable - 1);
141     pipe_size.set_vertical_source_size(mode.v_addressable - 1);
142     pipe_size.WriteTo(mmio_space());
143 }
144 
LoadActiveMode(display_mode_t * mode)145 void Pipe::LoadActiveMode(display_mode_t* mode) {
146     registers::TranscoderRegs trans_regs(transcoder());
147 
148     auto h_total_reg = trans_regs.HTotal().ReadFrom(mmio_space());
149     uint32_t h_total = h_total_reg.count_total();
150     uint32_t h_active = h_total_reg.count_active();
151     auto v_total_reg = trans_regs.VTotal().ReadFrom(mmio_space());
152     uint32_t v_total = v_total_reg.count_total();
153     uint32_t v_active = v_total_reg.count_active();
154 
155     auto h_sync_reg = trans_regs.HSync().ReadFrom(mmio_space());
156     uint32_t h_sync_start = h_sync_reg.sync_start();
157     uint32_t h_sync_end = h_sync_reg.sync_end();
158     auto v_sync_reg = trans_regs.VSync().ReadFrom(mmio_space());
159     uint32_t v_sync_start = v_sync_reg.sync_start();
160     uint32_t v_sync_end = v_sync_reg.sync_end();
161 
162     mode->h_addressable = h_active + 1;
163     mode->h_front_porch = h_sync_start - h_active;
164     mode->h_sync_pulse = h_sync_end - h_sync_start;
165     mode->h_blanking = h_total - h_active;
166 
167     mode->v_addressable = v_active + 1;
168     mode->v_front_porch = v_sync_start - v_active;
169     mode->v_sync_pulse = v_sync_end - v_sync_start;
170     mode->v_blanking = v_total - v_active;
171 
172     mode->flags = 0;
173     auto ddi_func_ctrl = trans_regs.DdiFuncControl().ReadFrom(mmio_space());
174     if (ddi_func_ctrl.sync_polarity() & 0x2) {
175         mode->flags |= MODE_FLAG_VSYNC_POSITIVE;
176     }
177     if (ddi_func_ctrl.sync_polarity() & 0x1) {
178         mode->flags |= MODE_FLAG_HSYNC_POSITIVE;
179     }
180     if (trans_regs.Conf().ReadFrom(mmio_space()).interlaced_mode()) {
181         mode->flags |= MODE_FLAG_INTERLACED;
182     }
183 
184     // If we're reusing hardware state, make sure the pipe source size matches
185     // the display mode size, since we never scale pipes.
186     registers::PipeRegs pipe_regs(pipe_);
187     auto pipe_size = pipe_regs.PipeSourceSize().FromValue(0);
188     pipe_size.set_horizontal_source_size(mode->h_addressable - 1);
189     pipe_size.set_vertical_source_size(mode->v_addressable - 1);
190     pipe_size.WriteTo(mmio_space());
191 }
192 
ApplyConfiguration(const display_config_t * config)193 void Pipe::ApplyConfiguration(const display_config_t* config) {
194     ZX_ASSERT(config);
195 
196     registers::pipe_arming_regs_t regs;
197     registers::PipeRegs pipe_regs(pipe_);
198 
199     if (config->cc_flags) {
200         float zero_offset[3] = {};
201         SetColorConversionOffsets(true, config->cc_flags & COLOR_CONVERSION_PREOFFSET ?
202                 config->cc_preoffsets : zero_offset);
203         SetColorConversionOffsets(false, config->cc_flags & COLOR_CONVERSION_POSTOFFSET ?
204                 config->cc_postoffsets : zero_offset);
205 
206         float identity[3][3] = {
207             { 1, 0, 0, },
208             { 0, 1, 0, },
209             { 0, 0, 1, },
210         };
211         for (uint32_t i = 0; i < 3; i++) {
212             for (uint32_t j = 0; j < 3; j++) {
213                 float val = config->cc_flags & COLOR_CONVERSION_COEFFICIENTS ?
214                         config->cc_coefficients[i][j] : identity[i][j];
215 
216                 auto reg = pipe_regs.CscCoeff(i, j).ReadFrom(mmio_space());
217                 reg.coefficient(i, j).set( float_to_i915_csc_coefficient(val));
218                 reg.WriteTo(mmio_space());
219             }
220         }
221     }
222     regs.csc_mode = pipe_regs.CscMode().ReadFrom(mmio_space()).reg_value();
223 
224     auto bottom_color = pipe_regs.PipeBottomColor().FromValue(0);
225     bottom_color.set_csc_enable(!!config->cc_flags);
226     bool has_color_layer = config->layer_count && config->layer_list[0]->type == LAYER_TYPE_COLOR;
227     if (has_color_layer) {
228         color_layer_t* layer = &config->layer_list[0]->cfg.color;
229         ZX_DEBUG_ASSERT(layer->format == ZX_PIXEL_FORMAT_RGB_x888
230                 || layer->format == ZX_PIXEL_FORMAT_ARGB_8888);
231         uint32_t color = *reinterpret_cast<const uint32_t*>(layer->color_list);
232 
233         bottom_color.set_r(encode_pipe_color_component(static_cast<uint8_t>(color >> 16)));
234         bottom_color.set_g(encode_pipe_color_component(static_cast<uint8_t>(color >> 8)));
235         bottom_color.set_b(encode_pipe_color_component(static_cast<uint8_t>(color)));
236     }
237     regs.pipe_bottom_color = bottom_color.reg_value();
238 
239     bool scaler_1_claimed = false;
240     for (unsigned plane = 0; plane < 3; plane++) {
241         primary_layer_t* primary = nullptr;
242         for (unsigned j = 0; j < config->layer_count; j++) {
243             layer_t* layer = config->layer_list[j];
244             if (layer->type == LAYER_TYPE_PRIMARY && (layer->z_index - has_color_layer) == plane) {
245                 primary = &layer->cfg.primary;
246                 break;
247             }
248         }
249         ConfigurePrimaryPlane(plane, primary, !!config->cc_flags, &scaler_1_claimed, &regs);
250     }
251     cursor_layer_t* cursor = nullptr;
252     if (config->layer_count &&
253         config->layer_list[config->layer_count - 1]->type == LAYER_TYPE_CURSOR) {
254         cursor = &config->layer_list[config->layer_count - 1]->cfg.cursor;
255     }
256     ConfigureCursorPlane(cursor, !!config->cc_flags, &regs);
257 
258     pipe_regs.CscMode().FromValue(regs.csc_mode).WriteTo(mmio_space());
259     pipe_regs.PipeBottomColor().FromValue(regs.pipe_bottom_color).WriteTo(mmio_space());
260     pipe_regs.CursorBase().FromValue(regs.cur_base).WriteTo(mmio_space());
261     pipe_regs.CursorPos().FromValue(regs.cur_pos).WriteTo(mmio_space());
262     for (unsigned i = 0; i < registers::kImagePlaneCount; i++) {
263         pipe_regs.PlaneSurface(i).FromValue(regs.plane_surf[i]).WriteTo(mmio_space());
264     }
265     pipe_regs.PipeScalerWinSize(0).FromValue(regs.ps_win_sz[0]).WriteTo(mmio_space());
266     if (pipe_ != registers::PIPE_C) {
267         pipe_regs.PipeScalerWinSize(1)
268                 .FromValue(regs.ps_win_sz[1]).WriteTo(mmio_space());
269     }
270 }
271 
ConfigurePrimaryPlane(uint32_t plane_num,const primary_layer_t * primary,bool enable_csc,bool * scaler_1_claimed,registers::pipe_arming_regs_t * regs)272 void Pipe::ConfigurePrimaryPlane(uint32_t plane_num, const primary_layer_t* primary,
273                                           bool enable_csc, bool* scaler_1_claimed,
274                                           registers::pipe_arming_regs_t* regs) {
275     registers::PipeRegs pipe_regs(pipe());
276 
277     auto plane_ctrl = pipe_regs.PlaneControl(plane_num).ReadFrom(controller_->mmio_space());
278     if (primary == nullptr) {
279         plane_ctrl.set_plane_enable(0).WriteTo(mmio_space());
280         regs->plane_surf[plane_num] = 0;
281         return;
282     }
283 
284     const image_t* image = &primary->image;
285 
286     const fbl::unique_ptr<GttRegion>& region = controller_->GetGttRegion(image->handle);
287     region->SetRotation(primary->transform_mode, *image);
288 
289     uint32_t plane_width;
290     uint32_t plane_height;
291     uint32_t stride;
292     uint32_t x_offset;
293     uint32_t y_offset;
294     if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY
295             || primary->transform_mode == FRAME_TRANSFORM_ROT_180) {
296         plane_width = primary->src_frame.width;
297         plane_height = primary->src_frame.height;
298         stride = width_in_tiles(image->type, image->width, image->pixel_format);
299         x_offset = primary->src_frame.x_pos;
300         y_offset = primary->src_frame.y_pos;
301     } else {
302         uint32_t tile_height = height_in_tiles(image->type, image->height, image->pixel_format);
303         uint32_t tile_px_height = get_tile_px_height(image->type, image->pixel_format);
304         uint32_t total_height = tile_height * tile_px_height;
305 
306         plane_width = primary->src_frame.height;
307         plane_height = primary->src_frame.width;
308         stride = tile_height;
309         x_offset = total_height - primary->src_frame.y_pos - primary->src_frame.height;
310         y_offset = primary->src_frame.x_pos;
311     }
312 
313     if (plane_width == primary->dest_frame.width
314             && plane_height == primary->dest_frame.height) {
315         auto plane_pos = pipe_regs.PlanePosition(plane_num).FromValue(0);
316         plane_pos.set_x_pos(primary->dest_frame.x_pos);
317         plane_pos.set_y_pos(primary->dest_frame.y_pos);
318         plane_pos.WriteTo(mmio_space());
319 
320         // If there's a scaler pointed at this plane, immediately disable it
321         // in case there's nothing else that will claim it this frame.
322         if (scaled_planes_[pipe()][plane_num]) {
323             uint32_t scaler_idx = scaled_planes_[pipe()][plane_num] - 1;
324             pipe_regs.PipeScalerCtrl(scaler_idx)
325                     .ReadFrom(mmio_space()).set_enable(0).WriteTo(mmio_space());
326             scaled_planes_[pipe()][plane_num] = 0;
327             regs->ps_win_sz[scaler_idx] = 0;
328         }
329     } else {
330         pipe_regs.PlanePosition(plane_num).FromValue(0).WriteTo(mmio_space());
331 
332         auto ps_ctrl = pipe_regs.PipeScalerCtrl(*scaler_1_claimed).ReadFrom(mmio_space());
333         ps_ctrl.set_mode(ps_ctrl.kDynamic);
334         if (primary->src_frame.width > 2048) {
335             float max_dynamic_height = static_cast<float>(plane_height)
336                     * registers::PipeScalerCtrl::kDynamicMaxVerticalRatio2049;
337             if (static_cast<uint32_t>(max_dynamic_height) < primary->dest_frame.height) {
338                 // TODO(stevensd): This misses some cases where 7x5 can be used.
339                 ps_ctrl.set_enable(ps_ctrl.k7x5);
340             }
341         }
342         ps_ctrl.set_binding(plane_num + 1);
343         ps_ctrl.set_enable(1);
344         ps_ctrl.WriteTo(mmio_space());
345 
346         auto ps_win_pos = pipe_regs.PipeScalerWinPosition(*scaler_1_claimed).FromValue(0);
347         ps_win_pos.set_x_pos(primary->dest_frame.x_pos);
348         ps_win_pos.set_x_pos(primary->dest_frame.y_pos);
349         ps_win_pos.WriteTo(mmio_space());
350 
351         auto ps_win_size = pipe_regs.PipeScalerWinSize(*scaler_1_claimed).FromValue(0);
352         ps_win_size.set_x_size(primary->dest_frame.width);
353         ps_win_size.set_y_size(primary->dest_frame.height);
354         regs->ps_win_sz[*scaler_1_claimed] = ps_win_size.reg_value();
355 
356         scaled_planes_[pipe()][plane_num] = (*scaler_1_claimed) + 1;
357         *scaler_1_claimed = true;
358     }
359 
360     auto plane_size = pipe_regs.PlaneSurfaceSize(plane_num).FromValue(0);
361     plane_size.set_width_minus_1(plane_width - 1);
362     plane_size.set_height_minus_1(plane_height - 1);
363     plane_size.WriteTo(mmio_space());
364 
365     auto plane_offset = pipe_regs.PlaneOffset(plane_num).FromValue(0);
366     plane_offset.set_start_x(x_offset);
367     plane_offset.set_start_y(y_offset);
368     plane_offset.WriteTo(mmio_space());
369 
370     auto stride_reg = pipe_regs.PlaneSurfaceStride(plane_num).FromValue(0);
371     stride_reg.set_stride(stride);
372     stride_reg.WriteTo(controller_->mmio_space());
373 
374     auto plane_key_mask = pipe_regs.PlaneKeyMask(plane_num).FromValue(0);
375     if (primary->alpha_mode != ALPHA_DISABLE && !isnan(primary->alpha_layer_val)) {
376         plane_key_mask.set_plane_alpha_enable(1);
377 
378         uint8_t alpha = static_cast<uint8_t>(round(primary->alpha_layer_val * 255));
379 
380         auto plane_key_max = pipe_regs.PlaneKeyMax(plane_num).FromValue(0);
381         plane_key_max.set_plane_alpha_value(alpha);
382         plane_key_max.WriteTo(mmio_space());
383     }
384     plane_key_mask.WriteTo(mmio_space());
385     if (primary->alpha_mode == ALPHA_DISABLE
386             || primary->image.pixel_format == ZX_PIXEL_FORMAT_RGB_x888) {
387         plane_ctrl.set_alpha_mode(plane_ctrl.kAlphaDisable);
388     } else if (primary->alpha_mode == ALPHA_PREMULTIPLIED) {
389         plane_ctrl.set_alpha_mode(plane_ctrl.kAlphaPreMultiply);
390     } else {
391         ZX_ASSERT(primary->alpha_mode == ALPHA_HW_MULTIPLY);
392         plane_ctrl.set_alpha_mode(plane_ctrl.kAlphaHwMultiply);
393     }
394 
395     plane_ctrl.set_plane_enable(1);
396     plane_ctrl.set_pipe_csc_enable(enable_csc);
397     plane_ctrl.set_source_pixel_format(plane_ctrl.kFormatRgb8888);
398     if (primary->image.type == IMAGE_TYPE_SIMPLE) {
399         plane_ctrl.set_tiled_surface(plane_ctrl.kLinear);
400     } else if (primary->image.type == IMAGE_TYPE_X_TILED) {
401         plane_ctrl.set_tiled_surface(plane_ctrl.kTilingX);
402     } else if (primary->image.type == IMAGE_TYPE_Y_LEGACY_TILED) {
403         plane_ctrl.set_tiled_surface(plane_ctrl.kTilingYLegacy);
404     } else {
405         ZX_ASSERT(primary->image.type == IMAGE_TYPE_YF_TILED);
406         plane_ctrl.set_tiled_surface(plane_ctrl.kTilingYF);
407     }
408     if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY) {
409         plane_ctrl.set_plane_rotation(plane_ctrl.kIdentity);
410     } else if (primary->transform_mode == FRAME_TRANSFORM_ROT_90) {
411         plane_ctrl.set_plane_rotation(plane_ctrl.k90deg);
412     } else if (primary->transform_mode == FRAME_TRANSFORM_ROT_180) {
413         plane_ctrl.set_plane_rotation(plane_ctrl.k180deg);
414     } else {
415         ZX_ASSERT(primary->transform_mode == FRAME_TRANSFORM_ROT_270);
416         plane_ctrl.set_plane_rotation(plane_ctrl.k270deg);
417     }
418     plane_ctrl.WriteTo(controller_->mmio_space());
419 
420     uint32_t base_address = static_cast<uint32_t>(region->base());
421 
422     auto plane_surface = pipe_regs.PlaneSurface(plane_num).ReadFrom(controller_->mmio_space());
423     plane_surface.set_surface_base_addr(base_address >> plane_surface.kRShiftCount);
424     regs->plane_surf[plane_num] = plane_surface.reg_value();
425 }
426 
ConfigureCursorPlane(const cursor_layer_t * cursor,bool enable_csc,registers::pipe_arming_regs_t * regs)427 void Pipe::ConfigureCursorPlane(const cursor_layer_t* cursor, bool enable_csc,
428                                          registers::pipe_arming_regs_t* regs) {
429     registers::PipeRegs pipe_regs(pipe());
430 
431     auto cursor_ctrl = pipe_regs.CursorCtrl().ReadFrom(controller_->mmio_space());
432     // The hardware requires that the cursor has at least one pixel on the display,
433     // so disable the plane if there is no overlap.
434     if (cursor == nullptr) {
435         cursor_ctrl.set_mode_select(cursor_ctrl.kDisabled).WriteTo(mmio_space());
436         regs->cur_base = regs->cur_pos = 0;
437         return;
438     }
439 
440     if (cursor->image.width == 64) {
441         cursor_ctrl.set_mode_select(cursor_ctrl.kArgb64x64);
442     } else if (cursor->image.width == 128) {
443         cursor_ctrl.set_mode_select(cursor_ctrl.kArgb128x128);
444     } else if (cursor->image.width == 256) {
445         cursor_ctrl.set_mode_select(cursor_ctrl.kArgb256x256);
446     } else {
447         // The configuration was not properly validated
448         ZX_ASSERT(false);
449     }
450     cursor_ctrl.set_pipe_csc_enable(enable_csc);
451     cursor_ctrl.WriteTo(mmio_space());
452 
453     auto cursor_pos = pipe_regs.CursorPos().FromValue(0);
454     if (cursor->x_pos < 0) {
455         cursor_pos.set_x_sign(1);
456         cursor_pos.set_x_pos(-cursor->x_pos);
457     } else {
458         cursor_pos.set_x_pos(cursor->x_pos);
459     }
460     if (cursor->y_pos < 0) {
461         cursor_pos.set_y_sign(1);
462         cursor_pos.set_y_pos(-cursor->y_pos);
463     } else {
464         cursor_pos.set_y_pos(cursor->y_pos);
465     }
466     regs->cur_pos = cursor_pos.reg_value();
467 
468     uint32_t base_address =
469             static_cast<uint32_t>(reinterpret_cast<uint64_t>(cursor->image.handle));
470     auto cursor_base = pipe_regs.CursorBase().ReadFrom(controller_->mmio_space());
471     cursor_base.set_cursor_base(base_address >> cursor_base.kPageShift);
472     regs->cur_base = cursor_base.reg_value();
473 }
474 
SetColorConversionOffsets(bool preoffsets,const float vals[3])475 void Pipe::SetColorConversionOffsets(bool preoffsets, const float vals[3]) {
476     registers::PipeRegs pipe_regs(pipe());
477 
478     for (uint32_t i = 0; i < 3; i++) {
479         float offset = vals[i];
480         auto offset_reg = pipe_regs.CscOffset(preoffsets, i).FromValue(0);
481         if (offset < 0) {
482             offset_reg.set_sign(1);
483             offset *= -1;
484         }
485         offset_reg.set_magnitude(float_to_i915_csc_offset(offset));
486         offset_reg.WriteTo(mmio_space());
487     }
488 }
489 
490 } // namespace i915
491