diff --git a/gdb/mips-linux-tdep.c b/gdb/mips-linux-tdep.c
index da5ccc0..d337a5b 100644
--- a/gdb/mips-linux-tdep.c
+++ b/gdb/mips-linux-tdep.c
@@ -1725,7 +1725,6 @@ mips_linux_init_abi (struct gdbarch_info info,
 				    mips_gdb_signal_to_target);
 
   tdep->syscall_next_pc = mips_linux_syscall_next_pc;
-  tdep->fp_register_mode_fixed_p = 1;
 
   if (((struct gdbarch_tdep_info*)(info.tdep_info))->tdesc_data)
     {
diff --git a/gdb/mips-tdep.c b/gdb/mips-tdep.c
index 2cc49aa..13c1532 100644
--- a/gdb/mips-tdep.c
+++ b/gdb/mips-tdep.c
@@ -78,6 +78,10 @@ static void mips_print_float_info (struct gdbarch *, struct ui_file *,
 /* This bit is set if we are emulating 32-bit FPRs on a 64-bit chip.  */
 #define ST0_FR (1 << 26)
 
+/* A useful bit in the CP0 Config5 register.
+   This bit is set in hybrid 64-bit FPR mode.  */
+#define CONF5_FRE (1 << 8)
+
 /* The sizes of floating point registers.  */
 
 enum
@@ -307,29 +311,36 @@ mips_set_float_regsize (struct gdbarch *gdbarch, struct regcache *regcache)
   struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
   struct gdbarch_tdep_info tdep_info = { NULL };
   struct gdbarch_info info;
-  int fpsize;
   enum mips_fpu_mode fp_mode;
+  enum register_status status;
+  ULONGEST sr, config5;
 
   if (tdep->fp_register_mode_fixed_p)
     return 0;
 
-  fpsize = mips_isa_regsize (gdbarch);
-  fp_mode = fpsize == 8 ? MIPS_FPU_64 : MIPS_FPU_32;
-  if (fpsize == 8)
-    {
-      enum register_status status;
-      ULONGEST sr;
+  status = regcache_raw_read_unsigned (regcache, MIPS_PS_REGNUM, &sr);
+  if (status == REG_VALID)
+    fp_mode = (sr & ST0_FR) ? MIPS_FPU_64 : MIPS_FPU_32;
+  else
+    fp_mode = mips_isa_regsize (gdbarch) == 8 ? MIPS_FPU_64 : MIPS_FPU_32;
 
-      status = regcache_raw_read_unsigned (regcache, MIPS_PS_REGNUM, &sr);
-      if (status == REG_VALID)
-	fp_mode = (sr & ST0_FR) ? MIPS_FPU_64 : MIPS_FPU_32;
+  if (fp_mode == MIPS_FPU_64 && mips_regnum (gdbarch)->config5 >= 0)
+    {
+      /* Find out if FRE is set */
+      status = regcache_raw_read_unsigned (regcache,
+					   mips_regnum (gdbarch)->config5,
+					   &config5);
+      if (status == REG_VALID && config5 & CONF5_FRE)
+	fp_mode = MIPS_FPU_HYBRID;
     }
 
   if (fp_mode == tdep->fp_mode)
     return 0;
 
-  /* Need a new gdbarch, go get one.  */
+  /* Need a new gdbarch, go get one.
+     Be careful to preserve target description. */
   gdbarch_info_init (&info);
+  info.target_desc = target_current_description ();
   info.tdep_info = &tdep_info;
   ((struct gdbarch_tdep_info*)(info.tdep_info))->fp_mode = fp_mode;
   gdbarch_update_p (info);
@@ -347,12 +358,22 @@ mips_float_regsize (struct gdbarch *gdbarch)
     case MIPS_FPU_32:
       return 4;
     case MIPS_FPU_64:
+    case MIPS_FPU_HYBRID:
       return 8;
     default:
       return 0;
     }
 }
 
+/* Return whether the FPU is currently in hybrid 64-bit mode (where odd singles
+   are found in the top half of the 64-bit even FP registers.  */
+
+static int
+mips_float_hybrid (struct gdbarch *gdbarch)
+{
+  return gdbarch_tdep (gdbarch)->fp_mode == MIPS_FPU_HYBRID;
+}
+
 /* MIPS16/microMIPS function addresses are odd (bit 0 is set).  Here
    are some functions to handle addresses associated with compressed
    code including but not limited to testing, setting, or clearing
@@ -835,6 +856,7 @@ mips_get_fp_single_location (struct gdbarch *gdbarch,
   switch (fp_mode)
     {
     case MIPS_FPU_32:
+    case MIPS_FPU_HYBRID:
       loc->regnum = raw_num + (idx & ~1);
       loc->offset = 4 * (big_endian ^ (idx & 1));
       return 1;
@@ -892,7 +914,7 @@ mips_get_fp_double_location (struct gdbarch *gdbarch,
 }
 
 /* Get the raw register part(s) composing a cooked float register.
-   Returns the number of parts (maximum 2) written through loc.	 */
+   Returns the number of parts (maximum 3) written through loc.	 */
 
 static unsigned int
 mips_get_fp_multi_location (struct gdbarch *gdbarch,
@@ -904,8 +926,9 @@ mips_get_fp_multi_location (struct gdbarch *gdbarch,
 
   /* The cooked formats supported are:
      fp32 (len=4):  just a single.
-     fp64 (len=8):  double (with aliased single).  */
-  if (cooked_len > 8 || cooked_len < 4 || cooked_len & 0x3)
+     fp64 (len=8):  double (with aliased single).
+     fp96 (len=12): consecutive double and single.  */
+  if (cooked_len > 12 || cooked_len < 4 || cooked_len & 0x3)
     internal_error (__FILE__, __LINE__, _("bad cooked register size"));
 
   /* Formats containing a distinct double.  */
@@ -988,7 +1011,7 @@ mips_pseudo_register_read (struct gdbarch *gdbarch, struct regcache *regcache,
 
   if (mips_float_register_p (gdbarch, rawnum))
     {
-      struct mips_reg_part loc[2];
+      struct mips_reg_part loc[3];
       unsigned int parts;
 
       fpnum = rawnum - mips_regnum (gdbarch)->fp0;
@@ -1034,7 +1057,7 @@ mips_pseudo_register_write (struct gdbarch *gdbarch,
 
   if (mips_float_register_p (gdbarch, rawnum))
     {
-      struct mips_reg_part loc[2];
+      struct mips_reg_part loc[3];
       unsigned int parts;
 
       fpnum = rawnum - mips_regnum (gdbarch)->fp0;
@@ -1149,6 +1172,20 @@ mips_convert_register_float_case_p (struct gdbarch *gdbarch, int regnum,
 	  && TYPE_CODE (type) == TYPE_CODE_FLT && TYPE_LENGTH (type) == 4);
 }
 
+/* This predicate tests for the case of a 4 or 8 byte floating point
+   value that is being transferred to or from a floating point
+   register which is 12 bytes wide (containing both single and double).  */
+
+static int
+mips_convert_register_float_fre_case_p (struct gdbarch *gdbarch,
+					int regnum, struct type *type)
+{
+  return (register_size (gdbarch, regnum) == 12
+	  && mips_float_register_p (gdbarch, regnum)
+	  && TYPE_CODE (type) == TYPE_CODE_FLT
+          && (TYPE_LENGTH (type) == 4 || TYPE_LENGTH (type) == 8));
+}
+
 /* This predicate tests for the case of a value of less than 8
    bytes in width that is being transfered to or from an 8 byte
    general purpose register.  */
@@ -1168,6 +1205,7 @@ mips_convert_register_p (struct gdbarch *gdbarch,
 			 int regnum, struct type *type)
 {
   return (mips_convert_register_float_case_p (gdbarch, regnum, type)
+	  || mips_convert_register_float_fre_case_p (gdbarch, regnum, type)
 	  || mips_convert_register_gpreg_case_p (gdbarch, regnum, type));
 }
 
@@ -1196,6 +1234,26 @@ mips_register_to_value (struct frame_info *frame, int regnum,
       *optimizedp = *unavailablep = 0;
       return 1;
     }
+  else if (mips_convert_register_float_fre_case_p (gdbarch, regnum, type))
+    {
+      int len = TYPE_LENGTH (type);
+      if (len == 8)
+	{
+	  /* double comes first */
+	  if (!get_frame_register_bytes (frame, regnum, 0, 8, to,
+					 optimizedp, unavailablep))
+	    return 0;
+	}
+      else
+	{
+	  /* followed by single */
+	  if (!get_frame_register_bytes (frame, regnum, 8, 4, to,
+					 optimizedp, unavailablep))
+	    return 0;
+	}
+      *optimizedp = *unavailablep = 0;
+      return 1;
+    }
   else if (mips_convert_register_gpreg_case_p (gdbarch, regnum, type))
     {
       int len = TYPE_LENGTH (type);
@@ -1314,6 +1372,56 @@ mips_fp64_type (struct gdbarch *gdbarch)
   return tdep->fp64_type;
 }
 
+/* Get FRE odd floating point type, which can contains separate single and
+   double precision floats, or a separate 32-bit or 64-bit signed integer.
+   This is used for odd fp registers when FR=1 and FRE=1 (the odd single comes
+   from the upper half of the even double, so odd singles and odd doubles do not
+   overlap).  */
+
+static struct type *
+mips_fp96_type (struct gdbarch *gdbarch)
+{
+  struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch);
+
+  if (tdep->fp96_type == NULL)
+    {
+      const struct builtin_type *bt = builtin_type (gdbarch);
+      struct type *t;
+      struct field *f;
+
+      /* The type we're building is roughly this:
+
+      struct __gdb_builtin_mips_fp96 {
+	  union {
+	      double f64;
+	      int64  i64;
+	  };
+	  union {
+	      float  f32;
+	      int32  i32;
+	  };
+      }; */
+
+
+      t = arch_composite_type (gdbarch, "__gdb_builtin_type_mips_fp96",
+			       TYPE_CODE_STRUCT);
+      f = append_composite_type_field_raw (t, "f32", bt->builtin_float);
+      SET_FIELD_BITPOS (*f, 64);
+      f = append_composite_type_field_raw (t, "f64", bt->builtin_double);
+      SET_FIELD_BITPOS (*f, 0);
+      f = append_composite_type_field_raw (t, "i32", bt->builtin_int32);
+      SET_FIELD_BITPOS (*f, 64);
+      f = append_composite_type_field_raw (t, "i64", bt->builtin_int64);
+      SET_FIELD_BITPOS (*f, 0);
+
+      TYPE_LENGTH (t) = 12;
+      TYPE_NAME (t) = "fp96";
+      tdep->fp96_type = t;
+    }
+
+  return tdep->fp96_type;
+}
+
 /* Get the floating point type for an arbitrary FP register. This returns the
    appropriate type depending on the possible types and overlaps of the
    register.  */
@@ -1321,9 +1429,12 @@ mips_fp64_type (struct gdbarch *gdbarch)
 static struct type *
 mips_fp_type (struct gdbarch *gdbarch, int fpnum)
 {
-  if ((fpnum & 1) == 0 || mips_float_regsize (gdbarch) == 8)
+  if ((fpnum & 1) == 1 && mips_float_hybrid (gdbarch) )
+    /* 64-bit hybrid registers: odd singles and doubles don't overlap.  */
+    return mips_fp96_type (gdbarch);
+  else if ((fpnum & 1) == 0 || mips_float_regsize (gdbarch) == 8)
     /* Even singles and doubles always overlap, as do odd singles and
-       doubles when FR=1.  */
+       doubles when FR=1 (and FRE=0).  */
     return mips_fp64_type (gdbarch);
   else
     /* 32-bit odd singles (there are no odd doubles).  */
@@ -1517,6 +1628,16 @@ mips_value_to_register (struct frame_info *frame, int regnum,
       else
 	put_frame_register_bytes (frame, regnum, 0, 4, from);
     }
+  else if (mips_convert_register_float_fre_case_p (gdbarch, regnum, type))
+    {
+      int len = TYPE_LENGTH (type);
+      if (len == 8)
+	  /* double goes first */
+	  put_frame_register_bytes (frame, regnum, 0, 8, from);
+      else
+	  /* followed by single */
+	  put_frame_register_bytes (frame, regnum, 8, 4, from);
+    }
   else if (mips_convert_register_gpreg_case_p (gdbarch, regnum, type))
     {
       gdb_byte fill[8];
@@ -1564,11 +1685,12 @@ mips_register_type (struct gdbarch *gdbarch, int regnum)
   if (regnum < gdbarch_num_regs (gdbarch))
     {
       /* The raw or ISA registers.  These are all sized according to
-	 the ISA regsize.  */
+	 the ISA regsize, except FP registers which may be double
+	 even on MIPS32 since rev 2 of the architecture.  */
       int regsize = mips_isa_regsize (gdbarch);
 
       if (mips_float_register_p (gdbarch, regnum))
-	return (regsize == 4
+	return (mips_float_regsize (gdbarch) == 4
 		? builtin_type (gdbarch)->builtin_float
 		: builtin_type (gdbarch)->builtin_double);
       else
@@ -6776,8 +6898,12 @@ mips_read_fp_register_single (struct frame_info *frame, int regno,
   if (!deprecated_frame_register_read (frame, regno, raw_buffer))
     return 0;
 
-  if (cooked_size == 8)
-    /* FR=1
+  if (cooked_size == 12)
+    /* FR=1, FRE=1
+       Single is after double. */
+    memcpy(rare_buffer, raw_buffer+8, 8);
+  else if (cooked_size == 8)
+    /* FR=1, FRE=0
        Single is overlapping double. */
     memcpy(rare_buffer, raw_buffer + 4*big_endian, 4);
   else
@@ -6814,7 +6940,7 @@ mips_print_fp_register (struct ui_file *file, struct frame_info *frame,
 			int regnum)
 {				/* Do values for FP (float) regs.  */
   struct gdbarch *gdbarch = get_frame_arch (frame);
-  int fpsize = register_size (gdbarch, regnum);
+  int fpsize = mips_float_regsize (gdbarch);
   gdb_byte *raw_buffer;
   double doub, flt1;	/* Doubles extracted from raw hex data.  */
   int res1, res2, inv1, inv2;
@@ -8910,7 +9036,6 @@ mips_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
       const struct tdesc_feature *feature;
       int valid_p;
       int fpsize;
-      enum mips_fpu_mode fpmode;
 
       feature = tdesc_find_feature (info.target_desc,
 				    "org.gnu.gdb.mips.cpu");
@@ -8983,12 +9108,11 @@ mips_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
          supplied the description got the current setting right wrt
          CP0 Status register's bit FR if applicable.  */
       fpsize = tdesc_register_size (feature, mips_fprs[0]) / 8;
-      fpmode = (fpsize == 8) ? MIPS_FPU_64 : MIPS_FPU_32;
 
       /* Only accept a description whose floating-point register size
          matches the requested size or if none was specified.  */
-      valid_p = (info.tdep_info->fp_mode == MIPS_FPU_UNKNOWN
-		 || info.tdep_info->fp_mode == fpmode);
+      valid_p = 1;
+
       for (i = 0; i < 32; i++)
 	valid_p &= tdesc_numbered_register (feature, tdesc_data,
 					    i + mips_regnum.fp0, mips_fprs[i]);
@@ -9043,9 +9167,6 @@ mips_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
 	    }
 	}
 
-      /* Fix the floating-point register mode found.  */
-      info.tdep_info->fp_mode = fpmode;
-
       /* It would be nice to detect an attempt to use a 64-bit ABI
 	 when only 32-bit registers are provided.  */
       reg_names = NULL;
@@ -9287,6 +9408,7 @@ mips_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdep->fp_ir_type = NULL;
   tdep->fp32_type = NULL;
   tdep->fp64_type = NULL;
+  tdep->fp96_type = NULL;
 
   if (info.target_desc)
     {
diff --git a/gdb/mips-tdep.h b/gdb/mips-tdep.h
index ad5cba3..110eb6c 100644
--- a/gdb/mips-tdep.h
+++ b/gdb/mips-tdep.h
@@ -89,6 +89,8 @@ enum mips_fpu_mode
   MIPS_FPU_UNKNOWN = 0,
   MIPS_FPU_32,		/* FR=0, 32bit FP regs, doubles in pairs.  */
   MIPS_FPU_64,		/* FR=1, 64bit FP regs.  */
+  MIPS_FPU_HYBRID,	/* FR=1, FRE=1, 64bit FP regs, odd singles in upper half
+			   of even doubles.  */
 };
 
 /* MIPS specific per-architecture information.  */
@@ -123,8 +125,8 @@ struct gdbarch_tdep
   int register_size;
 
   /* The floating-point register mode determined at run time.
-     This corresponds to CP0 Status register's FR bit unless fixed_p is
-     set.  */
+     This corresponds to CP0 Status register's FR bit and Config5's FRE bit
+     unless fixed_p is set.  */
   int fp_register_mode_fixed_p;
   enum mips_fpu_mode fp_mode;
 
@@ -136,6 +138,7 @@ struct gdbarch_tdep
   struct type *fp_ir_type;
   struct type *fp32_type;
   struct type *fp64_type;
+  struct type *fp96_type;
 
   /* Return the expected next PC if FRAME is stopped at a syscall
      instruction.  */
