From 3f280b8d23c3c26dc28b4c0d5672f17842c7d8f6 Mon Sep 17 00:00:00 2001 From: Mark Pizzutillo Date: Tue, 29 Oct 2019 12:20:39 -0400 Subject: Fix odt RD/WR fields Change-Id: I4aaa23af53a72e4f90218daedbed80d8721ff337 Reviewed-on: http://rchgit01.rchland.ibm.com/gerrit1/86280 Tested-by: FSP CI Jenkins Tested-by: Jenkins Server Tested-by: Hostboot CI Reviewed-by: Louis Stermole Reviewed-by: STEPHEN GLANCY Dev-Ready: STEPHEN GLANCY Tested-by: HWSV CI Reviewed-by: Jennifer A Stofer Reviewed-on: http://rchgit01.rchland.ibm.com/gerrit1/86542 Tested-by: Jenkins OP Build CI Tested-by: Jenkins OP HW Reviewed-by: Daniel M Crowell --- .../procedures/hwp/memory/lib/exp_draminit_utils.H | 72 ++++++++++++++++++++-- .../ocmb/procedures/hwp/initfiles/explorer_scom.C | 38 +++++++++--- .../memory/lib/data_engine/attr_engine_traits.H | 46 +++++++++----- .../memory/lib/data_engine/data_engine_utils.H | 34 ++++++++++ .../memory/lib/mss_generic_attribute_getters.H | 12 ++-- .../generic_memory_si_attributes.xml | 2 + 6 files changed, 170 insertions(+), 34 deletions(-) (limited to 'src/import') diff --git a/src/import/chips/ocmb/explorer/procedures/hwp/memory/lib/exp_draminit_utils.H b/src/import/chips/ocmb/explorer/procedures/hwp/memory/lib/exp_draminit_utils.H index 1e279f766..096cfcd60 100644 --- a/src/import/chips/ocmb/explorer/procedures/hwp/memory/lib/exp_draminit_utils.H +++ b/src/import/chips/ocmb/explorer/procedures/hwp/memory/lib/exp_draminit_utils.H @@ -154,7 +154,10 @@ enum msdg_enable /// enum odt_fields { - FLD_LENGTH = 4, + R2_FLD_LENGTH = 2, // R2 = 2 rank (normal / 2 rank mode: makes use of 2 bits) + R4_FLD_LENGTH = 4, // R4 = 4 rank (4 rank mode: makes use of 4 bits) + ODT_MIDPOINT = 4, + R4_SHIFT = 2, RANK3 = 12, RANK2 = 8, RANK1 = 4, @@ -527,6 +530,9 @@ class phy_params fapi2::ReturnCode populate_odt_buffer(const uint8_t (&i_odt_rd_wr_attr)[MAX_DIMM_PER_PORT][MAX_RANK_PER_DIMM], fapi2::buffer& o_odt_buffer) const { + // TK - Update code for encoded quad CS, waiting on SPD + // static constexpr bool ENCODED_QUAD_CS_ENABLE = true; + // Const vector to map phy ranks to their buffer offset position const std::vector l_buffer_rank_offset = { @@ -538,10 +544,66 @@ class phy_params for (const auto& l_rank : iv_rank_info) { - const auto OFFSET = l_buffer_rank_offset[l_rank.get_phy_rank()]; - const auto DIMM_RANK = l_rank.get_dimm_rank(); - const auto DIMM_INDEX = mss::index(l_rank.get_dimm_target()); - FAPI_TRY(o_odt_buffer.insert(i_odt_rd_wr_attr[DIMM_INDEX][DIMM_RANK], OFFSET, odt_fields::FLD_LENGTH)); + if (iv_params.iv_rank4_mode[0] == fapi2::ENUM_ATTR_MEM_EFF_FOUR_RANK_MODE_ENABLE) + { + // A & B separate. We need to do a bit if shifting from our attribute + // our attribute is aligned XX00YY00 but we want XXYY0000 + // The attr must be populated this way, as we only have 4 ODTs and they are aligned as such + // Otherwise, we have problems on the SPD/decoder side + // where XX is A0A1 (bits 0,1) and YY is B0B1 (bits 4,5) + + // From MCHP spec: + // OdtRdMapCs BIT [1:0] ODT_A[1:0] value when reading to rank 0 + // OdtRdMapCs BIT [3:2] ODT_B[1:0] value when reading to rank 0 + // ... + + const auto OFFSET = l_buffer_rank_offset[l_rank.get_phy_rank()]; + const auto DIMM_RANK = l_rank.get_dimm_rank(); + const auto DIMM_INDEX = mss::index(l_rank.get_dimm_target()); + + uint8_t l_data = 0; + + // l_data populated as such: + // XX000000 || 0000YY00 << 2 + l_data = i_odt_rd_wr_attr[DIMM_INDEX][DIMM_RANK]; + l_data |= (i_odt_rd_wr_attr[DIMM_INDEX][DIMM_RANK] << odt_fields::R4_SHIFT); + + // Sanity check: bitwise and the relevant bits + l_data &= 0b11110000; + + // Now we have XXYY0000 + // Insert into the buffer + FAPI_TRY(o_odt_buffer.insert(l_data, OFFSET, odt_fields::R4_FLD_LENGTH)); + } + // TK: need more information for encoded_quadcs (4U only) + // else if (iv_params.iv_encoded_quadcs == ENCODED_QUAD_CS_ENABLE) + // { + // } + else + { + // For DDIMM: + // A & B together. B0 (ODT2) mirrors A0 (ODT0), B1 (ODT3) mirrors A1 (ODT1) + // ODTA/B [1:0] == [ODT3/1:ODT2/0] + + // From MCHP spec: + // OdtRdMapCs BIT [1:0] ODTA/B[1:0] value when reading to rank 0 + // So it already accounts for any mirroring, we just need to plop in the value + + const auto OFFSET = l_buffer_rank_offset[l_rank.get_phy_rank()]; + const auto DIMM_RANK = l_rank.get_dimm_rank(); + const auto DIMM_INDEX = mss::index(l_rank.get_dimm_target()); + + uint8_t l_data = 0; + l_data = i_odt_rd_wr_attr[DIMM_INDEX][DIMM_RANK]; + + // Finally, put it back + + // Insert l_data (attribute) from the corresponding dimm's position: + // DIMM0 (ODT0, ODT1) (bits 0,1) or DIMM1 ODT0, ODT1 (bits 4,5) (though DIMM1 probably wouldn't be applicable here) + // at the offset to match the draminit field. + // + FAPI_TRY(o_odt_buffer.insert(l_data, OFFSET, odt_fields::R2_FLD_LENGTH, DIMM_INDEX * odt_fields::ODT_MIDPOINT)); + } } // Rest of the buffer should already be zeroed from declaration diff --git a/src/import/chips/ocmb/procedures/hwp/initfiles/explorer_scom.C b/src/import/chips/ocmb/procedures/hwp/initfiles/explorer_scom.C index 93f1a2167..964485726 100644 --- a/src/import/chips/ocmb/procedures/hwp/initfiles/explorer_scom.C +++ b/src/import/chips/ocmb/procedures/hwp/initfiles/explorer_scom.C @@ -198,6 +198,12 @@ fapi2::ReturnCode explorer_scom(const fapi2::Target literal_0) + && (l_TGT1_ATTR_MEM_EFF_NUM_MASTER_RANKS_PER_DIMM[literal_1] > literal_0)); + fapi2::ATTR_MEM_EFF_FOUR_RANK_MODE_Type l_TGT1_ATTR_MEM_EFF_FOUR_RANK_MODE; + FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_MEM_EFF_FOUR_RANK_MODE, TGT1, l_TGT1_ATTR_MEM_EFF_FOUR_RANK_MODE)); + uint64_t l_def_four_rank_mode = (l_TGT1_ATTR_MEM_EFF_FOUR_RANK_MODE[literal_0] == literal_1); + uint64_t l_def_cs_tied = ((l_def_four_rank_mode == literal_0) && (l_def_dual_drop == literal_0)); fapi2::ATTR_MEM_SI_ODT_WR_Type l_TGT1_ATTR_MEM_SI_ODT_WR; FAPI_TRY(FAPI_ATTR_GET(fapi2::ATTR_MEM_SI_ODT_WR, TGT1, l_TGT1_ATTR_MEM_SI_ODT_WR)); uint64_t l_def_NUM_RANKS = (l_TGT1_ATTR_MEM_EFF_LOGICAL_RANKS_PER_DIMM[literal_0] + @@ -629,12 +635,20 @@ fapi2::ReturnCode explorer_scom(const fapi2::Target((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_7) ); l_scom_buffer.insert<1, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_6) ); - l_scom_buffer.insert<2, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_3) ); - l_scom_buffer.insert<3, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_2) ); + l_scom_buffer.insert<2, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_3) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_7) & literal_0b1) && (l_def_cs_tied == literal_1))) ); + l_scom_buffer.insert<3, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_2) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_0] >> literal_6) & literal_0b1) && (l_def_cs_tied == literal_1))) ); l_scom_buffer.insert<4, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_7) ); l_scom_buffer.insert<5, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_6) ); - l_scom_buffer.insert<6, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_3) ); - l_scom_buffer.insert<7, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_2) ); + l_scom_buffer.insert<6, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_3) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_7) & literal_0b1) && (l_def_cs_tied == literal_1))) ); + l_scom_buffer.insert<7, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_2) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_1] >> literal_6) & literal_0b1) && (l_def_cs_tied == literal_1))) ); l_scom_buffer.insert<8, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_2] >> literal_7) ); l_scom_buffer.insert<9, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_2] >> literal_6) ); l_scom_buffer.insert<10, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_0][literal_2] >> literal_3) ); @@ -661,12 +675,20 @@ fapi2::ReturnCode explorer_scom(const fapi2::Target((l_TGT1_ATTR_MEM_SI_ODT_RD[literal_1][literal_3] >> literal_2) ); l_scom_buffer.insert<32, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_7) ); l_scom_buffer.insert<33, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_6) ); - l_scom_buffer.insert<34, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_3) ); - l_scom_buffer.insert<35, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_2) ); + l_scom_buffer.insert<34, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_3) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_7) & literal_0b1) && (l_def_cs_tied == literal_1))) ); + l_scom_buffer.insert<35, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_2) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_0] >> literal_6) & literal_0b1) && (l_def_cs_tied == literal_1))) ); l_scom_buffer.insert<36, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_7) ); l_scom_buffer.insert<37, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_6) ); - l_scom_buffer.insert<38, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_3) ); - l_scom_buffer.insert<39, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_2) ); + l_scom_buffer.insert<38, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_3) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_7) & literal_0b1) && (l_def_cs_tied == literal_1))) ); + l_scom_buffer.insert<39, 1, 63, uint64_t>(((((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_2) & + literal_0b1) && (l_def_cs_tied == literal_0)) + || (((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_1] >> literal_6) & literal_0b1) && (l_def_cs_tied == literal_1))) ); l_scom_buffer.insert<40, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_2] >> literal_7) ); l_scom_buffer.insert<41, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_2] >> literal_6) ); l_scom_buffer.insert<42, 1, 63, uint64_t>((l_TGT1_ATTR_MEM_SI_ODT_WR[literal_0][literal_2] >> literal_3) ); diff --git a/src/import/generic/memory/lib/data_engine/attr_engine_traits.H b/src/import/generic/memory/lib/data_engine/attr_engine_traits.H index c31a5ae30..adfd81b3f 100644 --- a/src/import/generic/memory/lib/data_engine/attr_engine_traits.H +++ b/src/import/generic/memory/lib/data_engine/attr_engine_traits.H @@ -40,11 +40,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include @@ -1831,6 +1831,8 @@ struct attrEngineTraitsget_ocmb_target(); + switch(i_efd_data->get_rank()) { case 0: @@ -1850,17 +1852,21 @@ struct attrEngineTraitsget_rank() ). + set_TARGET(l_ocmb), + "%s SPD decoder returned invalid rank: %d", + spd::c_str(l_ocmb), + i_efd_data->get_rank()); break; }; - // TK update to handle differentiating 2 DIMMs, defaulted to DIMM0 case for explorer - { - // Map to attribute bitmap - reverse(l_value); - o_setting = l_value; - } + // Map to attribute bitmap + l_value = mss::gen::align_odt_field_to_attr(l_value); + + o_setting = l_value; fapi_try_exit: return fapi2::current_err; @@ -1916,6 +1922,8 @@ struct attrEngineTraitsget_ocmb_target(); + switch(i_efd_data->get_rank()) { case 0: @@ -1935,17 +1943,21 @@ struct attrEngineTraitsget_rank() ). + set_TARGET(l_ocmb), + "%s SPD decoder returned invalid rank: %d", + spd::c_str(l_ocmb), + i_efd_data->get_rank()); break; }; - // TK update to handle differentiating 2 DIMMs, defaulted to DIMM0 case for explorer - { - // Map to attribute bitmap - reverse(l_value); - o_setting = l_value; - } + // Map to attribute bitmap + l_value = mss::gen::align_odt_field_to_attr(l_value); + + o_setting = l_value; fapi_try_exit: return fapi2::current_err; diff --git a/src/import/generic/memory/lib/data_engine/data_engine_utils.H b/src/import/generic/memory/lib/data_engine/data_engine_utils.H index d29f31e86..5e0c070fd 100644 --- a/src/import/generic/memory/lib/data_engine/data_engine_utils.H +++ b/src/import/generic/memory/lib/data_engine/data_engine_utils.H @@ -47,6 +47,7 @@ #include #include #include +#include namespace mss { @@ -478,6 +479,39 @@ fapi_try_exit: return fapi2::current_err; } +/// +/// @brief Shift the bits of the SPD field to match the attribute format +/// @param[in] i_value ODT field value from SPD +/// @return ATTR formatted uint8_t +/// +static inline uint8_t align_odt_field_to_attr(const uint8_t i_value) +{ + static constexpr uint8_t ODT2_OLD = 2; + static constexpr uint8_t ODT3_OLD = 3; + static constexpr uint8_t ODT2 = 4; + static constexpr uint8_t ODT3 = 5; + + fapi2::buffer l_value(i_value); + // Map to attribute bitmap + reverse(l_value); + + // l_value currently looks like: + // XXYY0000 + // ODT + // 0123---- + // + // We need it to look like: + // XX00YY00 + // 01--23-- + l_value.writeBit(l_value.getBit()); + l_value.writeBit(l_value.getBit()); + + l_value.clearBit(); + l_value.clearBit(); + + return l_value(); +} + }// gen }//mss diff --git a/src/import/generic/memory/lib/mss_generic_attribute_getters.H b/src/import/generic/memory/lib/mss_generic_attribute_getters.H index aa496b8d7..0dc348821 100644 --- a/src/import/generic/memory/lib/mss_generic_attribute_getters.H +++ b/src/import/generic/memory/lib/mss_generic_attribute_getters.H @@ -4190,7 +4190,8 @@ fapi_try_exit: /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK /// @note Array[DIMM][RANK] READ, On Die Termination triggering bitmap. Use bitmap to determine /// which ODT to fire for the designated rank. The bits in 8 bit field are [DIMM0 ODT0][DIMM0 -/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] +/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] For +/// Explorer: Only bits 0,1,4,5 are used. They correspond to A0 A1 -- -- B0 B1 -- -- /// inline fapi2::ReturnCode get_si_odt_rd(const fapi2::Target& i_target, uint8_t (&o_array)[4]) { @@ -4215,7 +4216,8 @@ fapi_try_exit: /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK /// @note Array[DIMM][RANK] READ, On Die Termination triggering bitmap. Use bitmap to determine /// which ODT to fire for the designated rank. The bits in 8 bit field are [DIMM0 ODT0][DIMM0 -/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] +/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] For +/// Explorer: Only bits 0,1,4,5 are used. They correspond to A0 A1 -- -- B0 B1 -- -- /// inline fapi2::ReturnCode get_si_odt_rd(const fapi2::Target& i_target, uint8_t (&o_array)[2][4]) @@ -4240,7 +4242,8 @@ fapi_try_exit: /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK /// @note Array[DIMM][RANK] WRITE, On Die Termination triggering bitmap. Use bitmap to determine /// which ODT to fire for the designated rank. The bits in 8 bit field are [DIMM0 ODT0][DIMM0 -/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] +/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] For +/// Explorer: Only bits 0,1,4,5 are used. They correspond to A0 A1 -- -- B0 B1 -- -- /// inline fapi2::ReturnCode get_si_odt_wr(const fapi2::Target& i_target, uint8_t (&o_array)[4]) { @@ -4265,7 +4268,8 @@ fapi_try_exit: /// @return fapi2::ReturnCode - FAPI2_RC_SUCCESS iff get is OK /// @note Array[DIMM][RANK] WRITE, On Die Termination triggering bitmap. Use bitmap to determine /// which ODT to fire for the designated rank. The bits in 8 bit field are [DIMM0 ODT0][DIMM0 -/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] +/// ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] For +/// Explorer: Only bits 0,1,4,5 are used. They correspond to A0 A1 -- -- B0 B1 -- -- /// inline fapi2::ReturnCode get_si_odt_wr(const fapi2::Target& i_target, uint8_t (&o_array)[2][4]) diff --git a/src/import/generic/procedures/xml/attribute_info/generic_memory_si_attributes.xml b/src/import/generic/procedures/xml/attribute_info/generic_memory_si_attributes.xml index 38eccdbe3..9ff21bdd1 100644 --- a/src/import/generic/procedures/xml/attribute_info/generic_memory_si_attributes.xml +++ b/src/import/generic/procedures/xml/attribute_info/generic_memory_si_attributes.xml @@ -458,6 +458,7 @@ READ, On Die Termination triggering bitmap. Use bitmap to determine which ODT to fire for the designated rank. The bits in 8 bit field are [DIMM0 ODT0][DIMM0 ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] + For Explorer: Only bits 0,1,4,5 are used. They correspond to A0 A1 -- -- B0 B1 -- -- uint8 @@ -474,6 +475,7 @@ WRITE, On Die Termination triggering bitmap. Use bitmap to determine which ODT to fire for the designated rank. The bits in 8 bit field are [DIMM0 ODT0][DIMM0 ODT1][DIMM0 ODT2][DIMM0 ODT3][DIMM1 ODT0][DIMM1 ODT1][DIMM1 ODT2][DIMM1 ODT3] + For Explorer: Only bits 0,1,4,5 are used. They correspond to A0 A1 -- -- B0 B1 -- -- uint8 -- cgit v1.2.1