Q:

Why can't we dereference constant integers?

In an effort to understand pointers when working with embedded systems, I am trying to understand what is happening in this code that is accessing a memory mapped register:
uint32_t regval = *(uint32_t *)0x40048004U;

In my mind, without the type casting it means that regval is now equal to the memory contents of the address. I imagine that should be enough but it isn't. But when type casting it, you are treating it like a pointer for a single operation, which seems a bit redundant.
So you are treating regval like the memory contents of an unknown address that points to the memory contents of 0x40048004U? Why is the type casting even necessary?

A:

I understand you're asking why we can't just do this:
uint32_t register_value = *0x40048004U;

(i.e. "why can't we dereference constant integers?").
There's a few answers:

C's syntax for integer constants does not have any annotation for pointer values, only signed/unsigned/short/long values (using U, L, etc suffices).
If you could do *0x40048004U there is no indication of the data-type of the pointer - how many bytes is the computer meant to read from the address? Is it signed? Unsigned? (remember, the type of the constant is concerned with the type of memory address, not the value it points to)
So by requiring the use of an explicit cast operator *(valueType*)0xDEADBEFF means that the type can be specified using the same syntax as a cast of a non-constant value - without needing to introduce a new separate syntax, and as this kind of cast is a static operation there's zero runtime performance hit.

A:

C was designed in a time when memory was small, and thousands of PhD's had not been issued for compiler type inferencing.
While it's possible that in this particular case the compiler might infer that 
uint32_t regval = *0x40048004U;

was supposed to be a dereference of an implied uint32_t pointer, as opposed to a missing parameter in a multiply operation, the reality is that there are cases where it might fail (for example, int x = 0x00->foo when there are multiple structures having members named foo).
So instead, the requirement was laid down that everything has a type, and if you want to use something as though it were a different type, you have to make that explicit. And it's been that way for 45 years. Fnord.

A:

The pointer-indirection operator * can be applied only to pointers.  Its action, of course, is to access the data object pointed to by the pointer.  But it has to know the size and type of the pointed-to object.
If you just said
uint32_t regval = *0x40048004;

the compiler would have no way to know the size and type.  (You might think it's implicit in the fact that you're assigning to a uint32_2 on the left hand side, but C doesn't work that way.)
If you want something that "remembers" the type, just create and initialize a pointer variable:
uint32_t *regptr = (uint32_t *)0x40048004;
uint32_t regval = *regptr;

