You could use a custom shader and a second texture to hold the UV data.
Here’s is how I think this could work:
One texture would be the texture atlas that contain the tiles (‘TileAtlas’).
The second will hold the TileMap.
You render a single quad with UVs from (0,0) to (1,1),
In the shader first you sample the TileMap (PointClamp), the four components would be a translation (.rg) + scale (.ba).
you take the UV modulo TileMap.Size, apply translation + scale to get the final UV for TileAtlas.
If all tiles in the atlas are the same size , then you can hardcode the scale factor in the shader and use the remaining 2 components (.ba) for a second tile layer.