diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index d7d815fe97f41efd60e0337a92cce330432c5771..ce773728e256511eda2011b59867c29ff129eb27 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -96,7 +96,7 @@
 //
 // Marlin now allow you to have a vendor boot image to be displayed on machine
 // start. When SHOW_CUSTOM_BOOTSCREEN is defined Marlin will first show your
-// custom boot image and them the default Marlin boot image is shown.
+// custom boot image and then the default Marlin boot image is shown.
 //
 // We suggest for you to take advantage of this new feature and keep the Marlin
 // boot image unmodified. For an example have a look at the bq Hephestos 2
@@ -1000,6 +1000,9 @@
 //                       |________|_________|_________|
 //                           T1        T2        T3
 //
+//   P2: This starts a circular pattern with circle with middle in
+//       NOZZLE_CLEAN_CIRCLE_MIDDLE radius of R and stroke count of S.
+//       Before starting the circle nozzle goes to NOZZLE_CLEAN_START_POINT.
 //
 // Caveats: End point Z should use the same value as Start point Z.
 //
@@ -1011,7 +1014,7 @@
 #if ENABLED(NOZZLE_CLEAN_FEATURE)
   // Default number of pattern repetitions
   #define NOZZLE_CLEAN_STROKES  12
-  
+
   // Default number of triangles
   #define NOZZLE_CLEAN_TRIANGLES  3
 
@@ -1019,6 +1022,13 @@
   #define NOZZLE_CLEAN_START_POINT { 30, 30, (Z_MIN_POS + 1)}
   #define NOZZLE_CLEAN_END_POINT   {100, 60, (Z_MIN_POS + 1)}
 
+  // Circular pattern radius
+  #define NOZZLE_CLEAN_CIRCLE_RADIUS 6.5
+  // Circular pattern circle fragments number
+  #define NOZZLE_CLEAN_CIRCLE_FN 10
+  // Middle point of circle
+  #define NOZZLE_CLEAN_CIRCLE_MIDDLE NOZZLE_CLEAN_START_POINT
+
   // Moves the nozzle to the initial position
   #define NOZZLE_CLEAN_GOBACK
 #endif
diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp
index 2d1ef53f047e48d79908e009589767009b04b4c8..eb96e892e6962ba464cd81fcbcab21166ca857cf 100755
--- a/Marlin/Marlin_main.cpp
+++ b/Marlin/Marlin_main.cpp
@@ -3164,8 +3164,9 @@ inline void gcode_G4() {
     const uint8_t pattern = code_seen('P') ? code_value_ushort() : 0,
                   strokes = code_seen('S') ? code_value_ushort() : NOZZLE_CLEAN_STROKES,
                   objects = code_seen('T') ? code_value_ushort() : NOZZLE_CLEAN_TRIANGLES;
+    const float radius = code_seen('R') ? code_value_float() : NOZZLE_CLEAN_CIRCLE_RADIUS;
 
-    Nozzle::clean(pattern, strokes, objects);
+    Nozzle::clean(pattern, strokes, radius, objects);
   }
 #endif
 
diff --git a/Marlin/nozzle.cpp b/Marlin/nozzle.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f890cca935e9be7bc0f9275cc17e06a8171a9188
--- /dev/null
+++ b/Marlin/nozzle.cpp
@@ -0,0 +1,236 @@
+#include "nozzle.h"
+
+#include "Marlin.h"
+#include "point_t.h"
+
+/**
+  * @brief Stroke clean pattern
+  * @details Wipes the nozzle back and forth in a linear movement
+  *
+  * @param start point_t defining the starting point
+  * @param end point_t defining the ending point
+  * @param strokes number of strokes to execute
+  */
+void Nozzle::stroke(
+  __attribute__((unused)) point_t const &start,
+  __attribute__((unused)) point_t const &end,
+  __attribute__((unused)) uint8_t const &strokes
+) {
+  #if ENABLED(NOZZLE_CLEAN_FEATURE)
+
+    #if ENABLED(NOZZLE_CLEAN_GOBACK)
+      // Store the current coords
+      point_t const initial = {
+        current_position[X_AXIS],
+        current_position[Y_AXIS],
+        current_position[Z_AXIS],
+        current_position[E_AXIS]
+      };
+    #endif // NOZZLE_CLEAN_GOBACK
+
+    // Move to the starting point
+    do_blocking_move_to_xy(start.x, start.y);
+    do_blocking_move_to_z(start.z);
+
+    // Start the stroke pattern
+    for (uint8_t i = 0; i < (strokes >>1); i++) {
+      do_blocking_move_to_xy(end.x, end.y);
+      do_blocking_move_to_xy(start.x, start.y);
+    }
+
+    #if ENABLED(NOZZLE_CLEAN_GOBACK)
+      // Move the nozzle to the initial point
+      do_blocking_move_to(initial.x, initial.y, initial.z);
+    #endif // NOZZLE_CLEAN_GOBACK
+
+  #endif // NOZZLE_CLEAN_FEATURE
+}
+
+/**
+  * @brief Zig-zag clean pattern
+  * @details Apply a zig-zag cleanning pattern
+  *
+  * @param start point_t defining the starting point
+  * @param end point_t defining the ending point
+  * @param strokes number of strokes to execute
+  * @param objects number of objects to create
+  */
+void Nozzle::zigzag(
+  __attribute__((unused)) point_t const &start,
+  __attribute__((unused)) point_t const &end,
+  __attribute__((unused)) uint8_t const &strokes,
+  __attribute__((unused)) uint8_t const &objects
+) {
+  #if ENABLED(NOZZLE_CLEAN_FEATURE)
+    float A = fabs(end.y - start.y); // [twice the] Amplitude
+    float P = fabs(end.x - start.x) / (objects << 1); // Period
+
+    // Don't allow impossible triangles
+    if (A <= 0.0f || P <= 0.0f ) return;
+
+    #if ENABLED(NOZZLE_CLEAN_GOBACK)
+      // Store the current coords
+      point_t const initial = {
+        current_position[X_AXIS],
+        current_position[Y_AXIS],
+        current_position[Z_AXIS],
+        current_position[E_AXIS]
+      };
+    #endif // NOZZLE_CLEAN_GOBACK
+
+    for (uint8_t j = 0; j < strokes; j++) {
+      for (uint8_t i = 0; i < (objects << 1); i++) {
+        float const x = start.x + i * P;
+        float const y = start.y + (A/P) * (P - fabs(fmod((i*P), (2*P)) - P));
+
+        do_blocking_move_to_xy(x, y);
+        if (i == 0) do_blocking_move_to_z(start.z);
+      }
+
+      for (int i = (objects << 1); i > -1; i--) {
+        float const x = start.x + i * P;
+        float const y = start.y + (A/P) * (P - fabs(fmod((i*P), (2*P)) - P));
+
+        do_blocking_move_to_xy(x, y);
+      }
+    }
+
+    #if ENABLED(NOZZLE_CLEAN_GOBACK)
+      // Move the nozzle to the initial point
+      do_blocking_move_to_z(initial.z);
+      do_blocking_move_to_xy(initial.x, initial.y);
+    #endif // NOZZLE_CLEAN_GOBACK
+
+  #endif // NOZZLE_CLEAN_FEATURE
+}
+
+
+/**
+  * @brief Circular clean pattern
+  * @details Apply a circular cleaning pattern
+  *
+  * @param start point_t defining the middle of circle
+  * @param strokes number of strokes to execute
+  * @param radius radius of circle
+  */
+void Nozzle::circle(
+  __attribute__((unused)) point_t const &start,
+  __attribute__((unused)) point_t const &middle,
+  __attribute__((unused)) uint8_t const &strokes,
+  __attribute__((unused)) float const &radius
+) {
+  #if ENABLED(NOZZLE_CLEAN_FEATURE)
+    if (strokes == 0) return;
+
+    #if ENABLED(NOZZLE_CLEAN_GOBACK)
+      // Store the current coords
+      point_t const initial = {
+        current_position[X_AXIS],
+        current_position[Y_AXIS],
+        current_position[Z_AXIS],
+        current_position[E_AXIS]
+      };
+    #endif // NOZZLE_CLEAN_GOBACK
+
+    if (start.z <= current_position[Z_AXIS]) {
+      // Order of movement is pretty darn important here
+      do_blocking_move_to_xy(start.x, start.y);
+      do_blocking_move_to_z(start.z);
+    } else {
+      do_blocking_move_to_z(start.z);
+      do_blocking_move_to_xy(start.x, start.y);
+    }
+
+    float x, y;
+    for (uint8_t s = 0; s < strokes; s++) {
+      for (uint8_t i = 0; i < NOZZLE_CLEAN_CIRCLE_FN; i++) {
+        x = middle.x + sin((M_2_PI / NOZZLE_CLEAN_CIRCLE_FN) * i) * radius;
+        y = middle.y + cos((M_2_PI / NOZZLE_CLEAN_CIRCLE_FN) * i) * radius;
+
+        do_blocking_move_to_xy(x, y);
+      }
+    }
+
+    // Let's be safe
+    do_blocking_move_to_xy(start.x, start.y);
+
+    #if ENABLED(NOZZLE_CLEAN_GOBACK)
+      // Move the nozzle to the initial point
+      if (start.z <= initial.z) {
+        // As above order is important
+        do_blocking_move_to_z(initial.z);
+        do_blocking_move_to_xy(initial.x, initial.y);
+      } else {
+        do_blocking_move_to_xy(initial.x, initial.y);
+        do_blocking_move_to_z(initial.z);
+      }
+    #endif // NOZZLE_CLEAN_GOBACK
+
+  #endif // NOZZLE_CLEAN_FEATURE
+}
+
+/**
+  * @brief Clean the nozzle
+  * @details Starts the selected clean procedure pattern
+  *
+  * @param pattern one of the available patterns
+  * @param argument depends on the cleaning pattern
+  */
+void Nozzle::clean(
+  __attribute__((unused)) uint8_t const &pattern,
+  __attribute__((unused)) uint8_t const &strokes,
+  __attribute__((unused)) float const &radius,
+  __attribute__((unused)) uint8_t const &objects
+) {
+  #if ENABLED(NOZZLE_CLEAN_FEATURE)
+    #if ENABLED(DELTA)
+      if (current_position[Z_AXIS] > delta_clip_start_height)
+        do_blocking_move_to_z(delta_clip_start_height);
+    #endif
+    switch (pattern) {
+      case 1:
+        Nozzle::zigzag(
+          NOZZLE_CLEAN_START_POINT,
+          NOZZLE_CLEAN_END_POINT, strokes, objects);
+        break;
+
+      case 2:
+        Nozzle::circle(
+          NOZZLE_CLEAN_START_POINT,
+          NOZZLE_CLEAN_CIRCLE_MIDDLE, strokes, radius);
+        break;
+
+      default:
+        Nozzle::stroke(
+          NOZZLE_CLEAN_START_POINT,
+          NOZZLE_CLEAN_END_POINT, strokes);
+    }
+  #endif // NOZZLE_CLEAN_FEATURE
+}
+
+void Nozzle::park(
+  __attribute__((unused)) uint8_t const &z_action
+) {
+  #if ENABLED(NOZZLE_PARK_FEATURE)
+    float const z = current_position[Z_AXIS];
+    point_t const park = NOZZLE_PARK_POINT;
+
+    switch(z_action) {
+      case 1: // force Z-park height
+        do_blocking_move_to_z(park.z);
+        break;
+
+      case 2: // Raise by Z-park height
+        do_blocking_move_to_z(
+          (z + park.z > Z_MAX_POS) ? Z_MAX_POS : z + park.z);
+        break;
+
+      default: // Raise to Z-park height if lower
+        if (current_position[Z_AXIS] < park.z)
+          do_blocking_move_to_z(park.z);
+    }
+
+    do_blocking_move_to_xy(park.x, park.y);
+
+  #endif // NOZZLE_PARK_FEATURE
+}
diff --git a/Marlin/nozzle.h b/Marlin/nozzle.h
index 14423adfbdd6aa498a8d666d72fce379be36e197..f98bc40dba6857acfeb4e0c006d572672fbeb086 100644
--- a/Marlin/nozzle.h
+++ b/Marlin/nozzle.h
@@ -53,40 +53,11 @@ class Nozzle {
       __attribute__((unused)) point_t const &start,
       __attribute__((unused)) point_t const &end,
       __attribute__((unused)) uint8_t const &strokes
-    ) __attribute__((optimize ("Os"))) {
-      #if ENABLED(NOZZLE_CLEAN_FEATURE)
-
-        #if ENABLED(NOZZLE_CLEAN_GOBACK)
-          // Store the current coords
-          point_t const initial = {
-            current_position[X_AXIS],
-            current_position[Y_AXIS],
-            current_position[Z_AXIS],
-            current_position[E_AXIS]
-          };
-        #endif // NOZZLE_CLEAN_GOBACK
-
-        // Move to the starting point
-        do_blocking_move_to_xy(start.x, start.y);
-        do_blocking_move_to_z(start.z);
-
-        // Start the stroke pattern
-        for (uint8_t i = 0; i < (strokes >>1); i++) {
-          do_blocking_move_to_xy(end.x, end.y);
-          do_blocking_move_to_xy(start.x, start.y);
-        }
-
-        #if ENABLED(NOZZLE_CLEAN_GOBACK)
-          // Move the nozzle to the initial point
-          do_blocking_move_to(initial.x, initial.y, initial.z);
-        #endif // NOZZLE_CLEAN_GOBACK
-
-      #endif // NOZZLE_CLEAN_FEATURE
-    }
+    ) __attribute__((optimize ("Os")));
 
     /**
      * @brief Zig-zag clean pattern
-     * @details Apply a zig-zag cleanning pattern
+     * @details Apply a zig-zag cleaning pattern
      *
      * @param start point_t defining the starting point
      * @param end point_t defining the ending point
@@ -98,49 +69,22 @@ class Nozzle {
       __attribute__((unused)) point_t const &end,
       __attribute__((unused)) uint8_t const &strokes,
       __attribute__((unused)) uint8_t const &objects
-    ) __attribute__((optimize ("Os"))) {
-      #if ENABLED(NOZZLE_CLEAN_FEATURE)
-        float A = nozzle_clean_horizontal ? nozzle_clean_height : nozzle_clean_length; // [twice the] Amplitude
-        float P = ( nozzle_clean_horizontal ? nozzle_clean_length : nozzle_clean_height ) / (objects << 1); // Period
-
-        // Don't allow impossible triangles
-        if (A <= 0.0f || P <= 0.0f ) return;
-
-        #if ENABLED(NOZZLE_CLEAN_GOBACK)
-          // Store the current coords
-          point_t const initial = {
-            current_position[X_AXIS],
-            current_position[Y_AXIS],
-            current_position[Z_AXIS],
-            current_position[E_AXIS]
-          };
-        #endif // NOZZLE_CLEAN_GOBACK
-
-        for (uint8_t j = 0; j < strokes; j++) {
-          for (uint8_t i = 0; i < (objects << 1); i++) {
-            float const x = start.x + ( nozzle_clean_horizontal ? i * P : (A/P) * (P - fabs(fmod((i*P), (2*P)) - P)) );
-            float const y = start.y + (!nozzle_clean_horizontal ? i * P : (A/P) * (P - fabs(fmod((i*P), (2*P)) - P)) );
-
-            do_blocking_move_to_xy(x, y);
-            if (i == 0) do_blocking_move_to_z(start.z);
-          }
-
-          for (int i = (objects << 1); i > -1; i--) {
-            float const x = start.x + ( nozzle_clean_horizontal ? i * P : (A/P) * (P - fabs(fmod((i*P), (2*P)) - P)) );
-            float const y = start.y + (!nozzle_clean_horizontal ? i * P : (A/P) * (P - fabs(fmod((i*P), (2*P)) - P)) );
-
-            do_blocking_move_to_xy(x, y);
-          }
-        }
-
-        #if ENABLED(NOZZLE_CLEAN_GOBACK)
-          // Move the nozzle to the initial point
-          do_blocking_move_to_z(initial.z);
-          do_blocking_move_to_xy(initial.x, initial.y);
-        #endif // NOZZLE_CLEAN_GOBACK
+    ) __attribute__((optimize ("Os")));
 
-      #endif // NOZZLE_CLEAN_FEATURE
-    }
+    /**
+     * @brief Circular clean pattern
+     * @details Apply a circular cleaning pattern
+     *
+     * @param start point_t defining the middle of circle
+     * @param strokes number of strokes to execute
+     * @param radius radius of circle
+     */
+    static void circle(
+      __attribute__((unused)) point_t const &start,
+      __attribute__((unused)) point_t const &middle,
+      __attribute__((unused)) uint8_t const &strokes,
+      __attribute__((unused)) float const &radius
+    ) __attribute__((optimize ("Os")));
 
   public:
     /**
@@ -153,54 +97,13 @@ class Nozzle {
     static void clean(
       __attribute__((unused)) uint8_t const &pattern,
       __attribute__((unused)) uint8_t const &strokes,
+      __attribute__((unused)) float const &radius,
       __attribute__((unused)) uint8_t const &objects = 0
-    ) __attribute__((optimize ("Os"))) {
-      #if ENABLED(NOZZLE_CLEAN_FEATURE)
-        #if ENABLED(DELTA)
-          if (current_position[Z_AXIS] > delta_clip_start_height)
-            do_blocking_move_to_z(delta_clip_start_height);
-        #endif
-        switch (pattern) {
-          case 1:
-            Nozzle::zigzag(
-              NOZZLE_CLEAN_START_POINT,
-              NOZZLE_CLEAN_END_POINT, strokes, objects);
-            break;
-
-          default:
-            Nozzle::stroke(
-              NOZZLE_CLEAN_START_POINT,
-              NOZZLE_CLEAN_END_POINT, strokes);
-        }
-      #endif // NOZZLE_CLEAN_FEATURE
-    }
+    ) __attribute__((optimize ("Os")));
 
     static void park(
       __attribute__((unused)) uint8_t const &z_action
-    ) __attribute__((optimize ("Os"))) {
-      #if ENABLED(NOZZLE_PARK_FEATURE)
-        float const z = current_position[Z_AXIS];
-        point_t const park = NOZZLE_PARK_POINT;
-
-        switch(z_action) {
-          case 1: // force Z-park height
-            do_blocking_move_to_z(park.z);
-            break;
-
-          case 2: // Raise by Z-park height
-            do_blocking_move_to_z(
-              (z + park.z > Z_MAX_POS) ? Z_MAX_POS : z + park.z);
-            break;
-
-          default: // Raise to Z-park height if lower
-            if (current_position[Z_AXIS] < park.z)
-              do_blocking_move_to_z(park.z);
-        }
-
-        do_blocking_move_to_xy(park.x, park.y);
-
-      #endif // NOZZLE_PARK_FEATURE
-    }
+    ) __attribute__((optimize ("Os")));
 };
 
 #endif