mbox series

[0/5] i2c: RTL9300 support

Message ID 20240917232932.3641992-1-chris.packham@alliedtelesis.co.nz
Headers show
Series i2c: RTL9300 support | expand

Message

Chris Packham Sept. 17, 2024, 11:29 p.m. UTC
This builds on top of my in-flight series that adds the syscon node for the
switch block[1]. The I2C controllers are part of that block of registers. The
controller driver is adapted from openwrt, the multiplexing support is added by
me and differs from the openwrt implementation.

[1] - https://lore.kernel.org/lkml/20240913024948.1317786-1-chris.packham@alliedtelesis.co.nz/

Chris Packham (5):
  dt-bindings: i2c: Add RTL9300 I2C controller
  i2c: Add driver for the RTL9300 I2C controller
  mips: dts: realtek: Add I2C controllers
  dt-bindings: i2c: Add RTL9300 I2C multiplexer
  i2c: rtl9300: Add multiplexing support

 .../bindings/i2c/realtek,rtl9300-i2c-mux.yaml |  82 +++
 .../bindings/i2c/realtek,rtl9300-i2c.yaml     |  73 +++
 MAINTAINERS                                   |   8 +
 arch/mips/boot/dts/realtek/rtl930x.dtsi       |  18 +
 drivers/i2c/busses/Kconfig                    |  10 +
 drivers/i2c/busses/Makefile                   |   1 +
 drivers/i2c/busses/i2c-rtl9300.c              | 543 ++++++++++++++++++
 7 files changed, 735 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/i2c/realtek,rtl9300-i2c-mux.yaml
 create mode 100644 Documentation/devicetree/bindings/i2c/realtek,rtl9300-i2c.yaml
 create mode 100644 drivers/i2c/busses/i2c-rtl9300.c

Comments

Andi Shyti Sept. 18, 2024, 8:27 p.m. UTC | #1
Hi Chris,

On Wed, Sep 18, 2024 at 11:29:29AM GMT, Chris Packham wrote:
> Add support for the I2C controller on the RTL9300 SoC. This is based on
> the openwrt implementation[1] but cleaned up to make use of the regmap
> APIs.

Can you please add a few more words to describe the device?

> [1] - https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-5.15/drivers/i2c/busses/i2c-rtl9300.c
> 
> Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>

...

> +#define I2C_MST_CTRL1		0x0
> +#define  MEM_ADDR_OFS		8
> +#define  MEM_ADDR_MASK		0xffffff
> +#define  SDA_OUT_SEL_OFS	4
> +#define  SDA_OUT_SEL_MASK	0x7
> +#define  GPIO_SCL_SEL		BIT(3)
> +#define  RWOP			BIT(2)
> +#define  I2C_FAIL		BIT(1)
> +#define  I2C_TRIG		BIT(0)
> +#define I2C_MST_CTRL2		0x4
> +#define  RD_MODE		BIT(15)
> +#define  DEV_ADDR_OFS		8
> +#define  DEV_ADDR_MASK		0x7f
> +#define  DATA_WIDTH_OFS		4
> +#define  DATA_WIDTH_MASK	0xf
> +#define  MEM_ADDR_WIDTH_OFS	2
> +#define  MEM_ADDR_WIDTH_MASK	0x3

can we have these masked already shifted? You could use
GENMASK().

> +#define  SCL_FREQ_OFS		0
> +#define  SCL_FREQ_MASK		0x3
> +#define I2C_MST_DATA_WORD0	0x8
> +#define I2C_MST_DATA_WORD1	0xc
> +#define I2C_MST_DATA_WORD2	0x10
> +#define I2C_MST_DATA_WORD3	0x14

Can we use a prefix for all these defines?

> +
> +#define RTL9300_I2C_STD_FREQ		0
> +#define RTL9300_I2C_FAST_FREQ		1

This can also be an enum.

> +
> +DEFINE_MUTEX(i2c_lock);

...

> +static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
> +{
> +	u32 vals[4] = {};
> +	int i, ret;
> +
> +	if (len > 16)
> +		return -EIO;
> +
> +	for (i = 0; i < len; i++) {
> +		if (i % 4 == 0)
> +			vals[i/4] = 0;
> +		vals[i/4] <<= 8;
> +		vals[i/4] |= buf[i];
> +	}
> +
> +	ret = regmap_bulk_write(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_DATA_WORD0,
> +				vals, ARRAY_SIZE(vals));
> +	if (ret)
> +		return ret;
> +
> +	return len;

why returning "len"? And in any case this is ignored.

> +}
> +
> +static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
> +{
> +	int ret;
> +
> +	ret = regmap_write(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_DATA_WORD0, data);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

return regmap_write(...) ?

In any case, the returned value of these functions is completely
ignored, not even printed. Should we either:

 - condier the return value in the _xfer() functions
 or
 - make all these functions void?

> +}
> +
> +static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
> +				int size, union i2c_smbus_data *data, int len)
> +{
> +	u32 val, mask;
> +	int ret;
> +
> +	if (read_write == I2C_SMBUS_READ)
> +		val = 0;
> +	else
> +		val = RWOP;
> +	mask = RWOP;
> +
> +	val |= I2C_TRIG;
> +	mask |= I2C_TRIG;

how about "mask = RWOP | I2C_TRIG" to make it in one line?

Also val can be simplified as:

	val = I2C_TRIG;
	if (read_write == I2C_SMBUS_WRITE)
		val |= RWOP;

Not a binding commeent, as you wish.

> +
> +	ret = regmap_update_bits(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_CTRL1, mask, val);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read_poll_timeout(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_CTRL1,
> +				       val, !(val & I2C_TRIG), 100, 2000);
> +	if (ret)
> +		return ret;
> +
> +	if (val & I2C_FAIL)

where is val taking taking this bit?

> +		return -EIO;
> +

...

> +	switch (size) {
> +	case I2C_SMBUS_QUICK:
...
> +	case I2C_SMBUS_BYTE:
...
> +	case I2C_SMBUS_BYTE_DATA:
...
> +	case I2C_SMBUS_WORD_DATA:
...
> +	case I2C_SMBUS_BLOCK_DATA:
...
> +	default:
> +		dev_warn(&adap->dev, "Unsupported transaction %d\n", size);

dev_err() ?

> +		ret = -EOPNOTSUPP;
> +		goto out_unlock;
> +	}

...

> +	switch (clock_freq) {
> +	case I2C_MAX_STANDARD_MODE_FREQ:
...
> +	case I2C_MAX_FAST_MODE_FREQ:
...
> +	default:
> +		dev_warn(i2c->dev, "clock-frequency %d not supported\n", clock_freq);
> +		return -EINVAL;

If we are returning an error we should print an error, let's make
it a "return dev_err_probe()"

But, I was thinking that by default we can assign
I2C_MAX_STANDARD_MODE_FREQ and if the DTS defines a different
frequency we could just print an error and stick to the default
value. Makes sense?

> +	}

...

> +	return i2c_add_adapter(adap);

return devm_i2c_add_adapter(adap);

and the remove function is not needed.

> +}
> +
> +static void rtl9300_i2c_remove(struct platform_device *pdev)
> +{
> +	struct rtl9300_i2c *i2c = platform_get_drvdata(pdev);
> +
> +	i2c_del_adapter(&i2c->adap);
> +}
> +
> +static const struct of_device_id i2c_rtl9300_dt_ids[] = {
> +	{ .compatible = "realtek,rtl9300-i2c" },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
> +
> +static struct platform_driver rtl9300_i2c_driver = {
> +	.probe = rtl9300_i2c_probe,
> +	.remove = rtl9300_i2c_remove,
> +	.driver = {
> +		.name = "i2c-rtl9300",
> +		.of_match_table = i2c_rtl9300_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(rtl9300_i2c_driver);
> +
> +MODULE_DESCRIPTION("RTL9300 I2C controller driver");
> +MODULE_LICENSE("GPL");
> +

Just a trailing blank line here.

Thanks,
Andi
Andi Shyti Sept. 18, 2024, 8:36 p.m. UTC | #2
Hi Chris,

...

> -module_platform_driver(rtl9300_i2c_driver);
> +static int rtl9300_i2c_select_chan(struct i2c_mux_core *muxc, u32 chan)
> +{
> +	struct i2c_adapter *adap = muxc->parent;
> +	struct rtl9300_i2c *i2c = i2c_get_adapdata(adap);
> +	int ret;
> +
> +	ret = rtl9300_i2c_config_io(i2c, chan);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

return "rtl9300_i2c_config_io()"?

> +}

...

> +static int rtl9300_i2c_mux_probe_fw(struct rtl9300_i2c_chan *mux, struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct fwnode_handle *fwnode = dev_fwnode(dev);
> +	struct device_node *np = dev->of_node;
> +	struct device_node *adap_np;
> +	struct i2c_adapter *adap = NULL;
> +	struct fwnode_handle *child;
> +	unsigned int *chans;
> +	int i = 0;
> +
> +	if (!is_of_node(fwnode))
> +		return -EOPNOTSUPP;
> +
> +	if (!np)
> +		return -ENODEV;
> +
> +	adap_np = of_parse_phandle(np, "i2c-parent", 0);
> +	if (!adap_np) {
> +		dev_err(&pdev->dev, "Cannot parse i2c-parent\n");
> +		return -ENODEV;

return dev_err_probe(...)?

> +	}
> +	adap = of_find_i2c_adapter_by_node(adap_np);
> +	of_node_put(adap_np);

...

> +static int __init rtl9300_i2c_init(void)
> +{
> +	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
> +}
> +module_init(rtl9300_i2c_init);
> +
> +static void __exit rtl9300_i2c_exit(void)
> +{
> +	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
> +}
> +module_exit(rtl9300_i2c_exit);

You could use module_platform_driver()

Thanks,
Andi

>  
>  MODULE_DESCRIPTION("RTL9300 I2C controller driver");
>  MODULE_LICENSE("GPL");
> -- 
> 2.46.1
>
Chris Packham Sept. 18, 2024, 9:18 p.m. UTC | #3
Hi Andy,

On 19/09/24 08:27, Andi Shyti wrote:
> Hi Chris,
>
> On Wed, Sep 18, 2024 at 11:29:29AM GMT, Chris Packham wrote:
>> Add support for the I2C controller on the RTL9300 SoC. This is based on
>> the openwrt implementation[1] but cleaned up to make use of the regmap
>> APIs.
> Can you please add a few more words to describe the device?

Sure will do.

>> [1] - https://git.openwrt.org/?p=openwrt/openwrt.git;a=blob;f=target/linux/realtek/files-5.15/drivers/i2c/busses/i2c-rtl9300.c
>>
>> Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
> ...
>
>> +#define I2C_MST_CTRL1		0x0
>> +#define  MEM_ADDR_OFS		8
>> +#define  MEM_ADDR_MASK		0xffffff
>> +#define  SDA_OUT_SEL_OFS	4
>> +#define  SDA_OUT_SEL_MASK	0x7
>> +#define  GPIO_SCL_SEL		BIT(3)
>> +#define  RWOP			BIT(2)
>> +#define  I2C_FAIL		BIT(1)
>> +#define  I2C_TRIG		BIT(0)
>> +#define I2C_MST_CTRL2		0x4
>> +#define  RD_MODE		BIT(15)
>> +#define  DEV_ADDR_OFS		8
>> +#define  DEV_ADDR_MASK		0x7f
>> +#define  DATA_WIDTH_OFS		4
>> +#define  DATA_WIDTH_MASK	0xf
>> +#define  MEM_ADDR_WIDTH_OFS	2
>> +#define  MEM_ADDR_WIDTH_MASK	0x3
> can we have these masked already shifted? You could use
> GENMASK().

I'll take a look.

>> +#define  SCL_FREQ_OFS		0
>> +#define  SCL_FREQ_MASK		0x3
>> +#define I2C_MST_DATA_WORD0	0x8
>> +#define I2C_MST_DATA_WORD1	0xc
>> +#define I2C_MST_DATA_WORD2	0x10
>> +#define I2C_MST_DATA_WORD3	0x14
> Can we use a prefix for all these defines?

Yes will add "RTL9300_".

I assume for the bit values too? So something like "MEM_ADDR_OFS" 
becomes "RTL9300_I2C_MST_CTRL1_MEM_ADDR_OFS" is that OK or too verbose?

>> +
>> +#define RTL9300_I2C_STD_FREQ		0
>> +#define RTL9300_I2C_FAST_FREQ		1
> This can also be an enum.
Ack
>
>> +
>> +DEFINE_MUTEX(i2c_lock);
> ...
>
>> +static int rtl9300_i2c_write(struct rtl9300_i2c *i2c, u8 *buf, int len)
>> +{
>> +	u32 vals[4] = {};
>> +	int i, ret;
>> +
>> +	if (len > 16)
>> +		return -EIO;
>> +
>> +	for (i = 0; i < len; i++) {
>> +		if (i % 4 == 0)
>> +			vals[i/4] = 0;
>> +		vals[i/4] <<= 8;
>> +		vals[i/4] |= buf[i];
>> +	}
>> +
>> +	ret = regmap_bulk_write(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_DATA_WORD0,
>> +				vals, ARRAY_SIZE(vals));
>> +	if (ret)
>> +		return ret;
>> +
>> +	return len;
> why returning "len"? And in any case this is ignored.
I copied that behaviour from the openwrt driver. I think making it the 
same as the other functions would make more sense.
>
>> +}
>> +
>> +static int rtl9300_i2c_writel(struct rtl9300_i2c *i2c, u32 data)
>> +{
>> +	int ret;
>> +
>> +	ret = regmap_write(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_DATA_WORD0, data);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
> return regmap_write(...) ?
>
> In any case, the returned value of these functions is completely
> ignored, not even printed. Should we either:
>
>   - condier the return value in the _xfer() functions
>   or
>   - make all these functions void?

I suppose it's a bit academic. Under the hood it's mmio so it's not as 
if it can really fail (famous last words). That said, this switch chip 
can be run in a core disabled mode and you could then in theory be doing 
I2C over SPI from an external SoC. If someone were just naively updating 
a hardware design to add the external SoC they might neglect to move the 
I2C connections. It's also just good practice so I'll propagate the 
returns up to the _xfer().

>> +}
>> +
>> +static int rtl9300_i2c_execute_xfer(struct rtl9300_i2c *i2c, char read_write,
>> +				int size, union i2c_smbus_data *data, int len)
>> +{
>> +	u32 val, mask;
>> +	int ret;
>> +
>> +	if (read_write == I2C_SMBUS_READ)
>> +		val = 0;
>> +	else
>> +		val = RWOP;
>> +	mask = RWOP;
>> +
>> +	val |= I2C_TRIG;
>> +	mask |= I2C_TRIG;
> how about "mask = RWOP | I2C_TRIG" to make it in one line?
>
> Also val can be simplified as:
>
> 	val = I2C_TRIG;
> 	if (read_write == I2C_SMBUS_WRITE)
> 		val |= RWOP;
>
> Not a binding commeent, as you wish.

I'll take a look. I kind of did like the pairing of val and mask for 
each thing being set.

>> +
>> +	ret = regmap_update_bits(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_CTRL1, mask, val);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = regmap_read_poll_timeout(i2c->regmap, i2c->i2c_mst_ofs + I2C_MST_CTRL1,
>> +				       val, !(val & I2C_TRIG), 100, 2000);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (val & I2C_FAIL)
> where is val taking taking this bit?
In the regmap_read_poll_timeout().
>
>> +		return -EIO;
>> +
> ...
>
>> +	switch (size) {
>> +	case I2C_SMBUS_QUICK:
> ...
>> +	case I2C_SMBUS_BYTE:
> ...
>> +	case I2C_SMBUS_BYTE_DATA:
> ...
>> +	case I2C_SMBUS_WORD_DATA:
> ...
>> +	case I2C_SMBUS_BLOCK_DATA:
> ...
>> +	default:
>> +		dev_warn(&adap->dev, "Unsupported transaction %d\n", size);
> dev_err() ?
Ack.
>> +		ret = -EOPNOTSUPP;
>> +		goto out_unlock;
>> +	}
> ...
>
>> +	switch (clock_freq) {
>> +	case I2C_MAX_STANDARD_MODE_FREQ:
> ...
>> +	case I2C_MAX_FAST_MODE_FREQ:
> ...
>> +	default:
>> +		dev_warn(i2c->dev, "clock-frequency %d not supported\n", clock_freq);
>> +		return -EINVAL;
> If we are returning an error we should print an error, let's make
> it a "return dev_err_probe()"
>
> But, I was thinking that by default we can assign
> I2C_MAX_STANDARD_MODE_FREQ and if the DTS defines a different
> frequency we could just print an error and stick to the default
> value. Makes sense?

I don't have a strong opinion. Failing the probe just because something 
in the dts is wrong where we can have a sane default does seem overly 
harsh. On the other hand I've had hardware QA folks complain when the 
I2C bus is running at 98khz instead of 100khz.

>
>> +	}
> ...
>
>> +	return i2c_add_adapter(adap);
> return devm_i2c_add_adapter(adap);
>
> and the remove function is not needed.

OK thanks. I did look for a devm variant but obviously not hard enough.

>> +}
>> +
>> +static void rtl9300_i2c_remove(struct platform_device *pdev)
>> +{
>> +	struct rtl9300_i2c *i2c = platform_get_drvdata(pdev);
>> +
>> +	i2c_del_adapter(&i2c->adap);
>> +}
>> +
>> +static const struct of_device_id i2c_rtl9300_dt_ids[] = {
>> +	{ .compatible = "realtek,rtl9300-i2c" },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, i2c_rtl9300_dt_ids);
>> +
>> +static struct platform_driver rtl9300_i2c_driver = {
>> +	.probe = rtl9300_i2c_probe,
>> +	.remove = rtl9300_i2c_remove,
>> +	.driver = {
>> +		.name = "i2c-rtl9300",
>> +		.of_match_table = i2c_rtl9300_dt_ids,
>> +	},
>> +};
>> +
>> +module_platform_driver(rtl9300_i2c_driver);
>> +
>> +MODULE_DESCRIPTION("RTL9300 I2C controller driver");
>> +MODULE_LICENSE("GPL");
>> +
> Just a trailing blank line here.
Ack.
>
> Thanks,
> Andi
>
Chris Packham Sept. 18, 2024, 9:44 p.m. UTC | #4
Hi Andi, Rob,

On 19/09/24 08:36, Andi Shyti wrote:
> Hi Chris,
>
> ...
>
>> -module_platform_driver(rtl9300_i2c_driver);
>> +static int rtl9300_i2c_select_chan(struct i2c_mux_core *muxc, u32 chan)
>> +{
>> +	struct i2c_adapter *adap = muxc->parent;
>> +	struct rtl9300_i2c *i2c = i2c_get_adapdata(adap);
>> +	int ret;
>> +
>> +	ret = rtl9300_i2c_config_io(i2c, chan);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
> return "rtl9300_i2c_config_io()"?

Ack.

>> +}
> ...
>
>> +static int rtl9300_i2c_mux_probe_fw(struct rtl9300_i2c_chan *mux, struct platform_device *pdev)
>> +{
>> +	struct device *dev = &pdev->dev;
>> +	struct fwnode_handle *fwnode = dev_fwnode(dev);
>> +	struct device_node *np = dev->of_node;
>> +	struct device_node *adap_np;
>> +	struct i2c_adapter *adap = NULL;
>> +	struct fwnode_handle *child;
>> +	unsigned int *chans;
>> +	int i = 0;
>> +
>> +	if (!is_of_node(fwnode))
>> +		return -EOPNOTSUPP;
>> +
>> +	if (!np)
>> +		return -ENODEV;
>> +
>> +	adap_np = of_parse_phandle(np, "i2c-parent", 0);
>> +	if (!adap_np) {
>> +		dev_err(&pdev->dev, "Cannot parse i2c-parent\n");
>> +		return -ENODEV;
> return dev_err_probe(...)?

Ack.

>> +	}
>> +	adap = of_find_i2c_adapter_by_node(adap_np);
>> +	of_node_put(adap_np);
> ...
>
>> +static int __init rtl9300_i2c_init(void)
>> +{
>> +	return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
>> +}
>> +module_init(rtl9300_i2c_init);
>> +
>> +static void __exit rtl9300_i2c_exit(void)
>> +{
>> +	platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
>> +}
>> +module_exit(rtl9300_i2c_exit);
> You could use module_platform_driver()

Can I though? I want to support both the simple I2C controller and the 
MUX mode with the same driver. Which is why I've ended up with two 
drivers to register.

On the binding patch, Rob made the suggestion that I just make the 
i2c-mux part of the parent. I did consider that but quickly got tied in 
knots because I couldn't figure out how to have a device that is both an 
adapter and a mux. The main problem was that any child nodes of an i2c 
adapter in the device tree are presumed to be I2C devices and get probed 
automatically by of_i2c_register_devices(). Equally I can't register a 
mux without having an adapter that the mux operates over.

>
> Thanks,
> Andi
>
>>   
>>   MODULE_DESCRIPTION("RTL9300 I2C controller driver");
>>   MODULE_LICENSE("GPL");
>> -- 
>> 2.46.1
>>
Chris Packham Sept. 19, 2024, 5:06 a.m. UTC | #5
On 19/09/24 09:44, Chris Packham wrote:
> Hi Andi, Rob,
>
> On 19/09/24 08:36, Andi Shyti wrote:
>> Hi Chris,
>>
>> ...
>>
>>> -module_platform_driver(rtl9300_i2c_driver);
>>> +static int rtl9300_i2c_select_chan(struct i2c_mux_core *muxc, u32 
>>> chan)
>>> +{
>>> +    struct i2c_adapter *adap = muxc->parent;
>>> +    struct rtl9300_i2c *i2c = i2c_get_adapdata(adap);
>>> +    int ret;
>>> +
>>> +    ret = rtl9300_i2c_config_io(i2c, chan);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>> +    return 0;
>> return "rtl9300_i2c_config_io()"?
>
> Ack.
>
>>> +}
>> ...
>>
>>> +static int rtl9300_i2c_mux_probe_fw(struct rtl9300_i2c_chan *mux, 
>>> struct platform_device *pdev)
>>> +{
>>> +    struct device *dev = &pdev->dev;
>>> +    struct fwnode_handle *fwnode = dev_fwnode(dev);
>>> +    struct device_node *np = dev->of_node;
>>> +    struct device_node *adap_np;
>>> +    struct i2c_adapter *adap = NULL;
>>> +    struct fwnode_handle *child;
>>> +    unsigned int *chans;
>>> +    int i = 0;
>>> +
>>> +    if (!is_of_node(fwnode))
>>> +        return -EOPNOTSUPP;
>>> +
>>> +    if (!np)
>>> +        return -ENODEV;
>>> +
>>> +    adap_np = of_parse_phandle(np, "i2c-parent", 0);
>>> +    if (!adap_np) {
>>> +        dev_err(&pdev->dev, "Cannot parse i2c-parent\n");
>>> +        return -ENODEV;
>> return dev_err_probe(...)?
>
> Ack.
>
>>> +    }
>>> +    adap = of_find_i2c_adapter_by_node(adap_np);
>>> +    of_node_put(adap_np);
>> ...
>>
>>> +static int __init rtl9300_i2c_init(void)
>>> +{
>>> +    return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
>>> +}
>>> +module_init(rtl9300_i2c_init);
>>> +
>>> +static void __exit rtl9300_i2c_exit(void)
>>> +{
>>> +    platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
>>> +}
>>> +module_exit(rtl9300_i2c_exit);
>> You could use module_platform_driver()
>
> Can I though? I want to support both the simple I2C controller and the 
> MUX mode with the same driver. Which is why I've ended up with two 
> drivers to register.
>
> On the binding patch, Rob made the suggestion that I just make the 
> i2c-mux part of the parent. I did consider that but quickly got tied 
> in knots because I couldn't figure out how to have a device that is 
> both an adapter and a mux. The main problem was that any child nodes 
> of an i2c adapter in the device tree are presumed to be I2C devices 
> and get probed automatically by of_i2c_register_devices(). Equally I 
> can't register a mux without having an adapter that the mux operates 
> over.

OK I think I've got something working that has a dt binding like

                 i2c@36c {
                         compatible = "realtek,rtl9300-i2c";
                         reg = <0x36c 0x14>;
                         status = "okay";
                         #address-cells = <0x01>;
                         #size-cells = <0x00>;

                         i2c@0 {
                                 reg = <0x00>;
                                 #address-cells = <1>;
                                 #size-cells = <1>;
                                 gpio@20 {
                                     ...
                                 };
                         };

                         i2c@2 {
                                 reg = <0x02>;
                         };
                 };

In the probe() I can iterate over the child nodes and create an adapter 
for each. The code is a bit fiddly but I think it's a net win if I can 
do away with the rtl9300-i2c-mux part. It also happily means that I 
don't have an extra I2C bus that is the same as the first mux channel.

I'll try an tidy things up and get another iteration out before my weekend.


>
>>
>> Thanks,
>> Andi
>>
>>>     MODULE_DESCRIPTION("RTL9300 I2C controller driver");
>>>   MODULE_LICENSE("GPL");
>>> -- 
>>> 2.46.1
>>>