First method: Unevaluated
------------

Wrap the argument you want to pass by reference with `Unevaluated`

    flip[posX_, posY_, lattice_] := 
      Module[{x = posX, y = posY}, 
       lattice = ReplacePart[lattice, {y, x} -> -valueAtPos[x, y, lattice]] //. {{1} :> 
           1, {-1} :> -1}
       ];
    valueAtPos[positionX_, positionY_, lattice_List] := 
     Flatten[Take[lattice, {positionY, positionY}, {positionX, positionX}]]
    s = {{1, 1, 1, -1}, {1, 1, -1, -1}, {-1, -1, 1, -1}, {-1, 1, -1, 1}};

Now

    flip[1, 1, Unevaluated[s]]
    s
    (*  {{-1, 1, 1, -1}, {1, 1, -1, -1}, {-1, -1, 1, -1}, {-1, 1, -1, 1}}  *)

Disadvantages: You lose the ability to add type checking (as in adding `lattice_List` in the formal parameter, must write it as `lattice` only )

Second method: HoldAll
-------------

Make the function itself HoldAll

    ClearAll[flip];
    flip[posX_, posY_, lattice_] := 
      Module[{x = posX, y = posY}, 
       lattice = 
        ReplacePart[lattice, {y, x} -> -valueAtPos[x, y, lattice]] //. {{1} :> 
           1, {-1} :> -1}
       ];
    Attributes[flip] = {HoldAll};
    valueAtPos[positionX_, positionY_, lattice_List] := 
     Flatten[Take[lattice, {positionY, positionY}, {positionX, positionX}]]
    s = {{1, 1, 1, -1}, {1, 1, -1, -1}, {-1, -1, 1, -1}, {-1, 1, -1, 1}};

now

    flip[1, 1, s];
    s
    (* {{-1, 1, 1, -1}, {1, 1, -1, -1}, {-1, -1, 1, -1}, {-1, 1, -1, 1}} *)

Third method  HoldFirst
------------

Move the one argument to be passed by reference to first argument and use `HoldFirst`

    ClearAll[flip];
    flip[lattice_, posX_, posY_] := 
      Module[{x = posX, y = posY}, 
       lattice = 
        ReplacePart[
          lattice, {y, x} -> -valueAtPos[x, y, lattice]] //. {{1} :> 
           1, {-1} :> -1}
       ];
    Attributes[flip] = HoldFirst;
    valueAtPos[positionX_, positionY_, lattice_List] := 
     Flatten[Take[lattice, {positionY, positionY}, {positionX, positionX}]]
    s = {{1, 1, 1, -1}, {1, 1, -1, -1}, {-1, -1, 1, -1}, {-1, 1, -1, 1}};

gives

    flip[s, 1, 1];
    s
    (* {{-1, 1, 1, -1}, {1, 1, -1, -1}, {-1, -1, 1, -1}, {-1, 1, -1, 1}} *)

But I think, in the spirit of Mathematica programming and functional programming, is that one should not do these things. i.e. functions should not have side-effects. To modify something, write `newValue = foo[ oldValue ]` and pass things by value, which is the default.