return !!(parms->i_flags & TUNNEL_KEY);
 }
 
+static bool mlxsw_sp_ipip_parms6_has_ikey(const struct __ip6_tnl_parm *parms)
+{
+       return !!(parms->i_flags & TUNNEL_KEY);
+}
+
 static bool mlxsw_sp_ipip_parms4_has_okey(const struct ip_tunnel_parm *parms)
 {
        return !!(parms->o_flags & TUNNEL_KEY);
 }
 
+static bool mlxsw_sp_ipip_parms6_has_okey(const struct __ip6_tnl_parm *parms)
+{
+       return !!(parms->o_flags & TUNNEL_KEY);
+}
+
 static u32 mlxsw_sp_ipip_parms4_ikey(const struct ip_tunnel_parm *parms)
 {
        return mlxsw_sp_ipip_parms4_has_ikey(parms) ?
                be32_to_cpu(parms->i_key) : 0;
 }
 
+static u32 mlxsw_sp_ipip_parms6_ikey(const struct __ip6_tnl_parm *parms)
+{
+       return mlxsw_sp_ipip_parms6_has_ikey(parms) ?
+               be32_to_cpu(parms->i_key) : 0;
+}
+
 static u32 mlxsw_sp_ipip_parms4_okey(const struct ip_tunnel_parm *parms)
 {
        return mlxsw_sp_ipip_parms4_has_okey(parms) ?
                be32_to_cpu(parms->o_key) : 0;
 }
 
+static u32 mlxsw_sp_ipip_parms6_okey(const struct __ip6_tnl_parm *parms)
+{
+       return mlxsw_sp_ipip_parms6_has_okey(parms) ?
+               be32_to_cpu(parms->o_key) : 0;
+}
+
 static union mlxsw_sp_l3addr
 mlxsw_sp_ipip_parms4_saddr(const struct ip_tunnel_parm *parms)
 {
                                                  &new_parms, extack);
 }
 
+static int
+mlxsw_sp_ipip_rem_addr_set_gre4(struct mlxsw_sp *mlxsw_sp,
+                               struct mlxsw_sp_ipip_entry *ipip_entry)
+{
+       return 0;
+}
+
+static void
+mlxsw_sp_ipip_rem_addr_unset_gre4(struct mlxsw_sp *mlxsw_sp,
+                                 const struct mlxsw_sp_ipip_entry *ipip_entry)
+{
+}
+
 static const struct mlxsw_sp_ipip_ops mlxsw_sp_ipip_gre4_ops = {
        .dev_type = ARPHRD_IPGRE,
        .ul_proto = MLXSW_SP_L3_PROTO_IPV4,
        .can_offload = mlxsw_sp_ipip_can_offload_gre4,
        .ol_loopback_config = mlxsw_sp_ipip_ol_loopback_config_gre4,
        .ol_netdev_change = mlxsw_sp_ipip_ol_netdev_change_gre4,
+       .rem_ip_addr_set = mlxsw_sp_ipip_rem_addr_set_gre4,
+       .rem_ip_addr_unset = mlxsw_sp_ipip_rem_addr_unset_gre4,
 };
 
 static struct mlxsw_sp_ipip_parms
        return -EINVAL;
 }
 
+static int
+mlxsw_sp1_ipip_rem_addr_set_gre6(struct mlxsw_sp *mlxsw_sp,
+                                struct mlxsw_sp_ipip_entry *ipip_entry)
+{
+       WARN_ON_ONCE(1);
+       return -EINVAL;
+}
+
+static void
+mlxsw_sp1_ipip_rem_addr_unset_gre6(struct mlxsw_sp *mlxsw_sp,
+                                  const struct mlxsw_sp_ipip_entry *ipip_entry)
+{
+       WARN_ON_ONCE(1);
+}
+
 static const struct mlxsw_sp_ipip_ops mlxsw_sp1_ipip_gre6_ops = {
        .dev_type = ARPHRD_IP6GRE,
        .ul_proto = MLXSW_SP_L3_PROTO_IPV6,
        .can_offload = mlxsw_sp1_ipip_can_offload_gre6,
        .ol_loopback_config = mlxsw_sp1_ipip_ol_loopback_config_gre6,
        .ol_netdev_change = mlxsw_sp1_ipip_ol_netdev_change_gre6,
+       .rem_ip_addr_set = mlxsw_sp1_ipip_rem_addr_set_gre6,
+       .rem_ip_addr_unset = mlxsw_sp1_ipip_rem_addr_unset_gre6,
 };
 
 const struct mlxsw_sp_ipip_ops *mlxsw_sp1_ipip_ops_arr[] = {
        [MLXSW_SP_IPIP_TYPE_GRE6] = &mlxsw_sp1_ipip_gre6_ops,
 };
 
+static struct mlxsw_sp_ipip_parms
+mlxsw_sp2_ipip_netdev_parms_init_gre6(const struct net_device *ol_dev)
+{
+       struct __ip6_tnl_parm parms = mlxsw_sp_ipip_netdev_parms6(ol_dev);
+
+       return (struct mlxsw_sp_ipip_parms) {
+               .proto = MLXSW_SP_L3_PROTO_IPV6,
+               .saddr = mlxsw_sp_ipip_parms6_saddr(&parms),
+               .daddr = mlxsw_sp_ipip_parms6_daddr(&parms),
+               .link = parms.link,
+               .ikey = mlxsw_sp_ipip_parms6_ikey(&parms),
+               .okey = mlxsw_sp_ipip_parms6_okey(&parms),
+       };
+}
+
+static int
+mlxsw_sp2_ipip_nexthop_update_gre6(struct mlxsw_sp *mlxsw_sp, u32 adj_index,
+                                  struct mlxsw_sp_ipip_entry *ipip_entry,
+                                  bool force, char *ratr_pl)
+{
+       u16 rif_index = mlxsw_sp_ipip_lb_rif_index(ipip_entry->ol_lb);
+       enum mlxsw_reg_ratr_op op;
+
+       op = force ? MLXSW_REG_RATR_OP_WRITE_WRITE_ENTRY :
+                    MLXSW_REG_RATR_OP_WRITE_WRITE_ENTRY_ON_ACTIVITY;
+       mlxsw_reg_ratr_pack(ratr_pl, op, true, MLXSW_REG_RATR_TYPE_IPIP,
+                           adj_index, rif_index);
+       mlxsw_reg_ratr_ipip6_entry_pack(ratr_pl,
+                                       ipip_entry->dip_kvdl_index);
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ratr), ratr_pl);
+}
+
+static int
+mlxsw_sp2_ipip_decap_config_gre6(struct mlxsw_sp *mlxsw_sp,
+                                struct mlxsw_sp_ipip_entry *ipip_entry,
+                                u32 tunnel_index)
+{
+       u16 rif_index = mlxsw_sp_ipip_lb_rif_index(ipip_entry->ol_lb);
+       u16 ul_rif_id = mlxsw_sp_ipip_lb_ul_rif_id(ipip_entry->ol_lb);
+       char rtdp_pl[MLXSW_REG_RTDP_LEN];
+       struct __ip6_tnl_parm parms;
+       unsigned int type_check;
+       bool has_ikey;
+       u32 ikey;
+
+       parms = mlxsw_sp_ipip_netdev_parms6(ipip_entry->ol_dev);
+       has_ikey = mlxsw_sp_ipip_parms6_has_ikey(&parms);
+       ikey = mlxsw_sp_ipip_parms6_ikey(&parms);
+
+       mlxsw_reg_rtdp_pack(rtdp_pl, MLXSW_REG_RTDP_TYPE_IPIP, tunnel_index);
+       mlxsw_reg_rtdp_egress_router_interface_set(rtdp_pl, ul_rif_id);
+
+       type_check = has_ikey ?
+               MLXSW_REG_RTDP_IPIP_TYPE_CHECK_ALLOW_GRE_KEY :
+               MLXSW_REG_RTDP_IPIP_TYPE_CHECK_ALLOW_GRE;
+
+       /* Linux demuxes tunnels based on packet SIP (which must match tunnel
+        * remote IP). Thus configure decap so that it filters out packets that
+        * are not IPv6 or have the wrong SIP. IPIP_DECAP_ERROR trap is
+        * generated for packets that fail this criterion. Linux then handles
+        * such packets in slow path and generates ICMP destination unreachable.
+        */
+       mlxsw_reg_rtdp_ipip6_pack(rtdp_pl, rif_index,
+                                 MLXSW_REG_RTDP_IPIP_SIP_CHECK_FILTER_IPV6,
+                                 type_check, has_ikey,
+                                 ipip_entry->dip_kvdl_index, ikey);
+
+       return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rtdp), rtdp_pl);
+}
+
+static bool mlxsw_sp2_ipip_can_offload_gre6(const struct mlxsw_sp *mlxsw_sp,
+                                           const struct net_device *ol_dev)
+{
+       struct __ip6_tnl_parm tparm = mlxsw_sp_ipip_netdev_parms6(ol_dev);
+       bool inherit_tos = tparm.flags & IP6_TNL_F_USE_ORIG_TCLASS;
+       bool inherit_ttl = tparm.hop_limit == 0;
+       __be16 okflags = TUNNEL_KEY; /* We can't offload any other features. */
+
+       return (tparm.i_flags & ~okflags) == 0 &&
+              (tparm.o_flags & ~okflags) == 0 &&
+              inherit_ttl && inherit_tos &&
+              mlxsw_sp_ipip_tunnel_complete(MLXSW_SP_L3_PROTO_IPV6, ol_dev);
+}
+
+static struct mlxsw_sp_rif_ipip_lb_config
+mlxsw_sp2_ipip_ol_loopback_config_gre6(struct mlxsw_sp *mlxsw_sp,
+                                      const struct net_device *ol_dev)
+{
+       struct __ip6_tnl_parm parms = mlxsw_sp_ipip_netdev_parms6(ol_dev);
+       enum mlxsw_reg_ritr_loopback_ipip_type lb_ipipt;
+
+       lb_ipipt = mlxsw_sp_ipip_parms6_has_okey(&parms) ?
+               MLXSW_REG_RITR_LOOPBACK_IPIP_TYPE_IP_IN_GRE_KEY_IN_IP :
+               MLXSW_REG_RITR_LOOPBACK_IPIP_TYPE_IP_IN_GRE_IN_IP;
+       return (struct mlxsw_sp_rif_ipip_lb_config){
+               .lb_ipipt = lb_ipipt,
+               .okey = mlxsw_sp_ipip_parms6_okey(&parms),
+               .ul_protocol = MLXSW_SP_L3_PROTO_IPV6,
+               .saddr = mlxsw_sp_ipip_netdev_saddr(MLXSW_SP_L3_PROTO_IPV6,
+                                                   ol_dev),
+       };
+}
+
+static int
+mlxsw_sp2_ipip_ol_netdev_change_gre6(struct mlxsw_sp *mlxsw_sp,
+                                    struct mlxsw_sp_ipip_entry *ipip_entry,
+                                    struct netlink_ext_ack *extack)
+{
+       struct mlxsw_sp_ipip_parms new_parms;
+
+       new_parms = mlxsw_sp2_ipip_netdev_parms_init_gre6(ipip_entry->ol_dev);
+       return mlxsw_sp_ipip_ol_netdev_change_gre(mlxsw_sp, ipip_entry,
+                                                 &new_parms, extack);
+}
+
+static int
+mlxsw_sp2_ipip_rem_addr_set_gre6(struct mlxsw_sp *mlxsw_sp,
+                                struct mlxsw_sp_ipip_entry *ipip_entry)
+{
+       char rips_pl[MLXSW_REG_RIPS_LEN];
+       struct __ip6_tnl_parm parms6;
+       int err;
+
+       err = mlxsw_sp_kvdl_alloc(mlxsw_sp,
+                                 MLXSW_SP_KVDL_ENTRY_TYPE_IPV6_ADDRESS, 1,
+                                 &ipip_entry->dip_kvdl_index);
+       if (err)
+               return err;
+
+       parms6 = mlxsw_sp_ipip_netdev_parms6(ipip_entry->ol_dev);
+       mlxsw_reg_rips_pack(rips_pl, ipip_entry->dip_kvdl_index,
+                           &parms6.raddr);
+       err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(rips), rips_pl);
+       if (err)
+               goto err_rips_write;
+
+       return 0;
+
+err_rips_write:
+       mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_IPV6_ADDRESS, 1,
+                          ipip_entry->dip_kvdl_index);
+       return err;
+}
+
+static void
+mlxsw_sp2_ipip_rem_addr_unset_gre6(struct mlxsw_sp *mlxsw_sp,
+                                  const struct mlxsw_sp_ipip_entry *ipip_entry)
+{
+       mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_IPV6_ADDRESS, 1,
+                          ipip_entry->dip_kvdl_index);
+}
+
+static const struct mlxsw_sp_ipip_ops mlxsw_sp2_ipip_gre6_ops = {
+       .dev_type = ARPHRD_IP6GRE,
+       .ul_proto = MLXSW_SP_L3_PROTO_IPV6,
+       .inc_parsing_depth = true,
+       .parms_init = mlxsw_sp2_ipip_netdev_parms_init_gre6,
+       .nexthop_update = mlxsw_sp2_ipip_nexthop_update_gre6,
+       .decap_config = mlxsw_sp2_ipip_decap_config_gre6,
+       .can_offload = mlxsw_sp2_ipip_can_offload_gre6,
+       .ol_loopback_config = mlxsw_sp2_ipip_ol_loopback_config_gre6,
+       .ol_netdev_change = mlxsw_sp2_ipip_ol_netdev_change_gre6,
+       .rem_ip_addr_set = mlxsw_sp2_ipip_rem_addr_set_gre6,
+       .rem_ip_addr_unset = mlxsw_sp2_ipip_rem_addr_unset_gre6,
+};
+
 const struct mlxsw_sp_ipip_ops *mlxsw_sp2_ipip_ops_arr[] = {
        [MLXSW_SP_IPIP_TYPE_GRE4] = &mlxsw_sp_ipip_gre4_ops,
-       [MLXSW_SP_IPIP_TYPE_GRE6] = &mlxsw_sp1_ipip_gre6_ops,
+       [MLXSW_SP_IPIP_TYPE_GRE6] = &mlxsw_sp2_ipip_gre6_ops,
 };
 
 static int mlxsw_sp_ipip_ecn_encap_init_one(struct mlxsw_sp *mlxsw_sp,
 {
        struct net *net = dev_net(ol_dev);
        struct ip_tunnel *tun4;
+       struct ip6_tnl *tun6;
 
        switch (ol_dev->type) {
        case ARPHRD_IPGRE:
                tun4 = netdev_priv(ol_dev);
                return dev_get_by_index_rcu(net, tun4->parms.link);
+       case ARPHRD_IP6GRE:
+               tun6 = netdev_priv(ol_dev);
+               return dev_get_by_index_rcu(net, tun6->parms.link);
        default:
                return NULL;
        }
 
        const struct mlxsw_sp_ipip_ops *ipip_ops;
        struct mlxsw_sp_ipip_entry *ipip_entry;
        struct mlxsw_sp_ipip_entry *ret = NULL;
+       int err;
 
        ipip_ops = mlxsw_sp->router->ipip_ops_arr[ipipt];
        ipip_entry = kzalloc(sizeof(*ipip_entry), GFP_KERNEL);
        ipip_entry->ipipt = ipipt;
        ipip_entry->ol_dev = ol_dev;
        ipip_entry->parms = ipip_ops->parms_init(ol_dev);
+
+       err = ipip_ops->rem_ip_addr_set(mlxsw_sp, ipip_entry);
+       if (err) {
+               ret = ERR_PTR(err);
+               goto err_rem_ip_addr_set;
+       }
+
        return ipip_entry;
 
+err_rem_ip_addr_set:
+       mlxsw_sp_rif_destroy(&ipip_entry->ol_lb->common);
 err_ol_ipip_lb_create:
        kfree(ipip_entry);
        return ret;
 }
 
-static void
-mlxsw_sp_ipip_entry_dealloc(struct mlxsw_sp_ipip_entry *ipip_entry)
+static void mlxsw_sp_ipip_entry_dealloc(struct mlxsw_sp *mlxsw_sp,
+                                       struct mlxsw_sp_ipip_entry *ipip_entry)
 {
+       const struct mlxsw_sp_ipip_ops *ipip_ops =
+               mlxsw_sp->router->ipip_ops_arr[ipip_entry->ipipt];
+
+       ipip_ops->rem_ip_addr_unset(mlxsw_sp, ipip_entry);
        mlxsw_sp_rif_destroy(&ipip_entry->ol_lb->common);
        kfree(ipip_entry);
 }
                saddr_len = 4;
                saddr_prefix_len = 32;
                break;
+       case MLXSW_SP_L3_PROTO_IPV6:
+               saddrp = &saddr.addr6;
+               saddr_len = 16;
+               saddr_prefix_len = 128;
+               break;
        default:
                WARN_ON(1);
                return NULL;
                            struct mlxsw_sp_ipip_entry *ipip_entry)
 {
        list_del(&ipip_entry->ipip_list_node);
-       mlxsw_sp_ipip_entry_dealloc(ipip_entry);
+       mlxsw_sp_ipip_entry_dealloc(mlxsw_sp, ipip_entry);
 }
 
 static bool
        struct mlxsw_sp_rif *rif = &lb_rif->common;
        struct mlxsw_sp *mlxsw_sp = rif->mlxsw_sp;
        char ritr_pl[MLXSW_REG_RITR_LEN];
+       struct in6_addr *saddr6;
        u32 saddr4;
 
        ipip_options = MLXSW_REG_RITR_LOOPBACK_IPIP_OPTIONS_GRE_KEY_PRESET;
                break;
 
        case MLXSW_SP_L3_PROTO_IPV6:
-               return -EAFNOSUPPORT;
+               saddr6 = &lb_cf.saddr.addr6;
+               mlxsw_reg_ritr_pack(ritr_pl, enable, MLXSW_REG_RITR_LOOPBACK_IF,
+                                   rif->rif_index, rif->vr_id, rif->dev->mtu);
+               mlxsw_reg_ritr_loopback_ipip6_pack(ritr_pl, lb_cf.lb_ipipt,
+                                                  ipip_options, ul_vr_id,
+                                                  ul_rif_id, saddr6,
+                                                  lb_cf.okey);
+               break;
        }
 
        return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ritr), ritr_pl);
        mlxsw_sp_nexthop6_group_update(mlxsw_sp, op_ctx, fib6_entry);
 }
 
-static void mlxsw_sp_fib6_entry_type_set(struct mlxsw_sp *mlxsw_sp,
-                                        struct mlxsw_sp_fib_entry *fib_entry,
-                                        const struct fib6_info *rt)
+static int
+mlxsw_sp_fib6_entry_type_set_local(struct mlxsw_sp *mlxsw_sp,
+                                  struct mlxsw_sp_fib_entry *fib_entry,
+                                  const struct fib6_info *rt)
+{
+       struct mlxsw_sp_nexthop_group_info *nhgi = fib_entry->nh_group->nhgi;
+       union mlxsw_sp_l3addr dip = { .addr6 = rt->fib6_dst.addr };
+       int ifindex = nhgi->nexthops[0].ifindex;
+       struct mlxsw_sp_ipip_entry *ipip_entry;
+
+       fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
+       ipip_entry = mlxsw_sp_ipip_entry_find_by_decap(mlxsw_sp, ifindex,
+                                                      MLXSW_SP_L3_PROTO_IPV6,
+                                                      dip);
+
+       if (ipip_entry && ipip_entry->ol_dev->flags & IFF_UP) {
+               fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_IPIP_DECAP;
+               return mlxsw_sp_fib_entry_decap_init(mlxsw_sp, fib_entry,
+                                                    ipip_entry);
+       }
+
+       return 0;
+}
+
+static int mlxsw_sp_fib6_entry_type_set(struct mlxsw_sp *mlxsw_sp,
+                                       struct mlxsw_sp_fib_entry *fib_entry,
+                                       const struct fib6_info *rt)
 {
-       if (rt->fib6_flags & (RTF_LOCAL | RTF_ANYCAST))
+       if (rt->fib6_flags & RTF_LOCAL)
+               return mlxsw_sp_fib6_entry_type_set_local(mlxsw_sp, fib_entry,
+                                                         rt);
+       if (rt->fib6_flags & RTF_ANYCAST)
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_TRAP;
        else if (rt->fib6_type == RTN_BLACKHOLE)
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_BLACKHOLE;
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_REMOTE;
        else
                fib_entry->type = MLXSW_SP_FIB_ENTRY_TYPE_LOCAL;
+
+       return 0;
 }
 
 static void
        if (err)
                goto err_nexthop_group_vr_link;
 
-       mlxsw_sp_fib6_entry_type_set(mlxsw_sp, fib_entry, rt_arr[0]);
+       err = mlxsw_sp_fib6_entry_type_set(mlxsw_sp, fib_entry, rt_arr[0]);
+       if (err)
+               goto err_fib6_entry_type_set;
 
        fib_entry->fib_node = fib_node;
 
        return fib6_entry;
 
+err_fib6_entry_type_set:
+       mlxsw_sp_nexthop_group_vr_unlink(fib_entry->nh_group, fib_node->fib);
 err_nexthop_group_vr_link:
        mlxsw_sp_nexthop6_group_put(mlxsw_sp, fib_entry);
 err_nexthop6_group_get:
        return ERR_PTR(err);
 }
 
+static void
+mlxsw_sp_fib6_entry_type_unset(struct mlxsw_sp *mlxsw_sp,
+                              struct mlxsw_sp_fib6_entry *fib6_entry)
+{
+       mlxsw_sp_fib_entry_type_unset(mlxsw_sp, &fib6_entry->common);
+}
+
 static void mlxsw_sp_fib6_entry_destroy(struct mlxsw_sp *mlxsw_sp,
                                        struct mlxsw_sp_fib6_entry *fib6_entry)
 {
        struct mlxsw_sp_fib_node *fib_node = fib6_entry->common.fib_node;
 
+       mlxsw_sp_fib6_entry_type_unset(mlxsw_sp, fib6_entry);
        mlxsw_sp_nexthop_group_vr_unlink(fib6_entry->common.nh_group,
                                         fib_node->fib);
        mlxsw_sp_nexthop6_group_put(mlxsw_sp, &fib6_entry->common);