diff --git a/bfd/elfnn-loongarch.c b/bfd/elfnn-loongarch.c
index e1cf7c1eda0..d8095aaae54 100644
--- a/bfd/elfnn-loongarch.c
+++ b/bfd/elfnn-loongarch.c
@@ -145,6 +145,12 @@ struct loongarch_elf_link_hash_table
 #define elf_backend_rela_normal 1
 #define elf_backend_default_execstack 0
 
+#define IS_LOONGARCH_TLS_DESC_RELOC(R_TYPE)    \
+  ((R_TYPE) == R_LARCH_TLS_DESC_PC_HI20	\
+   || (R_TYPE) == R_LARCH_TLS_DESC_LD_PC_LO12  \
+   || (R_TYPE) == R_LARCH_TLS_DESC_ADD_PC_LO12 \
+   || (R_TYPE) == R_LARCH_TLS_DESC_CALL)
+
 /* Generate a PLT header.  */
 
 static bool
@@ -605,6 +611,58 @@ loongarch_elf_record_tls_and_got_reference (bfd *abfd,
   return true;
 }
 
+/* Return true if DESC can transition to LE.  */
+static bool
+loongarch_can_relax_tls (struct bfd_link_info *info, unsigned int r_type,
+			 struct elf_link_hash_entry *h)
+{
+  if (! IS_LOONGARCH_TLS_DESC_RELOC (r_type))
+    return false;
+
+  bool local_exec = bfd_link_executable (info)
+		    && SYMBOL_REFERENCES_LOCAL (info, h);
+  if (! local_exec)
+      return false;
+
+  if (h && h->root.type == bfd_link_hash_undefweak)
+    return false;
+
+  return true;
+}
+
+/* Perform the transition from DESC relocation to LE relocation.  */
+static unsigned int
+loongarch_tls_transition_without_check (unsigned int r_type)
+{
+  switch (r_type)
+    {
+      case R_LARCH_TLS_DESC_PC_HI20:
+	return R_LARCH_TLS_LE_HI20;
+
+      case R_LARCH_TLS_DESC_LD_PC_LO12:
+	return R_LARCH_TLS_LE_LO12;
+
+      case R_LARCH_TLS_DESC_ADD_PC_LO12:
+      case R_LARCH_TLS_DESC_CALL:
+	return R_LARCH_NONE;
+
+      default:
+	break;
+    }
+
+  return r_type;
+}
+
+static unsigned int
+loongarch_tls_transition (struct bfd_link_info *info, unsigned int r_type,
+			  struct elf_link_hash_entry *h)
+{
+  if (! loongarch_can_relax_tls (info, r_type, h))
+    return r_type;
+
+  return loongarch_tls_transition_without_check (r_type);
+}
+
 /* Look through the relocs for a section during the first phase, and
    allocate space in the global offset table or procedure linkage
    table.  */
@@ -706,6 +764,7 @@ loongarch_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
       int need_dynreloc = 0;
       int only_need_pcrel = 0;
 
+      r_type = loongarch_tls_transition (info, r_type, h);
       switch (r_type)
 	{
 	case R_LARCH_GOT_PC_HI20:
@@ -2397,6 +2456,45 @@ loongarch_reloc_is_fatal (struct bfd_link_info *info,
       relocation += 0x100000000;			\
   })
 
+/* Transition DESC instruction sequence to LE instruction sequence.  */
+static bool
+loongarch_tls_relax (bfd *abfd, asection *sec, Elf_Internal_Rela *rel,
+		    int r_type)
+{
+  bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
+  switch (r_type)
+    {
+      case R_LARCH_TLS_DESC_PC_HI20:
+	/* DESC -> LE relaxation:
+	   pcalalau12i $a0,%desc_pc_hi20(var) => lu12i.w $a0,%le_hi20(var)
+	*/
+	bfd_put (32, abfd, LARCH_LU12I_W_A0, contents + rel->r_offset);
+
+	return true;
+
+      case R_LARCH_TLS_DESC_LD_PC_LO12:
+	/* DESC -> LE relaxation:
+	   ld.d $a1,$a0,%desc_ld_pc_lo12(var) => ori $a0,$a0,le_lo12(var)
+	*/
+	bfd_put (32, abfd, LARCH_ORI_A0, contents + rel->r_offset);
+
+	return true;
+
+      case R_LARCH_TLS_DESC_ADD_PC_LO12:
+      case R_LARCH_TLS_DESC_CALL:
+	/* DESC -> LE relaxation:
+	   addi.d $a0,$a0,%desc_add_pc_lo12(var) => NOP
+	   jirl $ra,$a1,%desc_call(var) => NOP
+	*/
+	bfd_put (32, abfd, LARCH_NOP, contents + rel->r_offset);
+
+	return true;
+    }
+
+  return false;
+}
+
+
 static int
 loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 				bfd *input_bfd, asection *input_section,
@@ -2420,7 +2518,7 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
   relend = relocs + input_section->reloc_count;
   for (rel = relocs; rel < relend; rel++)
     {
-      int r_type = ELFNN_R_TYPE (rel->r_info);
+      unsigned int r_type = ELFNN_R_TYPE (rel->r_info);
       unsigned long r_symndx = ELFNN_R_SYM (rel->r_info);
       bfd_vma pc = sec_addr (input_section) + rel->r_offset;
       reloc_howto_type *howto = NULL;
@@ -2430,6 +2528,7 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
       const char *name;
       bfd_reloc_status_type r = bfd_reloc_ok;
       bool is_ie, is_desc, is_undefweak, unresolved_reloc, defined_local;
+      unsigned int relaxed_r_type;
       bool resolved_local, resolved_dynly, resolved_to_const;
       char tls_type;
       bfd_vma relocation, off, ie_off, desc_off;
@@ -2561,6 +2660,13 @@ loongarch_elf_relocate_section (bfd *output_bfd, struct bfd_link_info *info,
 
       BFD_ASSERT (!resolved_local || defined_local);
 
+      relaxed_r_type = loongarch_tls_transition (info, r_type, h);
+      if (relaxed_r_type != r_type)
+      {
+	loongarch_tls_relax (input_bfd, input_section, rel, r_type);
+	r_type = relaxed_r_type;
+      }
+
       is_desc = false;
       is_ie = false;
       switch (r_type)
diff --git a/include/opcode/loongarch.h b/include/opcode/loongarch.h
index da936f7945a..9a4a95d1177 100644
--- a/include/opcode/loongarch.h
+++ b/include/opcode/loongarch.h
@@ -42,6 +42,9 @@ extern "C"
     ((value) < (-(1 << ((bits) - 1) << align)) 	\
       || (value) > ((((1 << ((bits) - 1)) - 1) << align)))
 
+  #define LARCH_LU12I_W_A0 0x14000004
+  #define LARCH_ORI_A0 0x03800084
+
   typedef uint32_t insn_t;
 
   struct loongarch_opcode
