diff --git a/config/example-extras.cfg b/config/example-extras.cfg
index e3424eece..5140a1bf1 100644
--- a/config/example-extras.cfg
+++ b/config/example-extras.cfg
@@ -322,33 +322,34 @@
 #   axis is triggered.
 
 
-# Stepper phase adjusted endstops. The following additional parameters
-# may be added to a stepper axis definition to improve the accuracy of
-# endstop switches.
-#[stepper_z]
-#homing_stepper_phases:
-#   One may set this to the number of phases of the stepper motor
-#   driver (which is the number of micro-steps multiplied by
-#   four). This parameter must be provided if using stepper phase
-#   adjustments.
-#homing_endstop_accuracy: 0.200
+# Stepper phase adjusted endstops. To use this feature, define a
+# config section with an "endstop_phase" prefix followed by the name
+# of the corresponding stepper config section (for example,
+# "[endstop_phase stepper_z]"). This feature can improve the accuracy
+# of endstop switches.
+#[endstop_phase stepper_z]
+#phases:
+#   Set this to the number of phases of the given stepper motor driver
+#   (which is the number of micro-steps multiplied by four). This
+#   parameter must be provided.
+#endstop_accuracy: 0.200
 #   Sets the expected accuracy (in mm) of the endstop. This represents
 #   the maximum error distance the endstop may trigger (eg, if an
 #   endstop may occasionally trigger 100um early or up to 100um late
 #   then set this to 0.200 for 200um). The default is
-#   homing_stepper_phases*step_distance.
-#homing_endstop_phase:
+#   phases*step_distance.
+#endstop_phase:
 #   This specifies the phase of the stepper motor driver to expect
 #   when hitting the endstop. Only set this value if one is sure the
 #   stepper motor driver is reset every time the mcu is reset. If this
 #   is not set, then the stepper phase will be detected on the first
 #   home and that phase will be used on all subsequent homes.
-#homing_endstop_align_zero: False
-#   If true then the code will arrange for the zero position on the
-#   axis to occur at a full step on the stepper motor. (If used on the
-#   Z axis and the print layer height is a multiple of a full step
-#   distance then every layer will occur on a full step.) The default
-#   is False.
+#endstop_align_zero: False
+#   If true then the position_endstop of the axis will effectively be
+#   modified so that the zero position for the axis occurs at a full
+#   step on the stepper motor. (If used on the Z axis and the print
+#   layer height is a multiple of a full step distance then every
+#   layer will occur on a full step.) The default is False.
 
 
 # Heater cooling fans (one may define any number of sections with a
diff --git a/config/printer-makergear-m2-2012.cfg b/config/printer-makergear-m2-2012.cfg
index 67df15375..52bdfeecb 100644
--- a/config/printer-makergear-m2-2012.cfg
+++ b/config/printer-makergear-m2-2012.cfg
@@ -12,8 +12,10 @@ endstop_pin: ^!PB6
 position_endstop: 0.0
 position_max: 200
 homing_speed: 50
-homing_stepper_phases: 32
-homing_endstop_accuracy: .200
+
+[endstop_phase stepper_x]
+phases: 32
+endstop_accuracy: .200
 
 [stepper_y]
 step_pin: PC1
@@ -24,8 +26,10 @@ endstop_pin: ^!PB5
 position_endstop: 0.0
 position_max: 250
 homing_speed: 50
-homing_stepper_phases: 32
-homing_endstop_accuracy: .200
+
+[endstop_phase stepper_y]
+phases: 32
+endstop_accuracy: .200
 
 [stepper_z]
 step_pin: PC2
@@ -37,8 +41,10 @@ position_min: 0.1
 position_endstop: 0.7
 position_max: 200
 homing_retract_dist: 2.0
-homing_stepper_phases: 32
-homing_endstop_accuracy: .070
+
+[endstop_phase stepper_z]
+phases: 32
+endstop_accuracy: .070
 
 [extruder]
 step_pin: PC3
diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py
new file mode 100644
index 000000000..d3deba7ce
--- /dev/null
+++ b/klippy/extras/endstop_phase.py
@@ -0,0 +1,75 @@
+# Endstop accuracy improvement via stepper phase tracking
+#
+# Copyright (C) 2016-2018  Kevin O'Connor <kevin@koconnor.net>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+import math, logging
+import homing
+
+class EndstopPhase:
+    def __init__(self, config):
+        self.printer = config.get_printer()
+        self.name = config.get_name().split()[1]
+        stepper_config = config.getsection(self.name)
+        self.step_dist = step_dist = stepper_config.getfloat('step_distance')
+        self.phases = config.getint('phases', minval=1)
+        self.endstop_phase = config.getint('endstop_phase', None,
+                                           minval=0, maxval=self.phases-1)
+        self.endstop_align_zero = config.getboolean('endstop_align_zero', False)
+        # Determine endstop accuracy
+        endstop_accuracy = config.getfloat('endstop_accuracy', None, above=0.)
+        if endstop_accuracy is None:
+            self.endstop_accuracy = self.phases//2 - 1
+        elif self.endstop_phase is not None:
+            self.endstop_accuracy = int(
+                math.ceil(endstop_accuracy * .5 / step_dist))
+        else:
+            self.endstop_accuracy = int(math.ceil(endstop_accuracy / step_dist))
+        if self.endstop_accuracy >= self.phases // 2:
+            raise config.error("Endstop for %s is not accurate enough for"
+                               " stepper phase adjustment" % (self.name,))
+        if self.printer.get_start_args().get('debugoutput') is not None:
+            self.endstop_accuracy = self.phases
+        # Register event handler
+        self.printer.register_event_handler(
+            "homing:homed_rails", self.handle_homed_rails)
+    def align_endstop(self, pos):
+        if not self.endstop_align_zero or self.endstop_phase is None:
+            return pos
+        # Adjust the endstop position so 0.0 is always at a full step
+        microsteps = self.phases // 4
+        half_microsteps = microsteps // 2
+        phase_offset = (((self.endstop_phase + half_microsteps) % microsteps)
+                        - half_microsteps) * self.step_dist
+        full_step = microsteps * self.step_dist
+        return int(pos / full_step + .5) * full_step + phase_offset
+    def get_homed_offset(self, stepper):
+        pos = stepper.get_mcu_position()
+        phase = pos % self.phases
+        if self.endstop_phase is None:
+            logging.info("Setting %s endstop phase to %d", self.name, phase)
+            self.endstop_phase = phase
+            return 0.
+        delta = (phase - self.endstop_phase) % self.phases
+        if delta >= self.phases - self.endstop_accuracy:
+            delta -= self.phases
+        elif delta > self.endstop_accuracy:
+            raise homing.EndstopError(
+                "Endstop %s incorrect phase (got %d vs %d)" % (
+                    self.name, phase, self.endstop_phase))
+        return delta * self.step_dist
+    def handle_homed_rails(self, homing_state, rails):
+        for rail in rails:
+            stepper = rail.get_steppers()[0]
+            if stepper.get_name() != self.name:
+                continue
+            orig_pos = rail.get_commanded_position()
+            offset = self.get_homed_offset(stepper)
+            pos = self.align_endstop(orig_pos) + offset
+            if pos == orig_pos:
+                return False
+            rail.set_commanded_position(pos)
+            return True
+
+def load_config_prefix(config):
+    return EndstopPhase(config)
diff --git a/klippy/homing.py b/klippy/homing.py
index f40994a09..30e62f2d7 100644
--- a/klippy/homing.py
+++ b/klippy/homing.py
@@ -124,14 +124,14 @@ class Homing:
             self.toolhead.set_position(forcepos)
             self.homing_move(movepos, endstops, second_homing_speed,
                              verify_movement=self.verify_retract)
-        # Apply homing offsets
-        for rail in rails:
-            cp = rail.get_commanded_position()
-            rail.set_commanded_position(cp + rail.get_homed_offset())
-        adjustpos = self.toolhead.get_kinematics().calc_position()
-        for axis in homing_axes:
-            movepos[axis] = adjustpos[axis]
-        self.toolhead.set_position(movepos)
+        # Signal home operation complete
+        ret = self.printer.send_event("homing:homed_rails", self, rails)
+        if any(ret):
+            # Apply any homing offsets
+            adjustpos = self.toolhead.get_kinematics().calc_position()
+            for axis in homing_axes:
+                movepos[axis] = adjustpos[axis]
+            self.toolhead.set_position(movepos)
     def home_axes(self, axes):
         self.changed_axes = axes
         try:
diff --git a/klippy/stepper.py b/klippy/stepper.py
index dcbc94840..786ea2eed 100644
--- a/klippy/stepper.py
+++ b/klippy/stepper.py
@@ -162,63 +162,6 @@ class PrinterRail:
                 raise config.error(
                     "Unable to infer homing_positive_dir in section '%s'" % (
                         config.get_name(),))
-        # Endstop stepper phase position tracking
-        self.homing_stepper_phases = config.getint(
-            'homing_stepper_phases', None, minval=0)
-        endstop_accuracy = config.getfloat(
-            'homing_endstop_accuracy', None, above=0.)
-        self.homing_endstop_accuracy = self.homing_endstop_phase = None
-        if self.homing_stepper_phases:
-            self.homing_step_dist = step_dist = stepper.get_step_dist()
-            self.homing_endstop_phase = config.getint(
-                'homing_endstop_phase', None, minval=0
-                , maxval=self.homing_stepper_phases-1)
-            if (self.homing_endstop_phase is not None
-                and config.getboolean('homing_endstop_align_zero', False)):
-                # Adjust the endstop position so 0.0 is always at a full step
-                micro_steps = self.homing_stepper_phases // 4
-                phase_offset = (
-                    ((self.homing_endstop_phase + micro_steps // 2)
-                     % micro_steps) - micro_steps // 2) * step_dist
-                full_step = micro_steps * step_dist
-                es_pos = (int(self.position_endstop / full_step + .5)
-                          * full_step + phase_offset)
-                if es_pos != self.position_endstop:
-                    logging.info("Changing %s endstop position to %.3f"
-                                 " (from %.3f)", self.name,
-                                 es_pos, self.position_endstop)
-                    self.position_endstop = es_pos
-            if endstop_accuracy is None:
-                self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
-            elif self.homing_endstop_phase is not None:
-                self.homing_endstop_accuracy = int(math.ceil(
-                    endstop_accuracy * .5 / step_dist))
-            else:
-                self.homing_endstop_accuracy = int(math.ceil(
-                    endstop_accuracy / step_dist))
-            if self.homing_endstop_accuracy >= self.homing_stepper_phases // 2:
-                logging.info("Endstop for %s is not accurate enough for stepper"
-                             " phase adjustment", self.name)
-                self.homing_stepper_phases = None
-            if mcu_endstop.get_mcu().is_fileoutput():
-                self.homing_endstop_accuracy = self.homing_stepper_phases
-    def get_homed_offset(self):
-        if not self.homing_stepper_phases:
-            return 0.
-        pos = self.steppers[0].get_mcu_position()
-        pos %= self.homing_stepper_phases
-        if self.homing_endstop_phase is None:
-            logging.info("Setting %s endstop phase to %d", self.name, pos)
-            self.homing_endstop_phase = pos
-            return 0.
-        delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
-        if delta >= self.homing_stepper_phases - self.homing_endstop_accuracy:
-            delta -= self.homing_stepper_phases
-        elif delta > self.homing_endstop_accuracy:
-            raise homing.EndstopError(
-                "Endstop %s incorrect phase (got %d vs %d)" % (
-                    self.name, pos, self.homing_endstop_phase))
-        return delta * self.homing_step_dist
     def get_range(self):
         return self.position_min, self.position_max
     def get_homing_info(self):