diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 00f89662c78..7d1547c0d02 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -2662,31 +2662,123 @@
   #define SPINDLE_LASER_ACTIVE_HIGH     false  // Set to "true" if the on/off function is active HIGH
   #define SPINDLE_LASER_PWM             true   // Set to "true" if your controller supports setting the speed/power
   #define SPINDLE_LASER_PWM_INVERT      true   // Set to "true" if the speed/power goes up when you want it to go slower
-  #define SPINDLE_LASER_POWERUP_DELAY   5000   // (ms) Delay to allow the spindle/laser to come up to speed/power
-  #define SPINDLE_LASER_POWERDOWN_DELAY 5000   // (ms) Delay to allow the spindle to stop
+
+  #define SPINDLE_LASER_FREQUENCY       2500   // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC)
+
+  /**
+   * Speed / Power can be set ('M3 S') and displayed in terms of:
+   *  - PWM     (S0 - S255)
+   *  - PERCENT (S0 - S100)
+   *  - RPM     (S0 - S50000)  Best for use with a spindle
+   */
+  #define CUTTER_POWER_DISPLAY PWM
+
+  /**
+   * Relative mode uses relative range (SPEED_POWER_MIN to SPEED_POWER_MAX) instead of normal range (0 to SPEED_POWER_MAX)
+   * Best use with SuperPID router controller where for example S0 = 5,000 RPM and S255 = 30,000 RPM
+   */
+  //#define CUTTER_POWER_RELATIVE              // Set speed proportional to [SPEED_POWER_MIN...SPEED_POWER_MAX] instead of directly
 
   #if ENABLED(SPINDLE_FEATURE)
     //#define SPINDLE_CHANGE_DIR               // Enable if your spindle controller can change spindle direction
     #define SPINDLE_CHANGE_DIR_STOP            // Enable if the spindle should stop before changing spin direction
     #define SPINDLE_INVERT_DIR          false  // Set to "true" if the spin direction is reversed
 
+    #define SPINDLE_LASER_POWERUP_DELAY   5000 // (ms) Delay to allow the spindle/laser to come up to speed/power
+    #define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop
+
     /**
-     *  The M3 & M4 commands use the following equation to convert PWM duty cycle to speed/power
+     * M3/M4 uses the following equation to convert speed/power to PWM duty cycle
+     * Power = ((DC / 255 * 100) - SPEED_POWER_INTERCEPT)) * (1 / SPEED_POWER_SLOPE)
+     *   where PWM DC varies from 0 to 255
      *
-     *  SPEED/POWER = PWM duty cycle * SPEED_POWER_SLOPE + SPEED_POWER_INTERCEPT
-     *    where PWM duty cycle varies from 0 to 255
-     *
-     *  set the following for your controller (ALL MUST BE SET)
+     * Set these required parameters for your controller
      */
-    #define SPEED_POWER_SLOPE    118.4
-    #define SPEED_POWER_INTERCEPT  0
-    #define SPEED_POWER_MIN     5000
-    #define SPEED_POWER_MAX    30000    // SuperPID router controller 0 - 30,000 RPM
+    #define SPEED_POWER_SLOPE           118.4  // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
+    #define SPEED_POWER_INTERCEPT         0
+    #define SPEED_POWER_MIN            5000
+    #define SPEED_POWER_MAX           30000    // SuperPID router controller 0 - 30,000 RPM
+    #define SPEED_POWER_STARTUP       25000    // The default value for speed power when M3 is called without arguments
+
   #else
-    #define SPEED_POWER_SLOPE      0.3922
-    #define SPEED_POWER_INTERCEPT  0
-    #define SPEED_POWER_MIN       10
-    #define SPEED_POWER_MAX      100    // 0-100%
+
+    #define SPEED_POWER_SLOPE             0.3922 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
+    #define SPEED_POWER_INTERCEPT         0
+    #define SPEED_POWER_MIN               0
+    #define SPEED_POWER_MAX             100    // 0-100%
+    #define SPEED_POWER_STARTUP          80    // The default value for speed power when M3 is called without arguments
+
+    /**
+     * Enable inline laser power to be handled in the planner / stepper routines.
+     * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I)
+     * or by the 'S' parameter in G0/G1/G2/G3 moves (see LASER_MOVE_POWER).
+     *
+     * This allows the laser to keep in perfect sync with the planner and removes
+     * the powerup/down delay since lasers require negligible time.
+     */
+    #define LASER_POWER_INLINE
+
+    #if ENABLED(LASER_POWER_INLINE)
+      /**
+       * Scale the laser's power in proportion to the movement rate.
+       *
+       * - Sets the entry power proportional to the entry speed over the nominal speed.
+       * - Ramps the power up every N steps to approximate the speed trapezoid.
+       * - Due to the limited power resolution this is only approximate.
+       */
+      #define LASER_POWER_INLINE_TRAPEZOID
+
+      /**
+       * Continuously calculate the current power (nominal_power * current_rate / nominal_rate).
+       * Required for accurate power with non-trapezoidal acceleration (e.g., S_CURVE_ACCELERATION).
+       * This is a costly calculation so this option is discouraged on 8-bit AVR boards.
+       *
+       * LASER_POWER_INLINE_TRAPEZOID_CONT_PER defines how many step cycles there are between power updates. If your
+       * board isn't able to generate steps fast enough (and you are using LASER_POWER_INLINE_TRAPEZOID_CONT), increase this.
+       * Note that when this is zero it means it occurs every cycle; 1 means a delay wait one cycle then run, etc.
+       */
+      //#define LASER_POWER_INLINE_TRAPEZOID_CONT
+
+      /**
+       * Stepper iterations between power updates. Increase this value if the board
+       * can't keep up with the processing demands of LASER_POWER_INLINE_TRAPEZOID_CONT.
+       * Disable (or set to 0) to recalculate power on every stepper iteration.
+       */
+      //#define LASER_POWER_INLINE_TRAPEZOID_CONT_PER 10
+
+      /**
+       * Include laser power in G0/G1/G2/G3/G5 commands with the 'S' parameter
+       */
+      //#define LASER_MOVE_POWER
+
+      #if ENABLED(LASER_MOVE_POWER)
+        // Turn off the laser on G0 moves with no power parameter.
+        // If a power parameter is provided, use that instead.
+        //#define LASER_MOVE_G0_OFF
+      #endif
+
+      /**
+       * Inline flag inverted
+       *
+       * WARNING: M5 will NOT turn off the laser unless another move
+       *          is done (so G-code files must end with 'M5 I').
+       */
+      //#define LASER_POWER_INLINE_INVERT
+
+      /**
+       * Continuously apply inline power. ('M3 S3' == 'G1 S3' == 'M3 S3 I')
+       *
+       * The laser might do some weird things, so only enable this
+       * feature if you understand the implications.
+       */
+      //#define LASER_POWER_INLINE_CONTINUOUS
+
+    #else
+
+      #define SPINDLE_LASER_POWERUP_DELAY     50 // (ms) Delay to allow the spindle/laser to come up to speed/power
+      #define SPINDLE_LASER_POWERDOWN_DELAY   50 // (ms) Delay to allow the spindle to stop
+
+    #endif
   #endif
 #endif
 
diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h
index 02551698195..f715295d0ad 100644
--- a/Marlin/src/HAL/AVR/HAL.h
+++ b/Marlin/src/HAL/AVR/HAL.h
@@ -395,6 +395,8 @@ inline void HAL_adc_init() {
 // AVR compatibility
 #define strtof strtod
 
+#define HAL_CAN_SET_PWM_FREQ   // This HAL supports PWM Frequency adjustment
+
 /**
  *  set_pwm_frequency
  *  Sets the frequency of the timer corresponding to the provided pin
diff --git a/Marlin/src/HAL/AVR/fast_pwm.cpp b/Marlin/src/HAL/AVR/fast_pwm.cpp
index 42e7cc3f107..e8550574864 100644
--- a/Marlin/src/HAL/AVR/fast_pwm.cpp
+++ b/Marlin/src/HAL/AVR/fast_pwm.cpp
@@ -23,7 +23,7 @@
 
 #include "../../inc/MarlinConfigPre.h"
 
-#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM
+#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
 
 #include "HAL.h"
 
@@ -278,5 +278,5 @@ void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255
   }
 }
 
-#endif // FAST_PWM_FAN || SPINDLE_LASER_PWM
+#endif // NEEDS_HARDWARE_PWM
 #endif // __AVR__
diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h
index f5ea629f169..70bb50e6920 100644
--- a/Marlin/src/HAL/LPC1768/HAL.h
+++ b/Marlin/src/HAL/LPC1768/HAL.h
@@ -197,6 +197,8 @@ void HAL_idletask();
 #define PLATFORM_M997_SUPPORT
 void flashFirmware(const int16_t);
 
+#define HAL_CAN_SET_PWM_FREQ   // This HAL supports PWM Frequency adjustment
+
 /**
  * set_pwm_frequency
  *  Set the frequency of the timer corresponding to the provided pin
diff --git a/Marlin/src/HAL/LPC1768/fast_pwm.cpp b/Marlin/src/HAL/LPC1768/fast_pwm.cpp
index a1feb259032..4f2e30ee725 100644
--- a/Marlin/src/HAL/LPC1768/fast_pwm.cpp
+++ b/Marlin/src/HAL/LPC1768/fast_pwm.cpp
@@ -24,7 +24,7 @@
 
 #include "../../inc/MarlinConfigPre.h"
 
-#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM
+#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
 
 #include <pwm.h>
 
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 3a6582bbb1e..5073c339cd1 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -420,7 +420,11 @@ void startOrResumeJob() {
     #if DISABLED(SD_ABORT_NO_COOLDOWN)
       thermalManager.disable_all_heaters();
     #endif
-    thermalManager.zero_fan_speeds();
+    #if !HAS_CUTTER
+      thermalManager.zero_fan_speeds();
+    #else
+      cutter.kill();              // Full cutter shutdown including ISR control
+    #endif
     wait_for_heatup = false;
     #if ENABLED(POWER_LOSS_RECOVERY)
       recovery.purge();
@@ -741,6 +745,10 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) {
 void kill(PGM_P const lcd_error/*=nullptr*/, PGM_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) {
   thermalManager.disable_all_heaters();
 
+  #if HAS_CUTTER
+    cutter.kill();              // Full cutter shutdown including ISR control
+  #endif
+
   SERIAL_ERROR_MSG(STR_ERR_KILLED);
 
   #if HAS_DISPLAY
@@ -770,6 +778,10 @@ void minkill(const bool steppers_off/*=false*/) {
   // Reiterate heaters off
   thermalManager.disable_all_heaters();
 
+  #if HAS_CUTTER
+    cutter.kill();  // Reiterate cutter shutdown
+  #endif
+
   // Power off all steppers (for M112) or just the E steppers
   steppers_off ? disable_all_steppers() : disable_e_steppers();
 
@@ -789,14 +801,14 @@ void minkill(const bool steppers_off/*=false*/) {
     // Wait for kill to be pressed
     while (READ(KILL_PIN)) watchdog_refresh();
 
-    void (*resetFunc)() = 0;  // Declare resetFunc() at address 0
+    void (*resetFunc)() = 0;      // Declare resetFunc() at address 0
     resetFunc();                  // Jump to address 0
 
-  #else // !HAS_KILL
+  #else
 
-    for (;;) watchdog_refresh(); // Wait for reset
+    for (;;) watchdog_refresh();  // Wait for reset
 
-  #endif // !HAS_KILL
+  #endif
 }
 
 /**
diff --git a/Marlin/src/core/drivers.h b/Marlin/src/core/drivers.h
index 833899bdcbc..418a24ec925 100644
--- a/Marlin/src/core/drivers.h
+++ b/Marlin/src/core/drivers.h
@@ -174,15 +174,6 @@
 // Defines that can't be evaluated now
 #define HAS_TMC_SW_SERIAL ANY_AXIS_HAS(SW_SERIAL)
 
-//
-// Stretching 'drivers.h' to include LPC/SAMD51 SD options
-//
-#define _SDCARD_LCD          1
-#define _SDCARD_ONBOARD      2
-#define _SDCARD_CUSTOM_CABLE 3
-#define _SDCARD_ID(V) _CAT(_SDCARD_, V)
-#define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V))
-
 #if HAS_DRIVER(L6470) || HAS_DRIVER(L6474) || HAS_DRIVER(L6480) || HAS_DRIVER(POWERSTEP01)
   #define HAS_L64XX 1
 #endif
diff --git a/Marlin/src/feature/spindle_laser.cpp b/Marlin/src/feature/spindle_laser.cpp
index 0567ba5d3e2..63c98911099 100644
--- a/Marlin/src/feature/spindle_laser.cpp
+++ b/Marlin/src/feature/spindle_laser.cpp
@@ -32,10 +32,17 @@
 
 SpindleLaser cutter;
 
-cutter_power_t SpindleLaser::power; // = 0
-
+cutter_power_t SpindleLaser::power;
+bool SpindleLaser::isOn;                                                       // state to determine when to apply setPower to power
+cutter_setPower_t SpindleLaser::setPower = interpret_power(SPEED_POWER_MIN);   // spindle/laser speed/power control in PWM, Percentage or RPM
+#if ENABLED(MARLIN_DEV_MODE)
+  cutter_frequency_t SpindleLaser::frequency;                                  // setting PWM frequency; range: 2K - 50K
+#endif
 #define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0)
 
+//
+// Init the cutter to a safe OFF state
+//
 void SpindleLaser::init() {
   OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Init spindle to off
   #if ENABLED(SPINDLE_CHANGE_DIR)
@@ -45,41 +52,39 @@ void SpindleLaser::init() {
     SET_PWM(SPINDLE_LASER_PWM_PIN);
     analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);  // set to lowest speed
   #endif
+  #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
+    set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
+    #if ENABLED(MARLIN_DEV_MODE)
+      frequency = SPINDLE_LASER_FREQUENCY;
+    #endif
+  #endif
 }
 
 #if ENABLED(SPINDLE_LASER_PWM)
 
   /**
-   * ocr_val_mode() is used for debugging and to get the points needed to compute the RPM vs ocr_val line
-   *
-   * it accepts inputs of 0-255
-   */
+  * Set the cutter PWM directly to the given ocr value
+  **/
   void SpindleLaser::set_ocr(const uint8_t ocr) {
-    WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on (active low)
+    WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on
     analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
   }
 
 #endif
 
+//
+// Set cutter ON state (and PWM) to the given cutter power value
+//
 void SpindleLaser::apply_power(const cutter_power_t inpow) {
   static cutter_power_t last_power_applied = 0;
   if (inpow == last_power_applied) return;
   last_power_applied = inpow;
   #if ENABLED(SPINDLE_LASER_PWM)
-    if (enabled()) {
-      #define _scaled(F) ((F - (SPEED_POWER_INTERCEPT)) * inv_slope)
-      constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE),
-                      min_ocr = _scaled(SPEED_POWER_MIN),
-                      max_ocr = _scaled(SPEED_POWER_MAX);
-      int16_t ocr_val;
-           if (inpow <= SPEED_POWER_MIN) ocr_val = min_ocr;       // Use minimum if set below
-      else if (inpow >= SPEED_POWER_MAX) ocr_val = max_ocr;       // Use maximum if set above
-      else ocr_val = _scaled(inpow);                              // Use calculated OCR value
-      set_ocr(ocr_val & 0xFF);                                    // ...limited to Atmel PWM max
-    }
+    if (enabled())
+      set_ocr(translate_power(inpow));
     else {
-      WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH);   // Turn spindle off (active low)
-      analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);  // Only write low byte
+      WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH);                           // Turn spindle off
+      analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);                   // Only write low byte
     }
   #else
     WRITE(SPINDLE_LASER_ENA_PIN, (SPINDLE_LASER_ACTIVE_HIGH) ? enabled() : !enabled());
@@ -88,6 +93,10 @@ void SpindleLaser::apply_power(const cutter_power_t inpow) {
 
 #if ENABLED(SPINDLE_CHANGE_DIR)
 
+  //
+  // Set the spindle direction and apply immediately
+  // Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled
+  //
   void SpindleLaser::set_direction(const bool reverse) {
     const bool dir_state = (reverse == SPINDLE_INVERT_DIR); // Forward (M3) HIGH when not inverted
     #if ENABLED(SPINDLE_STOP_ON_DIR_CHANGE)
diff --git a/Marlin/src/feature/spindle_laser.h b/Marlin/src/feature/spindle_laser.h
index ea035299be3..3d7ab6c360b 100644
--- a/Marlin/src/feature/spindle_laser.h
+++ b/Marlin/src/feature/spindle_laser.h
@@ -28,55 +28,98 @@
 
 #include "../inc/MarlinConfig.h"
 
-#if ENABLED(SPINDLE_FEATURE)
-  #define _MSG_CUTTER(M) MSG_SPINDLE_##M
-#else
-  #define _MSG_CUTTER(M) MSG_LASER_##M
-#endif
-#define MSG_CUTTER(M) _MSG_CUTTER(M)
+#include "spindle_laser_types.h"
 
-#if SPEED_POWER_MAX > 255
-  typedef uint16_t cutter_power_t;
-  #define CUTTER_MENU_TYPE uint16_5
-#else
-  typedef uint8_t cutter_power_t;
-  #define CUTTER_MENU_TYPE uint8
+#if ENABLED(LASER_POWER_INLINE)
+  #include "../module/planner.h"
 #endif
 
 class SpindleLaser {
 public:
+  static bool isOn;                             //  state to determine when to apply setPower to power
   static cutter_power_t power;
-  static inline uint8_t powerPercent(const uint8_t pp) { return ui8_to_percent(pp); } // for display
+  static cutter_setPower_t setPower;            //  spindle/laser menu set power; in PWM, Percentage or RPM
+  #if ENABLED(MARLIN_DEV_MODE)
+    static cutter_frequency_t frequency;        //  set PWM frequency; range: 2K-50K
+  #endif
+
+  static cutter_setPower_t interpret_power(const float pwr) {     // convert speed/power to configured PWM, Percentage or RPM in relative or normal range
+    #if CUTTER_DISPLAY_IS(PERCENT)
+      return (pwr / SPEED_POWER_MAX) * 100;                                               // to percent
+    #elif CUTTER_DISPLAY_IS(RPM)                                                          // to RPM is unaltered
+      return pwr;
+    #else                                                                                 // to PWM
+      #if ENABLED(CUTTER_POWER_RELATIVE)
+        return (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 255;     // using rpm range as relative percentage
+      #else
+        return (pwr / SPEED_POWER_MAX) * 255;
+      #endif
+    #endif
+  }
+  /**
+  * Translate speed/power --> percentage --> PWM value
+  **/
+  static cutter_power_t translate_power(const float pwr) {
+    float pwrpc;
+    #if CUTTER_DISPLAY_IS(PERCENT)
+      pwrpc = pwr;
+    #elif CUTTER_DISPLAY_IS(RPM)            // RPM to percent
+     #if ENABLED(CUTTER_POWER_RELATIVE)
+        pwrpc = (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 100;
+      #else
+        pwrpc = pwr / SPEED_POWER_MAX * 100;
+      #endif
+    #else
+      return pwr;                           // PWM
+    #endif
+
+    #if ENABLED(SPINDLE_FEATURE)
+      #if ENABLED(CUTTER_POWER_RELATIVE)
+        constexpr float spmin = 0;
+      #else
+        constexpr float spmin = SPEED_POWER_MIN / SPEED_POWER_MAX * 100; // convert to percentage
+      #endif
+      constexpr float spmax = 100;
+    #else
+      constexpr float spmin = SPEED_POWER_MIN;
+      constexpr float spmax = SPEED_POWER_MAX;
+    #endif
+
+    constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE),
+                    min_ocr = (spmin - (SPEED_POWER_INTERCEPT)) * inv_slope,         // Minimum allowed
+                    max_ocr = (spmax - (SPEED_POWER_INTERCEPT)) * inv_slope;         // Maximum allowed
+    float ocr_val;
+    if (pwrpc < spmin) ocr_val = min_ocr;                                           // Use minimum if set below
+    else if (pwrpc > spmax) ocr_val = max_ocr;                                      // Use maximum if set above
+    else ocr_val = (pwrpc - (SPEED_POWER_INTERCEPT)) * inv_slope;                   // Use calculated OCR value
+    return ocr_val;                                                                 // ...limited to Atmel PWM max
+  }
 
   static void init();
 
-  static inline bool enabled() { return !!power; }
-
-  static inline void set_power(const cutter_power_t pwr) { power = pwr; }
-
-  static inline void refresh() { apply_power(power); }
-
-  static inline void set_enabled(const bool enable) {
-    const bool was = enabled();
-    set_power(enable ? 255 : 0);
-    if (was != enable) power_delay();
-  }
-
+  // Modifying this function should update everywhere
+  static inline bool enabled(const cutter_power_t pwr) { return pwr > 0; }
+  static inline bool enabled() { return enabled(power); }
+  #if ENABLED(MARLIN_DEV_MODE)
+    static inline void refresh_frequency() { set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
+  #endif
   static void apply_power(const cutter_power_t inpow);
 
-  //static bool active() { return READ(SPINDLE_LASER_ENA_PIN) == SPINDLE_LASER_ACTIVE_HIGH; }
+  FORCE_INLINE static void refresh() { apply_power(power); }
+  FORCE_INLINE static void set_power(const cutter_power_t pwr) { power = pwr; refresh(); }
 
-  static void update_output();
+  static inline void set_enabled(const bool enable) { set_power(enable ? (power ?: interpret_power(SPEED_POWER_STARTUP)) : 0); }
 
   #if ENABLED(SPINDLE_LASER_PWM)
     static void set_ocr(const uint8_t ocr);
-    static inline void set_ocr_power(const cutter_power_t pwr) { power = pwr; set_ocr(pwr); }
+    static inline void set_ocr_power(const uint8_t pwr) { power = pwr; set_ocr(pwr); }
+    // static uint8_t translate_power(const cutter_power_t pwr); // Used by update output for power->OCR translation
   #endif
 
   // Wait for spindle to spin up or spin down
-  static inline void power_delay() {
-    #if SPINDLE_LASER_POWERUP_DELAY || SPINDLE_LASER_POWERDOWN_DELAY
-      safe_delay(enabled() ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
+  static inline void power_delay(const bool on) {
+    #if DISABLED(LASER_POWER_INLINE)
+      safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
     #endif
   }
 
@@ -86,10 +129,44 @@ public:
     static inline void set_direction(const bool) {}
   #endif
 
-  static inline void disable() { set_enabled(false); }
-  static inline void enable_forward() { set_direction(false); set_enabled(true); }
-  static inline void enable_reverse() { set_direction(true); set_enabled(true); }
+  static inline void disable() { isOn = false; set_enabled(false); }
+  #if HAS_LCD_MENU
+    static inline void enable_forward() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(false); set_enabled(true); }
+    static inline void enable_reverse() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(true); set_enabled(true); }
+  #endif
 
+  #if ENABLED(LASER_POWER_INLINE)
+    // Force disengage planner power control
+    static inline void inline_disable() { planner.settings.laser.status = 0; planner.settings.laser.power = 0; isOn = false;}
+
+    // Inline modes of all other functions; all enable planner inline power control
+    static inline void inline_enabled(const bool enable) { enable ? inline_power(SPEED_POWER_STARTUP) : inline_ocr_power(0); }
+
+    static void inline_power(const cutter_power_t pwr) {
+      #if ENABLED(SPINDLE_LASER_PWM)
+        inline_ocr_power(translate_power(pwr));
+      #else
+        planner.settings.laser.status = enabled(pwr) ? 0x03 : 0x01;
+        planner.settings.laser.power = pwr;
+      #endif
+    }
+
+    static inline void inline_direction(const bool reverse) { UNUSED(reverse); } // TODO is this ever going to be needed
+
+    #if ENABLED(SPINDLE_LASER_PWM)
+      static inline void inline_ocr_power(const uint8_t pwr) {
+        planner.settings.laser.status = pwr ? 0x03 : 0x01;
+        planner.settings.laser.power = pwr;
+      }
+    #endif
+  #endif
+
+  static inline void kill() {
+    #if ENABLED(LASER_POWER_INLINE)
+      inline_disable();
+    #endif
+    disable();
+  }
 };
 
 extern SpindleLaser cutter;
diff --git a/Marlin/src/feature/spindle_laser_types.h b/Marlin/src/feature/spindle_laser_types.h
new file mode 100644
index 00000000000..9e3f2bae48a
--- /dev/null
+++ b/Marlin/src/feature/spindle_laser_types.h
@@ -0,0 +1,50 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+/**
+ * feature/spindle_laser_types.h
+ * Support for Laser Power or Spindle Power & Direction
+ */
+
+#include "../inc/MarlinConfigPre.h"
+
+#if ENABLED(SPINDLE_FEATURE)
+  #define _MSG_CUTTER(M) MSG_SPINDLE_##M
+#else
+  #define _MSG_CUTTER(M) MSG_LASER_##M
+#endif
+#define MSG_CUTTER(M) _MSG_CUTTER(M)
+#if CUTTER_DISPLAY_IS(RPM) && SPEED_POWER_MAX > 255
+  #define cutter_power_t              uint16_t
+  #define cutter_setPower_t           uint16_t
+  #define CUTTER_MENU_POWER_TYPE      uint16_5
+#else
+  #define cutter_power_t              uint8_t
+  #define cutter_setPower_t           uint8_t
+  #define CUTTER_MENU_POWER_TYPE      uint8
+#endif
+
+#if ENABLED(MARLIN_DEV_MODE)
+  #define cutter_frequency_t          uint16_t
+  #define CUTTER_MENU_FREQUENCY_TYPE  uint16_5
+#endif
diff --git a/Marlin/src/gcode/control/M3-M5.cpp b/Marlin/src/gcode/control/M3-M5.cpp
index 56e1e0e4ec9..9c897abf01a 100644
--- a/Marlin/src/gcode/control/M3-M5.cpp
+++ b/Marlin/src/gcode/control/M3-M5.cpp
@@ -28,6 +28,12 @@
 #include "../../feature/spindle_laser.h"
 #include "../../module/stepper.h"
 
+inline cutter_power_t get_s_power() {
+  return cutter_power_t(
+    parser.intval('S', cutter.interpret_power(SPEED_POWER_STARTUP))
+  );
+}
+
 /**
  * Laser:
  *
@@ -71,29 +77,52 @@
  */
 void GcodeSuite::M3_M4(const bool is_M4) {
 
-  #if ENABLED(SPINDLE_FEATURE)
-    planner.synchronize();   // Wait for movement to complete before changing power
+  #if ENABLED(LASER_POWER_INLINE)
+    if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
+      // Laser power in inline mode
+      cutter.inline_direction(is_M4); // Should always be unused
+
+      #if ENABLED(SPINDLE_LASER_PWM)
+        if (parser.seen('O'))
+          cutter.inline_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
+        else
+          cutter.inline_power(get_s_power());
+      #else
+        cutter.inline_enabled(true);
+      #endif
+      return;
+    }
+    // Non-inline, standard case
+    cutter.inline_disable(); // Prevent future blocks re-setting the power
   #endif
 
+  planner.synchronize();   // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power
+
   cutter.set_direction(is_M4);
 
   #if ENABLED(SPINDLE_LASER_PWM)
     if (parser.seenval('O'))
       cutter.set_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
     else
-      cutter.set_power(parser.intval('S', 255));
+      cutter.set_power(get_s_power());
   #else
     cutter.set_enabled(true);
   #endif
 }
 
 /**
- * M5 - Cutter OFF
+ * M5 - Cutter OFF (when moves are complete)
  */
 void GcodeSuite::M5() {
-  #if ENABLED(SPINDLE_FEATURE)
-    planner.synchronize();
+  #if ENABLED(LASER_POWER_INLINE)
+    if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
+      cutter.inline_enabled(false); // Laser power in inline mode
+      return;
+    }
+    // Non-inline, standard case
+    cutter.inline_disable(); // Prevent future blocks re-setting the power
   #endif
+  planner.synchronize();
   cutter.set_enabled(false);
 }
 
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index 1f9d710bb4c..0937a86bd22 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -53,6 +53,10 @@ GcodeSuite gcode;
   #include "../feature/cancel_object.h"
 #endif
 
+#if ENABLED(LASER_MOVE_POWER)
+  #include "../feature/spindle_laser.h"
+#endif
+
 #include "../MarlinCore.h" // for idle()
 
 millis_t GcodeSuite::previous_move_ms;
@@ -172,6 +176,18 @@ void GcodeSuite::get_destination_from_command() {
   #if BOTH(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1)
     M165();
   #endif
+
+  #if ENABLED(LASER_MOVE_POWER)
+    // Set the laser power in the planner to configure this move
+    if (parser.seen('S'))
+      cutter.inline_power(parser.value_int());
+    else {
+      #if ENABLED(LASER_MOVE_G0_OFF)
+        if (parser.codenum == 0)        // G0
+          cutter.inline_enabled(false);
+      #endif
+    }
+  #endif
 }
 
 /**
diff --git a/Marlin/src/gcode/motion/G0_G1.cpp b/Marlin/src/gcode/motion/G0_G1.cpp
index 069d76fdad9..df0825a512d 100644
--- a/Marlin/src/gcode/motion/G0_G1.cpp
+++ b/Marlin/src/gcode/motion/G0_G1.cpp
@@ -69,7 +69,7 @@ void GcodeSuite::G0_G1(
       #endif
     #endif
 
-    get_destination_from_command();                 // Process X Y Z E F parameters
+    get_destination_from_command();                 // Get X Y Z E F (and set cutter power)
 
     #ifdef G0_FEEDRATE
       if (fast_move) {
diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp
index b0fb299ab26..d6b18bdd951 100644
--- a/Marlin/src/gcode/motion/G2_G3.cpp
+++ b/Marlin/src/gcode/motion/G2_G3.cpp
@@ -283,7 +283,7 @@ void GcodeSuite::G2_G3(const bool clockwise) {
       relative_mode = true;
     #endif
 
-    get_destination_from_command();
+    get_destination_from_command();   // Get X Y Z E F (and set cutter power)
 
     #if ENABLED(SF_ARC_FIX)
       relative_mode = relative_mode_backup;
diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h
index a52ae1af297..cda744d81b6 100644
--- a/Marlin/src/inc/Conditionals_adv.h
+++ b/Marlin/src/inc/Conditionals_adv.h
@@ -116,7 +116,23 @@
   #define Z_STEPPER_ALIGN_AMP 1.0
 #endif
 
-#define HAS_CUTTER EITHER(SPINDLE_FEATURE, LASER_FEATURE)
+//
+// Spindle/Laser power display types
+// Defined here so sanity checks can use them
+//
+#if EITHER(SPINDLE_FEATURE, LASER_FEATURE)
+  #define HAS_CUTTER 1
+  #define _CUTTER_DISP_PWM     1
+  #define _CUTTER_DISP_PERCENT 2
+  #define _CUTTER_DISP_RPM     3
+  #define _CUTTER_DISP(V)      _CAT(_CUTTER_DISP_, V)
+  #define CUTTER_DISPLAY_IS(V) (_CUTTER_DISP(CUTTER_POWER_DISPLAY) == _CUTTER_DISP(V))
+#endif
+
+// Add features that need hardware PWM here
+#if ANY(FAST_PWM_FAN, SPINDLE_LASER_PWM)
+  #define NEEDS_HARDWARE_PWM 1
+#endif
 
 #if !defined(__AVR__) || !defined(USBCON)
   // Define constants and variables for buffering serial data.
@@ -290,3 +306,17 @@
     #define MAXIMUM_STEPPER_RATE 250000
   #endif
 #endif
+
+//
+// SD Card connection methods
+// Defined here so pins and sanity checks can use them
+//
+#if ENABLED(SDSUPPORT)
+  #define _SDCARD_LCD          1
+  #define _SDCARD_ONBOARD      2
+  #define _SDCARD_CUSTOM_CABLE 3
+  #define _SDCARD_ID(V) _CAT(_SDCARD_, V)
+  #define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V))
+#else
+  #define SD_CONNECTION_IS(...) 0
+#endif
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 20867b4fea0..b38aaa13320 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -324,17 +324,36 @@
 
 /**
  * Override the SD_DETECT_STATE set in Configuration_adv.h
+ * and enable sharing of onboard SD host drives (all platforms but AGCM4)
  */
 #if ENABLED(SDSUPPORT)
-  #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
-    #undef SD_DETECT_STATE
-    #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
-      #define SD_DETECT_STATE HIGH
+
+  #if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4)
+    //
+    // The external SD card is not used. Hardware SPI is used to access the card.
+    // When sharing the SD card with a PC we want the menu options to
+    // mount/unmount the card and refresh it. So we disable card detect.
+    //
+    #undef SD_DETECT_PIN
+    #define SHARED_SD_CARD
+  #endif
+
+  #if DISABLED(SHARED_SD_CARD)
+    #define INIT_SDCARD_ON_BOOT 1
+  #endif
+
+  #if PIN_EXISTS(SD_DETECT)
+    #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
+      #undef SD_DETECT_STATE
+      #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
+        #define SD_DETECT_STATE HIGH
+      #endif
+    #endif
+    #ifndef SD_DETECT_STATE
+      #define SD_DETECT_STATE LOW
     #endif
   #endif
-  #ifndef SD_DETECT_STATE
-    #define SD_DETECT_STATE LOW
-  #endif
+
 #endif
 
 /**
@@ -2153,21 +2172,6 @@
   #endif
 #endif
 
-#if ENABLED(SDSUPPORT)
-  #if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4)
-    //
-    // The external SD card is not used. Hardware SPI is used to access the card.
-    // When sharing the SD card with a PC we want the menu options to
-    // mount/unmount the card and refresh it. So we disable card detect.
-    //
-    #undef SD_DETECT_PIN
-    #define SHARED_SD_CARD
-  #endif
-  #if DISABLED(SHARED_SD_CARD)
-    #define INIT_SDCARD_ON_BOOT 1
-  #endif
-#endif
-
 #if !NUM_SERIAL
   #undef BAUD_RATE_GCODE
 #endif
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 2fd61d4be10..088ad098db9 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -1451,7 +1451,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
  * Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis.
  */
 #if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
-  #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY"
+  #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY."
 #endif
 
 /**
@@ -2654,9 +2654,9 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
 
 #if ENABLED(BACKLASH_COMPENSATION)
   #ifndef BACKLASH_DISTANCE_MM
-    #error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM"
+    #error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM."
   #elif !defined(BACKLASH_CORRECTION)
-    #error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION"
+    #error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION."
   #elif IS_CORE
     constexpr float backlash_arr[] = BACKLASH_DISTANCE_MM;
     static_assert(!backlash_arr[CORE_AXIS_1] && !backlash_arr[CORE_AXIS_2],
@@ -2736,6 +2736,45 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
 #endif
 
 #if HAS_CUTTER
+  #ifndef CUTTER_POWER_DISPLAY
+    #error "CUTTER_POWER_DISPLAY is required with a spindle or laser. Please update your Configuration_adv.h."
+  #elif !CUTTER_DISPLAY_IS(PWM) && !CUTTER_DISPLAY_IS(PERCENT) && !CUTTER_DISPLAY_IS(RPM)
+    #error "CUTTER_POWER_DISPLAY must be PWM, PERCENT, or RPM. Please update your Configuration_adv.h."
+  #endif
+
+  #if ENABLED(LASER_POWER_INLINE)
+    #if ENABLED(SPINDLE_CHANGE_DIR)
+      #error "SPINDLE_CHANGE_DIR and LASER_POWER_INLINE are incompatible."
+    #elif ENABLED(LASER_MOVE_G0_OFF) && DISABLED(LASER_MOVE_POWER)
+      #error "LASER_MOVE_G0_OFF requires LASER_MOVE_POWER. Please update your Configuration_adv.h."
+    #endif
+    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+      #if DISABLED(SPINDLE_LASER_PWM)
+        #error "LASER_POWER_INLINE_TRAPEZOID requires SPINDLE_LASER_PWM to function."
+      #elif ENABLED(S_CURVE_ACCELERATION)
+        //#ifndef LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
+        //  #define LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
+        //  #warning "Combining LASER_POWER_INLINE_TRAPEZOID with S_CURVE_ACCELERATION may result in unintended behavior."
+        //#endif
+      #endif
+    #endif
+    #if ENABLED(LASER_POWER_INLINE_INVERT)
+      //#ifndef LASER_POWER_INLINE_INVERT_WARN
+      //  #define LASER_POWER_INLINE_INVERT_WARN
+      //  #warning "Enabling LASER_POWER_INLINE_INVERT means that `M5` won't kill the laser immediately; use `M5 I` instead."
+      //#endif
+    #endif
+  #else
+    #if SPINDLE_LASER_POWERUP_DELAY < 1
+      #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
+    #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
+      #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
+    #elif ENABLED(LASER_MOVE_POWER)
+      #error "LASER_MOVE_POWER requires LASER_POWER_INLINE."
+    #elif ANY(LASER_POWER_INLINE_TRAPEZOID, LASER_POWER_INLINE_INVERT, LASER_MOVE_G0_OFF, LASER_MOVE_POWER)
+      #error "Enabled an inline laser feature without inline laser power being enabled."
+    #endif
+  #endif
   #define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN)
   #if BOTH(SPINDLE_FEATURE, LASER_FEATURE)
     #error "Enable only one of SPINDLE_FEATURE or LASER_FEATURE."
@@ -2748,13 +2787,9 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
       #error "SPINDLE_LASER_PWM_PIN is required for SPINDLE_LASER_PWM."
     #elif !PWM_PIN(SPINDLE_LASER_PWM_PIN)
       #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
-    #elif SPINDLE_LASER_POWERUP_DELAY < 1
-      #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
-    #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
-      #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
     #elif !defined(SPINDLE_LASER_PWM_INVERT)
       #error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
-    #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX)
+    #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX) || !defined(SPEED_POWER_STARTUP)
       #error "SPINDLE_LASER_PWM equation constant(s) missing."
     #elif _PIN_CONFLICT(X_MIN)
       #error "SPINDLE_LASER_PWM pin conflicts with X_MIN_PIN."
diff --git a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
index be414ab3037..b4bba3782ce 100644
--- a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
+++ b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp
@@ -570,8 +570,12 @@ void MarlinUI::draw_status_screen() {
     // Laser / Spindle
     #if DO_DRAW_CUTTER
       if (cutter.power && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
-        lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.powerPercent(cutter.power)));
-        lcd_put_wchar('%');
+        lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.power));
+        #if CUTTER_DISPLAY_IS(PERCENT)
+          lcd_put_wchar('%');
+        #elif CUTTER_DISPLAY_IS(RPM)
+          lcd_put_wchar('K');
+        #endif
       }
     #endif
 
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index 79a6f814b2e..0aea571caab 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -90,6 +90,7 @@ namespace Language_en {
   PROGMEM Language_Str MSG_PREHEAT_2_SETTINGS              = _UxGT("Preheat ") PREHEAT_2_LABEL _UxGT(" Conf");
   PROGMEM Language_Str MSG_PREHEAT_CUSTOM                  = _UxGT("Preheat Custom");
   PROGMEM Language_Str MSG_COOLDOWN                        = _UxGT("Cooldown");
+  PROGMEM Language_Str MSG_CUTTER_FREQUENCY                = _UxGT("Frequency");
   PROGMEM Language_Str MSG_LASER_MENU                      = _UxGT("Laser Control");
   PROGMEM Language_Str MSG_LASER_OFF                       = _UxGT("Laser Off");
   PROGMEM Language_Str MSG_LASER_ON                        = _UxGT("Laser On");
@@ -603,7 +604,7 @@ namespace Language_en {
   PROGMEM Language_Str MSG_BACKLASH_C                      = LCD_STR_C;
   PROGMEM Language_Str MSG_BACKLASH_CORRECTION             = _UxGT("Correction");
   PROGMEM Language_Str MSG_BACKLASH_SMOOTHING              = _UxGT("Smoothing");
-  
+
   PROGMEM Language_Str MSG_LEVEL_X_AXIS                    = _UxGT("Level X Axis");
   PROGMEM Language_Str MSG_AUTO_CALIBRATE                  = _UxGT("Auto Calibrate");
   PROGMEM Language_Str MSG_HEATER_TIMEOUT                  = _UxGT("Heater Timeout");
diff --git a/Marlin/src/lcd/menu/menu_spindle_laser.cpp b/Marlin/src/lcd/menu/menu_spindle_laser.cpp
index fd42522839e..d8e680ec3c3 100644
--- a/Marlin/src/lcd/menu/menu_spindle_laser.cpp
+++ b/Marlin/src/lcd/menu/menu_spindle_laser.cpp
@@ -36,18 +36,29 @@
 
     START_MENU();
     BACK_ITEM(MSG_MAIN);
-    if (cutter.enabled()) {
-      #if ENABLED(SPINDLE_LASER_PWM)
-        EDIT_ITEM(CUTTER_MENU_TYPE, MSG_CUTTER(POWER), &cutter.power, SPEED_POWER_MIN, SPEED_POWER_MAX);
-      #endif
+    #if ENABLED(SPINDLE_LASER_PWM)
+      EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.setPower, cutter.interpret_power(SPEED_POWER_MIN), cutter.interpret_power(SPEED_POWER_MAX),
+      []{
+        if (cutter.isOn) {
+          cutter.power = cutter.setPower;
+        }
+      });
+    #endif
+
+    if (cutter.enabled() && cutter.isOn)
       ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable);
-    }
     else {
       ACTION_ITEM(MSG_CUTTER(ON), cutter.enable_forward);
       #if ENABLED(SPINDLE_CHANGE_DIR)
         ACTION_ITEM(MSG_SPINDLE_REVERSE, cutter.enable_reverse);
       #endif
     }
+
+    #if ENABLED(MARLIN_DEV_MODE)
+      #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
+        EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 50000,[]{ cutter.refresh_frequency();});
+      #endif
+    #endif
     END_MENU();
   }
 
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index e1a050a4592..2de46373b26 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -815,11 +815,10 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
   #if ENABLED(S_CURVE_ACCELERATION)
     // Jerk controlled speed requires to express speed versus time, NOT steps
     uint32_t acceleration_time = ((float)(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE),
-             deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE);
-
+             deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE),
     // And to offload calculations from the ISR, we also calculate the inverse of those times here
-    uint32_t acceleration_time_inverse = get_period_inverse(acceleration_time);
-    uint32_t deceleration_time_inverse = get_period_inverse(deceleration_time);
+             acceleration_time_inverse = get_period_inverse(acceleration_time),
+             deceleration_time_inverse = get_period_inverse(deceleration_time);
   #endif
 
   // Store new block parameters
@@ -834,6 +833,47 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
     block->cruise_rate = cruise_rate;
   #endif
   block->final_rate = final_rate;
+
+  /**
+   * Laser trapezoid calculations
+   *
+   * Approximate the trapezoid with the laser, incrementing the power every `entry_per` while accelerating
+   * and decrementing it every `exit_power_per` while decelerating, thus ensuring power is related to feedrate.
+   *
+   * LASER_POWER_INLINE_TRAPEZOID_CONT doesn't need this as it continuously approximates
+   *
+   * Note this may behave unreliably when running with S_CURVE_ACCELERATION
+   */
+  #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+    if (block->laser.power > 0) { // No need to care if power == 0
+      const uint8_t entry_power = block->laser.power * entry_factor; // Power on block entry
+      #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+        // Speedup power
+        const uint8_t entry_power_diff = block->laser.power - entry_power;
+        if (entry_power_diff) {
+          block->laser.entry_per = accelerate_steps / entry_power_diff;
+          block->laser.power_entry = entry_power;
+        }
+        else {
+          block->laser.entry_per = 0;
+          block->laser.power_entry = block->laser.power;
+        }
+        // Slowdown power
+        const uint8_t exit_power = block->laser.power * exit_factor, // Power on block entry
+                      exit_power_diff = block->laser.power - exit_power;
+        if (exit_power_diff) {
+          block->laser.exit_per = (block->step_event_count - block->decelerate_after) / exit_power_diff;
+          block->laser.power_exit = exit_power;
+        }
+        else {
+          block->laser.exit_per = 0;
+          block->laser.power_exit = block->laser.power;
+        }
+      #else
+        block->laser.power_entry = entry_power;
+      #endif
+    }
+  #endif
 }
 
 /*                            PLANNER SPEED DEFINITION
@@ -1813,6 +1853,12 @@ bool Planner::_populate_block(block_t * const block, bool split_move,
   // Set direction bits
   block->direction_bits = dm;
 
+  // Update block laser power
+  #if ENABLED(LASER_POWER_INLINE)
+    block->laser.status = settings.laser.status;
+    block->laser.power = settings.laser.power;
+  #endif
+
   // Number of steps for each axis
   // See http://www.corexy.com/theory.html
   #if CORE_IS_XY
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index 24c02c01c5c..a42c84c1faf 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -52,7 +52,7 @@
 #endif
 
 #if HAS_CUTTER
-  #include "../feature/spindle_laser.h"
+  #include "../feature/spindle_laser_types.h"
 #endif
 
 // Feedrate for manual moves
@@ -88,6 +88,23 @@ enum BlockFlag : char {
   BLOCK_FLAG_SYNC_POSITION        = _BV(BLOCK_BIT_SYNC_POSITION)
 };
 
+#if ENABLED(LASER_POWER_INLINE)
+
+  typedef struct {
+    uint8_t status,           // See planner settings for meaning
+            power;            // Ditto; When in trapezoid mode this is nominal power
+    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+      uint8_t   power_entry;  // Entry power for the laser
+      #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+        uint8_t   power_exit; // Exit power for the laser
+        uint32_t  entry_per,  // Steps per power increment (to avoid floats in stepper calcs)
+                  exit_per;   // Steps per power decrement
+      #endif
+    #endif
+  } block_laser_t;
+
+#endif
+
 /**
  * struct block_t
  *
@@ -174,12 +191,36 @@ typedef struct block_t {
     uint32_t sdpos;
   #endif
 
+  #if ENABLED(LASER_POWER_INLINE)
+    block_laser_t laser;
+  #endif
+
 } block_t;
 
 #define HAS_POSITION_FLOAT ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL)
 
 #define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
 
+#if ENABLED(LASER_POWER_INLINE)
+  typedef struct {
+    /**
+     * Laser status bitmask; most bits are unused;
+     *  0: Planner buffer enable
+     *  1: Laser enable
+     *  2: Reserved for direction
+     */
+    uint8_t status;
+    /**
+     * Laser power: 0 or 255 in case of PWM-less laser,
+     * or the OCR value;
+     *
+     * Using OCR instead of raw power,
+     * as it avoids floating points during move loop
+     */
+    uint8_t power;
+  } settings_laser_t;
+#endif
+
 typedef struct {
    uint32_t max_acceleration_mm_per_s2[XYZE_N], // (mm/s^2) M201 XYZE
             min_segment_time_us;                // (µs) M205 B
@@ -190,6 +231,9 @@ typedef struct {
             travel_acceleration;                // (mm/s^2) M204 T - Travel acceleration. DEFAULT ACCELERATION for all NON printing moves.
  feedRate_t min_feedrate_mm_s,                  // (mm/s) M205 S - Minimum linear feedrate
             min_travel_feedrate_mm_s;           // (mm/s) M205 T - Minimum travel feedrate
+  #if ENABLED(LASER_POWER_INLINE)
+    settings_laser_t laser;
+  #endif
 } planner_settings_t;
 
 #if DISABLED(SKEW_CORRECTION)
diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp
index ed48b83d5a6..f77a596b08c 100644
--- a/Marlin/src/module/stepper.cpp
+++ b/Marlin/src/module/stepper.cpp
@@ -133,6 +133,10 @@ Stepper stepper; // Singleton
   #include "../feature/powerloss.h"
 #endif
 
+#if HAS_CUTTER
+  #include "../feature/spindle_laser.h"
+#endif
+
 // public:
 
 #if HAS_EXTRA_ENDSTOPS || ENABLED(Z_STEPPER_AUTO_ALIGN)
@@ -236,6 +240,20 @@ xyz_long_t Stepper::endstops_trigsteps;
 xyze_long_t Stepper::count_position{0};
 xyze_int8_t Stepper::count_direction{0};
 
+#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+  Stepper::stepper_laser_t Stepper::laser = {
+    .trap_en = false,
+    .cur_power = 0,
+    .cruise_set = false,
+    #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+      .last_step_count = 0,
+      .acc_step_count = 0
+    #else
+      .till_update = 0
+    #endif
+  };
+#endif
+
 #define DUAL_ENDSTOP_APPLY_STEP(A,V)                                                                                        \
   if (separate_multi_axis) {                                                                                                \
     if (A##_HOME_DIR < 0) {                                                                                                 \
@@ -1674,10 +1692,9 @@ uint32_t Stepper::block_phase_isr() {
 
         #if ENABLED(S_CURVE_ACCELERATION)
           // Get the next speed to use (Jerk limited!)
-          uint32_t acc_step_rate =
-            acceleration_time < current_block->acceleration_time
-              ? _eval_bezier_curve(acceleration_time)
-              : current_block->cruise_rate;
+          uint32_t acc_step_rate = acceleration_time < current_block->acceleration_time
+                                   ? _eval_bezier_curve(acceleration_time)
+                                   : current_block->cruise_rate;
         #else
           acc_step_rate = STEP_MULTIPLY(acceleration_time, current_block->acceleration_rate) + current_block->initial_rate;
           NOMORE(acc_step_rate, current_block->nominal_rate);
@@ -1690,9 +1707,40 @@ uint32_t Stepper::block_phase_isr() {
         acceleration_time += interval;
 
         #if ENABLED(LIN_ADVANCE)
-          // Fire ISR if final adv_rate is reached
-          if (LA_steps && (!LA_use_advance_lead || LA_isr_rate != current_block->advance_speed))
-            initiateLA();
+          if (LA_use_advance_lead) {
+            // Fire ISR if final adv_rate is reached
+            if (LA_steps && LA_isr_rate != current_block->advance_speed) nextAdvanceISR = 0;
+          }
+          else if (LA_steps) nextAdvanceISR = 0;
+        #endif
+
+        // Update laser - Accelerating
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+          if (laser.trap_en) {
+            #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+              if (current_block->laser.entry_per) {
+                laser.acc_step_count -= step_events_completed - laser.last_step_count;
+                laser.last_step_count = step_events_completed;
+
+                // Should be faster than a divide, since this should trip just once
+                if (laser.acc_step_count < 0) {
+                  while (laser.acc_step_count < 0) {
+                    laser.acc_step_count += current_block->laser.entry_per;
+                    if (laser.cur_power < current_block->laser.power) laser.cur_power++;
+                  }
+                  cutter.set_ocr_power(laser.cur_power);
+                }
+              }
+            #else
+              if (laser.till_update)
+                laser.till_update--;
+              else {
+                laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
+                laser.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate;
+                cutter.set_ocr_power(laser.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles
+              }
+            #endif
+          }
         #endif
       }
       // Are we in Deceleration phase ?
@@ -1740,10 +1788,39 @@ uint32_t Stepper::block_phase_isr() {
               LA_isr_rate = current_block->advance_speed;
             }
           }
-          else if (LA_steps) initiateLA();
+          else if (LA_steps) nextAdvanceISR = 0;
+        #endif // LIN_ADVANCE
+
+        // Update laser - Decelerating
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+          if (laser.trap_en) {
+            #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+              if (current_block->laser.exit_per) {
+                laser.acc_step_count -= step_events_completed - laser.last_step_count;
+                laser.last_step_count = step_events_completed;
+
+                // Should be faster than a divide, since this should trip just once
+                if (laser.acc_step_count < 0) {
+                  while (laser.acc_step_count < 0) {
+                    laser.acc_step_count += current_block->laser.exit_per;
+                    if (laser.cur_power > current_block->laser.power_exit) laser.cur_power--;
+                  }
+                  cutter.set_ocr_power(laser.cur_power);
+                }
+              }
+            #else
+              if (laser.till_update)
+                laser.till_update--;
+              else {
+                laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
+                laser.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate;
+                cutter.set_ocr_power(laser.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles
+              }
+            #endif
+          }
         #endif
       }
-      // We must be in cruise phase otherwise
+      // Must be in cruise phase otherwise
       else {
 
         #if ENABLED(LIN_ADVANCE)
@@ -1759,6 +1836,22 @@ uint32_t Stepper::block_phase_isr() {
 
         // The timer interval is just the nominal value for the nominal speed
         interval = ticks_nominal;
+
+        // Update laser - Cruising
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+          if (laser.trap_en) {
+            if (!laser.cruise_set) {
+              laser.cur_power = current_block->laser.power;
+              cutter.set_ocr_power(laser.cur_power);
+              laser.cruise_set = true;
+            }
+            #if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+              laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
+            #else
+              laser.last_step_count = step_events_completed;
+            #endif
+          }
+        #endif
       }
     }
   }
@@ -1805,11 +1898,11 @@ uint32_t Stepper::block_phase_isr() {
          * If DeltaA ==  DeltaB, the movement is only in the 1st axis (X)
          */
         #if EITHER(COREXY, COREXZ)
-          #define X_CMP ==
+          #define X_CMP(A,B) ((A)==(B))
         #else
-          #define X_CMP !=
+          #define X_CMP(A,B) ((A)!=(B))
         #endif
-        #define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) X_CMP D_(2)) )
+        #define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && X_CMP(D_(1),D_(2))) )
       #else
         #define X_MOVE_TEST !!current_block->steps.a
       #endif
@@ -1823,11 +1916,11 @@ uint32_t Stepper::block_phase_isr() {
          * If DeltaA == -DeltaB, the movement is only in the 2nd axis (Y or Z)
          */
         #if EITHER(COREYX, COREYZ)
-          #define Y_CMP ==
+          #define Y_CMP(A,B) ((A)==(B))
         #else
-          #define Y_CMP !=
+          #define Y_CMP(A,B) ((A)!=(B))
         #endif
-        #define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Y_CMP D_(2)) )
+        #define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Y_CMP(D_(1),D_(2))) )
       #else
         #define Y_MOVE_TEST !!current_block->steps.b
       #endif
@@ -1841,11 +1934,11 @@ uint32_t Stepper::block_phase_isr() {
          * If DeltaA == -DeltaB, the movement is only in the 2nd axis (Z)
          */
         #if EITHER(COREZX, COREZY)
-          #define Z_CMP ==
+          #define Z_CMP(A,B) ((A)==(B))
         #else
-          #define Z_CMP !=
+          #define Z_CMP(A,B) ((A)!=(B))
         #endif
-        #define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Z_CMP D_(2)) )
+        #define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Z_CMP(D_(1),D_(2))) )
       #else
         #define Z_MOVE_TEST !!current_block->steps.c
       #endif
@@ -1938,6 +2031,39 @@ uint32_t Stepper::block_phase_isr() {
         set_directions();
       }
 
+      #if ENABLED(LASER_POWER_INLINE)
+        const uint8_t stat = current_block->laser.status;
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
+          laser.trap_en = (stat & 0x03) == 0x03;
+          laser.cur_power = current_block->laser.power_entry; // RESET STATE
+          laser.cruise_set = false;
+          #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+            laser.last_step_count = 0;
+            laser.acc_step_count = current_block->laser.entry_per / 2;
+          #else
+            laser.till_update = 0;
+          #endif
+          // Always have PWM in this case
+          if (TEST(stat, 0)) {                        // Planner controls the laser
+            if (TEST(stat, 1))                        // Laser is on
+              cutter.set_ocr_power(laser.cur_power);
+            else
+              cutter.set_power(0);
+          }
+        #else
+          if (TEST(stat, 0)) {                        // Planner controls the laser
+            #if ENABLED(SPINDLE_LASER_PWM)
+              if (TEST(stat, 1))                      // Laser is on
+                cutter.set_ocr_power(current_block->laser.power);
+              else
+                cutter.set_power(0);
+            #else
+              cutter.set_enabled(TEST(stat, 1));
+            #endif
+          }
+        #endif
+      #endif // LASER_POWER_INLINE
+
       // At this point, we must ensure the movement about to execute isn't
       // trying to force the head against a limit switch. If using interrupt-
       // driven change detection, and already against a limit then no call to
@@ -1957,21 +2083,35 @@ uint32_t Stepper::block_phase_isr() {
       // Mark the time_nominal as not calculated yet
       ticks_nominal = -1;
 
-      #if DISABLED(S_CURVE_ACCELERATION)
-        // Set as deceleration point the initial rate of the block
-        acc_step_rate = current_block->initial_rate;
-      #endif
-
       #if ENABLED(S_CURVE_ACCELERATION)
         // Initialize the Bézier speed curve
         _calc_bezier_curve_coeffs(current_block->initial_rate, current_block->cruise_rate, current_block->acceleration_time_inverse);
         // We haven't started the 2nd half of the trapezoid
         bezier_2nd_half = false;
+      #else
+        // Set as deceleration point the initial rate of the block
+        acc_step_rate = current_block->initial_rate;
       #endif
 
       // Calculate the initial timer interval
       interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
     }
+    #if ENABLED(LASER_POWER_INLINE_CONTINUOUS)
+      else { // No new block found; so apply inline laser parameters
+        // This should mean ending file with 'M5 I' will stop the laser; thus the inline flag isn't needed
+        const uint8_t stat = planner.settings.laser.status;
+        if (TEST(stat, 0)) {             // Planner controls the laser
+          #if ENABLED(SPINDLE_LASER_PWM)
+            if (TEST(stat, 1))           // Laser is on
+              cutter.set_ocr_power(planner.settings.laser.power);
+            else
+              cutter.set_power(0);
+          #else
+            cutter.set_enabled(TEST(stat, 1));
+          #endif
+        }
+      }
+    #endif
   }
 
   // Return the interval to wait
diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h
index 46c6c1c16a7..3876980ad06 100644
--- a/Marlin/src/module/stepper.h
+++ b/Marlin/src/module/stepper.h
@@ -339,23 +339,35 @@ class Stepper {
       static uint32_t acc_step_rate; // needed for deceleration start point
     #endif
 
-    //
     // Exact steps at which an endstop was triggered
-    //
     static xyz_long_t endstops_trigsteps;
 
-    //
     // Positions of stepper motors, in step units
-    //
     static xyze_long_t count_position;
 
-    //
-    // Current direction of stepper motors (+1 or -1)
-    //
+    // Current stepper motor directions (+1 or -1)
     static xyze_int8_t count_direction;
 
-  public:
+    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
 
+      typedef struct {
+        bool trap_en;       // Trapezoid needed flag (i.e., laser on, planner in control)
+        uint8_t cur_power;  // Current laser power
+        bool cruise_set;    // Power set up for cruising?
+
+        #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
+          uint32_t last_step_count, // Step count from the last update
+                   acc_step_count;  // Bresenham counter for laser accel/decel
+        #else
+          uint16_t till_update;     // Countdown to the next update
+        #endif
+      } stepper_laser_t;
+
+      static stepper_laser_t laser;
+
+    #endif
+
+  public:
     // Initialize stepper hardware
     static void init();