commonlib: Add new "ESE completed AUnit loading" TS
[coreboot2.git] / src / northbridge / intel / sandybridge / raminit_iosav.c
blobfbbbfba0f69f7139398d0b9fa31eaacf941807ca
1 /* SPDX-License-Identifier: GPL-2.0-only */
3 #include <commonlib/helpers.h>
4 #include <types.h>
6 #include "raminit_common.h"
7 #include "raminit_tables.h"
8 #include "sandybridge.h"
10 /* FIXME: no support for 3-channel chipsets */
12 /* Number of programmed IOSAV subsequences. */
13 static unsigned int ssq_count = 0;
15 void iosav_write_sequence(const int ch, const struct iosav_ssq *seq, const unsigned int length)
17 for (unsigned int i = 0; i < length; i++) {
18 mchbar_write32(IOSAV_n_SP_CMD_CTRL_ch(ch, i), seq[i].sp_cmd_ctrl.raw);
19 mchbar_write32(IOSAV_n_SUBSEQ_CTRL_ch(ch, i), seq[i].subseq_ctrl.raw);
20 mchbar_write32(IOSAV_n_SP_CMD_ADDR_ch(ch, i), seq[i].sp_cmd_addr.raw);
21 mchbar_write32(IOSAV_n_ADDR_UPDATE_ch(ch, i), seq[i].addr_update.raw);
24 ssq_count = length;
27 void iosav_run_queue(const int ch, const u8 loops, const u8 as_timer)
29 /* Should never happen */
30 if (ssq_count == 0)
31 return;
33 mchbar_write32(IOSAV_SEQ_CTL_ch(ch), loops | (ssq_count - 1) << 18 | as_timer << 22);
36 void wait_for_iosav(int channel)
38 while (1) {
39 if (mchbar_read32(IOSAV_STATUS_ch(channel)) & 0x50)
40 return;
44 void iosav_run_once_and_wait(const int ch)
46 iosav_run_queue(ch, 1, 0);
47 wait_for_iosav(ch);
50 void iosav_write_zqcs_sequence(int channel, int slotrank, u32 gap, u32 post, u32 wrap)
52 const struct iosav_ssq sequence[] = {
53 /* DRAM command ZQCS */
54 [0] = {
55 .sp_cmd_ctrl = {
56 .command = IOSAV_ZQCS,
58 .subseq_ctrl = {
59 .cmd_executions = 1,
60 .cmd_delay_gap = gap,
61 .post_ssq_wait = post,
62 .data_direction = SSQ_NA,
64 .sp_cmd_addr = {
65 .address = 0,
66 .rowbits = 6,
67 .bank = 0,
68 .rank = slotrank,
70 .addr_update = {
71 .addr_wrap = wrap,
75 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
78 void iosav_write_prea_sequence(int channel, int slotrank, u32 post, u32 wrap)
80 const struct iosav_ssq sequence[] = {
81 /* DRAM command PREA */
82 [0] = {
83 .sp_cmd_ctrl = {
84 .command = IOSAV_PRE,
85 .ranksel_ap = 1,
87 .subseq_ctrl = {
88 .cmd_executions = 1,
89 .cmd_delay_gap = 3,
90 .post_ssq_wait = post,
91 .data_direction = SSQ_NA,
93 .sp_cmd_addr = {
94 .address = 1 << 10,
95 .rowbits = 6,
96 .bank = 0,
97 .rank = slotrank,
99 .addr_update = {
100 .addr_wrap = wrap,
104 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
107 void iosav_write_read_mpr_sequence(
108 int channel, int slotrank, u32 tMOD, u32 loops, u32 gap, u32 loops2, u32 post2)
110 const struct iosav_ssq sequence[] = {
112 * DRAM command MRS
114 * Write MR3 MPR enable. In this mode only RD and RDA
115 * are allowed, and all reads return a predefined pattern.
117 [0] = {
118 .sp_cmd_ctrl = {
119 .command = IOSAV_MRS,
120 .ranksel_ap = 1,
122 .subseq_ctrl = {
123 .cmd_executions = 1,
124 .cmd_delay_gap = 3,
125 .post_ssq_wait = tMOD,
126 .data_direction = SSQ_NA,
128 .sp_cmd_addr = {
129 .address = 4,
130 .rowbits = 6,
131 .bank = 3,
132 .rank = slotrank,
135 /* DRAM command RD */
136 [1] = {
137 .sp_cmd_ctrl = {
138 .command = IOSAV_RD,
139 .ranksel_ap = 1,
141 .subseq_ctrl = {
142 .cmd_executions = loops,
143 .cmd_delay_gap = gap,
144 .post_ssq_wait = 4,
145 .data_direction = SSQ_RD,
147 .sp_cmd_addr = {
148 .address = 0,
149 .rowbits = 0,
150 .bank = 0,
151 .rank = slotrank,
154 /* DRAM command RD */
155 [2] = {
156 .sp_cmd_ctrl = {
157 .command = IOSAV_RD,
158 .ranksel_ap = 1,
160 .subseq_ctrl = {
161 .cmd_executions = loops2,
162 .cmd_delay_gap = 4,
163 .post_ssq_wait = post2,
164 .data_direction = SSQ_NA,
166 .sp_cmd_addr = {
167 .address = 0,
168 .rowbits = 6,
169 .bank = 0,
170 .rank = slotrank,
174 * DRAM command MRS
176 * Write MR3 MPR disable.
178 [3] = {
179 .sp_cmd_ctrl = {
180 .command = IOSAV_MRS,
181 .ranksel_ap = 1,
183 .subseq_ctrl = {
184 .cmd_executions = 1,
185 .cmd_delay_gap = 3,
186 .post_ssq_wait = tMOD,
187 .data_direction = SSQ_NA,
189 .sp_cmd_addr = {
190 .address = 0,
191 .rowbits = 6,
192 .bank = 3,
193 .rank = slotrank,
197 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
200 void iosav_write_prea_act_read_sequence(ramctr_timing *ctrl, int channel, int slotrank)
202 const struct iosav_ssq sequence[] = {
203 /* DRAM command PREA */
204 [0] = {
205 .sp_cmd_ctrl = {
206 .command = IOSAV_PRE,
207 .ranksel_ap = 1,
209 .subseq_ctrl = {
210 .cmd_executions = 1,
211 .cmd_delay_gap = 3,
212 .post_ssq_wait = ctrl->tRP,
213 .data_direction = SSQ_NA,
215 .sp_cmd_addr = {
216 .address = 1 << 10,
217 .rowbits = 6,
218 .bank = 0,
219 .rank = slotrank,
221 .addr_update = {
222 .addr_wrap = 18,
225 /* DRAM command ACT */
226 [1] = {
227 .sp_cmd_ctrl = {
228 .command = IOSAV_ACT,
229 .ranksel_ap = 1,
231 .subseq_ctrl = {
232 .cmd_executions = 8,
233 .cmd_delay_gap = MAX(ctrl->tRRD, (ctrl->tFAW >> 2) + 1),
234 .post_ssq_wait = ctrl->CAS,
235 .data_direction = SSQ_NA,
237 .sp_cmd_addr = {
238 .address = 0,
239 .rowbits = 6,
240 .bank = 0,
241 .rank = slotrank,
243 .addr_update = {
244 .inc_bank = 1,
245 .addr_wrap = 18,
248 /* DRAM command RD */
249 [2] = {
250 .sp_cmd_ctrl = {
251 .command = IOSAV_RD,
252 .ranksel_ap = 1,
254 .subseq_ctrl = {
255 .cmd_executions = 500,
256 .cmd_delay_gap = 4,
257 .post_ssq_wait = MAX(ctrl->tRTP, 8),
258 .data_direction = SSQ_RD,
260 .sp_cmd_addr = {
261 .address = 0,
262 .rowbits = 0,
263 .bank = 0,
264 .rank = slotrank,
266 .addr_update = {
267 .inc_addr_8 = 1,
268 .addr_wrap = 18,
271 /* DRAM command PREA */
272 [3] = {
273 .sp_cmd_ctrl = {
274 .command = IOSAV_PRE,
275 .ranksel_ap = 1,
277 .subseq_ctrl = {
278 .cmd_executions = 1,
279 .cmd_delay_gap = 3,
280 .post_ssq_wait = ctrl->tRP,
281 .data_direction = SSQ_NA,
283 .sp_cmd_addr = {
284 .address = 1 << 10,
285 .rowbits = 6,
286 .bank = 0,
287 .rank = slotrank,
289 .addr_update = {
290 .addr_wrap = 18,
294 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
297 void iosav_write_jedec_write_leveling_sequence(
298 ramctr_timing *ctrl, int channel, int slotrank, int bank, u32 mr1reg)
300 /* First DQS/DQS# rising edge after write leveling mode is programmed */
301 const u32 tWLMRD = 40;
303 const struct iosav_ssq sequence[] = {
304 /* DRAM command MRS: enable DQs on this slotrank */
305 [0] = {
306 .sp_cmd_ctrl = {
307 .command = IOSAV_MRS,
308 .ranksel_ap = 1,
310 .subseq_ctrl = {
311 .cmd_executions = 1,
312 .cmd_delay_gap = 3,
313 .post_ssq_wait = tWLMRD,
314 .data_direction = SSQ_NA,
316 .sp_cmd_addr = {
317 .address = mr1reg,
318 .rowbits = 6,
319 .bank = bank,
320 .rank = slotrank,
323 /* DRAM command NOP */
324 [1] = {
325 .sp_cmd_ctrl = {
326 .command = IOSAV_NOP,
327 .ranksel_ap = 1,
329 .subseq_ctrl = {
330 .cmd_executions = 1,
331 .cmd_delay_gap = 3,
332 .post_ssq_wait = ctrl->CWL + ctrl->tWLO,
333 .data_direction = SSQ_WR,
335 .sp_cmd_addr = {
336 .address = 8,
337 .rowbits = 0,
338 .bank = 0,
339 .rank = slotrank,
342 /* DRAM command NOP */
343 [2] = {
344 .sp_cmd_ctrl = {
345 .command = IOSAV_NOP_ALT,
346 .ranksel_ap = 1,
348 .subseq_ctrl = {
349 .cmd_executions = 1,
350 .cmd_delay_gap = 3,
351 .post_ssq_wait = ctrl->CAS + 38,
352 .data_direction = SSQ_RD,
354 .sp_cmd_addr = {
355 .address = 4,
356 .rowbits = 0,
357 .bank = 0,
358 .rank = slotrank,
361 /* DRAM command MRS: disable DQs on this slotrank */
362 [3] = {
363 .sp_cmd_ctrl = {
364 .command = IOSAV_MRS,
365 .ranksel_ap = 1,
367 .subseq_ctrl = {
368 .cmd_executions = 1,
369 .cmd_delay_gap = 3,
370 .post_ssq_wait = ctrl->tMOD,
371 .data_direction = SSQ_NA,
373 .sp_cmd_addr = {
374 .address = mr1reg | 1 << 12,
375 .rowbits = 6,
376 .bank = bank,
377 .rank = slotrank,
381 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
384 void iosav_write_misc_write_sequence(ramctr_timing *ctrl, int channel, int slotrank,
385 u32 gap0, u32 loops0, u32 gap1, u32 loops2, u32 wrap2)
387 const struct iosav_ssq sequence[] = {
388 /* DRAM command ACT */
389 [0] = {
390 .sp_cmd_ctrl = {
391 .command = IOSAV_ACT,
392 .ranksel_ap = 1,
394 .subseq_ctrl = {
395 .cmd_executions = loops0,
396 .cmd_delay_gap = gap0,
397 .post_ssq_wait = ctrl->tRCD,
398 .data_direction = SSQ_NA,
400 .sp_cmd_addr = {
401 .address = 0,
402 .rowbits = 6,
403 .bank = 0,
404 .rank = slotrank,
406 .addr_update = {
407 .inc_bank = loops0 == 1 ? 0 : 1,
408 .addr_wrap = loops0 == 1 ? 0 : 18,
411 /* DRAM command NOP */
412 [1] = {
413 .sp_cmd_ctrl = {
414 .command = IOSAV_NOP,
415 .ranksel_ap = 1,
417 .subseq_ctrl = {
418 .cmd_executions = 1,
419 .cmd_delay_gap = gap1,
420 .post_ssq_wait = 4,
421 .data_direction = SSQ_WR,
423 .sp_cmd_addr = {
424 .address = 8,
425 .rowbits = 0,
426 .bank = 0,
427 .rank = slotrank,
429 .addr_update = {
430 .addr_wrap = 31,
433 /* DRAM command WR */
434 [2] = {
435 .sp_cmd_ctrl = {
436 .command = IOSAV_WR,
437 .ranksel_ap = 1,
439 .subseq_ctrl = {
440 .cmd_executions = loops2,
441 .cmd_delay_gap = 4,
442 .post_ssq_wait = 4,
443 .data_direction = SSQ_WR,
445 .sp_cmd_addr = {
446 .address = 0,
447 .rowbits = 0,
448 .bank = 0,
449 .rank = slotrank,
451 .addr_update = {
452 .inc_addr_8 = 1,
453 .addr_wrap = wrap2,
456 /* DRAM command NOP */
457 [3] = {
458 .sp_cmd_ctrl = {
459 .command = IOSAV_NOP,
460 .ranksel_ap = 1,
462 .subseq_ctrl = {
463 .cmd_executions = 1,
464 .cmd_delay_gap = 3,
465 .post_ssq_wait = ctrl->CWL + ctrl->tWTR + 5,
466 .data_direction = SSQ_WR,
468 .sp_cmd_addr = {
469 .address = 8,
470 .rowbits = 0,
471 .bank = 0,
472 .rank = slotrank,
474 .addr_update = {
475 .addr_wrap = 31,
479 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
482 void iosav_write_command_training_sequence(
483 ramctr_timing *ctrl, int channel, int slotrank, unsigned int address)
485 const struct iosav_ssq sequence[] = {
486 /* DRAM command ACT */
487 [0] = {
488 .sp_cmd_ctrl = {
489 .command = IOSAV_ACT,
490 .ranksel_ap = 1,
492 .subseq_ctrl = {
493 .cmd_executions = 8,
494 .cmd_delay_gap = MAX(ctrl->tRRD, (ctrl->tFAW >> 2) + 1),
495 .post_ssq_wait = ctrl->tRCD,
496 .data_direction = SSQ_NA,
498 .sp_cmd_addr = {
499 .address = address,
500 .rowbits = 6,
501 .bank = 0,
502 .rank = slotrank,
504 .addr_update = {
505 .inc_bank = 1,
506 .addr_wrap = 18,
509 /* DRAM command WR */
510 [1] = {
511 .sp_cmd_ctrl = {
512 .command = IOSAV_WR,
513 .ranksel_ap = 1,
515 .subseq_ctrl = {
516 .cmd_executions = 32,
517 .cmd_delay_gap = 4,
518 .post_ssq_wait = ctrl->CWL + ctrl->tWTR + 8,
519 .data_direction = SSQ_WR,
521 .sp_cmd_addr = {
522 .address = 0,
523 .rowbits = 0,
524 .bank = 0,
525 .rank = slotrank,
527 .addr_update = {
528 .inc_addr_8 = 1,
529 .addr_wrap = 18,
530 .lfsr_upd = 3,
531 .lfsr_xors = 2,
534 /* DRAM command RD */
535 [2] = {
536 .sp_cmd_ctrl = {
537 .command = IOSAV_RD,
538 .ranksel_ap = 1,
540 .subseq_ctrl = {
541 .cmd_executions = 32,
542 .cmd_delay_gap = 4,
543 .post_ssq_wait = MAX(ctrl->tRTP, 8),
544 .data_direction = SSQ_RD,
546 .sp_cmd_addr = {
547 .address = 0,
548 .rowbits = 0,
549 .bank = 0,
550 .rank = slotrank,
552 .addr_update = {
553 .inc_addr_8 = 1,
554 .addr_wrap = 18,
555 .lfsr_upd = 3,
556 .lfsr_xors = 2,
559 /* DRAM command PRE */
560 [3] = {
561 .sp_cmd_ctrl = {
562 .command = IOSAV_PRE,
563 .ranksel_ap = 1,
565 .subseq_ctrl = {
566 .cmd_executions = 1,
567 .cmd_delay_gap = 4,
568 .post_ssq_wait = 15,
569 .data_direction = SSQ_NA,
571 .sp_cmd_addr = {
572 .address = 1 << 10,
573 .rowbits = 6,
574 .bank = 0,
575 .rank = slotrank,
577 .addr_update = {
578 .addr_wrap = 18,
582 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
585 void iosav_write_data_write_sequence(ramctr_timing *ctrl, int channel, int slotrank)
587 const struct iosav_ssq sequence[] = {
588 /* DRAM command ACT */
589 [0] = {
590 .sp_cmd_ctrl = {
591 .command = IOSAV_ACT,
592 .ranksel_ap = 1,
594 .subseq_ctrl = {
595 .cmd_executions = 4,
596 .cmd_delay_gap = MAX(ctrl->tRRD, (ctrl->tFAW >> 2) + 1),
597 .post_ssq_wait = ctrl->tRCD,
598 .data_direction = SSQ_NA,
600 .sp_cmd_addr = {
601 .address = 0,
602 .rowbits = 6,
603 .bank = 0,
604 .rank = slotrank,
606 .addr_update = {
607 .inc_bank = 0,
608 .addr_wrap = 18,
611 /* DRAM command WR */
612 [1] = {
613 .sp_cmd_ctrl = {
614 .command = IOSAV_WR,
615 .ranksel_ap = 1,
617 .subseq_ctrl = {
618 .cmd_executions = 32,
619 .cmd_delay_gap = 20,
620 .post_ssq_wait = ctrl->CWL + ctrl->tWTR + 8,
621 .data_direction = SSQ_WR,
623 .sp_cmd_addr = {
624 .address = 0,
625 .rowbits = 0,
626 .bank = 0,
627 .rank = slotrank,
629 .addr_update = {
630 .inc_addr_8 = 1,
631 .addr_wrap = 18,
634 /* DRAM command RD */
635 [2] = {
636 .sp_cmd_ctrl = {
637 .command = IOSAV_RD,
638 .ranksel_ap = 1,
640 .subseq_ctrl = {
641 .cmd_executions = 32,
642 .cmd_delay_gap = 20,
643 .post_ssq_wait = MAX(ctrl->tRTP, 8),
644 .data_direction = SSQ_RD,
646 .sp_cmd_addr = {
647 .address = 0,
648 .rowbits = 0,
649 .bank = 0,
650 .rank = slotrank,
652 .addr_update = {
653 .inc_addr_8 = 1,
654 .addr_wrap = 18,
657 /* DRAM command PRE */
658 [3] = {
659 .sp_cmd_ctrl = {
660 .command = IOSAV_PRE,
661 .ranksel_ap = 1,
663 .subseq_ctrl = {
664 .cmd_executions = 1,
665 .cmd_delay_gap = 3,
666 .post_ssq_wait = ctrl->tRP,
667 .data_direction = SSQ_NA,
669 .sp_cmd_addr = {
670 .address = 1 << 10,
671 .rowbits = 6,
672 .bank = 0,
673 .rank = slotrank,
677 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
680 void iosav_write_aggressive_write_read_sequence(ramctr_timing *ctrl, int channel, int slotrank)
682 const struct iosav_ssq sequence[] = {
683 /* DRAM command ACT */
684 [0] = {
685 .sp_cmd_ctrl = {
686 .command = IOSAV_ACT,
687 .ranksel_ap = 1,
689 .subseq_ctrl = {
690 .cmd_executions = 4,
691 .cmd_delay_gap = MAX((ctrl->tFAW >> 2) + 1, ctrl->tRRD),
692 .post_ssq_wait = ctrl->tRCD,
693 .data_direction = SSQ_NA,
695 .sp_cmd_addr = {
696 .address = 0,
697 .rowbits = 6,
698 .bank = 0,
699 .rank = slotrank,
701 .addr_update = {
702 .inc_bank = 1,
703 .addr_wrap = 18,
706 /* DRAM command WR */
707 [1] = {
708 .sp_cmd_ctrl = {
709 .command = IOSAV_WR,
710 .ranksel_ap = 1,
712 .subseq_ctrl = {
713 .cmd_executions = 480,
714 .cmd_delay_gap = 4,
715 .post_ssq_wait = ctrl->tWTR + ctrl->CWL + 8,
716 .data_direction = SSQ_WR,
718 .sp_cmd_addr = {
719 .address = 0,
720 .rowbits = 0,
721 .bank = 0,
722 .rank = slotrank,
724 .addr_update = {
725 .inc_addr_8 = 1,
726 .addr_wrap = 18,
729 /* DRAM command RD */
730 [2] = {
731 .sp_cmd_ctrl = {
732 .command = IOSAV_RD,
733 .ranksel_ap = 1,
735 .subseq_ctrl = {
736 .cmd_executions = 480,
737 .cmd_delay_gap = 4,
738 .post_ssq_wait = MAX(ctrl->tRTP, 8),
739 .data_direction = SSQ_RD,
741 .sp_cmd_addr = {
742 .address = 0,
743 .rowbits = 0,
744 .bank = 0,
745 .rank = slotrank,
747 .addr_update = {
748 .inc_addr_8 = 1,
749 .addr_wrap = 18,
752 /* DRAM command PRE */
753 [3] = {
754 .sp_cmd_ctrl = {
755 .command = IOSAV_PRE,
756 .ranksel_ap = 1,
758 .subseq_ctrl = {
759 .cmd_executions = 1,
760 .cmd_delay_gap = 4,
761 .post_ssq_wait = ctrl->tRP,
762 .data_direction = SSQ_NA,
764 .sp_cmd_addr = {
765 .address = 1 << 10,
766 .rowbits = 6,
767 .bank = 0,
768 .rank = slotrank,
772 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));
775 void iosav_write_memory_test_sequence(ramctr_timing *ctrl, int channel, int slotrank)
777 const struct iosav_ssq sequence[] = {
778 /* DRAM command ACT */
779 [0] = {
780 .sp_cmd_ctrl = {
781 .command = IOSAV_ACT,
782 .ranksel_ap = 1,
784 .subseq_ctrl = {
785 .cmd_executions = 4,
786 .cmd_delay_gap = 8,
787 .post_ssq_wait = 40,
788 .data_direction = SSQ_NA,
790 .sp_cmd_addr = {
791 .address = 0,
792 .rowbits = 6,
793 .bank = 0,
794 .rank = slotrank,
796 .addr_update = {
797 .inc_bank = 1,
798 .addr_wrap = 18,
801 /* DRAM command WR */
802 [1] = {
803 .sp_cmd_ctrl = {
804 .command = IOSAV_WR,
805 .ranksel_ap = 1,
807 .subseq_ctrl = {
808 .cmd_executions = 100,
809 .cmd_delay_gap = 4,
810 .post_ssq_wait = 40,
811 .data_direction = SSQ_WR,
813 .sp_cmd_addr = {
814 .address = 0,
815 .rowbits = 0,
816 .bank = 0,
817 .rank = slotrank,
819 .addr_update = {
820 .inc_addr_8 = 1,
821 .addr_wrap = 18,
824 /* DRAM command RD */
825 [2] = {
826 .sp_cmd_ctrl = {
827 .command = IOSAV_RD,
828 .ranksel_ap = 1,
830 .subseq_ctrl = {
831 .cmd_executions = 100,
832 .cmd_delay_gap = 4,
833 .post_ssq_wait = 40,
834 .data_direction = SSQ_RD,
836 .sp_cmd_addr = {
837 .address = 0,
838 .rowbits = 0,
839 .bank = 0,
840 .rank = slotrank,
842 .addr_update = {
843 .inc_addr_8 = 1,
844 .addr_wrap = 18,
847 /* DRAM command PRE */
848 [3] = {
849 .sp_cmd_ctrl = {
850 .command = IOSAV_PRE,
851 .ranksel_ap = 1,
853 .subseq_ctrl = {
854 .cmd_executions = 1,
855 .cmd_delay_gap = 3,
856 .post_ssq_wait = 40,
857 .data_direction = SSQ_NA,
859 .sp_cmd_addr = {
860 .address = 1 << 10,
861 .rowbits = 6,
862 .bank = 0,
863 .rank = slotrank,
865 .addr_update = {
866 .addr_wrap = 18,
870 iosav_write_sequence(channel, sequence, ARRAY_SIZE(sequence));