fe->data[0] |= MVPP2_CLS_FLOW_TBL0_PORT_ID(port);
 }
 
+static void mvpp2_cls_flow_port_remove(struct mvpp2_cls_flow_entry *fe,
+                                      u32 port)
+{
+       fe->data[0] &= ~MVPP2_CLS_FLOW_TBL0_PORT_ID(port);
+}
+
 static void mvpp2_cls_flow_lu_type_set(struct mvpp2_cls_flow_entry *fe,
                                       u8 lu_type)
 {
        }
 }
 
+static int mvpp2_cls_c2_port_flow_index(struct mvpp2_port *port, int loc)
+{
+       return MVPP22_CLS_C2_RFS_LOC(port->id, loc);
+}
+
 /* Initialize the flow table entries for the given flow */
 static void mvpp2_cls_flow_init(struct mvpp2 *priv,
                                const struct mvpp2_cls_flow *flow)
        return 0;
 }
 
+/* Returns the size, in bits, of the corresponding HEK field */
+static int mvpp2_cls_hek_field_size(u32 field)
+{
+       switch (field) {
+       case MVPP22_CLS_HEK_OPT_MAC_DA:
+               return 48;
+       case MVPP22_CLS_HEK_OPT_IP4SA:
+       case MVPP22_CLS_HEK_OPT_IP4DA:
+               return 32;
+       case MVPP22_CLS_HEK_OPT_IP6SA:
+       case MVPP22_CLS_HEK_OPT_IP6DA:
+               return 128;
+       case MVPP22_CLS_HEK_OPT_L4SIP:
+       case MVPP22_CLS_HEK_OPT_L4DIP:
+               return 16;
+       default:
+               return -1;
+       }
+}
+
 const struct mvpp2_cls_flow *mvpp2_cls_flow_get(int flow)
 {
        if (flow >= MVPP2_N_PRS_FLOWS)
        mvpp2_rss_port_c2_disable(port);
 }
 
+static void mvpp22_port_c2_lookup_disable(struct mvpp2_port *port, int entry)
+{
+       struct mvpp2_cls_c2_entry c2;
+
+       mvpp2_cls_c2_read(port->priv, entry, &c2);
+
+       /* Clear the port map so that the entry doesn't match anymore */
+       c2.tcam[4] &= ~(MVPP22_CLS_C2_PORT_ID(BIT(port->id)));
+
+       mvpp2_cls_c2_write(port->priv, &c2);
+}
+
 /* Set CPU queue number for oversize packets */
 void mvpp2_cls_oversize_rxq_set(struct mvpp2_port *port)
 {
        mvpp2_write(port->priv, MVPP2_CLS_SWFWD_PCTRL_REG, val);
 }
 
+static int mvpp2_port_c2_tcam_rule_add(struct mvpp2_port *port,
+                                      struct mvpp2_rfs_rule *rule)
+{
+       struct flow_action_entry *act;
+       struct mvpp2_cls_c2_entry c2;
+       u8 qh, ql, pmap;
+
+       memset(&c2, 0, sizeof(c2));
+
+       c2.index = mvpp2_cls_c2_port_flow_index(port, rule->loc);
+       if (c2.index < 0)
+               return -EINVAL;
+
+       act = &rule->flow->action.entries[0];
+
+       rule->c2_index = c2.index;
+
+       c2.tcam[0] = (rule->c2_tcam & 0xffff) |
+                    ((rule->c2_tcam_mask & 0xffff) << 16);
+       c2.tcam[1] = ((rule->c2_tcam >> 16) & 0xffff) |
+                    (((rule->c2_tcam_mask >> 16) & 0xffff) << 16);
+       c2.tcam[2] = ((rule->c2_tcam >> 32) & 0xffff) |
+                    (((rule->c2_tcam_mask >> 32) & 0xffff) << 16);
+       c2.tcam[3] = ((rule->c2_tcam >> 48) & 0xffff) |
+                    (((rule->c2_tcam_mask >> 48) & 0xffff) << 16);
+
+       pmap = BIT(port->id);
+       c2.tcam[4] = MVPP22_CLS_C2_PORT_ID(pmap);
+       c2.tcam[4] |= MVPP22_CLS_C2_TCAM_EN(MVPP22_CLS_C2_PORT_ID(pmap));
+
+       /* Match on Lookup Type */
+       c2.tcam[4] |= MVPP22_CLS_C2_TCAM_EN(MVPP22_CLS_C2_LU_TYPE(MVPP2_CLS_LU_TYPE_MASK));
+       c2.tcam[4] |= MVPP22_CLS_C2_LU_TYPE(rule->loc);
+
+       /* Mark packet as "forwarded to software", needed for RSS */
+       c2.act |= MVPP22_CLS_C2_ACT_FWD(MVPP22_C2_FWD_SW_LOCK);
+
+       c2.act |= MVPP22_CLS_C2_ACT_QHIGH(MVPP22_C2_UPD_LOCK) |
+                  MVPP22_CLS_C2_ACT_QLOW(MVPP22_C2_UPD_LOCK);
+
+       qh = ((act->queue.index + port->first_rxq) >> 3) & MVPP22_CLS_C2_ATTR0_QHIGH_MASK;
+       ql = (act->queue.index + port->first_rxq) & MVPP22_CLS_C2_ATTR0_QLOW_MASK;
+
+       c2.attr[0] = MVPP22_CLS_C2_ATTR0_QHIGH(qh) |
+                     MVPP22_CLS_C2_ATTR0_QLOW(ql);
+
+       c2.valid = true;
+
+       mvpp2_cls_c2_write(port->priv, &c2);
+
+       return 0;
+}
+
+static int mvpp2_port_c2_rfs_rule_insert(struct mvpp2_port *port,
+                                        struct mvpp2_rfs_rule *rule)
+{
+       return mvpp2_port_c2_tcam_rule_add(port, rule);
+}
+
+static int mvpp2_port_cls_rfs_rule_remove(struct mvpp2_port *port,
+                                         struct mvpp2_rfs_rule *rule)
+{
+       const struct mvpp2_cls_flow *flow;
+       struct mvpp2_cls_flow_entry fe;
+       int index, i;
+
+       for_each_cls_flow_id_containing_type(i, rule->flow_type) {
+               flow = mvpp2_cls_flow_get(i);
+               if (!flow)
+                       return 0;
+
+               index = MVPP2_CLS_FLT_C2_RFS(port->id, flow->flow_id, rule->loc);
+
+               mvpp2_cls_flow_read(port->priv, index, &fe);
+               mvpp2_cls_flow_port_remove(&fe, BIT(port->id));
+               mvpp2_cls_flow_write(port->priv, &fe);
+       }
+
+       if (rule->c2_index >= 0)
+               mvpp22_port_c2_lookup_disable(port, rule->c2_index);
+
+       return 0;
+}
+
+static int mvpp2_port_flt_rfs_rule_insert(struct mvpp2_port *port,
+                                         struct mvpp2_rfs_rule *rule)
+{
+       const struct mvpp2_cls_flow *flow;
+       struct mvpp2 *priv = port->priv;
+       struct mvpp2_cls_flow_entry fe;
+       int index, ret, i;
+
+       if (rule->engine != MVPP22_CLS_ENGINE_C2)
+               return -EOPNOTSUPP;
+
+       ret = mvpp2_port_c2_rfs_rule_insert(port, rule);
+       if (ret)
+               return ret;
+
+       for_each_cls_flow_id_containing_type(i, rule->flow_type) {
+               flow = mvpp2_cls_flow_get(i);
+               if (!flow)
+                       return 0;
+
+               index = MVPP2_CLS_FLT_C2_RFS(port->id, flow->flow_id, rule->loc);
+
+               mvpp2_cls_flow_read(priv, index, &fe);
+               mvpp2_cls_flow_eng_set(&fe, rule->engine);
+               mvpp2_cls_flow_port_id_sel(&fe, true);
+               mvpp2_flow_set_hek_fields(&fe, rule->hek_fields);
+               mvpp2_cls_flow_lu_type_set(&fe, rule->loc);
+               mvpp2_cls_flow_port_add(&fe, 0xf);
+
+               mvpp2_cls_flow_write(priv, &fe);
+       }
+
+       return 0;
+}
+
+static int mvpp2_cls_c2_build_match(struct mvpp2_rfs_rule *rule)
+{
+       struct flow_rule *flow = rule->flow;
+       struct flow_action_entry *act;
+       int offs = 64;
+
+       act = &flow->action.entries[0];
+
+       if (flow_rule_match_key(flow, FLOW_DISSECTOR_KEY_PORTS)) {
+               struct flow_match_ports match;
+
+               flow_rule_match_ports(flow, &match);
+               if (match.mask->src) {
+                       rule->hek_fields |= MVPP22_CLS_HEK_OPT_L4SIP;
+                       offs -= mvpp2_cls_hek_field_size(MVPP22_CLS_HEK_OPT_L4SIP);
+
+                       rule->c2_tcam |= ((u64)ntohs(match.key->src)) << offs;
+                       rule->c2_tcam_mask |= ((u64)ntohs(match.mask->src)) << offs;
+               }
+
+               if (match.mask->dst) {
+                       rule->hek_fields |= MVPP22_CLS_HEK_OPT_L4DIP;
+                       offs -= mvpp2_cls_hek_field_size(MVPP22_CLS_HEK_OPT_L4DIP);
+
+                       rule->c2_tcam |= ((u64)ntohs(match.key->dst)) << offs;
+                       rule->c2_tcam_mask |= ((u64)ntohs(match.mask->dst)) << offs;
+               }
+       }
+
+       if (hweight16(rule->hek_fields) > MVPP2_FLOW_N_FIELDS)
+               return -EOPNOTSUPP;
+
+       return 0;
+}
+
+static int mvpp2_cls_rfs_parse_rule(struct mvpp2_rfs_rule *rule)
+{
+       struct flow_rule *flow = rule->flow;
+       struct flow_action_entry *act;
+
+       act = &flow->action.entries[0];
+       if (act->id != FLOW_ACTION_QUEUE)
+               return -EOPNOTSUPP;
+
+       /* For now, only use the C2 engine which has a HEK size limited to 64
+        * bits for TCAM matching.
+        */
+       rule->engine = MVPP22_CLS_ENGINE_C2;
+
+       if (mvpp2_cls_c2_build_match(rule))
+               return -EINVAL;
+
+       return 0;
+}
+
+int mvpp2_ethtool_cls_rule_get(struct mvpp2_port *port,
+                              struct ethtool_rxnfc *rxnfc)
+{
+       struct mvpp2_ethtool_fs *efs;
+
+       if (rxnfc->fs.location >= MVPP2_N_RFS_RULES)
+               return -EINVAL;
+
+       efs = port->rfs_rules[rxnfc->fs.location];
+       if (!efs)
+               return -ENOENT;
+
+       memcpy(rxnfc, &efs->rxnfc, sizeof(efs->rxnfc));
+
+       return 0;
+}
+
+int mvpp2_ethtool_cls_rule_ins(struct mvpp2_port *port,
+                              struct ethtool_rxnfc *info)
+{
+       struct ethtool_rx_flow_spec_input input = {};
+       struct ethtool_rx_flow_rule *ethtool_rule;
+       struct mvpp2_ethtool_fs *efs, *old_efs;
+       int ret = 0;
+
+       if (info->fs.location >= 4 ||
+           info->fs.location < 0)
+               return -EINVAL;
+
+       efs = kzalloc(sizeof(*efs), GFP_KERNEL);
+       if (!efs)
+               return -ENOMEM;
+
+       input.fs = &info->fs;
+
+       ethtool_rule = ethtool_rx_flow_rule_create(&input);
+       if (IS_ERR(ethtool_rule)) {
+               ret = PTR_ERR(ethtool_rule);
+               goto clean_rule;
+       }
+
+       efs->rule.flow = ethtool_rule->rule;
+       efs->rule.flow_type = mvpp2_cls_ethtool_flow_to_type(info->fs.flow_type);
+
+       ret = mvpp2_cls_rfs_parse_rule(&efs->rule);
+       if (ret)
+               goto clean_eth_rule;
+
+       efs->rule.loc = info->fs.location;
+
+       /* Replace an already existing rule */
+       if (port->rfs_rules[efs->rule.loc]) {
+               old_efs = port->rfs_rules[efs->rule.loc];
+               ret = mvpp2_port_cls_rfs_rule_remove(port, &old_efs->rule);
+               if (ret)
+                       goto clean_eth_rule;
+               kfree(old_efs);
+               port->n_rfs_rules--;
+       }
+
+       ret = mvpp2_port_flt_rfs_rule_insert(port, &efs->rule);
+       if (ret)
+               goto clean_eth_rule;
+
+       memcpy(&efs->rxnfc, info, sizeof(*info));
+       port->rfs_rules[efs->rule.loc] = efs;
+       port->n_rfs_rules++;
+
+       return ret;
+
+clean_eth_rule:
+       ethtool_rx_flow_rule_destroy(ethtool_rule);
+clean_rule:
+       kfree(efs);
+       return ret;
+}
+
+int mvpp2_ethtool_cls_rule_del(struct mvpp2_port *port,
+                              struct ethtool_rxnfc *info)
+{
+       struct mvpp2_ethtool_fs *efs;
+       int ret;
+
+       efs = port->rfs_rules[info->fs.location];
+       if (!efs)
+               return -EINVAL;
+
+       /* Remove the rule from the engines. */
+       ret = mvpp2_port_cls_rfs_rule_remove(port, &efs->rule);
+       if (ret)
+               return ret;
+
+       port->n_rfs_rules--;
+       port->rfs_rules[info->fs.location] = NULL;
+       kfree(efs);
+
+       return 0;
+}
+
 static inline u32 mvpp22_rxfh_indir(struct mvpp2_port *port, u32 rxq)
 {
        int nrxqs, cpu, cpus = num_possible_cpus();
 
 #define MVPP22_CLS_C2_N_ENTRIES                256
 
 /* Number of per-port dedicated entries in the C2 TCAM */
-#define MVPP22_CLS_C2_PORT_RANGE       8
+#define MVPP22_CLS_C2_PORT_N_FLOWS     MVPP2_N_RFS_ENTRIES_PER_FLOW
 
-#define MVPP22_CLS_C2_PORT_FIRST(p)    (MVPP22_CLS_C2_N_ENTRIES - \
-                                       ((p) * MVPP22_CLS_C2_PORT_RANGE))
-#define MVPP22_CLS_C2_RSS_ENTRY(p)     (MVPP22_CLS_C2_PORT_FIRST(p) - 1)
+/* Each port has oen range per flow type + one entry controling the global RSS
+ * setting and the default rx queue
+ */
+#define MVPP22_CLS_C2_PORT_RANGE       (MVPP22_CLS_C2_PORT_N_FLOWS + 1)
+#define MVPP22_CLS_C2_PORT_FIRST(p)    ((p) * MVPP22_CLS_C2_PORT_RANGE)
+#define MVPP22_CLS_C2_RSS_ENTRY(p)     (MVPP22_CLS_C2_PORT_FIRST((p) + 1) - 1)
+
+#define MVPP22_CLS_C2_PORT_FLOW_FIRST(p)       (MVPP22_CLS_C2_PORT_FIRST(p))
+
+#define MVPP22_CLS_C2_RFS_LOC(p, loc)  (MVPP22_CLS_C2_PORT_FLOW_FIRST(p) + (loc))
 
 /* Packet flow ID */
 enum mvpp2_prs_flow {
        MVPP2_FL_LAST,
 };
 
-enum mvpp2_cls_lu_type {
-       MVPP2_CLS_LU_ALL = 0,
-};
-
 /* LU Type defined for all engines, and specified in the flow table */
 #define MVPP2_CLS_LU_TYPE_MASK                 0x3f
 
        struct mvpp2_prs_result_info prs_ri;
 };
 
-#define MVPP2_CLS_FLT_ENTRIES_PER_FLOW         (MVPP2_MAX_PORTS + 1)
+#define MVPP2_CLS_FLT_ENTRIES_PER_FLOW         (MVPP2_MAX_PORTS + 1 + 16)
 #define MVPP2_CLS_FLT_FIRST(id)                        (((id) - MVPP2_FL_START) * \
                                                 MVPP2_CLS_FLT_ENTRIES_PER_FLOW)
-#define MVPP2_CLS_FLT_C2_RSS_ENTRY(id)         (MVPP2_CLS_FLT_FIRST(id))
-#define MVPP2_CLS_FLT_HASH_ENTRY(port, id)     (MVPP2_CLS_FLT_C2_RSS_ENTRY(id) + (port) + 1)
+
+#define MVPP2_CLS_FLT_C2_RFS(port, id, rfs_n)  (MVPP2_CLS_FLT_FIRST(id) + \
+                                                ((port) * MVPP2_MAX_PORTS) + \
+                                                (rfs_n))
+
+#define MVPP2_CLS_FLT_C2_RSS_ENTRY(id)         (MVPP2_CLS_FLT_C2_RFS(MVPP2_MAX_PORTS, id, 0))
+#define MVPP2_CLS_FLT_HASH_ENTRY(port, id)     (MVPP2_CLS_FLT_C2_RSS_ENTRY(id) + 1 + (port))
 #define MVPP2_CLS_FLT_LAST(id)                 (MVPP2_CLS_FLT_FIRST(id) + \
                                                 MVPP2_CLS_FLT_ENTRIES_PER_FLOW - 1)
 
                        continue;                                             \
                else
 
+#define for_each_cls_flow_id_containing_type(i, type)                        \
+       for_each_cls_flow_id((i))                                             \
+               if ((cls_flows[(i)].flow_type & (type)) != (type))            \
+                       continue;                                             \
+               else
+
 struct mvpp2_cls_flow_entry {
        u32 index;
        u32 data[MVPP2_CLS_FLOWS_TBL_DATA_WORDS];
 void mvpp2_cls_c2_read(struct mvpp2 *priv, int index,
                       struct mvpp2_cls_c2_entry *c2);
 
+int mvpp2_ethtool_cls_rule_get(struct mvpp2_port *port,
+                              struct ethtool_rxnfc *rxnfc);
+
+int mvpp2_ethtool_cls_rule_ins(struct mvpp2_port *port,
+                              struct ethtool_rxnfc *info);
+
+int mvpp2_ethtool_cls_rule_del(struct mvpp2_port *port,
+                              struct ethtool_rxnfc *info);
+
 #endif