PostgreSQL ignores implicit casts in some situations
The difference is this:
In the first two (working) examples, 'a' is just an untyped string literal - which is strictly different from varchar. The conversion to your custom enum type is provided by basic input/output functions of the enum type. (There is no explicit entry in the system catalog pg_cast.) Postgres determines the best match for a data type as the user did not provide one. Your custom cast is not even needed. Neither for type resolution nor for operator resolution.
In the following examples, you pass a typed value (varchar). Now, your custom cast may be of use. For example, this wouldn't work without your custom cast:
INSERT INTO t VALUES ('a'::varchar);

Your examples are not plain assignments, but expressions with the operator =. We are in the realm of Operator Type Resolution. Item 2a states:

If one argument of a binary operator invocation is of the unknown type, then assume it is the same type as the other argument for this check.

That would cover my_enum_column = 'foo' - with untyped literal.
But there is nothing that can be stretched to cover my_enum_column = 'foo'::varchar. Seems like the fact that only a generic = operator for left and right operand anyenum is available is not enough to clue in operator resolution:
-- missing operators:
SELECT oprleft::regtype, oprright::regtype, *
FROM   pg_operator
WHERE  oprname = '='
AND   ('anyenum'::regtype IN (oprleft, oprright) OR
       'my_enum'::regtype IN (oprleft, oprright));

 oprleft | oprright | ...
 --------+----------+----
 anyenum | anyenum  | ...
(1 row)

You would have to register a more explicit operator for this. But I wouldn't do that. I would do things like (also note various orthogonal syntax fixes):
PREPARE p(my_enum) AS SELECT * FROM t WHERE x = $1;  -- prepare with type my_enum
EXECUTE p('a'::varchar); -- then your cast kicks in
EXECUTE p('a'); -- untyped string literal works in any case

CREATE FUNCTION f1(ix my_enum, OUT ox my_enum)   -- again, declare enum
  RETURNS SETOF my_enum AS
$$
SELECT x FROM t WHERE x = ix
$$ LANGUAGE sql;

SELECT * FROM f1('a'::varchar);

CREATE FUNCTION f2(ix my_enum)  -- same here
  RETURNS SETOF t AS
$$
BEGIN
   RETURN QUERY
   SELECT * FROM t WHERE x = ix;
END;
$$ LANGUAGE plpgsql;

SELECT * FROM f2('a'::varchar);
SELECT * FROM f2('a');

db&lt;&gt;fiddle here
Or just add explicit casts. That's simplest and safest. The manual warns (on the same page you quoted):

It is wise to be conservative about marking casts as implicit. ...

There is more - recommended reading.
If you must create that custom cast (or even add an operator), consider using text instead of varchar, which is the &quot;preferred type&quot; among string types and hence more robust against possible corner-case issues.