devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Lee Jones <lee.jones@linaro.org>
To: Daniel Mack <daniel@zonque.org>
Cc: linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org,
	linux-i2c@vger.kernel.org, alsa-devel@alsa-project.org,
	devicetree@vger.kernel.org, linux-clk@vger.kernel.org,
	mturquette@baylibre.com, sboyd@kernel.org, robh+dt@kernel.org,
	broonie@kernel.org, lars@metafoo.de, pascal.huerst@gmail.com
Subject: Re: [PATCH 06/10] mfd: Add core driver for AD242x A2B transceivers
Date: Tue, 17 Dec 2019 13:39:52 +0000	[thread overview]
Message-ID: <20191217133952.GJ18955@dell> (raw)
In-Reply-To: <20191209183511.3576038-8-daniel@zonque.org>

On Mon, 09 Dec 2019, Daniel Mack wrote:

> The core driver for these devices is split into several parts.
> 
> The master node driver is an I2C client. It is responsible for
> bringing up the bus topology and discovering the slave nodes.
> This process requries some knowlegde of the slave node configuration
> to program the bus timings correctly, so the master drivers walks
> the tree of nodes in the devicetree. The slave driver handles platform
> devices that are instantiated by the master node driver after
> discovery has finished.
> 
> Master nodes expose two addresses on the I2C bus, one (referred to as
> 'BASE' in the datasheet) for accessing registers on the transceiver
> node itself, and one (referred to as 'BUS') for accessing remote
> registers, either on the remote transceiver itself, or on I2C hardware
> connected to that remote transceiver, which then acts as a remote I2C
> bus master.
> 
> In order to allow MFD sub-devices to be registered as children of
> either the master or any slave node, the details on how to access the
> registers are hidden behind a regmap config. A pointer to the regmap
> is then exposed in the struct shared with the sub-devices.
> 
> The ad242x-bus driver is a simple proxy that occupies the BUS I2C
> address and which is referred to through a devicetree handle by the
> master driver.
> 
> For the discovery process, the driver has to wait for an interrupt
> to occur. In case no interrupt is configured in DT, the driver falls
> back to interrupt polling. After the discovery phase is completed,
> interrupts are only needed for error handling and GPIO handling,
> both of which is not currenty implemented.
> 
> Code common to both the master and the slave driver lives in
> 'ad242x-node.c'.
> 
> Signed-off-by: Daniel Mack <daniel@zonque.org>
> 
> mfd

?

> ---
>  drivers/mfd/Kconfig         |  11 +
>  drivers/mfd/Makefile        |   1 +
>  drivers/mfd/ad242x-bus.c    |  42 +++
>  drivers/mfd/ad242x-master.c | 611 ++++++++++++++++++++++++++++++++++++
>  drivers/mfd/ad242x-node.c   | 262 ++++++++++++++++
>  drivers/mfd/ad242x-slave.c  | 234 ++++++++++++++
>  include/linux/mfd/ad242x.h  | 400 +++++++++++++++++++++++

This device, or at least the way it's been coded is batty!

It's going to need a lot of massaging before being accepted.

Let's start with a quick (it's taken 2 hours already!) glance.

See below ...

>  7 files changed, 1561 insertions(+)
>  create mode 100644 drivers/mfd/ad242x-bus.c
>  create mode 100644 drivers/mfd/ad242x-master.c
>  create mode 100644 drivers/mfd/ad242x-node.c
>  create mode 100644 drivers/mfd/ad242x-slave.c
>  create mode 100644 include/linux/mfd/ad242x.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 420900852166..727a35053d8c 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -99,6 +99,17 @@ config PMIC_ADP5520
>  	  individual components like LCD backlight, LEDs, GPIOs and Kepad
>  	  under the corresponding menus.
>  
> +config MFD_AD242X
> +	bool "Analog Devices AD242x A2B support"
> +	select MFD_CORE
> +	select REGMAP_I2C
> +	depends on I2C=y && OF
> +	help
> +	  If you say yes here, you get support for devices from the AD242x
> +	  familiy. This driver provides common support for accessing the
> +	  devices, additional drivers must be enabled in order to use the
> +	  functionality of the devices.
> +
>  config MFD_AAT2870_CORE
>  	bool "AnalogicTech AAT2870"
>  	select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index aed99f08739f..2361c676f6c8 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -203,6 +203,7 @@ obj-$(CONFIG_MFD_SPMI_PMIC)	+= qcom-spmi-pmic.o
>  obj-$(CONFIG_TPS65911_COMPARATOR)	+= tps65911-comparator.o
>  obj-$(CONFIG_MFD_TPS65090)	+= tps65090.o
>  obj-$(CONFIG_MFD_AAT2870_CORE)	+= aat2870-core.o
> +obj-$(CONFIG_MFD_AD242X)	+= ad242x-master.o ad242x-slave.o ad242x-bus.o ad242x-node.o
>  obj-$(CONFIG_MFD_AT91_USART)	+= at91-usart.o
>  obj-$(CONFIG_MFD_ATMEL_FLEXCOM)	+= atmel-flexcom.o
>  obj-$(CONFIG_MFD_ATMEL_HLCDC)	+= atmel-hlcdc.o
> diff --git a/drivers/mfd/ad242x-bus.c b/drivers/mfd/ad242x-bus.c
> new file mode 100644
> index 000000000000..6660e13ce43d
> --- /dev/null
> +++ b/drivers/mfd/ad242x-bus.c
> @@ -0,0 +1,42 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/mfd/ad242x.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +
> +static int ad242x_bus_i2c_probe(struct i2c_client *i2c,
> +				const struct i2c_device_id *id)
> +{
> +	dev_set_drvdata(&i2c->dev, i2c);
> +	i2c_set_clientdata(i2c, &i2c->dev);

Please explain to me what you think is happening here.

> +	return 0;
> +}

What does this driver do?  Seems kinda pointless?

> +static const struct of_device_id ad242x_bus_of_match[] = {
> +	{ .compatible = "adi,ad2428w-bus" },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(of, ad242x_bus_of_match);
> +
> +static const struct i2c_device_id ad242x_bus_i2c_id[] = {
> +	{ "ad242x_bus", 0 },
> +	{ },
> +};
> +MODULE_DEVICE_TABLE(i2c, ad242x_bus_i2c_id);

This table cam be removed if you use probe_new.

> +static struct i2c_driver ad242x_bus_i2c_driver = {
> +	.driver = {
> +		.name = "ad242x-bus",
> +		.of_match_table = ad242x_bus_of_match,
> +	},
> +	.probe = ad242x_bus_i2c_probe,
> +	.id_table = ad242x_bus_i2c_id,
> +};
> +
> +module_i2c_driver(ad242x_bus_i2c_driver);
> +
> +MODULE_DESCRIPTION("AD242x bus driver");
> +MODULE_AUTHOR("Daniel Mack <daniel@zonque.org>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mfd/ad242x-master.c b/drivers/mfd/ad242x-master.c
> new file mode 100644
> index 000000000000..1b0bf90442a2
> --- /dev/null
> +++ b/drivers/mfd/ad242x-master.c
> @@ -0,0 +1,611 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/mfd/ad242x.h>
> +#include <linux/mfd/core.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +
> +struct ad242x_master {
> +	struct ad242x_node	node;
> +	struct clk		*sync_clk;
> +	struct completion	run_completion;
> +	struct completion	discover_completion;
> +	struct ad242x_i2c_bus	bus;
> +	unsigned int		up_slot_size;
> +	unsigned int		dn_slot_size;
> +	bool			up_slot_alt_fmt;
> +	bool			dn_slot_alt_fmt;
> +	unsigned int		sync_clk_rate;
> +	int			irq;
> +	u8			response_cycles;
> +};

> +struct ad242x_node *ad242x_master_get_node(struct ad242x_master *master)
> +{
> +	return &master->node;
> +}
> +EXPORT_SYMBOL_GPL(ad242x_master_get_node);
> +
> +struct ad242x_i2c_bus *ad242x_master_get_bus(struct ad242x_master *master)
> +{
> +	return &master->bus;
> +}
> +EXPORT_SYMBOL_GPL(ad242x_master_get_bus);
> +
> +const char *ad242x_master_get_clk_name(struct ad242x_master *master)
> +{
> +	return __clk_get_name(master->sync_clk);
> +}
> +EXPORT_SYMBOL_GPL(ad242x_master_get_clk_name);
> +
> +unsigned int ad242x_master_get_clk_rate(struct ad242x_master *master)
> +{
> +	return master->sync_clk_rate;
> +}
> +EXPORT_SYMBOL_GPL(ad242x_master_get_clk_rate);

All of these functions provide abstraction for the sake of
abstraction.  They should be removed and replaced with the code
contained within them.

> +static int ad242x_read_one_irq(struct ad242x_master *master)
> +{
> +	struct regmap *regmap = master->node.regmap;
> +	struct device *dev = master->node.dev;
> +	unsigned int val, inttype;
> +	int ret;
> +
> +	ret = regmap_read(regmap, AD242X_INTSTAT, &val);
> +	if (ret < 0) {
> +		dev_err(dev, "unable to read INTSTAT register: %d\n", ret);

Users do not care about registers.

"Failed to obtain interrupt status"

> +		return ret;
> +	}
> +
> +	if (!(val & AD242X_INTSTAT_IRQ))
> +		return -ENOENT;

What happened here?  No interrupts fired?

IRQ_NONE would be better than "No such file or directory".

> +	ret = regmap_read(regmap, AD242X_INTTYPE, &inttype);
> +	if (ret < 0) {
> +		dev_err(dev, "unable to read INTTYPE register: %d\n", ret);

Same for all log messages throughout this patch-set.

> +		return ret;
> +	}
> +
> +	ret = regmap_read(regmap, AD242X_INTSRC, &val);
> +	if (ret < 0) {
> +		dev_err(dev, "unable to read INTSRC register: %d\n", ret);
> +		return ret;
> +	}

What does this prove?  Why aren't you doing anything with the value?

> +	ret = regmap_read(regmap, AD242X_INTPND0, &val);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_INTPND0, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_read(regmap, AD242X_INTPND1, &val);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_INTPND1, val);
> +	if (ret < 0)
> +		return ret;

What does writing back the value do?  Comments please.

> +	if (val & AD242X_INTSRC_MSTINT) {
> +		ret = regmap_read(regmap, AD242X_INTPND2, &val);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = regmap_write(regmap, AD242X_INTPND2, val);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	dev_err(dev, "%s() inttype: 0x%02x\n", __func__, inttype);

No debugging type 'func's please.

What makes this an error?

> +	switch (inttype) {
> +	case AD242X_INTTYPE_DSCDONE:
> +		complete(&master->discover_completion);
> +		break;
> +	case AD242X_INTTYPE_MSTR_RUNNING:
> +		complete(&master->run_completion);
> +		break;
> +	default:
> +		dev_info(dev, "Unhandled interrupt type 0x%02x\n", inttype);
> +	}
> +
> +	return 0;
> +}
> +
> +static int ad242x_read_irqs(struct ad242x_master *master)
> +{
> +	int ret;
> +	bool first = true;
> +
> +	while (true) {
> +		ret = ad242x_read_one_irq(master);
> +		if (ret < 0)
> +			return ret;
> +		if (ret == -ENOENT)
> +			return first ? ret : 0;
> +
> +		first = false;
> +	}
> +}
> +
> +static irqreturn_t ad242x_handle_irq(int irq, void *devid)
> +{
> +	struct ad242x_master *master = devid;
> +	int ret;
> +
> +	ret = ad242x_read_irqs(master);
> +	if (ret == -ENOENT)
> +		return IRQ_NONE;
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int ad242x_wait_for_irq(struct ad242x_master *master,
> +			       struct completion *completion,
> +			       unsigned int timeout)
> +{
> +	int ret;
> +
> +	if (master->irq > 0) {
> +		ret = wait_for_completion_timeout(completion,
> +						  msecs_to_jiffies(timeout));
> +	} else {
> +		usleep_range(timeout * 1000, timeout * 1500);
> +		ad242x_read_irqs(master);
> +		ret = completion_done(completion);
> +	}

What are the semantics of this function.  Comments please.

> +	return ret == 0 ? -ETIMEDOUT : 0;
> +}
> +
> +/* See Table 3-2 in the datasheet */

Do you provide a link to the datasheet anywhere?

All I can find is a 1 page overview.

Please provide a description to what you're doing *here*.

> +static unsigned int ad242x_bus_bits(unsigned int slot_size, bool alt_fmt)
> +{
> +	int alt_bits[8] = { 0, 13, 17, 21, 30, 0, 39, 0 };
> +	int idx = AD242X_SLOTFMT_DNSIZE(slot_size);
> +
> +	return alt_fmt ? alt_bits[idx] : slot_size + 1;
> +}
> +
> +/* See Table 9-1 in the datasheet */

It's okay to reference the datasheet, but tell us what you're doing
here as well.

> +static unsigned int ad242x_master_respoffs(struct ad242x_node *node)
> +{
> +	if (node->tdm_mode == 2 && node->tdm_slot_size == 16)
> +		return 238;
> +
> +	if ((node->tdm_mode == 2 && node->tdm_slot_size == 32) ||
> +	    (node->tdm_mode == 4 && node->tdm_slot_size == 16))
> +		return 245;
> +
> +	return 248;

No magic numbers please.  You need to define them.

> +}
> +
> +static int ad242x_discover(struct ad242x_master *master,
> +			   struct device_node *nodes_np)
> +{
> +	struct regmap *regmap = master->node.regmap;
> +	struct device *dev = master->node.dev;
> +	struct device_node *child_np;
> +	unsigned int val, n = 0, i, respoffs, respcycs;

> +	unsigned int respcycs_up_min = UINT_MAX;
> +	unsigned int respcycs_dn_max = 0;

What are these?

> +	unsigned int master_up_slots = 0;
> +	unsigned int master_dn_slots = 0;
> +	bool up_enabled = false, dn_enabled = false;
> +	uint8_t slave_control = 0;
> +	int ret;
> +
> +	respoffs = ad242x_master_respoffs(&master->node);
> +
> +	for_each_available_child_of_node(nodes_np, child_np) {

What are we discovering here?  Child devices, or something else?

> +		unsigned int dnslot_activity, upslot_activity;
> +		unsigned int slave_dn_slots, slave_up_slots;
> +		unsigned int respcycs_dn, respcycs_up;
> +		struct ad242x_slot_config slot_config;
> +
> +		ret = ad242x_read_slot_config(dev, child_np, &slot_config);
> +		if (ret < 0) {
> +			dev_err(dev, "slot config of slave %d is invalid\n", n);
> +			return ret;
> +		}

What is a 'slot' defined as?

> +		/* See section 3-18 in the datasheet */

Give us a quick explanation.

> +		slave_dn_slots = max_t(int, slot_config.dn_n_forward_slots,
> +				       fls(slot_config.dn_rx_slots));
> +		slave_up_slots = max_t(int, slot_config.up_n_forward_slots,
> +				       fls(slot_config.up_rx_slots));
> +
> +		if (n == 0) {
> +			master_up_slots = slave_up_slots;
> +			master_dn_slots = slave_dn_slots;
> +		}
> +
> +		/* See Appendix B in the datasheet */

Give us a quick explanation.

> +		dnslot_activity = slave_dn_slots *
> +			ad242x_bus_bits(master->dn_slot_size,
> +					master->dn_slot_alt_fmt);
> +		upslot_activity = slave_up_slots *
> +			ad242x_bus_bits(master->up_slot_size,
> +					master->up_slot_alt_fmt);
> +
> +		respcycs_dn = DIV_ROUND_UP(64 + dnslot_activity, 4) + 4*n + 2;

Spaces around the '*'.  If it's not clear, use brackets.

> +		respcycs_up = respoffs -
> +			      (DIV_ROUND_UP(64 + upslot_activity, 4) + 1);

No idea what's going on here.

You need to define these magic numbers to make it clear.

> +		if (respcycs_dn > respcycs_dn_max)
> +			respcycs_dn_max = respcycs_dn;
> +
> +		if (respcycs_up < respcycs_up_min)
> +			respcycs_up_min = respcycs_up;
> +
> +		if (slave_dn_slots > 0)
> +			dn_enabled = true;
> +
> +		if (slave_up_slots > 0)
> +			up_enabled = true;
> +
> +		n++;
> +	}
> +
> +	if (n == 0) {
> +		dev_err(dev, "No child nodes specified\n");
> +		return -EINVAL;
> +	}
> +
> +	if (of_property_read_bool(dev->of_node, "adi,invert-xcvr-b")) {
> +		ret = regmap_update_bits(regmap, AD242X_CONTROL,
> +					 AD242X_CONTROL_XCVRBINV,
> +					 AD242X_CONTROL_XCVRBINV);
> +		if (ret < 0)
> +			return ret;
> +
> +		slave_control = AD242X_CONTROL_XCVRBINV;
> +	}
> +
> +	if (respcycs_dn_max > respcycs_up_min) {
> +		dev_err(dev, "Unsupported bus topology\n");
> +		return -EINVAL;
> +	}
> +
> +	respcycs = (respcycs_dn_max + respcycs_up_min) / 2;
> +	ret = regmap_write(regmap, AD242X_RESPCYCS, respcycs);
> +	if (ret < 0)
> +		return ret;

Comments please.

In fact, comments throughout please.

Anything that isn't absolutely crystal clear should have at least a
little one liner to clarify what is being calculated/set.

> +	ret = regmap_update_bits(regmap, AD242X_CONTROL,
> +				 AD242X_CONTROL_NEWSTRCT,
> +				 AD242X_CONTROL_NEWSTRCT);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_SWCTL, AD242X_SWCTL_ENSW);
> +	if (ret < 0)
> +		return ret;
> +
> +	for (i = 0; i < n; i++) {
> +		ret = regmap_write(regmap, AD242X_DISCVRY, respcycs - (4*i));

Spaces.

What is 4?

> +		if (ret < 0)
> +			return ret;
> +
> +		ret = ad242x_wait_for_irq(master,
> +					  &master->discover_completion, 35);

Define magic numbers throughout.

> +		if (ret < 0) {
> +			dev_err(dev, "Discovery of node %d timed out\n", i);
> +			return ret;
> +		}
> +
> +		val = AD242X_SWCTL_MODE(2) | AD242X_SWCTL_ENSW;
> +
> +		if (i == 0)
> +			ret = regmap_write(regmap, AD242X_SWCTL, val);
> +		else
> +			ret = ad242x_slave_write(&master->bus, regmap, i,
> +						 AD242X_SWCTL, val);
> +
> +		if (ret < 0)
> +			return ret;
> +
> +		dev_info(dev, "Node %d discovered\n", i);
> +
> +		/* Last node? */
> +		if (i == n - 1)
> +			break;
> +
> +		ret = ad242x_slave_write(&master->bus, regmap, i,
> +					 AD242X_INTMSK2,
> +					 AD242X_INTMSK2_DSCDIEN);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = ad242x_slave_write(&master->bus, regmap, i,
> +					 AD242X_CONTROL, slave_control);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = ad242x_slave_write(&master->bus, regmap, i,
> +					 AD242X_SWCTL, AD242X_SWCTL_ENSW);
> +		if (ret < 0)
> +			return ret;
> +
> +		reinit_completion(&master->discover_completion);
> +	}
> +
> +	ret = regmap_write(regmap, AD242X_DNSLOTS, master_dn_slots);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_UPSLOTS, master_up_slots);
> +	if (ret < 0)
> +		return ret;
> +
> +	val = 0;
> +	if (dn_enabled)
> +		val |= AD242X_DATCTL_DNS;
> +
> +	if (up_enabled)
> +		val |= AD242X_DATCTL_UPS;
> +
> +	ret = regmap_write(regmap, AD242X_DATCTL, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ad242x_init_irq(struct ad242x_master *master)
> +{
> +	struct regmap *regmap = master->node.regmap;
> +	struct device *dev = master->node.dev;
> +	int ret;
> +
> +	if (master->irq > 0) {
> +		ret = devm_request_threaded_irq(dev, master->irq, NULL,
> +						ad242x_handle_irq, IRQF_ONESHOT,
> +						dev_name(dev), master);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	ret = regmap_write(regmap, AD242X_INTMSK0,
> +			   AD242X_INTMSK0_SRFEIEN | AD242X_INTMSK0_BECIEN |
> +			   AD242X_INTMSK0_PWREIEN | AD242X_INTMSK0_CRCEIEN |
> +			   AD242X_INTMSK0_DDEIEN  | AD242X_INTMSK0_HCEIEN);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_INTMSK2,
> +			   AD242X_INTMSK2_DSCDIEN | AD242X_INTMSK2_SLVIRQEN);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct regmap_config ad242x_regmap_config = {
> +	.reg_bits	= 8,
> +	.val_bits	= 8,
> +	.volatile_reg	= ad242x_is_volatile_reg,
> +	.writeable_reg	= ad242x_is_writeable_reg,
> +	.max_register	= AD242X_MAX_REG,
> +	.cache_type	= REGCACHE_RBTREE,
> +};
> +
> +static int ad242x_master_probe(struct i2c_client *i2c,
> +			       const struct i2c_device_id *id)
> +{
> +	struct device_node *bus_np, *nodes_np, *np;
> +	struct device *busdev, *dev = &i2c->dev;
> +	struct ad242x_master *master;
> +	struct regmap *regmap;
> +	unsigned int val;
> +	int ret;
> +
> +	nodes_np = of_get_child_by_name(dev->of_node, "nodes");
> +	if (!nodes_np) {
> +		dev_err(dev, "no 'nodes' property given\n");
> +		return -EINVAL;
> +	}
> +
> +	bus_np = of_parse_phandle(dev->of_node, "adi,a2b-bus", 0);
> +	if (!bus_np) {
> +		dev_err(dev, "no 'adi,a2b-bus' handle specified for master node\n");
> +		return -EINVAL;
> +	}
> +
> +	busdev = bus_find_device_by_of_node(&i2c_bus_type, bus_np);
> +	if (!busdev) {
> +		dev_err(dev, "'adi,a2b-bus' handle invalid\n");
> +		return -EINVAL;
> +	}
> +
> +	master = devm_kzalloc(dev, sizeof(struct ad242x_master), GFP_KERNEL);

sizeof(*master)

> +	if (!master)
> +		return -ENOMEM;
> +
> +	mutex_init(&master->bus.mutex);
> +	init_completion(&master->run_completion);
> +	init_completion(&master->discover_completion);

> +	dev_set_drvdata(dev, &master->node);
> +	i2c_set_clientdata(i2c, master);

What do you think is happening here?

> +	regmap = devm_regmap_init_i2c(i2c, &ad242x_regmap_config);
> +	if (IS_ERR(regmap)) {
> +		ret = PTR_ERR(regmap);
> +		dev_err(dev, "regmap init failed: %d\n", ret);

"initialisation"

Or even better "Failed to initialise I2C Regmap"

> +		return ret;
> +	}
> +
> +	master->bus.client = to_i2c_client(busdev);

What does 'bus' do in this context?

> +	master->node.regmap = regmap;
> +	master->node.dev = dev;
> +	master->node.master = master;
> +	master->node.id = AD242X_MASTER_ID;
> +	master->irq = i2c->irq;
> +
> +	master->sync_clk = devm_clk_get(dev, "sync");
> +	if (IS_ERR(master->sync_clk)) {
> +		ret = PTR_ERR(master->sync_clk);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(dev, "failed to get sync clk: %d\n", ret);
> +
> +		return ret;
> +	}
> +
> +	if (of_property_read_u32(dev->of_node, "clock-frequency",
> +				 &master->sync_clk_rate)) {
> +		ret = clk_set_rate(master->sync_clk, master->sync_clk_rate);
> +		if (ret < 0) {
> +			dev_err(dev, "Cannot set sync clock rate: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	master->sync_clk_rate = clk_get_rate(master->sync_clk);
> +	if (master->sync_clk_rate != 44100 && master->sync_clk_rate != 48000) {

Please define these magic numbers.

Something descriptive that tells us what the different clock speeds
do.

> +		dev_err(dev, "SYNC clock rate %d is invalid\n",
> +			master->sync_clk_rate);
> +		return -EINVAL;
> +	}
> +
> +	ret = clk_prepare_enable(master->sync_clk);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to enable sync clk: %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Master node setup */
> +
> +	ret = regmap_write(regmap, AD242X_CONTROL,
> +			   AD242X_CONTROL_MSTR | AD242X_CONTROL_SOFTRST);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = ad242x_wait_for_irq(master, &master->run_completion, 10);
> +	if (ret < 0) {
> +		dev_err(dev, "timeout waiting for PLL sync: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_update_bits(regmap, AD242X_CONTROL,
> +				 AD242X_CONTROL_SOFTRST, 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = ad242x_node_probe(&master->node);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = ad242x_init_irq(master);
> +	if (ret < 0) {
> +		dev_err(dev, "Unable to set up IRQ: %d", ret);
> +		return ret;
> +	}
> +
> +	/* Slot format setup */
> +
> +	of_property_read_u32(dev->of_node, "adi,upstream-slot-size", &val);
> +	if (val < 8 || val > 32 || (val % 4 != 0)) {
> +		dev_err(dev, "invalid upstream-slot-size %d\n", val);
> +		return -EINVAL;
> +	}
> +	master->up_slot_size = val;
> +
> +	of_property_read_u32(dev->of_node, "adi,downstream-slot-size", &val);
> +	if (val < 8 || val > 32 || (val % 4 != 0)) {
> +		dev_err(dev, "invalid downstream-slot-size %d\n", val);
> +		return -EINVAL;
> +	}
> +	master->dn_slot_size = val;
> +
> +	master->dn_slot_alt_fmt =
> +		of_property_read_bool(dev->of_node,
> +				      "adi,alternate-downstream-slot-format");
> +	master->up_slot_alt_fmt =
> +		of_property_read_bool(dev->of_node,
> +				      "adi,alternate-upstream-slot-format");

Obviously this all needs to be run past the DT maintainer(s).

> +	val = AD242X_SLOTFMT_DNSIZE(master->dn_slot_size) |
> +	      AD242X_SLOTFMT_UPSIZE(master->up_slot_size);
> +
> +	if (master->dn_slot_alt_fmt)
> +		val |= AD242X_SLOTFMT_DNFMT;
> +
> +	if (master->up_slot_alt_fmt)
> +		val |= AD242X_SLOTFMT_UPFMT;
> +
> +	ret = regmap_write(regmap, AD242X_SLOTFMT, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Node discovery and MFD setup */
> +
> +	ret = ad242x_discover(master, nodes_np);
> +	if (ret < 0) {
> +		dev_err(dev, "error discovering nodes: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = ad242x_node_add_mfd_cells(dev);

Why is this called twice with the same children?

> +	if (ret < 0) {
> +		dev_err(dev, "failed to add MFD devices %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Register platform devices for nodes */
> +
> +	for_each_available_child_of_node(nodes_np, np)
> +		of_platform_device_create(np, NULL, dev);

What are you doing here?

Either use OF to register all child devices OR use MFD, not a mixture
of both.

> +	of_node_put(nodes_np);
> +
> +	return 0;
> +}
> +
> +static int ad242x_master_remove(struct i2c_client *i2c)
> +{
> +	struct ad242x_master *master = i2c_get_clientdata(i2c);
> +
> +	if (master->sync_clk)
> +		clk_disable_unprepare(master->sync_clk);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ad242x_master_of_match[] = {
> +	{ .compatible = "adi,ad2428w-master" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, ad242x_master_of_match);
> +
> +static const struct i2c_device_id ad242x_master_i2c_id[] = {
> +	{"ad242x-master", 0},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ad242x_master_i2c_id);

If you one the OF match table, you don't need this empty I2C table.
Grep for probe_new.

> +static struct i2c_driver ad242x_master_i2c_driver = {
> +	.driver	= {
> +		.name = "ad242x-master",
> +		.of_match_table	= ad242x_master_of_match,
> +	},
> +	.probe = ad242x_master_probe,
> +	.remove = ad242x_master_remove,
> +	.id_table = ad242x_master_i2c_id,
> +};
> +

Remove this line.

> +module_i2c_driver(ad242x_master_i2c_driver);
> +
> +MODULE_DESCRIPTION("AD242x master master driver");

Typo.

> +MODULE_AUTHOR("Daniel Mack <daniel@zonque.org>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/mfd/ad242x-node.c b/drivers/mfd/ad242x-node.c
> new file mode 100644
> index 000000000000..f9db689380a7
> --- /dev/null
> +++ b/drivers/mfd/ad242x-node.c
> @@ -0,0 +1,262 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/ad242x.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/regmap.h>
> +
> +/* See Table 7-43 in the datasheet */

More information please.

> +static int ad242x_tdmmode_index(unsigned int mode, bool slave)
> +{
> +	switch (mode) {
> +	case 2:
> +		return 0;
> +	case 4:
> +		return 1;
> +	case 8:
> +		return 2;
> +	case 12:
> +		return slave ? -EINVAL : 3;
> +	case 16:
> +		return 4;
> +	case 20:
> +		return slave ? -EINVAL : 5;
> +	case 24:
> +		return slave ? -EINVAL : 6;
> +	case 32:
> +		return 7;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +int ad242x_node_probe(struct ad242x_node *node)
> +{
> +	struct device_node *np = node->dev->of_node;
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(node->regmap, AD242X_VENDOR, &val);
> +	if (ret < 0) {
> +		dev_err(node->dev, "failed to read VENDOR register %d\n", ret);

Please re-write all of your kernel log messages to be user friendly.

> +		return ret;
> +	}
> +
> +	if (val != 0xad) {

No magic numbers - please define them all.

> +		dev_err(node->dev, "bogus value 0x%02x in VENDOR register\n",
> +			val);
> +		return -ENODEV;
> +	}
> +
> +	ret = regmap_read(node->regmap, AD242X_PRODUCT, &val);
> +	if (ret < 0) {
> +		dev_err(node->dev, "failed to read PRODUCT register %d\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	if (val != 0x28) {
> +		dev_err(node->dev, "bogus value 0x%02x in PRODUCT register\n",
> +			val);
> +		return -ENODEV;
> +	}
> +
> +	ret = regmap_read(node->regmap, AD242X_VERSION, &val);
> +	if (ret < 0) {
> +		dev_err(node->dev, "failed to read VERSION register %d\n", ret);
> +		return ret;
> +	}
> +
> +	if (node->id == AD242X_MASTER_ID)
> +		dev_info(node->dev,
> +			 "Detected AD242x master node, version %d.%d\n",
> +			 val >> 4, val & 0xf);
> +	else
> +		dev_info(node->dev,
> +			 "Detected AD242x slave node, version %d.%d, id %d\n",
> +			 val >> 4, val & 0xf, node->id);
> +
> +	ret = regmap_read(node->regmap, AD242X_CAPABILITY, &val);
> +	if (ret < 0) {
> +		dev_err(node->dev, "failed to read CAPABILITY register %d\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	node->caps = val;
> +
> +	val = 0;
> +
> +	if (of_property_read_bool(np, "adi,spread-a2b-clock"))
> +		val |= AD242X_PLLCTL_SSMODE_AB;
> +	else if (of_property_read_bool(np, "adi,spread-a2b-i2s-clock"))
> +		val |= AD242X_PLLCTL_SSMODE_AB_I2S;
> +
> +	if (of_property_read_bool(np, "adi,spread-spectrum-high"))
> +		val |= AD242X_PLLCTL_SSDEPTH;
> +
> +	ret = regmap_write(node->regmap, AD242X_PLLCTL, val);
> +	if (ret < 0) {
> +		dev_err(node->dev, "failed to write PLLCTL register %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* I2S global setup */
> +
> +	of_property_read_u32(np, "adi,tdm-mode", &node->tdm_mode);
> +	of_property_read_u32(np, "adi,tdm-slot-size", &node->tdm_slot_size);
> +
> +	ret = ad242x_tdmmode_index(node->tdm_mode, false);
> +	if (ret < 0) {
> +		dev_err(node->dev, "invalid TDM mode %d\n", node->tdm_mode);
> +		return -EINVAL;
> +	}
> +
> +	val = AD242X_I2SGCTL_TDMMODE(ret);
> +
> +	if (node->tdm_slot_size == 16) {
> +		val |= AD242X_I2SGCTL_TDMSS;
> +	} else if (node->tdm_slot_size != 32) {
> +		dev_err(node->dev, "invalid TDM slot size %d\n",
> +			node->tdm_slot_size);
> +		return -EINVAL;
> +	}
> +
> +	if (of_property_read_bool(np, "adi,alternating-sync"))
> +		val |= AD242X_I2SGCTL_ALT;
> +
> +	if (of_property_read_bool(np, "adi,early-sync"))
> +		val |= AD242X_I2SGCTL_EARLY;
> +
> +	if (of_property_read_bool(np, "adi,invert-sync"))
> +		val |= AD242X_I2SGCTL_INV;
> +
> +	ret = regmap_write(node->regmap, AD242X_I2SGCTL, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct mfd_cell ad242x_mfd_cells[] = {
> +	{
> +		.of_compatible	= "adi,ad2428w-i2c",
> +		.name		= "ad242x-i2c",

Swap these around.  Or better still, use the macros found in:

  include/linux/mfd/core.h

> +	},
> +	{
> +		.of_compatible	= "adi,ad2428w-gpio",
> +		.name		= "ad242x-gpio",
> +	},
> +	{
> +		.of_compatible	= "adi,ad2428w-clk",
> +		.name		= "ad242x-clk",
> +	},
> +	{
> +		.of_compatible	= "adi,ad2428w-codec",
> +		.name		= "ad242x-codec",
> +	},
> +};
> +
> +int ad242x_node_add_mfd_cells(struct device *dev)
> +{
> +	return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO,
> +				    ad242x_mfd_cells,
> +				    ARRAY_SIZE(ad242x_mfd_cells),
> +				    NULL, 0, NULL);
> +}
> +
> +static int ad242x_get_slot_mask(const struct device_node *np,
> +				const char *propname, u32 *mask)
> +{
> +	unsigned int i, num;
> +	int ret, proplen;
> +	u32 slots[32];

You should define 32 as the maximum number of slots available, then
use it again for most of the random '32's below.

> +	if (!of_get_property(np, propname, &proplen))
> +		return -ENOENT;

This whole piece becomes simpler if you use:

 of_property_read_variable_u32_array()

> +	num = proplen / sizeof(u32);
> +
> +	if (num > ARRAY_SIZE(slots))
> +		return -EOVERFLOW;
> +
> +	ret = of_property_read_u32_array(np, propname, slots, num);
> +	if (ret < 0)
> +		return ret;
> +
> +	*mask = 0;
> +
> +	for (i = 0; i < num; i++) {
> +		if (slots[i] >= 32)
> +			return -EINVAL;
> +
> +		*mask |= BIT(slots[i]);
> +	}
> +
> +	return 0;
> +}
> +
> +int ad242x_read_slot_config(struct device *dev,
> +			    struct device_node *np,
> +			    struct ad242x_slot_config *config)
> +{
> +	struct device_node *dn_np, *up_np;
> +	int ret;
> +
> +	dn_np = of_get_child_by_name(np, "downstream");
> +	if (!dn_np) {
> +		dev_err(dev, "no downstream node\n");
> +		return -EINVAL;
> +	}
> +
> +	up_np = of_get_child_by_name(np, "upstream");
> +	if (!dn_np) {
> +		dev_err(dev, "no upstream node\n");
> +		ret = -EINVAL;
> +		goto err_put_dn_node;
> +	}
> +
> +	ret = ad242x_get_slot_mask(dn_np, "rx-slots", &config->dn_rx_slots);
> +	if (ret < 0 && ret != -ENOENT) {

If you're going to ignore -ENOENT, why not just return 0?

> +		dev_err(dev, "invalid downstream rx-slots property\n");
> +		goto err_put_nodes;
> +	}
> +
> +	of_property_read_u32(dn_np, "#tx-slots", &config->dn_n_tx_slots);
> +	of_property_read_u32(dn_np, "#forward-slots",
> +			     &config->dn_n_forward_slots);
> +	if (config->dn_n_tx_slots + config->dn_n_forward_slots >= 32) {
> +		dev_err(dev, "invalid downstream tx-slots property\n");
> +		goto err_put_nodes;
> +	}
> +
> +

Superfluous '\n'.

> +	ret = ad242x_get_slot_mask(up_np, "rx-slots", &config->up_rx_slots);
> +	if (ret < 0) {
> +		dev_err(dev, "invalid upstream rx-slots property\n");
> +		goto err_put_nodes;
> +	}
> +
> +	of_property_read_u32(up_np, "#tx-slots", &config->up_n_tx_slots);
> +	of_property_read_u32(up_np, "#forward-slots",
> +			     &config->up_n_forward_slots);
> +	if (config->up_n_tx_slots + config->up_n_forward_slots >= 32) {
> +		dev_err(dev, "invalid downstream tx-slots property\n");
> +		goto err_put_nodes;
> +	}
> +
> +err_put_nodes:
> +	of_node_put(up_np);
> +err_put_dn_node:
> +	of_node_put(dn_np);
> +
> +	return ret;
> +}
> diff --git a/drivers/mfd/ad242x-slave.c b/drivers/mfd/ad242x-slave.c
> new file mode 100644
> index 000000000000..ad255d67a5b6
> --- /dev/null
> +++ b/drivers/mfd/ad242x-slave.c
> @@ -0,0 +1,234 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/mfd/ad242x.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mutex.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +struct ad242x_slave {
> +	struct ad242x_node		node;
> +	struct ad242x_node		*master;
> +	struct ad242x_slot_config	slot_config;
> +	unsigned int			sync_offset;
> +};
> +
> +int ad242x_slave_read(struct ad242x_i2c_bus *bus,
> +		      struct regmap *master_regmap,
> +		      uint8_t node_id, uint8_t reg, unsigned int *val)
> +{
> +	int ret;
> +
> +	mutex_lock(&bus->mutex);
> +
> +	ret = regmap_write(master_regmap, AD242X_NODEADR, node_id);
> +	if (ret < 0)
> +		goto err_unlock;
> +
> +	ret = i2c_smbus_read_byte_data(bus->client, reg);
> +	if (ret < 0)
> +		goto err_unlock;
> +
> +	*val = ret;
> +	ret = 0;
> +
> +err_unlock:
> +	mutex_unlock(&bus->mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(ad242x_slave_read);
> +
> +int ad242x_slave_write(struct ad242x_i2c_bus *bus,
> +		       struct regmap *master_regmap,
> +		       uint8_t node_id, uint8_t reg, unsigned int val)
> +{
> +	int ret;
> +
> +	mutex_lock(&bus->mutex);
> +
> +	ret = regmap_write(master_regmap, AD242X_NODEADR, node_id);
> +	if (ret < 0)
> +		goto err_unlock;
> +
> +	ret = i2c_smbus_write_byte_data(bus->client, reg, val);
> +	if (ret < 0)
> +		goto err_unlock;
> +
> +	ret = 0;
> +
> +err_unlock:
> +	mutex_unlock(&bus->mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(ad242x_slave_write);
> +
> +static int ad242x_slave_regmap_read(void *context, unsigned int reg,
> +				    unsigned int *val)
> +{
> +	struct ad242x_slave *slave = context;
> +	struct ad242x_i2c_bus *bus = ad242x_master_get_bus(slave->node.master);
> +	struct ad242x_node *mnode = ad242x_master_get_node(slave->node.master);
> +
> +	if (reg > 0xff)
> +		return -EINVAL;
> +
> +	return ad242x_slave_read(bus, mnode->regmap, slave->node.id, reg, val);
> +}
> +
> +static int ad242x_slave_regmap_write(void *context, unsigned int reg,
> +				     unsigned int val)
> +{
> +	struct ad242x_slave *slave = context;
> +	struct ad242x_i2c_bus *bus = ad242x_master_get_bus(slave->node.master);
> +	struct ad242x_node *mnode = ad242x_master_get_node(slave->node.master);
> +
> +	if (val > 0xff || reg > 0xff)
> +		return -EINVAL;
> +
> +	return ad242x_slave_write(bus, mnode->regmap, slave->node.id, reg, val);
> +}
> +
> +static const struct regmap_config ad242x_regmap_config = {
> +	.reg_bits	= 8,
> +	.val_bits	= 8,
> +	.volatile_reg	= ad242x_is_volatile_reg,
> +	.writeable_reg	= ad242x_is_writeable_reg,
> +	.reg_read	= ad242x_slave_regmap_read,
> +	.reg_write	= ad242x_slave_regmap_write,
> +	.max_register	= AD242X_MAX_REG,
> +	.cache_type	= REGCACHE_RBTREE,
> +};
> +
> +static int ad242x_calc_sync_offset(unsigned int val)
> +{
> +	if (val == 0)
> +		return 0;
> +
> +	if (val > 127)
> +		return -EINVAL;
> +
> +	return 256 - val;

More magic numbers to define.

> +}
> +
> +static int ad242x_slave_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct ad242x_slave *slave;
> +	struct ad242x_node *mnode;
> +	struct regmap *regmap;
> +	unsigned int val;
> +	int i, ret;
> +
> +	slave = devm_kzalloc(dev, sizeof(*slave), GFP_KERNEL);
> +	if (!slave)
> +		return -ENOMEM;
> +
> +	regmap = devm_regmap_init(dev, NULL, slave, &ad242x_regmap_config);
> +	if (IS_ERR(regmap)) {
> +		ret = PTR_ERR(regmap);
> +		dev_err(dev, "regmap init failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	of_property_read_u32(dev->of_node, "reg", &val);
> +	slave->node.id = val;

This looks like an abuse of the 'reg' property.

> +	slave->node.dev = dev;
> +	slave->node.regmap = regmap;
> +
> +	mnode = dev_get_drvdata(dev->parent);

What is the parent of the slave?

(it's not clear without looking at the DT I guess)

> +	slave->node.master = mnode->master;
> +
> +	dev_set_name(dev, "%s-a2b-%d", dev_name(dev->parent), slave->node.id);
> +	dev_set_drvdata(dev, &slave->node);
> +
> +	ret = ad242x_node_probe(&slave->node);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = ad242x_read_slot_config(dev, dev->of_node, &slave->slot_config);
> +	if (ret < 0) {
> +		dev_err(dev, "slot config is invalid: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_write(regmap, AD242X_UPSLOTS,
> +			   slave->slot_config.up_n_forward_slots);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_DNSLOTS,
> +			   slave->slot_config.dn_n_forward_slots);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_LUPSLOTS,
> +			   slave->slot_config.up_n_tx_slots);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_LDNSLOTS,
> +			   slave->slot_config.dn_n_tx_slots |
> +			   AD242X_LDNSLOTS_DNMASKEN);
> +	if (ret < 0)
> +		return ret;
> +
> +	for (i = 0; i < 4; i++) {

Why 4?  Please define it.

> +		ret = regmap_write(regmap, AD242X_UPMASK(i),
> +			(slave->slot_config.up_rx_slots >> (i * 8)) & 0xff);
> +		if (ret < 0)
> +			return ret;
> +
> +		ret = regmap_write(regmap, AD242X_DNMASK(i),
> +			(slave->slot_config.dn_rx_slots >> (i * 8)) & 0xff);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	of_property_read_u32(dev->of_node, "adi,sync-offset",
> +			     &slave->sync_offset);
> +
> +	ret = ad242x_calc_sync_offset(slave->sync_offset);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = regmap_write(regmap, AD242X_SYNCOFFSET, ret);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = ad242x_node_add_mfd_cells(dev);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to add MFD devices %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id ad242x_slave_of_match[] = {
> +	{ .compatible = "adi,ad2428w-slave" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, ad242x_slave_of_match);
> +
> +static struct platform_driver ad242x_slave_driver = {
> +	.driver = {
> +		.name = "ad242x-slave",
> +		.of_match_table = ad242x_slave_of_match,
> +	},
> +	.probe = ad242x_slave_probe,
> +};
> +
> +module_platform_driver(ad242x_slave_driver);
> +
> +MODULE_DESCRIPTION("AD242x slave node driver");
> +MODULE_AUTHOR("Daniel Mack <daniel@zonque.org>");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/ad242x.h b/include/linux/mfd/ad242x.h
> new file mode 100644
> index 000000000000..02a174824f85
> --- /dev/null
> +++ b/include/linux/mfd/ad242x.h
> @@ -0,0 +1,400 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#ifndef __LINUX_MFD_AD242X_H
> +#define __LINUX_MFD_AD242X_H
> +
> +#define AD242X_CHIP			0x00
> +#define AD242X_NODEADR			0x01
> +#define AD242X_NODEADR_MASK		0x0f
> +#define AD242X_NODEADR_PERI		BIT(5)
> +#define AD242X_NODEADR_BRCST		BIT(7)
> +
> +#define AD242X_VENDOR			0x02
> +#define AD242X_PRODUCT			0x03
> +#define AD242X_VERSION			0x04
> +
> +#define AD242X_CAPABILITY		0x05
> +#define AD242X_CAPABILITY_I2C		BIT(0)
> +
> +#define AD242X_SWCTL			0x09
> +#define AD242X_SWCTL_ENSW		BIT(0)
> +#define AD242X_SWCTL_DIAGMODE		BIT(3)
> +#define AD242X_SWCTL_MODE(X)		(((X) & 3) << 4)
> +#define AD242X_SWCTL_MODE_MASK		(3 << 4)
> +#define AD242X_SWCTL_DISNXT		BIT(6)
> +
> +#define AD242X_BCDNSLOTS		0x0a
> +#define AD242X_BCDNSLOTS_MASK		0x3f
> +
> +#define AD242X_LDNSLOTS			0x0b
> +#define AD242X_LDNSLOTS_MASK		0x3f
> +#define AD242X_LDNSLOTS_DNMASKEN	BIT(7)
> +
> +#define AD242X_LUPSLOTS			0x0c
> +#define AD242X_LUPSLOTS_MASK		0x3f
> +
> +#define AD242X_DNSLOTS			0x0d
> +#define AD242X_DNSLOTS_MASK		0x3f
> +
> +#define AD242X_UPSLOTS			0x0e
> +#define AD242X_UPSLOTS_MASK		0x3f
> +
> +#define AD242X_RESPCYCS			0x0f
> +
> +#define AD242X_SLOTFMT			0x10
> +#define AD242X_SLOTFMT_DNSIZE(X)	((((X) - 8) >> 2) & 7)
> +#define AD242X_SLOTFMT_DNFMT		BIT(3)
> +#define AD242X_SLOTFMT_UPSIZE(X)	(((((X) - 8) >> 2) & 7) << 4)
> +#define AD242X_SLOTFMT_UPFMT		BIT(7)
> +
> +#define AD242X_DATCTL			0x11
> +#define AD242X_DATCTL_DNS		BIT(0)
> +#define AD242X_DATCTL_UPS		BIT(1)
> +#define AD242X_DATCTL_ENDSNIFF		BIT(5)
> +#define AD242X_DATCTL_STANDBY		BIT(7)
> +
> +#define AD242X_CONTROL			0x12
> +#define AD242X_CONTROL_NEWSTRCT		BIT(0)
> +#define AD242X_CONTROL_ENDDSC		BIT(1)
> +#define AD242X_CONTROL_SOFTRST		BIT(2)
> +#define AD242X_CONTROL_SWBYP		BIT(3)
> +#define AD242X_CONTROL_XCVRBINV		BIT(4)
> +#define AD242X_CONTROL_MSTR		BIT(7)
> +
> +#define AD242X_DISCVRY			0x13
> +
> +#define AD242X_SWSTAT			0x14
> +#define AD242X_SWSTAT_FIN		BIT(0)
> +#define AD242X_SWSTAT_FAULT		BIT(1)
> +#define AD242X_SWSTAT_FAULTCODE(X)	(((X) & 0x7) >> 4)
> +#define AD242X_SWSTAT_FAULT_NLOC	BIT(7)
> +
> +#define AD242X_INTSTAT			0x15
> +#define AD242X_INTSTAT_IRQ		BIT(0)
> +
> +#define AD242X_INTSRC			0x16
> +#define AD242X_INTSRC_INODE		0x0f
> +#define AD242X_INTSRC_SLVINT		BIT(6)
> +#define AD242X_INTSRC_MSTINT		BIT(7)
> +
> +#define AD242X_INTTYPE			0x17
> +
> +#define AD242X_INTTYPE_DSCDONE		24
> +#define AD242X_INTTYPE_MSTR_RUNNING	255
> +
> +#define AD242X_INTPND0			0x18
> +#define AD242X_INTPDN0_HDCNTERR		BIT(0)
> +#define AD242X_INTPDN0_DDERR		BIT(1)
> +#define AD242X_INTPDN0_CRCERR		BIT(2)
> +#define AD242X_INTPDN0_DPERR		BIT(3)
> +#define AD242X_INTPDN0_PWRERR		BIT(4)
> +#define AD242X_INTPDN0_BECOVF		BIT(5)
> +#define AD242X_INTPDN0_SRFERR		BIT(6)
> +#define AD242X_INTPDN0_SRFCRCERR	BIT(7)
> +
> +#define AD242X_INTPND1			0x19
> +#define AD242X_INTPND1_IOPND(X)		BIT(X)
> +
> +#define AD242X_INTPND2			0x1a
> +#define AD242X_INTPND2_DSCDONE		BIT(0)
> +#define AD242X_INTPND2_I2CERR		BIT(1)
> +#define AD242X_INTPND2_ICRCERR		BIT(2)
> +#define AD242X_INTPND2_SLVIRQ		BIT(3)
> +
> +#define AD242X_INTMSK0			0x1b
> +#define AD242X_INTMSK0_HCEIEN		BIT(0)
> +#define AD242X_INTMSK0_DDEIEN		BIT(1)
> +#define AD242X_INTMSK0_CRCEIEN		BIT(2)
> +#define AD242X_INTMSK0_DPEIEN		BIT(3)
> +#define AD242X_INTMSK0_PWREIEN		BIT(4)
> +#define AD242X_INTMSK0_BECIEN		BIT(5)
> +#define AD242X_INTMSK0_SRFEIEN		BIT(6)
> +#define AD242X_INTMSK0_SRFCRCEIEN	BIT(7)
> +
> +#define AD242X_INTMSK1			0x1c
> +#define AD242X_INTMSK1_IOIRQEN(X)	BIT(X)
> +
> +#define AD242X_INTMSK2			0x1d
> +#define AD242X_INTMSK2_DSCDIEN		BIT(0)
> +#define AD242X_INTMSK2_I2CEIEN		BIT(1)
> +#define AD242X_INTMSK2_ICRCEIEN		BIT(2)
> +#define AD242X_INTMSK2_SLVIRQEN		BIT(3)
> +
> +#define AD242X_BECCTL			0x1e
> +#define AD242X_BECCTL_ENHDCNT		BIT(0)
> +#define AD242X_BECCTL_ENDD		BIT(1)
> +#define AD242X_BECCTL_ENCRC		BIT(2)
> +#define AD242X_BECCTL_ENDP		BIT(3)
> +#define AD242X_BECCTL_ENICRC		BIT(4)
> +#define AD242X_BECCTL_THRESHLD(X)	((X) >> 5)
> +
> +#define AD242X_BECNT			0x1f
> +
> +#define AD242X_TESTMODE			0x20
> +#define AD242X_TESTMODE_PRBSUP		BIT(0)
> +#define AD242X_TESTMODE_PRBSDN		BIT(1)
> +#define AD242X_TESTMODE_PRBSN2N		BIT(2)
> +#define AD242X_TESTMODE_RXDPTH(X)	((X) >> 4)
> +
> +#define AD242X_ERRCNT0			0x21
> +#define AD242X_ERRCNT1			0x22
> +#define AD242X_ERRCNT2			0x23
> +#define AD242X_ERRCNT3			0x24
> +
> +#define AD242X_NODE			0x29
> +#define AD242X_NODE_MASK		0xf
> +#define AD242X_NODE_DISCVD		BIT(5)
> +#define AD242X_NODE_NLAST		BIT(6)
> +#define AD242X_NODE_LAST		BIT(7)
> +
> +#define AD242X_DISCSTAT			0x2b
> +#define AD242X_DISCSTAT_DNODE(X)	((X) & 0xf)
> +#define AD242X_DISCSTAT_DSCACT		BIT(7)
> +
> +#define AD242X_TXACTL			0x2e
> +#define AD242X_TXACTL_LEVEL_HIGH	0
> +#define AD242X_TXACTL_LEVEL_MEDIUM	2
> +#define AD242X_TXACTL_LEVEL_LOW		3
> +
> +#define AD242X_TXBCTL			0x30
> +#define AD242X_TXBCTL_LEVEL_HIGH	0
> +#define AD242X_TXBCTL_LEVEL_MEDIUM	2
> +#define AD242X_TXBCTL_LEVEL_LOW		3
> +
> +#define AD242X_LINTTYPE			0x3e
> +
> +#define AD242X_I2CCFG			0x3f
> +#define AD242X_I2CCFG_DATARATE		BIT(0)
> +#define AD242X_I2CCFG_EACK		BIT(1)
> +#define AD242X_I2CCFG_FRAMERATE		BIT(2)
> +
> +#define AD242X_PLLCTL			0x40
> +#define AD242X_PLLCTL_SSFREQ(X)		((X) & 3)
> +#define AD242X_PLLCTL_SSDEPTH		BIT(2)
> +#define AD242X_PLLCTL_SSMODE_AB		(1 << 6)
> +#define AD242X_PLLCTL_SSMODE_AB_I2S	(2 << 6)
> +
> +#define AD242X_I2SGCTL			0x41
> +#define AD242X_I2SGCTL_TDMMODE(X)	((X) & 3)
> +#define AD242X_I2SGCTL_RXONDTX1		BIT(3)
> +#define AD242X_I2SGCTL_TDMSS		BIT(4)
> +#define AD242X_I2SGCTL_ALT		BIT(5)
> +#define AD242X_I2SGCTL_EARLY		BIT(6)
> +#define AD242X_I2SGCTL_INV		BIT(7)
> +
> +#define AD242X_I2SCTL			0x42
> +#define AD242X_I2SCTL_TX0EN		BIT(0)
> +#define AD242X_I2SCTL_TX1EN		BIT(1)
> +#define AD242X_I2SCTL_TX2PINTL		BIT(2)
> +#define AD242X_I2SCTL_TXBCLKINV		BIT(3)
> +#define AD242X_I2SCTL_RX0EN		BIT(4)
> +#define AD242X_I2SCTL_RX1EN		BIT(5)
> +#define AD242X_I2SCTL_RX2PINTL		BIT(6)
> +#define AD242X_I2SCTL_RXBCLKINV		BIT(7)
> +
> +#define AD242X_I2SRATE			0x43
> +#define AD242X_I2SRATE_I2SRATE(X)	((X) & 3)
> +#define AD242X_I2SRATE_BCLKRATE(X)	(((X) << 3) & 3)
> +#define AD242X_I2SRATE_REDUCE		BIT(6)
> +#define AD242X_I2SRATE_SHARE		BIT(7)
> +
> +#define AD242X_I2STXOFFSET		0x44
> +#define AD242X_I2STXOFFSET_VAR(X)	((X) & 0x3f)
> +#define AD242X_I2STXOFFSET_TSAFTER	BIT(6)
> +#define AD242X_I2STXOFFSET_TSBEFORE	BIT(7)
> +
> +#define AD242X_2SRXOFFSET		0x45
> +#define AD242X_I2SRXOFFSET_VAR(X)	((X) & 0x3f)
> +
> +#define AD242X_SYNCOFFSET		0x46
> +
> +#define AD242X_PDMCTL			0x47
> +#define AD242X_PDMCTL_PDM0EN		BIT(0)
> +#define AD242X_PDMCTL_PDM0SLOTS		BIT(1)
> +#define AD242X_PDMCTL_PDM1EN		BIT(2)
> +#define AD242X_PDMCTL_PDM1SLOTS		BIT(3)
> +#define AD242X_PDMCTL_HPFEN		BIT(4)
> +#define AD242X_PDMCTL_PDMRATE(X)	(((X) & 3) << 5)
> +
> +#define AD242X_ERRMGMT			0x48
> +#define AD242X_ERRMGMT_ERRLSB		BIT(0)
> +#define AD242X_ERRMGMT_ERRSIG		BIT(1)
> +#define AD242X_ERRMGMT_ERRSLOT		BIT(2)
> +
> +#define AD242X_GPIODAT			0x4a
> +#define AD242X_GPIODAT_SET		0x4b
> +#define AD242X_GPIODAT_CLR		0x4c
> +#define AD242X_GPIOOEN			0x4d
> +#define AD242X_GPIOIEN			0x4e
> +#define AD242X_GPIODAT_IN		0x4f
> +#define AD242X_PINTEN			0x50
> +#define AD242X_PINTINV			0x51
> +
> +#define AD242X_PINCFG			0x52
> +#define AD242X_PINCFG_DRVSTR		BIT(0)
> +#define AD242X_PINCFG_IRQINV		BIT(4)
> +#define AD242X_PINCFG_IRQTS		BIT(5)
> +
> +#define AD242X_I2STEST			0x53
> +#define AD242X_I2STEST_PATTRN2TX	BIT(0)
> +#define AD242X_I2STEST_LOOPBK2TX	BIT(1)
> +#define AD242X_I2STEST_RX2LOOPBK	BIT(2)
> +#define AD242X_I2STEST_SELRX1		BIT(3)
> +#define AD242X_I2STEST_BUSLOOPBK	BIT(4)
> +
> +#define AD242X_RAISE			0x54
> +
> +#define AD242X_GENERR			0x55
> +#define AD242X_GENERR_GENHCERR		BIT(0)
> +#define AD242X_GENERR_GENDDERR		BIT(1)
> +#define AD242X_GENERR_GENCRCERR		BIT(2)
> +#define AD242X_GENERR_GENDPERR		BIT(3)
> +#define AD242X_GENERR_GENICRCERR	BIT(4)
> +
> +#define AD242X_I2SRRATE			0x56
> +#define AD242X_I2SRRATE_RRDIV(X)	((X) & 0x3f)
> +#define AD242X_I2SRRATE_RBUS		BIT(7)
> +
> +#define AD242X_I2SRRCTL			0x57
> +#define AD242X_I2SRRCTL_ENVLSB		BIT(0)
> +#define AD242X_I2SRRCTL_ENXBIT		BIT(1)
> +#define AD242X_I2SRRCTL_ENSTRB		BIT(4)
> +#define AD242X_I2SRRCTL_STRBDIR		BIT(5)
> +
> +#define AD242X_I2SRRSOFFS		0x58
> +
> +#define AD242X_CLK1CFG			0x59
> +#define AD242X_CLK2CFG			0x5a
> +#define AD242X_CLKCFG_DIV(X)		((X) & 0xf)
> +#define AD242X_CLKCFG_DIVMSK		0xf
> +#define AD242X_CLKCFG_PDIV32		BIT(5)
> +#define AD242X_CLKCFG_INV		BIT(6)
> +#define AD242X_CLKCFG_EN		BIT(7)
> +
> +#define AD242X_BMMCFG			0x5b
> +#define AD242X_BMMCFG_BMMEN		BIT(0)
> +#define AD242X_BMMCFG_BMMRXEN		BIT(1)
> +#define AD242X_BMMCFG_BMMNDSC		BIT(2)
> +
> +#define AD242X_PDMCTL2			0x5d
> +#define AD242X_PDMCTL2_DEST_A2B		0
> +#define AD242X_PDMCTL2_DEST_DTX		1
> +#define AD242X_PDMCTL2_DEST_A2B_DTX	2
> +
> +#define AD242X_UPMASK(X)		(0x60 + ((X) & 3))
> +
> +#define AD242X_UPOFFSET			0x64
> +#define AD242X_UPOFFSET_VAL(X)		((X) & 0x1f)
> +
> +#define AD242X_DNMASK(X)		(0x65 + ((X) % 3))
> +
> +#define AD242X_DNOFFSET			0x69
> +#define AD242X_DNOFFSET_VAL(X)		((X) & 0x1f)
> +
> +#define AD242X_CHIPID(X)		((X) + 0x6a)
> +
> +#define AD242X_GPIODEN			0x80
> +#define AD242X_GPIOD_MSK(X)		((X) + 0x81)
> +
> +#define AD242X_GPIODDAT			0x89
> +#define AD242X_GPIODINV			0x8a
> +
> +#define AD242X_MAX_REG			0x9b
> +
> +static inline bool ad242x_is_volatile_reg(struct device *dev, unsigned int reg)
> +{
> +	switch (reg) {
> +	case AD242X_VENDOR:
> +	case AD242X_PRODUCT:
> +	case AD242X_VERSION:
> +	case AD242X_CAPABILITY:
> +	case AD242X_SWSTAT:
> +	case AD242X_INTSTAT:
> +	case AD242X_INTSRC:
> +	case AD242X_INTTYPE:
> +	case AD242X_INTPND0:
> +	case AD242X_INTPND1:
> +	case AD242X_INTPND2:
> +	case AD242X_BECNT:
> +	case AD242X_ERRCNT0:
> +	case AD242X_ERRCNT1:
> +	case AD242X_ERRCNT2:
> +	case AD242X_ERRCNT3:
> +	case AD242X_NODE:
> +	case AD242X_DISCSTAT:
> +	case AD242X_LINTTYPE:
> +	case AD242X_GPIODAT:
> +	case AD242X_GPIODAT_IN:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static inline bool ad242x_is_writeable_reg(struct device *dev, unsigned int reg)
> +{
> +	/* Write-to-clean registers */
> +	switch (reg) {
> +	case AD242X_INTPND0:
> +	case AD242X_INTPND1:
> +	case AD242X_INTPND2:
> +	case AD242X_BECNT:
> +		return true;
> +	default:
> +		return !ad242x_is_volatile_reg(dev, reg);
> +	}
> +}
> +
> +#define AD242X_MASTER_ID 0xff
> +
> +struct ad242x_master;
> +
> +struct ad242x_i2c_bus {
> +	struct i2c_client	*client;
> +	struct mutex		mutex;
> +};
> +
> +struct ad242x_node {
> +	struct device		*dev;
> +	struct regmap		*regmap;
> +	struct ad242x_master	*master;
> +	unsigned int		tdm_mode;
> +	unsigned int		tdm_slot_size;
> +	uint8_t			id;
> +	uint8_t			caps;
> +};
> +
> +struct ad242x_slot_config {
> +	unsigned int dn_rx_slots;
> +	unsigned int dn_n_tx_slots;
> +	unsigned int dn_n_forward_slots;
> +	unsigned int up_rx_slots;
> +	unsigned int up_n_tx_slots;
> +	unsigned int up_n_forward_slots;
> +};
> +
> +int ad242x_read_slot_config(struct device *dev,
> +			    struct device_node *np,
> +			    struct ad242x_slot_config *config);
> +
> +static inline bool ad242x_node_is_master(struct ad242x_node *node)
> +{
> +	return node->id == AD242X_MASTER_ID;
> +}
> +
> +int ad242x_node_probe(struct ad242x_node *node);
> +int ad242x_node_add_mfd_cells(struct device *dev);
> +
> +struct ad242x_node *ad242x_master_get_node(struct ad242x_master *master);
> +struct ad242x_i2c_bus *ad242x_master_get_bus(struct ad242x_master *master);
> +const char *ad242x_master_get_clk_name(struct ad242x_master *master);
> +unsigned int ad242x_master_get_clk_rate(struct ad242x_master *master);
> +
> +int ad242x_slave_read(struct ad242x_i2c_bus *bus,
> +		      struct regmap *master_regmap,
> +		      uint8_t node_id, uint8_t reg, unsigned int *val);
> +int ad242x_slave_write(struct ad242x_i2c_bus *bus,
> +		       struct regmap *master_regmap,
> +		       uint8_t node_id, uint8_t reg, unsigned int val);
> +
> +#endif

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

  reply	other threads:[~2019-12-17 13:40 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-12-09 18:35 [PATCH 00/10] mfd: Add support for Analog Devices A2B transceiver Daniel Mack
2019-12-09 18:35 ` [PATCH 01/10] dt-bindings: mfd: Add documentation for ad242x Daniel Mack
2019-12-19 19:29   ` Rob Herring
2019-12-09 18:35 ` [PATCH 02/10] dt-bindings: i2c: Add documentation for ad242x i2c controllers Daniel Mack
2020-01-08  3:45   ` Rob Herring
2019-12-09 18:35 ` [PATCH 03/10] dt-bindings: gpio: Add documentation for ad242x GPIO controllers Daniel Mack
2019-12-09 18:35 ` [PATCH 03/10] dt-bindings: gpio: Add documentation for AD242x " Daniel Mack
2019-12-09 18:35 ` [PATCH 04/10] dt-bindings: clock: Add documentation for AD242x clock providers Daniel Mack
2019-12-24  7:32   ` Stephen Boyd
2019-12-09 18:35 ` [PATCH 05/10] dt-bindings: sound: Add documentation for AD242x codecs Daniel Mack
2019-12-09 18:35 ` [PATCH 06/10] mfd: Add core driver for AD242x A2B transceivers Daniel Mack
2019-12-17 13:39   ` Lee Jones [this message]
2019-12-17 13:46     ` Lee Jones
2019-12-17 19:36       ` Daniel Mack
2019-12-17 19:24     ` Daniel Mack
2019-12-18 11:20       ` Luca Ceresoli
2019-12-17 19:16   ` [alsa-devel] " Pierre-Louis Bossart
2019-12-18  9:40     ` Daniel Mack
2019-12-09 18:35 ` [PATCH 07/10] i2c: Add driver for AD242x bus controller Daniel Mack
2019-12-12 16:11   ` Luca Ceresoli
2019-12-12 16:33     ` Wolfram Sang
2019-12-15 20:27       ` Daniel Mack
2019-12-17  8:35         ` Luca Ceresoli
2019-12-17 18:17           ` Daniel Mack
2019-12-09 18:35 ` [PATCH 08/10] gpio: Add driver for AD242x GPIO controllers Daniel Mack
2019-12-09 18:35 ` [PATCH 09/10] clk: Add support for AD242x clock output providers Daniel Mack
2019-12-24  7:46   ` Stephen Boyd
2019-12-09 18:35 ` [PATCH 10/10] ASoC: Add codec component for AD242x nodes Daniel Mack
2019-12-16 14:23   ` Mark Brown
2019-12-17 19:28   ` [alsa-devel] " Pierre-Louis Bossart
2019-12-18  9:49     ` Daniel Mack
2019-12-18 15:32       ` Pierre-Louis Bossart
2019-12-17 19:29 ` [alsa-devel] [PATCH 00/10] mfd: Add support for Analog Devices A2B transceiver Pierre-Louis Bossart
2019-12-18  9:53   ` Daniel Mack

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20191217133952.GJ18955@dell \
    --to=lee.jones@linaro.org \
    --cc=alsa-devel@alsa-project.org \
    --cc=broonie@kernel.org \
    --cc=daniel@zonque.org \
    --cc=devicetree@vger.kernel.org \
    --cc=lars@metafoo.de \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mturquette@baylibre.com \
    --cc=pascal.huerst@gmail.com \
    --cc=robh+dt@kernel.org \
    --cc=sboyd@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).