How would i make it, that only device A can access the i2c register
addresses for a?
Use encapsulation. The device A register definitions should be limited to the scope of the device A driver.
Create a struct?
I don't really see how a struct is going to help you unless you need to keep a copy of the device's register values in RAM. A struct is useful for memory-mapped registers but these I2C devices are not memory-mapped (they're accessed via the I2C bus).
In the end i need to pass the device address (0x12) and the register
address (0xAF), to a function. This function should be able to handle
all 3 different i2c devices. I think something inheritance like would
work maybe in c++, but how would i do it easy, and clean, and in c ?
Yes, you want the function you describe but no, I wouldn't think of it as inheritance. Rather, think of it as different levels of abstraction layered upon one another. At a low level you have an I2C driver that implements the function you described. The low level I2C driver knows nothing about any particular I2C device that may be connected to the bus. Then at a layer above the I2C driver you have device drivers for each of the device types connected to the I2C bus. These device drivers know and encapsulate the details of their device's registers. And these device drivers call into the lower level I2C driver function. The higher level device drivers use the lower level I2C driver (they do not inherit the lower level I2C driver).
File i2c.h contains declarations for the low-level I2C driver's interface including functions like you described:
void I2CSetDeviceRegister(uint8_t device_address, uint8_t register_address, uint8_t register_value);
uint8_t I2CGetDeviceRegister(uint8_t device_address, uint8_t register_address);
File device_a.c includes i2c.h and defines encapsulated register definitions:
#include "i2c.h" // This device uses the lower level I2C driver
#define REGISTER_STATUS 0 // The scope of this register definition is encapsulated within device_a.c
File device_b.c includes i2c.h and defines encapsulated register definitions:
#include "i2c.h" // This device uses the lower level I2C driver
#define REGISTER_INT_MASK 0 // The scope of this register definition is encapsulated within device_b.c