a5psw_port_pattern_set(a5psw, port, A5PSW_PATTERN_MGMTFWD, enable);
 }
 
+static void a5psw_port_tx_enable(struct a5psw *a5psw, int port, bool enable)
+{
+       u32 mask = A5PSW_PORT_ENA_TX(port);
+       u32 reg = enable ? mask : 0;
+
+       /* Even though the port TX is disabled through TXENA bit in the
+        * PORT_ENA register, it can still send BPDUs. This depends on the tag
+        * configuration added when sending packets from the CPU port to the
+        * switch port. Indeed, when using forced forwarding without filtering,
+        * even disabled ports will be able to send packets that are tagged.
+        * This allows to implement STP support when ports are in a state where
+        * forwarding traffic should be stopped but BPDUs should still be sent.
+        */
+       a5psw_reg_rmw(a5psw, A5PSW_PORT_ENA, mask, reg);
+}
+
 static void a5psw_port_enable_set(struct a5psw *a5psw, int port, bool enable)
 {
        u32 port_ena = 0;
        return 0;
 }
 
+static void a5psw_port_learning_set(struct a5psw *a5psw, int port, bool learn)
+{
+       u32 mask = A5PSW_INPUT_LEARN_DIS(port);
+       u32 reg = !learn ? mask : 0;
+
+       a5psw_reg_rmw(a5psw, A5PSW_INPUT_LEARN, mask, reg);
+}
+
+static void a5psw_port_rx_block_set(struct a5psw *a5psw, int port, bool block)
+{
+       u32 mask = A5PSW_INPUT_LEARN_BLOCK(port);
+       u32 reg = block ? mask : 0;
+
+       a5psw_reg_rmw(a5psw, A5PSW_INPUT_LEARN, mask, reg);
+}
+
 static void a5psw_flooding_set_resolution(struct a5psw *a5psw, int port,
                                          bool set)
 {
 
 static void a5psw_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 {
-       u32 mask = A5PSW_INPUT_LEARN_DIS(port) | A5PSW_INPUT_LEARN_BLOCK(port);
+       bool learning_enabled, rx_enabled, tx_enabled;
        struct a5psw *a5psw = ds->priv;
-       u32 reg = 0;
 
        switch (state) {
        case BR_STATE_DISABLED:
        case BR_STATE_BLOCKING:
-               reg |= A5PSW_INPUT_LEARN_DIS(port);
-               reg |= A5PSW_INPUT_LEARN_BLOCK(port);
-               break;
        case BR_STATE_LISTENING:
-               reg |= A5PSW_INPUT_LEARN_DIS(port);
+               rx_enabled = false;
+               tx_enabled = false;
+               learning_enabled = false;
                break;
        case BR_STATE_LEARNING:
-               reg |= A5PSW_INPUT_LEARN_BLOCK(port);
+               rx_enabled = false;
+               tx_enabled = false;
+               learning_enabled = true;
                break;
        case BR_STATE_FORWARDING:
-       default:
+               rx_enabled = true;
+               tx_enabled = true;
+               learning_enabled = true;
                break;
+       default:
+               dev_err(ds->dev, "invalid STP state: %d\n", state);
+               return;
        }
 
-       a5psw_reg_rmw(a5psw, A5PSW_INPUT_LEARN, mask, reg);
+       a5psw_port_learning_set(a5psw, port, learning_enabled);
+       a5psw_port_rx_block_set(a5psw, port, !rx_enabled);
+       a5psw_port_tx_enable(a5psw, port, tx_enabled);
 }
 
 static void a5psw_port_fast_age(struct dsa_switch *ds, int port)