diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h
index d9d63a9704baf041c1e54f3856373eca5d12912b..a98595fdd5bd09e0486d2c9c0358d88de5f56383 100644
--- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h
+++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h
@@ -359,6 +359,7 @@ typedef struct
   nfapi_nr_ue_ul_beamforming_t beamforming;
   //OAI specific
   int8_t absolute_delta_PUSCH;
+  int16_t tx_power;
   fapi_nr_tx_request_body_t tx_request_body;
 } nfapi_nr_ue_pusch_pdu_t;
 
diff --git a/openair2/LAYER2/NR_MAC_COMMON/nr_mac.h b/openair2/LAYER2/NR_MAC_COMMON/nr_mac.h
index c8d8232382dec0ea2bc9f020255f24a5de50c820..dba0f8bcd5b9e6f22bc0b860fd3851b8a7899a38 100644
--- a/openair2/LAYER2/NR_MAC_COMMON/nr_mac.h
+++ b/openair2/LAYER2/NR_MAC_COMMON/nr_mac.h
@@ -571,6 +571,7 @@ typedef struct NR_UE_UL_BWP {
   uint8_t mcs_table;
   nr_dci_format_t dci_format;
   int max_fb_time;
+  long *p0_NominalWithGrant;
 } NR_UE_UL_BWP_t;
 
 // non-BWP serving cell configuration
diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c
index 52d45c938b4e9ec81e2fa6381540ad40c69714f5..50577fc4f72e8b198811cf1284cd7887ebc287e7 100644
--- a/openair2/LAYER2/NR_MAC_UE/config_ue.c
+++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c
@@ -1409,11 +1409,14 @@ static void configure_common_BWP_ul(NR_UE_MAC_INST_t *mac, int bwp_id, NR_BWP_Up
                   ul_common->pusch_ConfigCommon->choice.setup->pusch_TimeDomainAllocationList,
                   NR_PUSCH_TimeDomainResourceAllocationList_t);
         UPDATE_IE(bwp->msg3_DeltaPreamble, ul_common->pusch_ConfigCommon->choice.setup->msg3_DeltaPreamble, long);
+        UPDATE_IE(bwp->p0_NominalWithGrant, ul_common->pusch_ConfigCommon->choice.setup->p0_NominalWithGrant, long);
       }
       if (ul_common->pusch_ConfigCommon->present == NR_SetupRelease_PUSCH_ConfigCommon_PR_release) {
         asn1cFreeStruc(asn_DEF_NR_PUSCH_TimeDomainResourceAllocationList, bwp->tdaList_Common);
         free(bwp->msg3_DeltaPreamble);
         bwp->msg3_DeltaPreamble = NULL;
+        free(bwp->p0_NominalWithGrant);
+        bwp->p0_NominalWithGrant = NULL;
       }
     }
   }
@@ -2130,6 +2133,8 @@ void release_ul_BWP(NR_UE_MAC_INST_t *mac, int index)
   asn1cFreeStruc(asn_DEF_NR_SRS_Config, bwp->srs_Config);
   free(bwp->msg3_DeltaPreamble);
   bwp->msg3_DeltaPreamble = NULL;
+  free(bwp->p0_NominalWithGrant);
+  bwp->p0_NominalWithGrant = NULL;
   free(bwp);
 }
 
diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h
index 96e6238128a820ef73ca46e73252538bfb207b48..88973af84971f73f185bea8dfdf0ad56ae4f0366 100644
--- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h
+++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h
@@ -219,6 +219,20 @@ int16_t get_pucch_tx_power_ue(NR_UE_MAC_INST_t *mac,
                               int subframe_number,
                               int O_uci,
                               uint16_t start_prb);
+int get_pusch_tx_power_ue(
+  NR_UE_MAC_INST_t *mac,
+  int num_rb,
+  int start_prb,
+  uint16_t nb_symb_sch,
+  uint16_t nb_dmrs_prb,
+  uint16_t nb_ptrs_prb,
+  uint16_t qm,
+  uint16_t R,
+  uint16_t beta_offset_csi1,
+  uint32_t sum_bits_in_codeblocks,
+  int delta_pusch,
+  bool is_rar_tx_retx,
+  bool transform_precoding);
 
 int nr_ue_configure_pucch(NR_UE_MAC_INST_t *mac,
                            int slot,
diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_power_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_power_procedures.c
index 459c93f26decf84fef2b32b4e38b3ca526e10604..0dbaa5debf1e65beac95888bf0c663b788eb8e4e 100644
--- a/openair2/LAYER2/NR_MAC_UE/nr_ue_power_procedures.c
+++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_power_procedures.c
@@ -434,3 +434,136 @@ int16_t compute_nr_SSB_PL(NR_UE_MAC_INST_t *mac, short ssb_rsrp_dBm)
 
   return pathloss;
 }
+
+// PUSCH transmission power according to 38.213 7.1
+int get_pusch_tx_power_ue(NR_UE_MAC_INST_t *mac,
+                          int num_rb,
+                          int start_prb,
+                          uint16_t nb_symb_sch,
+                          uint16_t nb_dmrs_prb,
+                          uint16_t nb_ptrs_prb,
+                          uint16_t qm,
+                          uint16_t R,
+                          uint16_t beta_offset_csi1,
+                          uint32_t sum_bits_in_codeblocks,
+                          int delta_pusch,
+                          bool is_rar_tx_retx,
+                          bool transform_precoding)
+{
+  LOG_D(NR_MAC,
+        "PUSCH tx power determination num_rb=%d start_prb=%d nb_symb_sch=%u nb_dmrs_prb=%u nb_ptrs_prb=%u Qm=%u R= %u "
+        "beta_offset_cs1=%u sum_bits_in_codeblocks=%u delta_pusch=%d is_rar_tx_retx=%d transform_precoding=%d\n",
+        num_rb,
+        start_prb,
+        nb_symb_sch,
+        nb_dmrs_prb,
+        nb_ptrs_prb,
+        qm,
+        R,
+        beta_offset_csi1,
+        sum_bits_in_codeblocks,
+        delta_pusch,
+        is_rar_tx_retx,
+        transform_precoding);
+  NR_UE_UL_BWP_t *current_UL_BWP = mac->current_UL_BWP;
+  AssertFatal(current_UL_BWP, "Missing configuration: need UL_BWP to calculate PUSCH tx power\n");
+  NR_PUSCH_Config_t *pusch_Config = current_UL_BWP->pusch_Config;
+  bool has_pusch_config = pusch_Config != NULL;
+  bool has_pusch_power_control_config = has_pusch_config && pusch_Config->pusch_PowerControl != NULL;
+  bool is_provided_alpha_sets = has_pusch_power_control_config && pusch_Config->pusch_PowerControl->p0_AlphaSets != NULL;
+  AssertFatal(!has_pusch_power_control_config || pusch_Config->pusch_PowerControl->sri_PUSCH_MappingToAddModList == NULL,
+              "SRI-PUSCH-PowerControl handling not implemented\n");
+
+  int P_O_NOMINAL_PUSCH;
+  float alpha;
+  const float alpha_factor_table[8] = {0.0f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f};
+  if (is_rar_tx_retx || !is_provided_alpha_sets || current_UL_BWP->p0_NominalWithGrant == NULL) {
+    int DELTA_PREAMBLE_MSG3 = 0;
+    if (current_UL_BWP->msg3_DeltaPreamble) {
+      DELTA_PREAMBLE_MSG3 = *current_UL_BWP->msg3_DeltaPreamble;
+    }
+    NR_RACH_ConfigCommon_t *nr_rach_ConfigCommon = current_UL_BWP->rach_ConfigCommon;
+    long preambleReceivedTargetPower = nr_rach_ConfigCommon->rach_ConfigGeneric.preambleReceivedTargetPower;
+    int P_O_PRE = preambleReceivedTargetPower;
+    P_O_NOMINAL_PUSCH = P_O_PRE + DELTA_PREAMBLE_MSG3;
+
+    if (has_pusch_power_control_config && pusch_Config->pusch_PowerControl->msg3_Alpha) {
+      alpha = alpha_factor_table[*pusch_Config->pusch_PowerControl->msg3_Alpha];
+    } else {
+      alpha = 1.0f;
+    }
+  } else {
+    P_O_NOMINAL_PUSCH = *current_UL_BWP->p0_NominalWithGrant;
+    if (pusch_Config->pusch_PowerControl->p0_AlphaSets->list.array[0]->alpha) {
+      alpha = alpha_factor_table[*pusch_Config->pusch_PowerControl->p0_AlphaSets->list.array[0]->alpha];
+    } else {
+      // Default according to 38.331 P0-PUSCH-AlphaSet field descriptions
+      alpha = 1.0f;
+    }
+  }
+
+  int P_O_UE_PUSCH;
+  if (is_rar_tx_retx || !is_provided_alpha_sets) {
+    P_O_UE_PUSCH = 0;
+  } else {
+    if (pusch_Config->pusch_PowerControl->p0_AlphaSets->list.array[0]->p0) {
+      P_O_UE_PUSCH = *pusch_Config->pusch_PowerControl->p0_AlphaSets->list.array[0]->p0;
+    } else {
+      // Default according to 38.331 P0-PUSCH-AlphaSet field descriptions
+      P_O_UE_PUSCH = 0;
+    }
+  }
+
+  int mu = current_UL_BWP->scs;
+
+  int M_pusch_component = 10 * log10((pow(2, mu)) * num_rb);
+  int P_CMAX = nr_get_Pcmax(mac->p_Max,
+                            mac->nr_band,
+                            mac->frequency_range,
+                            2,
+                            false,
+                            mac->current_UL_BWP->scs,
+                            mac->current_UL_BWP->BWPSize,
+                            transform_precoding,
+                            1,
+                            start_prb);
+
+  int P_O_PUSCH = P_O_NOMINAL_PUSCH + P_O_UE_PUSCH;
+
+  float DELTA_TF = 0;
+  if (has_pusch_power_control_config && pusch_Config->pusch_PowerControl->deltaMCS) {
+    float beta_offset = 1;
+    float BPRE;
+    if (sum_bits_in_codeblocks == 0) {
+      float table_38_213_9_3_2[] = {
+          1.125,   11.250,  21.375,  31.625,  41.750,  52.000,   62.250,   72.500,   82.875,   93.125,
+          103.500, 114.000, 125.000, 136.250, 148.000, 1510.000, 1612.625, 1715.875, 1820.000,
+      };
+      beta_offset = table_38_213_9_3_2[beta_offset_csi1];
+      BPRE = (qm * R / beta_offset) / 1024;
+    } else {
+      const int nb_subcarrier_per_rb = 12;
+      const uint32_t N_RE = nb_subcarrier_per_rb * nb_symb_sch - nb_dmrs_prb - nb_ptrs_prb;
+      BPRE = sum_bits_in_codeblocks / (float)(N_RE * num_rb);
+    }
+    DELTA_TF = 10 * log10(pow(2, BPRE * 1.25f) * beta_offset);
+  }
+
+  // TODO: compute pathoss using correct reference
+  int16_t pathloss = compute_nr_SSB_PL(mac, mac->ssb_measurements.ssb_rsrp_dBm);
+
+  int f_b_f_c = 0;
+  if (has_pusch_power_control_config && pusch_Config->pusch_PowerControl->tpc_Accumulation) {
+    f_b_f_c = delta_pusch;
+  } else {
+    // TODO: PUSCH power control state
+  }
+  LOG_D(NR_MAC,
+        "PUSCH tx power components P_O_PUSCH=%d, M_pusch_component=%d, alpha*pathloss=%f, delta_TF=%f, f_b_f_c=%d\n",
+        P_O_PUSCH,
+        M_pusch_component,
+        alpha * pathloss,
+        DELTA_TF,
+        f_b_f_c);
+  return min(P_CMAX, P_O_PUSCH + M_pusch_component + alpha * pathloss + DELTA_TF + f_b_f_c);
+}
diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c
index 29b36d3e9d420361470d115aa77d23bd98da1650..20b0e80f61c607708ed05ddaadbe1a0edc450b2c 100644
--- a/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c
+++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_scheduler.c
@@ -913,6 +913,22 @@ int nr_config_pusch_pdu(NR_UE_MAC_INST_t *mac,
     pusch_config_pdu->pusch_data.tb_size = mac->ul_harq_info[pid].TBS;
   }
 
+  bool is_rar_tx_retx = rnti_type == TYPE_TC_RNTI_;
+
+  pusch_config_pdu->tx_power = get_pusch_tx_power_ue(mac,
+                                                     pusch_config_pdu->rb_size,
+                                                     pusch_config_pdu->rb_start,
+                                                     pusch_config_pdu->nr_of_symbols,
+                                                     nb_dmrs_re_per_rb * number_dmrs_symbols,
+                                                     0, //TODO: count PTRS per RB
+                                                     pusch_config_pdu->qam_mod_order,
+                                                     pusch_config_pdu->target_code_rate,
+                                                     pusch_config_pdu->pusch_uci.beta_offset_csi1,
+                                                     pusch_config_pdu->pusch_data.tb_size << 3,
+                                                     pusch_config_pdu->absolute_delta_PUSCH,
+                                                     is_rar_tx_retx,
+                                                     pusch_config_pdu->transform_precoding);
+
   pusch_config_pdu->ldpcBaseGraph = get_BG(pusch_config_pdu->pusch_data.tb_size << 3, pusch_config_pdu->target_code_rate);
 
   //The MAC entity shall restart retxBSR-Timer upon reception of a grant for transmission of new data on any UL-SCH
diff --git a/openair2/LAYER2/NR_MAC_UE/tests/test_nr_ue_power_procedures.cpp b/openair2/LAYER2/NR_MAC_UE/tests/test_nr_ue_power_procedures.cpp
index cb2714791b52f62719a06cfda31d5b1377ea3af6..49f2d58f279876deaea4f470aa684e168b36815c 100644
--- a/openair2/LAYER2/NR_MAC_UE/tests/test_nr_ue_power_procedures.cpp
+++ b/openair2/LAYER2/NR_MAC_UE/tests/test_nr_ue_power_procedures.cpp
@@ -164,15 +164,169 @@ TEST(test_pucch_power_state, test_accumulated_delta_pucch)
   }
 }
 
-TEST(pc_min, check_all_bw_indexes) {
-  const int NB_RB_UL[] ={
-    11, 24, 38, 51, 65, 78, 106, 133, 162, 217, 245, 273
-  };
-  for (auto i = 0U; i < sizeof(NB_RB_UL)/sizeof(NB_RB_UL[0]); i++) {
+TEST(pc_min, check_all_bw_indexes)
+{
+  const int NB_RB_UL[] = {11, 24, 38, 51, 65, 78, 106, 133, 162, 217, 245, 273};
+  for (auto i = 0U; i < sizeof(NB_RB_UL) / sizeof(NB_RB_UL[0]); i++) {
     (void)nr_get_Pcmin(1, 20, NB_RB_UL[i]);
   }
 }
 
+TEST(pusch_power_control, pusch_power_control_msg3)
+{
+  NR_UE_MAC_INST_t mac = {0};
+  NR_UE_UL_BWP_t current_UL_BWP = {0};
+  current_UL_BWP.scs = 1;
+  current_UL_BWP.BWPSize = 106;
+  mac.current_UL_BWP = &current_UL_BWP;
+  NR_RACH_ConfigCommon_t nr_rach_ConfigCommon = {0};
+  current_UL_BWP.rach_ConfigCommon = &nr_rach_ConfigCommon;
+  mac.nr_band = 78;
+  NR_PUSCH_Config_t pusch_Config = {0};
+  current_UL_BWP.pusch_Config = &pusch_Config;
+  NR_PUSCH_PowerControl pusch_PowerControl = {0};
+  pusch_Config.pusch_PowerControl = &pusch_PowerControl;
+  pusch_PowerControl.tpc_Accumulation = (long*)1;
+
+  // msg3 cofiguration as in 5g_rfsimulator testcase
+  int num_rb = 8;
+  int start_prb = 0;
+  uint16_t nb_symb_sch = 3;
+  uint16_t nb_dmrs_prb = 12;
+  uint16_t nb_ptrs_prb = 0;
+  uint16_t Qm = 2;
+  uint16_t R = 1570;
+  uint16_t beta_offset_csi1 = 0;
+  uint32_t sum_bits_in_codeblocks = 56;
+  int delta_pusch = 0;
+  bool is_rar_tx_retx = true;
+
+  int P_CMAX = nr_get_Pcmax(23, mac.nr_band, FR1, Qm, false, current_UL_BWP.scs, current_UL_BWP.BWPSize, false, num_rb, start_prb);
+
+  long preambleReceivedTargetPower = -96;
+  nr_rach_ConfigCommon.rach_ConfigGeneric.preambleReceivedTargetPower = preambleReceivedTargetPower;
+
+  int power = get_pusch_tx_power_ue(&mac,
+                                    num_rb,
+                                    start_prb,
+                                    nb_symb_sch,
+                                    nb_dmrs_prb,
+                                    nb_ptrs_prb,
+                                    Qm,
+                                    R,
+                                    beta_offset_csi1,
+                                    sum_bits_in_codeblocks,
+                                    delta_pusch,
+                                    is_rar_tx_retx,
+                                    false);
+  EXPECT_EQ(power, -84);
+  EXPECT_LT(power, P_CMAX);
+  nr_rach_ConfigCommon.rach_ConfigGeneric.preambleReceivedTargetPower -= 2;
+
+  int reduced_power = get_pusch_tx_power_ue(&mac,
+                                            num_rb,
+                                            start_prb,
+                                            nb_symb_sch,
+                                            nb_dmrs_prb,
+                                            nb_ptrs_prb,
+                                            Qm,
+                                            R,
+                                            beta_offset_csi1,
+                                            sum_bits_in_codeblocks,
+                                            delta_pusch,
+                                            is_rar_tx_retx,
+                                            false);
+  EXPECT_EQ(std::min(P_CMAX, power - 2), reduced_power)
+      << "Incorrect handling of preambleReceivedTargetPower";
+  EXPECT_LT(reduced_power, P_CMAX) << "Power above P_CMAX";
+
+  delta_pusch = 4;
+  int increased_power = get_pusch_tx_power_ue(&mac,
+                                            num_rb,
+                                            start_prb,
+                                            nb_symb_sch,
+                                            nb_dmrs_prb,
+                                            nb_ptrs_prb,
+                                            Qm,
+                                            R,
+                                            beta_offset_csi1,
+                                            sum_bits_in_codeblocks,
+                                            delta_pusch,
+                                            is_rar_tx_retx,
+                                            false);
+  EXPECT_EQ(std::min(P_CMAX, reduced_power + delta_pusch), increased_power)
+      << "delta_pusch should increase tx power";
+  EXPECT_LT(increased_power, P_CMAX) << "Power above P_CMAX";
+}
+
+TEST(pusch_power_control, pusch_power_data)
+{
+  NR_UE_MAC_INST_t mac = {0};
+  NR_UE_UL_BWP_t current_UL_BWP = {0};
+  current_UL_BWP.scs = 1;
+  current_UL_BWP.BWPSize = 106;
+  mac.current_UL_BWP = &current_UL_BWP;
+  NR_RACH_ConfigCommon_t nr_rach_ConfigCommon = {0};
+  current_UL_BWP.rach_ConfigCommon = &nr_rach_ConfigCommon;
+  mac.nr_band = 78;
+
+  bool is_rar_tx_retx = false;
+  int num_rb = 5;
+  int start_prb = 0;
+  uint16_t nb_symb_sch = 3;
+  uint16_t nb_dmrs_prb = 6;
+  uint16_t nb_ptrs_prb = 0;
+  uint16_t Qm = 2;
+  uint16_t R = 6790;
+  uint16_t beta_offset_csi1 = 0;
+  uint32_t sum_bits_in_codeblocks = 192;
+  int delta_pusch = 4;
+  bool transform_precoding = false;
+  NR_PUSCH_Config_t pusch_Config = {0};
+  current_UL_BWP.pusch_Config = &pusch_Config;
+  NR_PUSCH_PowerControl pusch_PowerControl = {0};
+  pusch_Config.pusch_PowerControl = &pusch_PowerControl;
+  pusch_PowerControl.tpc_Accumulation = (long*)1;
+  long p0_NominalWithGrant = 0;
+  current_UL_BWP.p0_NominalWithGrant = &p0_NominalWithGrant;
+  pusch_PowerControl.deltaMCS = (long*)1;
+
+  int P_CMAX = nr_get_Pcmax(23, mac.nr_band, FR1, Qm, false, current_UL_BWP.scs, current_UL_BWP.BWPSize, transform_precoding, num_rb, start_prb);
+
+  int power = get_pusch_tx_power_ue(&mac,
+                                    num_rb,
+                                    start_prb,
+                                    nb_symb_sch,
+                                    nb_dmrs_prb,
+                                    nb_ptrs_prb,
+                                    Qm,
+                                    R,
+                                    beta_offset_csi1,
+                                    sum_bits_in_codeblocks,
+                                    delta_pusch,
+                                    is_rar_tx_retx,
+                                    transform_precoding);
+  EXPECT_LE(power, P_CMAX);
+  EXPECT_EQ(power, 18);
+
+  const int BETA_OFFSET_CSI1_DEFAULT = 13;
+  sum_bits_in_codeblocks = 0; // CSI-only
+  power = get_pusch_tx_power_ue(&mac,
+                                num_rb,
+                                start_prb,
+                                nb_symb_sch,
+                                nb_dmrs_prb,
+                                nb_ptrs_prb,
+                                Qm,
+                                R,
+                                BETA_OFFSET_CSI1_DEFAULT,
+                                sum_bits_in_codeblocks,
+                                delta_pusch,
+                                is_rar_tx_retx,
+                                transform_precoding);
+  EXPECT_EQ(power, P_CMAX) << "Expecting max tx power because of deltaMCS with CSI-only";
+}
+
 int main(int argc, char** argv)
 {
   logInit();