2024-05-17 12:59:22

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 00/13] Analog Devices Inc. Automotive Audio Bus (A2B) support

This series adds support for Analog Devices' Automotive Audio Bus (A2B)
transceivers in the AD242x, AD241x and AD240x series.

The series is based on today's linux-next.

INTRODUCTION
============

A2B buses consist of a seires of daisy-chained A2B transceiver ICs known
as nodes. Every bus consists of a "main" node (attached to the host
processor via I2C) and, typically, a series of "subordinate" nodes
connected in a daisy-chained manner via UTP (unshielded twisted pair).
Digital audio transport is delivered via a standard I2S/TDM interface
between the host and main node. The UTP links may extend for several
meters, exposing peripheral function to the host over a distance.

┌──────┐ I2C ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ ├─────┤ │ UTP │ │ │ │ │ │
│ host │ │ main │XXXXX│ sub1 │XXXXX│ sub2 │XX ... XX│ subN │
│ ├─────┤ │ │ │ │ │ │ │
└──────┘ TDM └──────┘ └──────┘ └──────┘ └──────┘
\______________________________ ... ______/

A2B bus

All A2B nodes expose a number of peripheral functions, and the A2B
protocol allows for them to be controlled directly by the host processor
over I2C:

┌──────────────────────┐
├─────┐ ┌─────┤
<--- ... \/\/\/│ TRX │ AD2428 │ TRX │\/\/\/ ... --->
/\/\/\│ A │ │ B │/\/\/\
towards ├─────┘ └─────┤ towards last
main node │ ┌──────┐ ┌─────────┐ │ subordinate node
│ │ I2C │ │ I2S/TDM │ │
│ │ │ │ PDM │ │
│ └──────┘ └─────────┘ │
│ ┌──────┐ ┌─────────┐ │
│ │ GPIO │ │ CLOCK │ │
│ └──────┘ └─────────┘ │
└──────────────────────┘

The peripheral functions are as follows:

I2C interface
-------------

On main nodes the I2C interface must by definition function in
target (slave) mode, as it is attached to the host for management.
On subordinate nodes the I2C interface functions in controller
(master) mode, providing an additional I2C adapter to the host for
each subordinate node connected to the A2B bus.

The control registers of each node, both main and subordinate, can
be accessed directly (resp. indirectly) via the I2C interface between
the host and main node.

I2S/TDM and PDM interface
-------------------------

This is the main feature of A2B, whence the name Audio Bus. Each node
has two TX and RX pins for I2S function. On the main node the
interface functions as a bit- and frame-clock consumer. On the
subordinate nodes it functions as a provider. PCM data is transported
"upstream" (towards the main node) and "downstream" (towards the last
subordinate node) via the A2B protocol in so-called A2B superframes.

Through control registers of the respective nodes, a user can
configure specific TDM slots to be placed into the superframes for
transport up or down the A2B bus. Similarly, slots can be pulled from
the superframes and retransmitted on a given node's I2S/TDM interface.

In addition, the pins on a node can be reconfigured to run in PDM mode
for applications such as microphone recording. The data is placed into
the superframe for forwarding up or down the bus.

GPIO
----

Unused pins of a node can be muxed to GPIO mode. The GPIO block
supports interrupts as well, so each node connected to the bus can
expose a gpiochip/irqchip to the host.

CLOCK
-----

Certain pins can also be muxed to become clock outputs. The clock
outputs divide the internal PLL, which runs at 2048 times the frame
sync clock frequency of the main node.

For further details on A2B architecture, check the documentation from
Analog Devices, which is freely available online; this documentation was
used to write the driver:

AD2420(W)/6(W)/7(W)/8(W)/9(W) Automotive Audio Bus A2B Transceiver
Technical Reference, Revision 1.1, October 2019, Part Number 82-100138-01

DRIVER DESIGN
=============

To control these chips, a bus driver core has been added under the
drivers/a2b/ directory. Drivers for peripheral functionality are instead
placed in their relevant subsystem directory. What follows is a brief
description of the design.

The A2B core introduces a bus for two device types, and a class type to
represent the overall bus:

* struct bus_type a2b_bus:

A bus type for A2B buses. There are two device types:

* struct device_type a2b_node: an A2B node, main or subordinate
* struct device_type a2b_func: a peripheral function of a node,
e.g. gpio or codec

* struct class a2b_bus_class:

A class type representing the overall A2B bus instance. There is one
device type:

* struct device_type a2b_bus: an A2B bus instance

Interrupts can be raised by any node and are multiplexed via the IRQ
line of the main node connected to the host processor. The interrupts
are then dispatched to the relevant node, or, in the case of GPIO, to
the a2b_func device representing the irqchip.

At the top level there is the i2c driver, ad24xx-i2c, hereby called an
A2B interface driver. It sets up the bare necessities required to
interface with the nodes and to demultiplex interrupts. In principle
there exist other interfaces for A2B - newer series chips, for example,
support also SPI, but the hardware is not available for me to test on,
so this series only implements the I2C interface.

The role of the interface driver is to register a class device of type
a2b_bus. The interface driver should implement relevant a2b_bus_ops,
which provide the core with a way to talk to the nodes in an
interface-agnostic fashion.

When an a2b_bus device is registered, the core will traverse the OF node
of the top-level i2c node and look for a child node named node@0. This
node is used to create a new bus device of type a2b_node. It corresponds
to the main node, and so it is always assumed to be present.

The main node is probed by the node driver, ad24xx-node. This is a
generic driver and supports both main and subordinate nodes, as
the setup is very similar.

The A2B core takes a specific interest in the initialization of a2b_node
devices because it must administer the "discovery" process. Discovery
can be thought of as enumeration, and involves programming specific
control registers of the main node and waiting for a specific interrupt
to determine whether there is another subordinate node attached.
According to the design of A2B, the discovery process must happen in a
serial manner, which is to say that it is not possible to discover
subordinate node N until subordinate node N-1 has been discovered.
Moreover, the main node must also be set up before any discovery can
take place.

To address this, the node driver must configure certain a2b_node_ops
before registering its a2b_node with the core via a2b_register_node().
Most of the node setup then takes place in the a2b_node_ops::setup()
function, which is called synchronously by the core. When a node has
been successfully set up, the core will then attempt (another)
discovery. This algorithm will proceed until there are no further nodes
to discover.

When a node is set up by the node driver, it can then optionally add
peripheral function a2b_func devices. These devices are probed by their
respective driver, be it for the GPIO subsystem, sound subsystem, etc.

As was alluded to earlier, the driver relies on OF/device tree for the
hardware description. Here is a simplified example:

i2c {
a2b@68 { interface
compatible = "adi,ad2428";
reg = <0x68>;
#interrupt-cells = <1>;
interrupt-controller;

node@0 { # main node
compatible = "adi,ad2428-node";
reg = <0>
interrupts = <0>;

clock { # main node clock
compatible = "adi,ad2428-clk";
#clock-cells = <1>;
clock-output-names = "clkout1", "clkout2";
};

codec { # main node audio codec
compatible = "adi,ad2428-codec";
};
};

node@1 { # subordinate node 1
compatible = "adi,ad2428-node";
reg = <1>;
interrupts = <1>;

gpio { # subordinate node 1 GPIO
compatible = "adi,ad2428-gpio";
#interrupt-cells = <2>;
interrupt-controller;
#gpio-cells = <2>;
gpio-controller;
};

codec { # subordinate node 1 audio codec
compatible = "adi,ad2428-codec";
};
};

node@2 { # subordinate node 2 ...
compatible = "adi,ad2428-node";
reg = <2>;
interrupts = <2>;

codec { # subordinate node 2 audio codec
compatible = "adi,ad2428-codec";
};
};

... more nodes can follow here
};
};

It is not a requirement that all described nodes are actually connected
to the bus.

While the device tree structure is flat, the device topology is nested.
That means a parent-child device topology as follows:

1-0068
└─ a2b-0
└─ a2b-0.0
├─ a2b-0.1
│ ├─ a2b-0.2
│ │ └─ a2b-0.2-codec
│ ├─ a2b-0.1-gpio
│ └─ a2b-0.1-codec
├─ a2b-0.0-clk
└─ a2b-0.0-codec

Where:

* 1-0068 is the top-level I2C device
* a2b-0 is the a2b_bus class device
* a2b-0.0 is the main node a2b_node device
* a2b-0.N is the Nth subordinate node a2b_node device
* a2b-0.N-codec is the Nth node's audio codec a2b_func device
* ditto for a2b-0.N-{clk,gpio} etc.

PATCH ORDER
===========

The series is split up into per-subsystem pieces to make review easier
for relevant maintainers.

Broadly speaking the series goes as follows:

1. Add the driver core

2. Add device tree bindings for AD24xx chips

3. Add device drivers for AD24xx chips
a) I2C interface driver
b) node driver
c) per-subsystem functional block drivers

4. Add an example "custom" A2B node driver for a Bang & Olufsen
speaker (Beosound Shape)

5. Add relevant entries to the MAINTAINERS file

HELP WANTED
===========

I am aware that the current implementation may not be entirely correct
from a driver model perspective, and there are also some shortcomings
which are already evident. Having said that, I am in need of people to
bounce ideas off, so I figure it is OK to submit the code in its current
form. Below I want to identify a few issues and get some feedback.

How will this get merged?
-------------------------

I am a bit clueless as to the review and merge process for a patch
series that affects multiple subsystems. If somebody can point me to
an explanation (perhaps already stated on the mailing list?), that
would be great.

I made some assumptions on how to structure this series but I am happy
to amend if things are not correct.

fw_devlink=on and device registration during discovery
------------------------------------------------------

In the current architecture, device_register() is called for each A2B
node if and only if the node has been discovered. Since the driver is
using OF, this seems to violate some assumptions of fw_devlink. Let me
quote a comment from device_links_driver_bound():

| If a device binds successfully, it's expected to have created all
| the device links it needs to or make new device links as it needs
| them. So, fw_devlink no longer needs to create device links to any
| of the device's suppliers.

When using the audio-graph-card2 ASoC machine driver, I have
experienced fw_devlink=on preventing both the subordinate nodes'
codecs and the machine driver from being probed, due to a cyclic
dependency. I have been able to work around this by using Saravana's
new post-init-providers device tree property, whereby I reference by
phandle the corresponding OF graph endpoint of the machine driver in
each codec's device tree node. But this seems like a bit of a hack.
And I suspect that the logic (and benefits) of fw_devlink=on will be
broken in other places by this behaviour as well.

Looking at other subsystems, I see that i2c will register a device for
each child node, and allow the matching driver's probe function to
return -ENODEV/-ENXIO if the device doesn't respond. In that case, the
devices are still registered but remain unbound.

A similar strategy could be done for A2B as well I think.

First it is important to note that discovery must happen sequentially,
i.e. node N cannot be discovered until node N-1 has been discovered
and its driver bound. This is because node N-1's driver is needed to
access some of its registers (to enable B-side transceiver switching,
etc.).

Node discovery could instead be wired into the a2b_bus_probe()
function, such that if called for a struct device corresponding to
node N, and if nodes 0..N-1 have already been probed, then the code
currently in a2b_bus_discovery_work() can be performed to try and
discover node N. If discovered, the a2b_drv->probe() call can then be
performed. If not discovered, a2b_bus_probe() can return -ENODEV and
we can conclude that the initial discovery algorithm has completed.

Here I need some help though. As already stated, there is a strict
ordering dependency, so I need to ensure that nodes 0..N-1 have
actually been probed. If I am already calling device_register() on all
nodes with an OF node to begin with, then I need to establish some
managed device links to work with this properly. Correct?

If that works fine, then there is at least one remaining problem.
Namely, we at B&O are already using this driver in a product which has
an OF description for up to 10 A2B nodes, but where the customer may
not necessarily have all nodes connected. This is the Beosound Shape
setup described in the last patches of this series. To handle this, I
use a modified audio-graph-card2 driver which defers its probe until
the A2B bus has finished its discovery. But in the case where some are
not connected, I will get -ENODEV. What I want to understand here is,
how will fw_devlink respond? To understand, here is a simplified
representation of the device tree:

sound {
compatible = "adi,a2b-audio-graph-card2";
...

ports {
port@0 { snd_0_ep: endpoint { remote-endpoint = <&a2b0_codec_ep>; }; };
port@1 { snd_1_ep: endpoint { remote-endpoint = <&a2b1_codec_ep>; }; };
...
port@10 { snd_10_ep: endpoint { remote-endpoint = <&a2b10_codec_ep>; }; };
};
};

a2b@68 {
compatible = "adi,ad2428";
...

node@0 {
codec {
port { a2b0_codec_ep: endpoint { remote-endpoint = <&snd_0_ep>; }; };
};
};

node@1 {
codec {
port { a2b1_codec_ep: endpoint { remote-endpoint = <&snd_1_ep>; }; };
};
};

...

node@10 {
codec {
port { a2b1_codec_ep: endpoint { remote-endpoint = <&snd_10_ep>; }; };
};
};
};

The question I have is, how can I ensure that the machine driver will
probe (sound node) when discovery has completed, even if not all
codecs are available because not all A2B nodes are connected?

I understand this might be pushing the limits of device tree somewhat,
but ASoC is quite device tree heavy and I would like to get some
advice on what the best way to proceed is. And if going for the
proposed amendment above (in the style of i2c, with discovery
integrated into a2b_bus_probe()), will post-init-providers still save
me in the above situation?

Perhaps there is a simpler approach here if the assumption I quoted at
the top of this section is not so crucial. In that case, maybe the
current implementation is OK?

Device dependencies and discovery
---------------------------------

Yet another complication that we have recently come upon is what to do
when the discovery of a node N requires probing devices which
topologically are beneath the I2C controller of node N. Concretely, we
have two boards 1 and 2 where the B-side of board 1's A2B node is
routed through a mux. The mux is controlled via GPIO, and those GPIOs
are exported by an I2C GPIO expander connected to board 1's A2B's I2C
controller. Suppose board 1's A2B node is called sub1 and board 2's is
called sub2. Then the situation looks like this:

X
┌──────┐ X ┌──────┐
│ │ ┌─────┐ │ │
... XXX│ sub1 │XXXXX│ mux │XXXXX│ sub2 │
│ │ └──▲──┘ │ │
└──────┘ │ └──────┘
│ │
│ I2C │
│ │
┌──────┐ │
│ GPIO │────────┘
└──────┘

The mux itself can be modelled as a simple GPIO mux and hence a
platform driver. But in order to discover node sub2, it must get
probed and configured appropriately; the default hardware strapping
would cause discovery to fail.

This is not a crucial issue right now and is something we can work
around, and I don't think it's necessarily a blocker for getting this
series merged upstream. But feedback on how to approach this might
inform some useful changes to this series.

The issue here is that some driver needs to acquire the mux and set
it. The discovery process must be delayed as long as this returns
-EPROBE_DEFER. Would it be appropriate for the A2B core to acquire
this mux based on device tree properties of sub1?

Thank you in advance for all review comments and feedback.

Signed-off-by: Alvin Šipraga <[email protected]>
---
Alvin Šipraga (13):
a2b: add A2B driver core
regmap: add A2B support
dt-bindings: a2b: Analog Devices AD24xx devices
a2b: add AD24xx I2C interface driver
a2b: add AD24xx node driver
gpio: add AD24xx GPIO driver
ASoC: codecs: add AD24xx codec driver
clk: add AD24xx clock driver
i2c: add AD24xx I2C controller driver
dt-bindings: vendor-prefixes: add Bang & Olufsen a/s
dt-bindings: a2b: add compatible string for Beosound Shape node
a2b: add Beosound Shape node driver
MAINTAINERS: add maintainership for A2B drivers

.../devicetree/bindings/a2b/adi,ad24xx-clk.yaml | 53 +
.../devicetree/bindings/a2b/adi,ad24xx-codec.yaml | 52 +
.../devicetree/bindings/a2b/adi,ad24xx-gpio.yaml | 76 ++
.../devicetree/bindings/a2b/adi,ad24xx-i2c.yaml | 55 +
.../devicetree/bindings/a2b/adi,ad24xx.yaml | 254 ++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 12 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/a2b/Kconfig | 60 +
drivers/a2b/Makefile | 13 +
drivers/a2b/a2b.c | 1252 ++++++++++++++++++++
drivers/a2b/ad24xx-i2c.c | 532 +++++++++
drivers/a2b/ad24xx-node.c | 887 ++++++++++++++
drivers/a2b/ad24xx-node.h | 42 +
drivers/a2b/beo-shape-node.c | 584 +++++++++
drivers/base/regmap/Kconfig | 6 +-
drivers/base/regmap/Makefile | 1 +
drivers/base/regmap/regmap-a2b.c | 82 ++
drivers/clk/Kconfig | 7 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-ad24xx.c | 341 ++++++
drivers/gpio/Kconfig | 6 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ad24xx.c | 302 +++++
drivers/i2c/busses/Kconfig | 7 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ad24xx.c | 121 ++
include/linux/a2b/a2b.h | 444 +++++++
include/linux/a2b/ad24xx.h | 892 ++++++++++++++
include/linux/regmap.h | 38 +
sound/soc/codecs/Kconfig | 5 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ad24xx-codec.c | 665 +++++++++++
34 files changed, 6798 insertions(+), 1 deletion(-)
---
base-commit: c75962170e49f24399141276ae119e6a879f36dc
change-id: 20231212-a2b-e54ba46a9e05



2024-05-17 12:59:34

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 03/13] dt-bindings: a2b: Analog Devices AD24xx devices

From: Alvin Šipraga <[email protected]>

Add device tree bindings for the AD24xx series A2B transceiver chips,
including their functional blocks.

Signed-off-by: Alvin Šipraga <[email protected]>
---
.../devicetree/bindings/a2b/adi,ad24xx-clk.yaml | 53 +++++
.../devicetree/bindings/a2b/adi,ad24xx-codec.yaml | 52 +++++
.../devicetree/bindings/a2b/adi,ad24xx-gpio.yaml | 76 +++++++
.../devicetree/bindings/a2b/adi,ad24xx-i2c.yaml | 55 +++++
.../devicetree/bindings/a2b/adi,ad24xx.yaml | 253 +++++++++++++++++++++
5 files changed, 489 insertions(+)

diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml
new file mode 100644
index 000000000000..819efaa6a3f9
--- /dev/null
+++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/a2b/adi,ad24xx-clk.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices Inc. AD24xx clock functional block
+
+maintainers:
+ - Alvin Šipraga <[email protected]>
+
+allOf:
+ - $ref: /schemas/clock/clock.yaml
+
+properties:
+ compatible:
+ enum:
+ - adi,ad2420-clk
+ - adi,ad2421-clk
+ - adi,ad2422-clk
+ - adi,ad2425-clk
+ - adi,ad2426-clk
+ - adi,ad2427-clk
+ - adi,ad2428-clk
+ - adi,ad2429-clk
+
+required:
+ - compatible
+ - clock-output-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ a2b {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ node@1 {
+ compatible = "adi,ad2425-node";
+ reg = <1>;
+ interrupts = <1>;
+ adi,tdm-mode = <16>;
+ adi,tdm-slot-size = <32>;
+
+ clock {
+ compatible = "adi,ad2425-clk";
+ #clock-cells = <1>;
+ clock-indices = <1>;
+ clock-output-names = "A2B1 CLKOUT2";
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml
new file mode 100644
index 000000000000..eee12f1c810e
--- /dev/null
+++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml
@@ -0,0 +1,52 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/a2b/adi,ad24xx-codec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices Inc. AD24xx I2S/TDM functional block
+
+maintainers:
+ - Alvin Šipraga <[email protected]>
+
+allOf:
+ - $ref: /schemas/sound/dai-common.yaml#
+
+properties:
+ compatible:
+ enum:
+ - adi,ad2403-codec
+ - adi,ad2410-codec
+ - adi,ad2425-codec
+ - adi,ad2428-codec
+ - adi,ad2429-codec
+
+ '#sound-dai-cells':
+ const: 0
+
+required:
+ - compatible
+ - '#sound-dai-cells'
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ a2b {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ node@2 {
+ compatible = "adi,ad2428-node";
+ reg = <2>;
+ interrupts = <2>;
+ adi,tdm-mode = <8>;
+ adi,tdm-slot-size = <32>;
+
+ codec {
+ compatible = "adi,ad2428-codec";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "A2B Sub2";
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-gpio.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-gpio.yaml
new file mode 100644
index 000000000000..e2b99c711a47
--- /dev/null
+++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-gpio.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/a2b/adi,ad24xx-gpio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices Inc. AD24xx GPIO functional block
+
+maintainers:
+ - Alvin Šipraga <[email protected]>
+
+properties:
+ compatible:
+ enum:
+ - adi,ad2401-gpio
+ - adi,ad2402-gpio
+ - adi,ad2403-gpio
+ - adi,ad2410-gpio
+ - adi,ad2420-gpio
+ - adi,ad2421-gpio
+ - adi,ad2422-gpio
+ - adi,ad2425-gpio
+ - adi,ad2426-gpio
+ - adi,ad2427-gpio
+ - adi,ad2428-gpio
+ - adi,ad2429-gpio
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ const: 2
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+
+ gpio-line-names: true
+ gpio-reserved-ranges: true
+
+required:
+ - compatible
+ - gpio-controller
+ - '#gpio-cells'
+
+dependencies:
+ interrupt-controller: [ '#interrupt-cells' ]
+ '#interrupt-cells': [ interrupt-controller ]
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ a2b {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ node@0 {
+ compatible = "adi,ad2428-node";
+ reg = <0>;
+ interrupts = <0>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ adi,tdm-mode = <16>;
+ adi,tdm-slot-size = <32>;
+
+ gpio {
+ compatible = "adi,ad2428-gpio";
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ gpio-reserved-ranges = <0 1>;
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-i2c.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-i2c.yaml
new file mode 100644
index 000000000000..ac52f184004d
--- /dev/null
+++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-i2c.yaml
@@ -0,0 +1,55 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/a2b/adi,ad24xx-i2c.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices Inc. AD24xx I2C controller functional block
+
+maintainers:
+ - Alvin Šipraga <[email protected]>
+
+allOf:
+ - $ref: /schemas/i2c/i2c-controller.yaml
+
+properties:
+ compatible:
+ enum:
+ - adi,ad2401-i2c
+ - adi,ad2402-i2c
+ - adi,ad2403-i2c
+ - adi,ad2410-i2c
+ - adi,ad2420-i2c
+ - adi,ad2421-i2c
+ - adi,ad2422-i2c
+ - adi,ad2425-i2c
+ - adi,ad2426-i2c
+ - adi,ad2427-i2c
+ - adi,ad2428-i2c
+
+required:
+ - compatible
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ a2b {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ node@1 {
+ compatible = "adi,ad2425-node";
+ reg = <1>;
+ interrupts = <1>;
+ adi,tdm-mode = <16>;
+ adi,tdm-slot-size = <32>;
+
+ i2c {
+ compatible = "adi,ad2425-i2c";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clock-frequency = <400000>;
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
new file mode 100644
index 000000000000..dcda15e8032a
--- /dev/null
+++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
@@ -0,0 +1,253 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/a2b/adi,ad24xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices Inc. AD24xx Automotive Audio Bus A2B Transceiver
+
+description: |
+ AD24xx chips provide A2B bus functionality together with several peripheral
+ functions, including GPIO, I2S/TDM, an I2C controller interface, and
+ programmable clock outputs.
+
+maintainers:
+ - Alvin Šipraga <[email protected]>
+
+properties:
+ compatible:
+ enum:
+ - adi,ad2403
+ - adi,ad2410
+ - adi,ad2425
+ - adi,ad2428
+ - adi,ad2429
+
+ reg-names:
+ items:
+ - const: base
+ - const: bus
+
+ reg:
+ items:
+ - description: Normal I2C address of the chip
+ - description: Auxiliary BUS_ADDR I2C address of the chip
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ clock-names:
+ items:
+ - const: sync
+
+ clocks:
+ items:
+ - description: SYNC input pin clock source
+
+ vin-supply:
+ description: Optional regulator for supply voltage to VIN pin
+
+ bus-supply:
+ description: Optional regulator for out-of-band supply voltage to
+ subodrinate nodes' VIN pins
+
+ interrupts: true
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+
+patternProperties:
+ '^node@[0-9]+$':
+ type: object
+ unevaluatedProperties: false
+
+ properties:
+ compatible:
+ enum:
+ - adi,ad2401-node
+ - adi,ad2402-node
+ - adi,ad2403-node
+ - adi,ad2410-node
+ - adi,ad2420-node
+ - adi,ad2421-node
+ - adi,ad2422-node
+ - adi,ad2425-node
+ - adi,ad2426-node
+ - adi,ad2427-node
+ - adi,ad2428-node
+ - adi,ad2429-node
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 1
+
+ gpio:
+ $ref: adi,ad24xx-gpio.yaml#
+
+ codec:
+ $ref: adi,ad24xx-codec.yaml#
+
+ i2c:
+ $ref: adi,ad24xx-i2c.yaml#
+
+ clock:
+ $ref: adi,ad24xx-clk.yaml#
+
+ adi,tdm-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: TDM mode
+ enum: [2, 4, 8, 12, 16, 20, 24, 32]
+
+ adi,tdm-slot-size:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: TDM slot size
+ enum: [16, 32]
+
+ adi,invert-sync:
+ description: Falling edge of SYNC pin indicates the start of an audio
+ frame, as opposed to rising edge.
+ type: boolean
+
+ adi,early-sync:
+ description: The SYNC pin changes one cycle before the MSB of the first
+ data slot.
+ type: boolean
+
+ adi,alternating-sync:
+ description: Drive SYNC pin during first half of I2S/TDM data
+ transmission rather than just pulsing it for once cycle.
+ type: boolean
+
+ adi,rx-on-dtx1:
+ description: Use the DTX1 pin for I2S/TDM RX in place of the DRX1 pin.
+ type: boolean
+
+ adi,a2b-external-switch-mode-1:
+ description: Use external switch mode 1 instead of 0 on the assumption
+ that the downstream node is not using A2B bus power.
+ type: boolean
+
+ adi,drive-strength:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Configures drive strength low (0) or high (1, default).
+ enum: [0, 1]
+ default: 1
+
+ adi,invert-interrupt:
+ description: Invert polarity of IRQ pin, making it active low.
+ type: boolean
+
+ adi,tristate-interrupt:
+ description: Rather than always actively driving the IRQ pin, only drive
+ when the interrupt is active and otherwise set to tristate (high-Z).
+ type: boolean
+
+ required:
+ - compatible
+ - reg
+ - adi,tdm-mode
+ - adi,tdm-slot-size
+
+ dependencies:
+ interrupt-controller: [ '#interrupt-cells' ]
+ '#interrupt-cells': [ interrupt-controller ]
+
+required:
+ - compatible
+ - reg-names
+ - reg
+ - clock-names
+ - clocks
+ - '#address-cells'
+ - '#size-cells'
+ - interrupts
+ - interrupt-controller
+ - '#interrupt-cells'
+ - node@0
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ sync_clk: sync-clock {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ clock-frequency = <48000>;
+ };
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ a2b@68 {
+ compatible = "adi,ad2428";
+ reg-names = "base", "bus";
+ reg = <0x68>, <0x69>;
+ clock-names = "sync";
+ clocks = <&sync_clk>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupts = <42>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
+
+ node@0 {
+ compatible = "adi,ad2428-node";
+ reg = <0>;
+ interrupts = <0>;
+ adi,tdm-mode = <16>;
+ adi,tdm-slot-size = <32>;
+
+ codec {
+ compatible = "adi,ad2428-codec";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "A2B Main";
+ };
+ };
+
+ node@1 {
+ compatible = "adi,ad2425-node";
+ reg = <1>;
+ interrupts = <1>;
+ adi,tdm-mode = <8>;
+ adi,tdm-slot-size = <32>;
+
+ gpio {
+ compatible = "adi,ad2425-gpio";
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ gpio-controller;
+ #gpio-cells = <2>;
+ };
+
+ codec {
+ compatible = "adi,ad2425-codec";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "A2B Sub1";
+ };
+
+ i2c {
+ compatible = "adi,ad2425-i2c";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ eeprom@50 {
+ compatible = "atmel,24c64";
+ reg = <0x50>;
+ };
+ };
+ };
+ };
+ };

--
2.44.0


2024-05-17 12:59:37

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 02/13] regmap: add A2B support

From: Alvin Šipraga <[email protected]>

Add regmap support for A2B drivers.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/base/regmap/Kconfig | 6 ++-
drivers/base/regmap/Makefile | 1 +
drivers/base/regmap/regmap-a2b.c | 82 ++++++++++++++++++++++++++++++++++++++++
include/linux/regmap.h | 38 +++++++++++++++++++
4 files changed, 126 insertions(+), 1 deletion(-)

diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index b1affac70d5d..df9ad0c9a338 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -5,7 +5,7 @@

config REGMAP
bool
- default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM || REGMAP_MDIO || REGMAP_FSI)
+ default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM || REGMAP_MDIO || REGMAP_FSI || REGMAP_A2B)
select IRQ_DOMAIN if REGMAP_IRQ
select MDIO_BUS if REGMAP_MDIO
help
@@ -91,3 +91,7 @@ config REGMAP_SPI_AVMM
config REGMAP_FSI
tristate
depends on FSI
+
+config REGMAP_A2B
+ tristate
+ depends on A2B
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index 5fdd0845b45e..979e10419f8f 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o
obj-$(CONFIG_REGMAP_SPI_AVMM) += regmap-spi-avmm.o
obj-$(CONFIG_REGMAP_MDIO) += regmap-mdio.o
obj-$(CONFIG_REGMAP_FSI) += regmap-fsi.o
+obj-$(CONFIG_REGMAP_A2B) += regmap-a2b.o
diff --git a/drivers/base/regmap/regmap-a2b.c b/drivers/base/regmap/regmap-a2b.c
new file mode 100644
index 000000000000..ba5fbc5ed6eb
--- /dev/null
+++ b/drivers/base/regmap/regmap-a2b.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Register map access API - A2B support
+//
+// Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+
+#include <linux/regmap.h>
+#include <linux/a2b/a2b.h>
+
+static int regmap_a2b_write(void *context, const void *data, size_t count)
+{
+ struct a2b_node *node = context;
+ struct a2b_bus *bus = node->bus;
+ const u8 *d = data;
+ u8 reg;
+ int ret;
+ int i;
+
+ reg = d[0];
+
+ for (i = 0; i < count - 1; i++) {
+ ret = bus->ops->write(bus, node, reg + i, d[i + 1]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int regmap_a2b_read(void *context, const void *reg_buf, size_t reg_size,
+ void *val_buf, size_t val_size)
+{
+ struct a2b_node *node = context;
+ struct a2b_bus *bus = node->bus;
+ u8 reg = ((u8 *)reg_buf)[0];
+ u8 *v = val_buf;
+ int ret;
+ int i;
+
+ if (reg_size != 1)
+ return -EINVAL;
+
+ for (i = 0; i < val_size; i++) {
+ unsigned int tmp;
+
+ ret = bus->ops->read(bus, node, reg + i, &tmp);
+ if (ret)
+ return ret;
+
+ v[i] = tmp & 0xFF;
+ }
+
+ return 0;
+}
+
+static const struct regmap_bus regmap_a2b = {
+ .write = regmap_a2b_write,
+ .read = regmap_a2b_read,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+struct regmap *__devm_regmap_init_a2b_node(struct a2b_node *node,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name)
+{
+ return __devm_regmap_init(&node->dev, &regmap_a2b, node, config,
+ lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__devm_regmap_init_a2b_node);
+
+struct regmap *__devm_regmap_init_a2b_func(struct a2b_func *func,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name)
+{
+ return __devm_regmap_init(&func->dev, &regmap_a2b, func->node, config,
+ lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__devm_regmap_init_a2b_func);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index a6bc2980a98b..742bcc110a95 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -37,6 +37,8 @@ struct regmap_range_cfg;
struct regmap_field;
struct snd_ac97;
struct sdw_slave;
+struct a2b_node;
+struct a2b_func;

/*
* regmap_mdio address encoding. IEEE 802.3ae clause 45 addresses consist of a
@@ -655,6 +657,14 @@ struct regmap *__regmap_init_fsi(struct fsi_device *fsi_dev,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name);
+struct regmap *__devm_regmap_init_a2b_node(struct a2b_node *node,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name);
+struct regmap *__devm_regmap_init_a2b_func(struct a2b_func *func,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name);

struct regmap *__devm_regmap_init(struct device *dev,
const struct regmap_bus *bus,
@@ -1207,6 +1217,34 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
__regmap_lockdep_wrapper(__devm_regmap_init_fsi, #config, \
fsi_dev, config)

+/**
+ * devm_regmap_init_a2b_node() - Initialise managed register map for A2B node
+ *
+ * @node: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap. The regmap will be automatically freed by the
+ * device management code.
+ */
+#define devm_regmap_init_a2b_node(node, config) \
+ __regmap_lockdep_wrapper(__devm_regmap_init_a2b_node, #config, node, \
+ config)
+
+/**
+ * devm_regmap_init_a2b_func() - Initialise managed register map for A2B func
+ *
+ * @func: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap. The regmap will be automatically freed by the
+ * device management code.
+ */
+#define devm_regmap_init_a2b_func(func, config) \
+ __regmap_lockdep_wrapper(__devm_regmap_init_a2b_func, #config, func, \
+ config)
+
int regmap_mmio_attach_clk(struct regmap *map, struct clk *clk);
void regmap_mmio_detach_clk(struct regmap *map);
void regmap_exit(struct regmap *map);

--
2.44.0


2024-05-17 12:59:39

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 01/13] a2b: add A2B driver core

From: Alvin Šipraga <[email protected]>

Add the initial driver core for the Automotive Audio Bus (A2B) from
Analog Devices Inc.

The driver core introduces a new bus type which will allow A2B drivers
to be added. The drivers are either for A2B nodes (read: A2B transceiver
chips) or for functional blocks of A2B (GPIO, codec, etc.). The driver
core implements a discovery algorithm and manages bus errors and device
lifetime.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/a2b/Kconfig | 13 +
drivers/a2b/Makefile | 6 +
drivers/a2b/a2b.c | 1252 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/a2b/a2b.h | 444 +++++++++++++++++
6 files changed, 1718 insertions(+)

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 7bdad836fc62..70b4d8156589 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -245,4 +245,6 @@ source "drivers/cdx/Kconfig"

source "drivers/dpll/Kconfig"

+source "drivers/a2b/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index fe9ceb0d2288..83ce67a854bd 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -191,5 +191,6 @@ obj-$(CONFIG_HTE) += hte/
obj-$(CONFIG_DRM_ACCEL) += accel/
obj-$(CONFIG_CDX_BUS) += cdx/
obj-$(CONFIG_DPLL) += dpll/
+obj-$(CONFIG_A2B) += a2b/

obj-$(CONFIG_S390) += s390/
diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
new file mode 100644
index 000000000000..4aaef2ea4460
--- /dev/null
+++ b/drivers/a2b/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A2B driver configuration
+#
+
+menuconfig A2B
+ tristate "A2B support"
+ select OF
+ help
+ A2B (Automotive Audio Bus) is a digital audio and control bus from
+ Analog Devices Inc.
+
+ If unsure, say N.
diff --git a/drivers/a2b/Makefile b/drivers/a2b/Makefile
new file mode 100644
index 000000000000..40c9821f61ee
--- /dev/null
+++ b/drivers/a2b/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for A2B drivers
+#
+
+obj-$(CONFIG_A2B) += a2b.o
diff --git a/drivers/a2b/a2b.c b/drivers/a2b/a2b.c
new file mode 100644
index 000000000000..c0837edde903
--- /dev/null
+++ b/drivers/a2b/a2b.c
@@ -0,0 +1,1252 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * A2B driver core
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ *
+ * Analog Devices Inc. documentation cited in some of the comments below:
+ *
+ * [1] AD2420(W)/6(W)/7(W)/8(W)/9(W) Automotive Audio Bus A2B Transceiver
+ * Technical Reference, Revision 1.1, October 2019, Part Number 82-100138-01
+ *
+ * [2] Datasheet for AD2420(W)/AD2426(W)/AD2427(W)/AD2428(W)/AD2429(W) Rev. C,
+ * July 2021
+ */
+
+#include <linux/of_device.h>
+#include <linux/delay.h>
+#include <linux/a2b/a2b.h>
+
+static bool is_registered;
+static DEFINE_IDA(a2b_ida);
+
+/*
+ * MISC
+ */
+
+static const char *a2b_error_to_string(enum a2b_error error)
+{
+ switch (error) {
+ case A2B_HDCNTERR:
+ return "HDCNTERR (header count error)";
+ case A2B_DDERR:
+ return "DDERR (data decoding error)";
+ case A2B_CRCERR:
+ return "CRCERR (CRC error)";
+ case A2B_DPERR:
+ return "DPERR (data parity error)";
+ case A2B_BECOVF:
+ return "BECOVF (bit error counter overflow)";
+ case A2B_SRFERR:
+ return "SRFERR (SRF miss error)";
+ case A2B_SRFCRCERR:
+ return "SRFCRCERR (SRF CRC error)";
+ case A2B_PWRERR_0:
+ return "PWRERR (positive terminal BP shorted to GND)";
+ case A2B_PWRERR_1:
+ return "PWRERR (negative terminal BN shorted to VBAT)";
+ case A2B_PWRERR_2:
+ return "PWRERR (BP shorted to BN)";
+ case A2B_PWRERR_3:
+ return "PWRERR (cable disconnected/open circuit/wrong port)";
+ case A2B_PWRERR_4:
+ return "PWRERR (cable is reverse connected/wrong port)";
+ case A2B_PWRERR_5:
+ return "PWRERR (undetermined fault)";
+ case A2B_I2CERR:
+ return "I2CERR (I2C error)";
+ case A2B_ICRCERR:
+ return "ICRCERR (interrupt CRC error)";
+ case A2B_PWRERR_6:
+ return "PWRERR (non-localized negative terminal BN short to GND)";
+ case A2B_PWRERR_7:
+ return "PWRERR (non-localized positive terminal BP short to VBAT)";
+ case A2B_IRQMSGERR:
+ return "IRQMSGERR (interrupt messaging error)";
+ case A2B_STARTUPERR:
+ return "STARTUPERR (startup error - return to factory)";
+ case A2B_SLVINTTYPERR:
+ return "SLVINTTYPERR (slave INTTYPE read error)";
+ default:
+ return "unknown error";
+ };
+}
+
+/*
+ * A2B BUS
+ */
+
+#define __a2b_bus_for_each_node(__bus, __node, __i) \
+ for (__i = 0; __i < A2B_MAX_NODES && (__node = __bus->nodes[__i]); i++)
+
+#define __a2b_bus_for_each_sub_node(__bus, __node, __i) \
+ for (__i = A2B_MAIN_ADDR + 1; \
+ __i < A2B_MAX_NODES && (__node = __bus->nodes[__i]); i++)
+
+static struct a2b_node *__a2b_bus_main_node(struct a2b_bus *bus)
+{
+ return bus->nodes[A2B_MAIN_ADDR];
+}
+
+static struct a2b_node *__a2b_bus_next_node(struct a2b_node *node)
+{
+ struct a2b_bus *bus = node->bus;
+
+ if (node->addr == A2B_MAX_NODES - 1)
+ return NULL;
+
+ return bus->nodes[node->addr + 1];
+}
+
+static struct a2b_node *__a2b_bus_last_node(struct a2b_bus *bus)
+{
+ struct a2b_node *last = NULL;
+ struct a2b_node *node;
+ int i;
+
+ __a2b_bus_for_each_node(bus, node, i)
+ last = node;
+
+ return last;
+}
+
+/* From [1] Table 9-1: A2B Master Node Response Offset (RESPOFFS) */
+static const unsigned int a2b_respoffs[A2B_TDMMODE_END][A2B_TDMSS_END] = {
+ [A2B_TDMMODE_2] = { 245, 238 },
+ [A2B_TDMMODE_4] = { 248, 245 },
+ [A2B_TDMMODE_8] = { 248, 248 },
+ [A2B_TDMMODE_12] = { 248, 248 },
+ [A2B_TDMMODE_16] = { 248, 248 },
+ [A2B_TDMMODE_20] = { 248, 248 },
+ [A2B_TDMMODE_24] = { 248, 248 },
+ [A2B_TDMMODE_32] = { 248, 248 },
+};
+
+/* Look-up table: [FMT][SIZE] -> A2B bus bits, cf. [1] Table 3-2 */
+static const unsigned int a2b_slot_bits[2][8] = {
+ [0] = {
+ [0] = 9, /* 8-bit w/o compression; parity */
+ [1] = 13, /* 12-bit w/o compression; parity */
+ [2] = 17, /* 16-bit w/o compression; parity */
+ [3] = 21, /* 20-bit w/o compression; parity */
+ [4] = 25, /* 24-bit w/o compression; parity */
+ [5] = 29, /* 28-bit w/o compression; parity */
+ [6] = 33, /* 32-bit w/o compression; parity */
+ [7] = 0, /* reserved */
+ },
+ [1] = {
+ [0] = 0, /* reserved */
+ [1] = 13, /* 16-bit w/ floating-point compression; parity */
+ [2] = 17, /* 20-bit w/ floating-point compression; parity */
+ [3] = 21, /* 24-bit w/ floating-point compression; parity */
+ [4] = 30, /* 24-bit w/o compression; ECC protection */
+ [5] = 0, /* reserved */
+ [6] = 39, /* 32-bit w/o compression; ECC protection */
+ [7] = 0, /* reserved */
+ },
+};
+
+static void __a2b_bus_calc_min_max_respcycs(struct a2b_bus *bus,
+ unsigned int *min_respcycs_up,
+ unsigned int *max_respcycs_dn)
+{
+ struct a2b_node *main = __a2b_bus_main_node(bus);
+ struct a2b_node *node;
+ struct a2b_slot_config *slot_config = &main->slot_req.slot_config;
+ enum a2b_slot_format slot_format_dn = slot_config->format[A2B_DIR_DOWN];
+ enum a2b_slot_format slot_format_up = slot_config->format[A2B_DIR_UP];
+ enum a2b_slot_size slot_size_dn = slot_config->size[A2B_DIR_DOWN];
+ enum a2b_slot_size slot_size_up = slot_config->size[A2B_DIR_UP];
+ unsigned int dnslot_size = a2b_slot_bits[slot_format_dn][slot_size_dn];
+ unsigned int upslot_size = a2b_slot_bits[slot_format_up][slot_size_up];
+ unsigned int respoffs =
+ a2b_respoffs[main->tdm_mode][main->tdm_slot_size];
+ int i;
+
+ /*
+ * More information about the RESPCYCS formula can be found in the
+ * Technical Reference [1] Appendix B "Response Cycle Formula".
+ */
+
+ *min_respcycs_up = 0xFF;
+ *max_respcycs_dn = 0;
+
+ __a2b_bus_for_each_sub_node(bus, node, i) {
+ unsigned int num_dnslots = node->slot_req.a_dnslots;
+ unsigned int num_upslots = node->slot_req.a_upslots;
+ unsigned int dnslot_activity = num_dnslots * dnslot_size;
+ unsigned int upslot_activity = num_upslots * upslot_size;
+ unsigned int respcycs_dn =
+ DIV_ROUND_UP(64 + dnslot_activity, 4) +
+ (4 * node->addr) + 2;
+ unsigned int respcycs_up =
+ respoffs - DIV_ROUND_UP(64 + upslot_activity, 4) + 1;
+
+ if (respcycs_dn > *max_respcycs_dn)
+ *max_respcycs_dn = respcycs_dn;
+
+ if (respcycs_up < *min_respcycs_up)
+ *min_respcycs_up = respcycs_up;
+ }
+}
+
+static unsigned int __a2b_bus_respcycs(struct a2b_bus *bus, int addr)
+{
+ unsigned int main_respcycs;
+ unsigned int min_respcycs_up;
+ unsigned int max_respcycs_dn;
+
+ __a2b_bus_calc_min_max_respcycs(bus, &min_respcycs_up,
+ &max_respcycs_dn);
+
+ main_respcycs = (max_respcycs_dn + min_respcycs_up) / 2;
+
+ if (addr == A2B_MAIN_ADDR)
+ return main_respcycs;
+
+ /*
+ * This formula is taken from [1] section 9-4 "Configuring Slave Node
+ * Response Cycles". Note that the driver indexes subordinate node
+ * addresses starting from 1.
+ */
+ return main_respcycs - (4 * (addr - 1));
+}
+
+static bool __a2b_bus_validate_structure(struct a2b_bus *bus)
+{
+ struct a2b_node *node;
+ unsigned int min_respcycs_up;
+ unsigned int max_respcycs_dn;
+ int i;
+
+ __a2b_bus_for_each_node(bus, node, i) {
+ struct a2b_node *next = __a2b_bus_next_node(node);
+ struct a2b_slot_req *req;
+ struct a2b_slot_req *nreq;
+
+ if (!next)
+ break;
+
+ req = &node->slot_req;
+ nreq = &next->slot_req;
+
+ if (req->b_dnslots != nreq->a_dnslots) {
+ dev_warn(&bus->dev,
+ "structure validation failed: "
+ "downstream slot mismatch: node %u(B) sends "
+ "%u slots but node (A)%u receives %u slots\n",
+ node->addr, req->b_dnslots, next->addr,
+ nreq->a_dnslots);
+
+ return false;
+ }
+
+ if (req->b_upslots != nreq->a_upslots) {
+ dev_warn(&bus->dev,
+ "structure validation failed: "
+ "upstream slot mismatch: node %u(B) receives "
+ "%u slots but node (A)%u sends %u slots\n",
+ node->addr, req->b_upslots, next->addr,
+ nreq->a_upslots);
+
+ return false;
+ }
+ }
+
+ __a2b_bus_calc_min_max_respcycs(bus, &min_respcycs_up,
+ &max_respcycs_dn);
+
+ if (max_respcycs_dn > min_respcycs_up) {
+ dev_warn(&bus->dev,
+ "structure validation failed: "
+ "insufficient bandwidth: "
+ "max_respcycs_dn(%u) > min_respcycs_up(%u)\n",
+ max_respcycs_dn, min_respcycs_up);
+
+ return false;
+ }
+
+ return true;
+}
+
+static bool __a2b_bus_new_structure_ready(struct a2b_bus *bus)
+{
+ struct a2b_node *node;
+ bool all = true;
+ bool none = true;
+ int i;
+
+ /*
+ * This is a primitive synchronization mechanism for
+ * a2b_node_request_slots(). The rule here is that a new structure is
+ * ready to be applied if all nodes have requested slots, or if none of
+ * them have requested slots.
+ *
+ * In the latter case, synchronous transmission of upstream and
+ * downstream data will be disabled globally on the bus. This protects
+ * against the scenario where the slot configuration written to the
+ * register map of a node in the system is invalid when compared with
+ * the configuration in other nodes.
+ */
+ __a2b_bus_for_each_node(bus, node, i) {
+ none &= !node->slots_requested;
+ all &= node->slots_requested;
+ }
+
+ return all || none;
+}
+
+static int __a2b_bus_new_structure(struct a2b_bus *bus)
+{
+ struct a2b_node *main = __a2b_bus_main_node(bus);
+ struct a2b_node *node;
+ bool dn_enable = false;
+ bool up_enable = false;
+ int ret;
+ int i;
+
+ __a2b_bus_for_each_node(bus, node, i) {
+ unsigned int respcycs = __a2b_bus_respcycs(bus, node->addr);
+
+ ret = node->ops->set_respcycs(node, respcycs);
+ if (ret)
+ return ret;
+
+ if (is_a2b_main(node))
+ continue;
+
+ /*
+ * Check for any downstream (resp. upstream) activity on the
+ * A-side of each subordinate node. This informs whether or not
+ * to enable synchronous transmission of data in each direction.
+ */
+ if (node->slot_req.a_dnslots)
+ dn_enable = true;
+
+ if (node->slot_req.a_upslots)
+ up_enable = true;
+ }
+
+ ret = main->ops->new_structure(main, &main->slot_req.slot_config,
+ dn_enable, up_enable);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int a2b_bus_new_structure(struct a2b_bus *bus)
+{
+ int ret;
+
+ mutex_lock(&bus->mutex);
+ ret = __a2b_bus_new_structure(bus);
+ mutex_unlock(&bus->mutex);
+
+ return ret;
+}
+
+unsigned long a2b_bus_status(struct a2b_bus *bus)
+{
+ unsigned long status;
+
+ mutex_lock(&bus->mutex);
+ status = bus->status;
+ mutex_unlock(&bus->mutex);
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(a2b_bus_status);
+
+static unsigned int __a2b_bus_num_subs(struct a2b_bus *bus)
+{
+ struct a2b_node *node;
+ unsigned int num = 0;
+ int i;
+
+ __a2b_bus_for_each_sub_node(bus, node, i)
+ num++;
+
+ return num;
+}
+
+unsigned int a2b_bus_num_subs(struct a2b_bus *bus)
+{
+ unsigned int n;
+
+ mutex_lock(&bus->mutex);
+ n = __a2b_bus_num_subs(bus);
+ mutex_unlock(&bus->mutex);
+
+ return n;
+}
+EXPORT_SYMBOL_GPL(a2b_bus_num_subs);
+
+static unsigned int __a2b_bus_num_nodes(struct a2b_bus *bus)
+{
+ return __a2b_bus_num_subs(bus) + 1;
+}
+
+unsigned int a2b_bus_num_nodes(struct a2b_bus *bus)
+{
+ unsigned int n;
+
+ mutex_lock(&bus->mutex);
+ n = __a2b_bus_num_nodes(bus);
+ mutex_unlock(&bus->mutex);
+
+ return n;
+}
+EXPORT_SYMBOL_GPL(a2b_bus_num_nodes);
+
+struct a2b_bus_del_node_data {
+ unsigned int stop_addr;
+ unsigned int nodes_deleted;
+};
+
+static int a2b_bus_del_node(struct device *dev, void *d)
+{
+ struct a2b_bus_del_node_data *data = d;
+ struct a2b_node *node;
+
+ if (dev->type != &a2b_node_type)
+ return 0;
+
+ node = to_a2b_node(dev);
+
+ /* Break out early if this is the node to stop at */
+ if (node->addr < data->stop_addr)
+ return 1;
+
+ device_unregister(dev);
+ data->nodes_deleted++;
+
+ return 0;
+}
+
+static unsigned int a2b_bus_del_nodes_until(struct a2b_bus *bus,
+ unsigned int stop_addr)
+{
+ struct a2b_bus_del_node_data data = {
+ .stop_addr = stop_addr,
+ .nodes_deleted = 0,
+ };
+
+ device_for_each_child_reverse(&bus->dev, &data, a2b_bus_del_node);
+
+ return data.nodes_deleted;
+}
+
+static void a2b_bus_del_nodes(struct a2b_bus *bus)
+{
+ a2b_bus_del_nodes_until(bus, A2B_MAIN_ADDR);
+}
+
+static int a2b_bus_of_add_node(struct a2b_bus *bus, struct device_node *np,
+ unsigned int addr)
+{
+ struct a2b_node *node;
+ int ret = 0;
+
+ if (!bus || !np)
+ return -EINVAL;
+
+ if (addr >= A2B_MAX_NODES)
+ return -EINVAL;
+
+ if (!of_device_is_available(np))
+ return -ENODEV;
+
+ if (of_node_test_and_set_flag(np, OF_POPULATED))
+ return -EBUSY;
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (IS_ERR(node))
+ return -ENOMEM;
+
+ node->dev.bus = &a2b_bus;
+ node->dev.type = &a2b_node_type;
+ node->dev.parent = &bus->dev;
+ node->dev.of_node = np;
+ node->dev.fwnode = of_fwnode_handle(np);
+ dev_set_name(&node->dev, "a2b-%d.%d", bus->id, addr);
+
+ node->bus = bus;
+ node->addr = addr;
+
+ /*
+ * Register the node device. Note that due to asynchronous probing,
+ * there is no guarantee that the node driver's probe function has been
+ * called just yet. The synchronization point is a2b_register_node(),
+ * which should be called unconditionally by node drivers.
+ */
+ ret = device_register(&node->dev);
+ if (ret)
+ goto err_put_device;
+
+ return 0;
+
+err_put_device:
+ put_device(&node->dev);
+
+ return ret;
+}
+
+static struct device_node *a2b_bus_of_get_node_of_node(struct a2b_bus *bus,
+ unsigned int addr)
+{
+ struct device_node *np = NULL;
+ bool found = false;
+ u32 val;
+
+ for_each_available_child_of_node(bus->dev.of_node, np) {
+ if (of_property_read_u32(np, "reg", &val))
+ continue;
+
+ if (val == addr) {
+ found = true;
+ break;
+ }
+ }
+
+ return found ? np : NULL;
+}
+
+static void a2b_bus_event_discovery_done(struct a2b_bus *bus)
+{
+ bool done;
+
+ mutex_lock(&bus->mutex);
+ done = test_and_clear_bit(A2B_BUS_STATUS_DISCOVERY_ALGO, &bus->status);
+ mutex_unlock(&bus->mutex);
+
+ if (!done)
+ return;
+
+ dev_info(&bus->dev, "discovered %d subordinate nodes\n",
+ a2b_bus_num_subs(bus));
+}
+
+static void a2b_bus_discovery_work(struct work_struct *work)
+{
+ struct delayed_work *discovery_work = to_delayed_work(work);
+ struct device_node *np = NULL;
+ struct a2b_bus *bus =
+ container_of(discovery_work, struct a2b_bus, discovery_work);
+ struct a2b_node *main;
+ struct a2b_node *last;
+ struct a2b_node *node;
+ unsigned int new_addr;
+ int ret = -ENODEV;
+ int i;
+
+ mutex_lock(&bus->mutex);
+
+ main = __a2b_bus_main_node(bus);
+ last = __a2b_bus_last_node(bus);
+ new_addr = last->addr + 1;
+
+ if (new_addr > main->chip_info->max_subs)
+ goto out;
+
+ if (!(last->chip_info->caps & A2B_CHIP_CAP_B_SIDE))
+ goto out;
+
+ np = a2b_bus_of_get_node_of_node(bus, new_addr);
+ if (!np)
+ goto out;
+
+ set_bit(A2B_BUS_STATUS_DISCOVERY_ALGO, &bus->status);
+ set_bit(A2B_BUS_STATUS_DISCOVERING, &bus->status);
+
+ /*
+ * Enable switching on the last currently discovered node. All preceding
+ * nodes continue switching and have their External Switch Mode set to 2
+ * as prescribed in [1] Figure 8-3 "Advanced Discovery Flow".
+ */
+ __a2b_bus_for_each_node(bus, node, i) {
+ ret = last->ops->set_switching(
+ node, true, node == last ? A2B_SWMODE_0 : A2B_SWMODE_2);
+ if (ret) {
+ dev_err(&last->dev, "failed to enable switching: %d\n",
+ ret);
+ goto out;
+ }
+ }
+
+ /*
+ * Apply a new structure, which generally ensures that the RESPCYCS are
+ * sane before the discovery process begins. Failure to do so may result
+ * in bus errors.
+ */
+ __a2b_bus_new_structure(bus);
+
+ /* Begin discovery with the expected RESPCYCS value for the new node */
+ ret = main->ops->discover(main, __a2b_bus_respcycs(bus, new_addr));
+ if (ret < 0) {
+ dev_err(&bus->dev, "discovery error: %d\n", ret);
+ goto out;
+ } else if (ret) {
+ /*
+ * Discovery timed out, presumably meaning that there are no
+ * nodes left to discover. Disable switching on the last node to
+ * prevent spurious bus errors. All other nodes ought to revert
+ * to a normal External Switching Mode, cf. [1] Figure 8-32.
+ */
+ __a2b_bus_for_each_node(bus, node, i)
+ {
+ ret = last->ops->set_switching(node, node != last,
+ A2B_SWMODE_0);
+ if (ret) {
+ dev_err(&last->dev,
+ "failed to disable switching: %d\n",
+ ret);
+ goto out;
+ }
+ }
+
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+
+ ret = a2b_bus_of_add_node(bus, np, new_addr);
+ if (ret)
+ dev_err(&bus->dev, "failed to add new node %d: %d\n", i, ret);
+
+out:
+ clear_bit(A2B_BUS_STATUS_DISCOVERING, &bus->status);
+ mutex_unlock(&bus->mutex);
+
+ /*
+ * If there is no new node after this discovery, then the discovery
+ * process is finished. Signal the event.
+ */
+ if (!np || ret)
+ a2b_bus_event_discovery_done(bus);
+
+ if (np)
+ of_node_put(np);
+}
+
+static void a2b_bus_discover(struct a2b_bus *bus)
+{
+ schedule_delayed_work(&bus->discovery_work, msecs_to_jiffies(100));
+}
+
+int a2b_register_bus(struct a2b_bus *bus)
+{
+ struct device_node *np;
+ int ret;
+
+ if (!bus->parent || !bus->ops)
+ return -EINVAL;
+
+ /* Initialize private bus data */
+ mutex_init(&bus->mutex);
+ INIT_DELAYED_WORK(&bus->discovery_work, a2b_bus_discovery_work);
+ set_bit(A2B_BUS_STATUS_DISCOVERY_ALGO, &bus->status);
+ bus->id = ida_alloc(&a2b_ida, GFP_KERNEL);
+ if (bus->id < 0)
+ return -ENOMEM;
+
+ /* Initialize bus device data and register it */
+ bus->dev.class = &a2b_bus_class;
+ bus->dev.parent = bus->parent;
+ device_set_of_node_from_dev(&bus->dev, bus->parent);
+ bus->dev.type = &a2b_bus_type;
+ dev_set_name(&bus->dev, "a2b-%d", bus->id);
+
+ ret = device_register(&bus->dev);
+ if (ret) {
+ put_device(&bus->dev);
+ return ret;
+ }
+
+ /* It is mandatory to specify an OF node for the main node */
+ np = a2b_bus_of_get_node_of_node(bus, A2B_MAIN_ADDR);
+ if (!np) {
+ ret = -EINVAL;
+ goto err_device_unregister;
+ }
+
+ ret = a2b_bus_of_add_node(bus, np, A2B_MAIN_ADDR);
+ of_node_put(np);
+ if (ret)
+ goto err_device_unregister;
+
+ return 0;
+
+err_device_unregister:
+ device_unregister(&bus->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(a2b_register_bus);
+
+void a2b_unregister_bus(struct a2b_bus *bus)
+{
+ cancel_delayed_work_sync(&bus->discovery_work);
+
+ a2b_bus_del_nodes(bus);
+
+ device_unregister(&bus->dev);
+}
+EXPORT_SYMBOL_GPL(a2b_unregister_bus);
+
+struct a2b_bus *a2b_find_bus_by_of_node(struct device_node *np)
+{
+ struct device *dev = class_find_device_by_of_node(&a2b_bus_class, np);
+
+ return dev ? to_a2b_bus(dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(a2b_find_bus_by_of_node);
+
+void a2b_put_bus(struct a2b_bus *bus)
+{
+ put_device(&bus->dev);
+}
+EXPORT_SYMBOL_GPL(a2b_put_bus);
+
+/*
+ * A2B NODE
+ */
+
+int a2b_node_read(struct a2b_node *node, unsigned int reg, unsigned int *val)
+{
+ struct a2b_bus *bus = node->bus;
+
+ return bus->ops->read(bus, node, reg, val);
+}
+EXPORT_SYMBOL_GPL(a2b_node_read);
+
+int a2b_node_write(struct a2b_node *node, unsigned int reg, unsigned int val)
+{
+ struct a2b_bus *bus = node->bus;
+
+ return bus->ops->write(bus, node, reg, val);
+}
+EXPORT_SYMBOL_GPL(a2b_node_write);
+
+int a2b_node_i2c_xfer(struct a2b_node *node, struct i2c_msg *msgs, int num)
+{
+ struct a2b_bus *bus = node->bus;
+
+ return bus->ops->i2c_xfer(bus, node, msgs, num);
+}
+EXPORT_SYMBOL_GPL(a2b_node_i2c_xfer);
+
+int a2b_node_get_inttype(struct a2b_node *node, unsigned int *val)
+{
+ struct a2b_bus *bus = node->bus;
+
+ /*
+ * Obviously, this function should only be used if the node in question
+ * received an IRQ
+ */
+
+ return bus->ops->get_inttype(bus, val);
+}
+EXPORT_SYMBOL_GPL(a2b_node_get_inttype);
+
+struct clk *a2b_node_get_sync_clk(struct a2b_node *node)
+{
+ struct a2b_bus *bus = node->bus;
+
+ return bus->ops->get_sync_clk(bus);
+}
+EXPORT_SYMBOL_GPL(a2b_node_get_sync_clk);
+
+static void a2b_node_bus_drop_work(struct work_struct *work)
+{
+ struct a2b_node *node =
+ container_of(work, struct a2b_node, bus_drop_work);
+ struct a2b_bus *bus = node->bus;
+ unsigned int nodes_deleted;
+ int ret;
+
+ ret = node->ops->set_switching(node, false, A2B_SWMODE_0);
+ if (ret)
+ dev_err_ratelimited(&node->dev,
+ "failed to disable switching: %d\n", ret);
+
+ /* Delete the nodes that have left the bus */
+ nodes_deleted = a2b_bus_del_nodes_until(bus, node->addr + 1);
+
+ /* Schedule a rediscovery attempt of any lost nodes */
+ if (nodes_deleted)
+ schedule_delayed_work(&bus->discovery_work,
+ msecs_to_jiffies(1000));
+}
+
+void a2b_node_report_error(struct a2b_node *node, enum a2b_error error)
+{
+ struct a2b_bus *bus = node->bus;
+
+ /*
+ * According to [1] section 3-14 "Slave Node Response Cycles", the
+ * following errors can be observed during discovery: CRCERR, SRFERR,
+ * SRFCRCERR. Additionally a PWRERR_3 has been observed in practice when
+ * enabling switching on a node whose B-Side is not connected. The
+ * DISCOVERING status bit covers these cases - don't bother warning
+ * about them.
+ */
+ if (test_bit(A2B_BUS_STATUS_DISCOVERING, &bus->status)) {
+ switch (error) {
+ case A2B_CRCERR:
+ case A2B_SRFERR:
+ case A2B_SRFCRCERR:
+ case A2B_PWRERR_3:
+ dev_dbg_ratelimited(
+ &node->dev,
+ "A2B bus error %d during discovery: %s\n",
+ error, a2b_error_to_string(error));
+ return;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * An SRF miss error normally indicates that the next downstream node
+ * has dropped off the bus. When a node detects this error in 32
+ * consecutive superframes, it assumes a bus drop, signals an SRF miss
+ * error, and asserts itself as the last node on the bus, cf. [1]
+ * section 5-5 "Line Diagnostics After Discovery".
+ */
+ if (error == A2B_SRFERR) {
+ int last = node->ops->is_last(node);
+
+ if (last < 0) {
+ dev_err_ratelimited(
+ &node->dev,
+ "failed to determine lastness of node: %d\n",
+ last);
+ return;
+ }
+
+ if (last)
+ schedule_work(&node->bus_drop_work);
+
+ return;
+ }
+
+ dev_warn_ratelimited(&node->dev, "A2B bus error %d: %s\n", error,
+ a2b_error_to_string(error));
+}
+EXPORT_SYMBOL_GPL(a2b_node_report_error);
+
+int a2b_node_request_slots(struct a2b_node *node, struct a2b_slot_req *slot_req)
+{
+ struct a2b_bus *bus = node->bus;
+ int ret = 0;
+
+ mutex_lock(&bus->mutex);
+
+ if (node->slots_requested) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ node->slot_req = *slot_req;
+ node->slots_requested = true;
+
+ if (!__a2b_bus_new_structure_ready(bus))
+ goto out;
+
+ if (!__a2b_bus_validate_structure(bus)) {
+ ret = -EINVAL;
+ goto err_reset;
+ }
+
+ ret = __a2b_bus_new_structure(bus);
+ if (ret)
+ goto err_reset;
+
+ goto out;
+
+err_reset:
+ memset(&node->slot_req, 0, sizeof(node->slot_req));
+ node->slots_requested = false;
+
+out:
+ mutex_unlock(&bus->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(a2b_node_request_slots);
+
+int a2b_node_free_slots(struct a2b_node *node)
+{
+ struct a2b_bus *bus = node->bus;
+ int ret = 0;
+
+ mutex_lock(&bus->mutex);
+
+ if (!node->slots_requested)
+ goto out;
+
+ memset(&node->slot_req, 0, sizeof(node->slot_req));
+ node->slots_requested = false;
+
+ if (!__a2b_bus_new_structure_ready(bus))
+ goto out;
+
+ ret = __a2b_bus_new_structure(bus);
+ if (ret)
+ dev_err(&bus->dev,
+ "failed to apply new structure: %d\n", ret);
+
+out:
+ mutex_unlock(&bus->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(a2b_node_free_slots);
+
+int a2b_register_node(struct a2b_node *node)
+{
+ struct a2b_bus *bus = node->bus;
+ int ret;
+
+ /* Obligatory */
+ if (!node->chip_info || !node->ops || !node->ops->setup ||
+ !node->ops->set_respcycs || !node->ops->set_switching ||
+ !node->ops->is_last)
+ return -EINVAL;
+
+ /* Main obligatory */
+ if (is_a2b_main(node) &&
+ (!node->ops->discover || !node->ops->new_structure))
+ return -EINVAL;
+
+ if (node->setup)
+ return 0;
+
+ ret = node->ops->setup(node);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ else if (ret) {
+ dev_err(&node->dev, "failed to setup node: %d\n", ret);
+ goto err_discovery_done;
+ }
+
+ node->setup = true;
+
+ INIT_WORK(&node->bus_drop_work, a2b_node_bus_drop_work);
+
+ /* The node is now ready and can be used by other parts of the core */
+ mutex_lock(&bus->mutex);
+ bus->nodes[node->addr] = node;
+ mutex_unlock(&bus->mutex);
+
+ dev_info(&node->dev,
+ "registered %s node vendor 0x%02x prod 0x%02x ver 0x%02x\n",
+ is_a2b_main(node) ? "main" : "subordinate", node->vendor,
+ node->product, node->version);
+
+ /*
+ * Before kicking off the discovery process, ensure that the default
+ * RESPCYCS value is programmed into the main node. This isn't needed
+ * for subordinate nodes because their default RESPCYCS value is
+ * automatically programmed when they are discovered.
+ */
+ if (is_a2b_main(node)) {
+ ret = a2b_bus_new_structure(bus);
+ if (ret)
+ dev_err(&bus->dev,
+ "failed to apply new structure: %d\n", ret);
+ }
+
+ a2b_bus_discover(node->bus);
+
+ return 0;
+
+err_discovery_done:
+ a2b_bus_event_discovery_done(bus);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(a2b_register_node);
+
+void a2b_unregister_node(struct a2b_node *node)
+{
+ struct a2b_bus *bus = node->bus;
+
+ if (!node->setup)
+ return;
+
+ /*
+ * Only hold the mutex to remove the node from the bus node list. It is
+ * safe to teardown the node once it is removed.
+ */
+ mutex_lock(&bus->mutex);
+ bus->nodes[node->addr] = NULL;
+ mutex_unlock(&bus->mutex);
+
+ cancel_work_sync(&node->bus_drop_work);
+
+ if (node->ops->teardown)
+ node->ops->teardown(node);
+
+ node->priv = NULL;
+ node->setup = false;
+
+ dev_info(&node->dev, "unregistered node\n");
+}
+EXPORT_SYMBOL_GPL(a2b_unregister_node);
+
+/*
+ * A2B FUNC
+ */
+
+struct a2b_func *a2b_node_of_add_func(struct a2b_node *node,
+ struct device_node *np)
+{
+ struct a2b_func *func;
+ int ret = 0;
+
+ if (!node || !np)
+ return ERR_PTR(-EINVAL);
+
+ if (!of_device_is_available(np))
+ return ERR_PTR(-ENODEV);
+
+ if (of_node_test_and_set_flag(np, OF_POPULATED))
+ return ERR_PTR(-EBUSY);
+
+ func = kzalloc(sizeof(*func), GFP_KERNEL);
+ if (IS_ERR(func))
+ return ERR_PTR(-ENOMEM);
+
+ func->dev.bus = &a2b_bus;
+ func->dev.type = &a2b_func_type;
+ func->dev.parent = &node->dev;
+ func->dev.of_node = np;
+ func->dev.fwnode = of_fwnode_handle(np);
+ dev_set_name(&func->dev, "%s-%s", dev_name(&node->dev), np->name);
+
+ func->node = node;
+
+ ret = device_register(&func->dev);
+ if (ret)
+ goto err_put_device;
+
+ return func;
+
+err_put_device:
+ put_device(&func->dev);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(a2b_node_of_add_func);
+
+/*
+ * A2B BUS CLASS
+ */
+
+static void a2b_bus_class_dev_release(struct device *dev)
+{
+ struct a2b_bus *bus = to_a2b_bus(dev);
+
+ ida_free(&a2b_ida, bus->id);
+}
+
+const struct class a2b_bus_class = {
+ .name = "a2b",
+ .dev_release = a2b_bus_class_dev_release,
+};
+
+static ssize_t discover_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct a2b_bus *bus = to_a2b_bus(dev);
+
+ a2b_bus_discover(bus);
+
+ return count;
+}
+static DEVICE_ATTR_WO(discover);
+
+static struct attribute *a2b_bus_attrs[] = {
+ &dev_attr_discover.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(a2b_bus);
+
+const struct device_type a2b_bus_type = {
+ .name = "a2b-bus",
+ .groups = a2b_bus_groups,
+};
+
+/*
+ * BUS DRIVER
+ */
+
+static int a2b_node_uevent(const struct device *dev,
+ struct kobj_uevent_env *env)
+{
+ const struct a2b_node *node = to_a2b_node(dev);
+
+ if (add_uevent_var(env, "A2B_NODE_ADDR=%u", node->addr))
+ return -ENOMEM;
+
+ if (node->setup) {
+ if (add_uevent_var(env, "A2B_NODE_VENDOR=%02x", node->vendor))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "A2B_NODE_PRODUCT=%02x", node->product))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "A2B_NODE_VERSION=%02x", node->version))
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void a2b_node_release(struct device *dev)
+{
+ struct a2b_node *node = to_a2b_node(dev);
+
+ of_node_clear_flag(dev->of_node, OF_POPULATED);
+ kfree(node);
+}
+
+const struct device_type a2b_node_type = {
+ .name = "a2b-node",
+ .uevent = a2b_node_uevent,
+ .release = a2b_node_release,
+};
+
+static void a2b_func_release(struct device *dev)
+{
+ struct a2b_func *func = to_a2b_func(dev);
+
+ of_node_clear_flag(dev->of_node, OF_POPULATED);
+ kfree(func);
+}
+
+const struct device_type a2b_func_type = {
+ .name = "a2b-func",
+ .release = a2b_func_release,
+};
+
+int __a2b_driver_register(struct a2b_driver *a2b_drv, struct module *owner)
+{
+ if (WARN_ON(!is_registered))
+ return -EAGAIN;
+
+ a2b_drv->driver.bus = &a2b_bus;
+ a2b_drv->driver.owner = owner;
+
+ return driver_register(&a2b_drv->driver);
+}
+EXPORT_SYMBOL_GPL(__a2b_driver_register);
+
+void a2b_driver_unregister(struct a2b_driver *a2b_drv)
+{
+ if (a2b_drv)
+ driver_unregister(&a2b_drv->driver);
+}
+EXPORT_SYMBOL_GPL(a2b_driver_unregister);
+
+static int a2b_bus_match(struct device *dev, struct device_driver *drv)
+{
+ if (of_driver_match_device(dev, drv))
+ return 1;
+
+ return 0;
+}
+
+static int a2b_bus_probe(struct device *dev)
+{
+ struct a2b_driver *a2b_drv = to_a2b_driver(dev->driver);
+
+ return a2b_drv->probe(dev);
+}
+
+static void a2b_bus_remove(struct device *dev)
+{
+ struct a2b_driver *a2b_drv = to_a2b_driver(dev->driver);
+
+ if (dev->type == &a2b_node_type) {
+ struct a2b_node *node = to_a2b_node(dev);
+
+ /*
+ * Remove all nodes downstream from this one, because proper bus
+ * functionality cannot be guaranteed if an upstream node is not
+ * registered with the core.
+ */
+ a2b_bus_del_nodes_until(node->bus, node->addr + 1);
+ }
+
+ if (a2b_drv->remove)
+ a2b_drv->remove(dev);
+}
+
+static void a2b_bus_shutdown(struct device *dev)
+{
+ struct a2b_driver *a2b_drv = to_a2b_driver(dev->driver);
+
+ if (!dev || !a2b_drv)
+ return;
+
+ if (a2b_drv->shutdown)
+ a2b_drv->shutdown(dev);
+}
+
+static int a2b_bus_uevent(const struct device *dev, struct kobj_uevent_env *env)
+{
+ int ret;
+
+ ret = of_device_uevent_modalias(dev, env);
+ if (ret != -ENODEV)
+ return ret;
+
+ return 0;
+}
+
+const struct bus_type a2b_bus = {
+ .name = "a2b",
+ .match = a2b_bus_match,
+ .probe = a2b_bus_probe,
+ .remove = a2b_bus_remove,
+ .shutdown = a2b_bus_shutdown,
+ .uevent = a2b_bus_uevent,
+};
+EXPORT_SYMBOL_GPL(a2b_bus);
+
+static int __init a2b_bus_init(void)
+{
+ int ret;
+
+ ret = bus_register(&a2b_bus);
+ if (ret)
+ return ret;
+
+ ret = class_register(&a2b_bus_class);
+ if (ret)
+ goto err_unregister_bus;
+
+ is_registered = true;
+
+ return 0;
+
+err_unregister_bus:
+ bus_unregister(&a2b_bus);
+
+ return ret;
+}
+
+static void __exit a2b_bus_exit(void)
+{
+ class_unregister(&a2b_bus_class);
+ bus_unregister(&a2b_bus);
+}
+
+subsys_initcall(a2b_bus_init);
+module_exit(a2b_bus_exit);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("A2B driver core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/a2b/a2b.h b/include/linux/a2b/a2b.h
new file mode 100644
index 000000000000..2f4e013cb2ca
--- /dev/null
+++ b/include/linux/a2b/a2b.h
@@ -0,0 +1,444 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * A2B driver core
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ */
+#ifndef _A2B_H
+#define _A2B_H
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+struct clk;
+struct i2c_msg;
+
+/*
+ * MISC
+ */
+
+/**
+ * enum a2b_chip_caps - A2B chip capabilities
+ *
+ * @A2B_CHIP_CAP_MAIN: the chip can function in main mode
+ * @A2B_CHIP_CAP_A_SIDE: the chip has an A-side transceiver
+ * @A2B_CHIP_CAP_B_SIDE: the chip has a B-side transceiver
+ * @A2B_CHIP_CAP_I2S: the chip has an I2S/TDM interface
+ * @A2B_CHIP_CAP_PDM: the chip has a PDM interface
+ * @A2B_CHIP_CAP_REDUCED_RATE: the chip supports the reduced rate feature
+ * @A2B_CHIP_CAP_CLKOUT: the chip supports CLKOUT1/CLKOUT2
+ * @A2B_CHIP_CAP_BUS_MONITOR: the chip supports the bus monitor feature
+ * @A2B_CHIP_CAP_SUSTAIN: the chip supports the sustain feature
+ * @A2B_CHIP_CAP_DATA_RX_MASK: the chip supports specifying slot RX masks
+ * @A2B_CHIP_CAP_GPIO_DISTANCE: the chip supports the GPIO over distance feature
+ * @A2B_CHIP_CAP_MAILBOX: the chip supports the mailbox feature
+ */
+enum a2b_chip_caps {
+ A2B_CHIP_CAP_MAIN = BIT(0),
+ A2B_CHIP_CAP_A_SIDE = BIT(1),
+ A2B_CHIP_CAP_B_SIDE = BIT(2),
+ A2B_CHIP_CAP_I2S = BIT(3),
+ A2B_CHIP_CAP_PDM = BIT(4),
+ A2B_CHIP_CAP_REDUCED_RATE = BIT(5),
+ A2B_CHIP_CAP_CLKOUT = BIT(6),
+ A2B_CHIP_CAP_BUS_MONITOR = BIT(7),
+ A2B_CHIP_CAP_SUSTAIN = BIT(8),
+ A2B_CHIP_CAP_DATA_RX_MASK = BIT(9),
+ A2B_CHIP_CAP_GPIO_DISTANCE = BIT(10),
+ A2B_CHIP_CAP_MAILBOX = BIT(11),
+};
+
+/**
+ * struct a2b_chip_info - chip information
+ *
+ * @caps: chip capabilities
+ * @max_subs: maximum number of discoverable A2B nodes if this node is main
+ * @max_gpios: maximum number of available GPIOs
+ */
+struct a2b_chip_info {
+ unsigned int caps;
+ unsigned int max_subs;
+ unsigned int max_gpios;
+};
+
+enum a2b_superframe_freq {
+ A2B_SFF_48000,
+ A2B_SFF_44100,
+};
+
+enum a2b_tdm_mode {
+ A2B_TDMMODE_2,
+ A2B_TDMMODE_4,
+ A2B_TDMMODE_8,
+ A2B_TDMMODE_12,
+ A2B_TDMMODE_16,
+ A2B_TDMMODE_20,
+ A2B_TDMMODE_24,
+ A2B_TDMMODE_32,
+ A2B_TDMMODE_END,
+};
+
+enum a2b_tdm_slot_size {
+ A2B_TDMSS_32,
+ A2B_TDMSS_16,
+ A2B_TDMSS_END,
+};
+
+/**
+ * enum a2b_swmode - A2B transceiver External Switch Mode
+ *
+ * For more information about the meaning of these modes, see the Technical
+ * Reference [1] Table 7-8 A2B_SWCTL Register Fields.
+ */
+enum a2b_swmode {
+ A2B_SWMODE_0 = 0,
+ A2B_SWMODE_1 = 1,
+ A2B_SWMODE_2 = 2,
+};
+
+enum a2b_direction {
+ A2B_DIR_UP,
+ A2B_DIR_DOWN,
+};
+
+enum a2b_slot_size {
+ A2B_SLOT_SIZE_8 = 0,
+ A2B_SLOT_SIZE_12 = 1,
+ A2B_SLOT_SIZE_16 = 2,
+ A2B_SLOT_SIZE_20 = 3,
+ A2B_SLOT_SIZE_24 = 4,
+ A2B_SLOT_SIZE_28 = 5,
+ A2B_SLOT_SIZE_32 = 6,
+};
+
+enum a2b_slot_format {
+ A2B_SLOT_FORMAT_NORMAL = 0,
+ A2B_SLOT_FORMAT_ALT = 1,
+};
+
+struct a2b_slot_config {
+ enum a2b_slot_size size[2];
+ enum a2b_slot_format format[2];
+};
+
+struct a2b_slot_req {
+ unsigned int a_dnslots;
+ unsigned int a_upslots;
+ unsigned int b_dnslots;
+ unsigned int b_upslots;
+ struct a2b_slot_config slot_config;
+};
+
+/*
+ * A2B NODE
+ */
+
+/*
+ * Per the specification of the Interrupt Source Register in the reference
+ * manual, cf. [1] Figure 7-20, the maximum number of nodes is hard-coded to 17,
+ * because the register supports signalling of interrupts from up to 16
+ * subordinate nodes through the 4-bit INODE field.
+ *
+ * A2B_INTSRC: Interrupt Source Register (Main Only)
+ * _______________________________
+ * | 7 | 6 | | | 3 2 1 0 |
+ * -v---v-----------v-------------
+ * | | |
+ * | | `-> INODE (Interrupt Node ID)
+ * | |
+ * | `-------------> SLVINT (Slave/Subordinate Interrupt)
+ * |
+ * `-----------------> MSTINT (Master/Main Interrupt)
+ *
+ * In practice many A2B main mode transceivers support discovery of far fewer
+ * subordinate nodes.
+ *
+ * Note that unlike in this driver, the A2B hardware itself indexes subordinate
+ * nodes starting at zero, i.e. A2B_INTSRC.INODE=0 means that the first
+ * (nearest) subordinate node is signalling an interrupt. The reference manual
+ * also uses this convention. Here, the main node is zero and the first
+ * subordinate node is 1. The difference only needs to be accounted for in a few
+ * places such as interrupt handling and indirect register access to subordinate
+ * nodes.
+ */
+#define A2B_MAX_NODES 17
+#define A2B_MAIN_ADDR 0
+
+struct a2b_node;
+
+/**
+ * struct a2b_node_ops - node driver ops
+ *
+ * @set_respcycs: invoked by the core to configure the RESPCYCS register
+ * @set_switching: invoked by the core to configure the switch control register
+ * @discover: (main only) invoked by the core to initiate the discovery process;
+ * the respcycs argument is automatically programmed into the newly
+ * discovered node's RESPCYCS register on success; the node driver
+ * must ensure that DISCVRY.DSCACT=0 before this function returns;
+ * return 0 on success or non-zero on discovery timeout
+ * @new_structure: (main only) invoked by the core to program a new structure
+ * @is_last: invoked by the core to query whether the target node thinks it is
+ * the last node on the bus
+ * @setup: the A2B core invokes this function when the node is registered by the
+ * node driver; setup of any peripheral functions (cf. &struct a2b_func)
+ * should happen here
+ * @teardown: (optional) invoked by the core when the node is unregistered; the
+ * node driver should undo whatever it may have done in setup
+ */
+struct a2b_node_ops {
+ int (*set_respcycs)(struct a2b_node *node, unsigned int respcycs);
+ int (*set_switching)(struct a2b_node *node, bool enable, enum a2b_swmode mode);
+ int (*discover)(struct a2b_node *node, unsigned int respcycs);
+ int (*new_structure)(struct a2b_node *node,
+ const struct a2b_slot_config *slot_config,
+ bool dn_enable, bool up_enable);
+ int (*is_last)(struct a2b_node *node);
+ int (*setup)(struct a2b_node *node);
+ void (*teardown)(struct a2b_node *node);
+};
+
+struct a2b_node {
+ /* A2B node driver fills this in */
+ const struct a2b_node_ops *ops;
+ const struct a2b_chip_info *chip_info;
+ unsigned int vendor;
+ unsigned int product;
+ unsigned int version;
+ unsigned int invert_sync : 1;
+ unsigned int early_sync : 1;
+ unsigned int alternating_sync : 1;
+ unsigned int rx_on_dtx1 : 1;
+ unsigned int swmode_1: 1;
+ enum a2b_tdm_mode tdm_mode;
+ enum a2b_tdm_slot_size tdm_slot_size;
+ void *priv;
+
+ /* A2B core only */
+ struct device dev;
+ bool setup;
+ struct a2b_bus *bus;
+ struct work_struct bus_drop_work;
+ unsigned int addr;
+ struct a2b_slot_req slot_req;
+ bool slots_requested;
+};
+
+static inline bool is_a2b_main(const struct a2b_node *node)
+{
+ return node->addr == A2B_MAIN_ADDR;
+}
+
+static inline bool is_a2b_sub(const struct a2b_node *node)
+{
+ return !is_a2b_main(node);
+}
+
+enum a2b_inttype {
+ A2B_INTTYPE_HDCNTERR = 0,
+ A2B_INTTYPE_DDERR = 1,
+ A2B_INTTYPE_CRCERR = 2,
+ A2B_INTTYPE_DPERR = 3,
+ A2B_INTTYPE_BECOVF = 4,
+ A2B_INTTYPE_SRFERR = 5,
+ A2B_INTTYPE_SRFCRCERR = 6,
+ /* 7~8 reserved */
+ A2B_INTTYPE_PWRERR_0 = 9,
+ A2B_INTTYPE_PWRERR_1 = 10,
+ A2B_INTTYPE_PWRERR_2 = 11,
+ A2B_INTTYPE_PWRERR_3 = 12,
+ A2B_INTTYPE_PWRERR_4 = 13,
+ /* 14 reserved */
+ A2B_INTTYPE_PWRERR_5 = 15,
+ A2B_INTTYPE_IO0PND = 16,
+ A2B_INTTYPE_IO1PND = 17,
+ A2B_INTTYPE_IO2PND = 18,
+ A2B_INTTYPE_IO3PND = 19,
+ A2B_INTTYPE_IO4PND = 20,
+ A2B_INTTYPE_IO5PND = 21,
+ A2B_INTTYPE_IO6PND = 22,
+ A2B_INTTYPE_IO7PND = 23,
+ A2B_INTTYPE_DSCDONE = 24,
+ A2B_INTTYPE_I2CERR = 25,
+ A2B_INTTYPE_ICRCERR = 26,
+ /* 27~40 reserved */
+ A2B_INTTYPE_PWRERR_6 = 41,
+ A2B_INTTYPE_PWRERR_7 = 42,
+ /* 42~47 reserved */
+ A2B_INTTYPE_MBOX0FULL = 48,
+ A2B_INTTYPE_MBOX0EMPTY = 49,
+ A2B_INTTYPE_MBOX1FULL = 50,
+ A2B_INTTYPE_MBOX1EMPTY = 51,
+ /* 52~127 reserved */
+ A2B_INTTYPE_IRQMSGERR = 128,
+ /* 129~251 reserved */
+ A2B_INTTYPE_STARTUPERR = 252,
+ A2B_INTTYPE_SLVINTTYPERR = 253,
+ A2B_INTTYPE_STBYDONE = 254,
+ A2B_INTTYPE_MSTR_RUNNING = 255,
+};
+
+enum a2b_error {
+ A2B_HDCNTERR = 0,
+ A2B_DDERR = 1,
+ A2B_CRCERR = 2,
+ A2B_DPERR = 3,
+ A2B_BECOVF = 4,
+ A2B_SRFERR = 5,
+ A2B_SRFCRCERR = 6,
+ /* 7~8 reserved */
+ A2B_PWRERR_0 = 9,
+ A2B_PWRERR_1 = 10,
+ A2B_PWRERR_2 = 11,
+ A2B_PWRERR_3 = 12,
+ A2B_PWRERR_4 = 13,
+ /* 14 reserved */
+ A2B_PWRERR_5 = 15,
+ /* non-error interrupt type codes */
+ A2B_I2CERR = 25,
+ A2B_ICRCERR = 26,
+ /* 27~40 reserved */
+ A2B_PWRERR_6 = 41,
+ A2B_PWRERR_7 = 42,
+ /* 42~47 reserved */
+ /* non-error interrupt type codes */
+ /* 52~127 reserved */
+ A2B_IRQMSGERR = 128,
+ /* 129~251 reserved */
+ A2B_STARTUPERR = 252,
+ A2B_SLVINTTYPERR = 253,
+ /* non-error interrupt type codes */
+};
+
+int a2b_node_read(struct a2b_node *node, unsigned int reg, unsigned int *val);
+int a2b_node_write(struct a2b_node *node, unsigned int reg, unsigned int val);
+int a2b_node_i2c_xfer(struct a2b_node *node, struct i2c_msg *msgs, int num);
+int a2b_node_get_inttype(struct a2b_node *node, unsigned int *val);
+struct clk *a2b_node_get_sync_clk(struct a2b_node *node);
+
+void a2b_node_report_error(struct a2b_node *node, enum a2b_error error);
+
+int a2b_node_request_slots(struct a2b_node *node,
+ struct a2b_slot_req *slot_req);
+int a2b_node_free_slots(struct a2b_node *node);
+
+int a2b_register_node(struct a2b_node *node);
+void a2b_unregister_node(struct a2b_node *node);
+
+/*
+ * A2B FUNC
+ */
+
+struct a2b_func {
+ struct device dev;
+ struct a2b_node *node;
+};
+
+struct a2b_func *a2b_node_of_add_func(struct a2b_node *node,
+ struct device_node *np);
+
+/*
+ * A2B BUS
+ */
+
+struct a2b_bus_ops;
+
+/**
+ * enum a2b_bus_status - A2B bus status bits
+ *
+ * @A2B_BUS_STATUS_DISCOVERY_ALGO - the discovery (read: enumeration) algorithm
+ * is in progress and the number of available nodes it not yet determined
+ * @A2B_BUS_STATUS_DISCOVERING - the main node is currently in discovery mode,
+ * i.e. DISCSTAT.DSCACT=1; used internally to ignore spurious bus errors
+ */
+enum a2b_bus_status {
+ A2B_BUS_STATUS_DISCOVERY_ALGO,
+ A2B_BUS_STATUS_DISCOVERING,
+ A2B_BUS_STATUS_END,
+};
+
+struct a2b_bus {
+ /* A2B interface driver fills this in */
+ const struct a2b_bus_ops *ops;
+ enum a2b_superframe_freq sff;
+ struct device *parent;
+ void *priv;
+
+ /* A2B core only */
+ struct device dev;
+ int id;
+ struct mutex mutex;
+ struct a2b_node *nodes[A2B_MAX_NODES];
+ unsigned long status;
+ struct delayed_work discovery_work;
+};
+
+int a2b_register_bus(struct a2b_bus *bus);
+void a2b_unregister_bus(struct a2b_bus *bus);
+struct a2b_bus *a2b_find_bus_by_of_node(struct device_node *np);
+void a2b_put_bus(struct a2b_bus *bus);
+unsigned long a2b_bus_status(struct a2b_bus *bus);
+unsigned int a2b_bus_num_subs(struct a2b_bus *bus);
+unsigned int a2b_bus_num_nodes(struct a2b_bus *bus);
+
+/**
+ * a2b_bus_ops - A2B host bus operations
+ *
+ * @read: register read from the address on the target node
+ * @write: write with same semantics as @read
+ * @i2c_xfer: perform a raw I2C transfer from a subordinate node's I2C interface
+ * @get_inttype: in the event of an interrupt on a node, the node must use this
+ * function to determine what type of interrupt it has received
+ * @get_sync_clk: return the &struct clk pointer associated with the SYNC clock
+ */
+struct a2b_bus_ops {
+ int (*read)(struct a2b_bus *bus, const struct a2b_node *node,
+ unsigned int reg, unsigned int *val);
+ int (*write)(struct a2b_bus *bus, const struct a2b_node *node,
+ unsigned int reg, unsigned int val);
+ int (*i2c_xfer)(struct a2b_bus *bus, const struct a2b_node *node,
+ struct i2c_msg *msgs, int num);
+ int (*get_inttype)(struct a2b_bus *bus, unsigned int *val);
+ struct clk *(*get_sync_clk)(struct a2b_bus *bus);
+};
+
+/*
+ * BUS DRIVER
+ */
+
+struct a2b_driver {
+ struct device_driver driver;
+ int (*probe)(struct device *dev);
+ void (*remove)(struct device *dev);
+ void (*shutdown)(struct device *dev);
+};
+
+#define to_a2b_driver(drv) container_of(drv, struct a2b_driver, driver)
+
+int __a2b_driver_register(struct a2b_driver *a2b_drv, struct module *owner);
+void a2b_driver_unregister(struct a2b_driver *a2b_drv);
+
+#define a2b_driver_register(a2b_drv) __a2b_driver_register(a2b_drv, THIS_MODULE)
+#define module_a2b_driver(__a2b_driver) \
+ module_driver(__a2b_driver, a2b_driver_register, a2b_driver_unregister)
+
+#define to_a2b_node(dev) container_of_const(dev, struct a2b_node, dev)
+#define to_a2b_func(dev) container_of_const(dev, struct a2b_func, dev)
+
+extern const struct device_type a2b_node_type;
+extern const struct device_type a2b_func_type;
+extern const struct bus_type a2b_bus;
+
+/*
+ * A2B BUS CLASS
+ */
+
+static inline struct a2b_bus *to_a2b_bus(struct device *dev)
+{
+ return container_of(dev, struct a2b_bus, dev);
+}
+
+extern const struct device_type a2b_bus_type;
+extern const struct class a2b_bus_class;
+
+#endif /* _A2B_H */

--
2.44.0


2024-05-17 12:59:58

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 06/13] gpio: add AD24xx GPIO driver

From: Alvin Šipraga <[email protected]>

This driver adds GPIO function support for AD24xx A2B transceiver chips.
When a GPIO is requested, the relevant pin is automatically muxed to
GPIO mode. The device tree property gpio-reserved-ranges can be used to
protect certain pins which are reserved for other functionality such as
I2S/TDM data.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 1 +
drivers/gpio/Kconfig | 6 +
drivers/gpio/Makefile | 1 +
drivers/gpio/gpio-ad24xx.c | 302 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 310 insertions(+)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 1f6d836463f3..8c894579e2fc 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -32,6 +32,7 @@ config A2B_AD24XX_I2C
config A2B_AD24XX_NODE
tristate "Analog Devices Inc. AD24xx node support"
select REGMAP_A2B
+ imply GPIO_AD24XX
help
Say Y here to enable support for AD24xx A2B transceiver nodes. This
applies to both main nodes and subordinate nodes. Supported models
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 3dbddec07028..72bd0d88d6b3 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1241,6 +1241,12 @@ config GPIO_ALTERA_A10SR
includes reads of pushbuttons and DIP switches as well
as writes to LEDs.

+config GPIO_AD24XX
+ tristate "Analog Devies Inc. AD24xx GPIO support"
+ depends on A2B_AD24XX_NODE
+ help
+ Say Y here to enable GPIO support for AD24xx A2B transceivers.
+
config GPIO_ARIZONA
tristate "Wolfson Microelectronics Arizona class devices"
depends on MFD_ARIZONA
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index e2a53013780e..f625bb140143 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o
obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o
obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o
obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o
+obj-$(CONFIG_GPIO_AD24XX) += gpio-ad24xx.o
obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
obj-$(CONFIG_GPIO_AGGREGATOR) += gpio-aggregator.o
diff --git a/drivers/gpio/gpio-ad24xx.c b/drivers/gpio/gpio-ad24xx.c
new file mode 100644
index 000000000000..097ea9e2d629
--- /dev/null
+++ b/drivers/gpio/gpio-ad24xx.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD24xx GPIO driver
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+struct ad24xx_gpio {
+ struct device *dev;
+ struct a2b_func *func;
+ struct a2b_node *node;
+ struct regmap *regmap;
+ int irqs[AD24XX_MAX_GPIOS];
+ struct gpio_chip gpio_chip;
+ struct irq_chip irq_chip;
+ struct mutex mutex;
+ unsigned int irq_invert : AD24XX_MAX_GPIOS;
+ unsigned int irq_enable : AD24XX_MAX_GPIOS;
+};
+
+static int ad24xx_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ad24xx_gpio *adg = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(adg->regmap, A2B_GPIOOEN, &val);
+ if (ret)
+ return ret;
+
+ if (val & BIT(offset))
+ return 0; /* output */
+
+ return 1; /* input */
+}
+
+static int ad24xx_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+ struct ad24xx_gpio *adg = gpiochip_get_data(gc);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(adg->regmap, A2B_GPIOIN, &val);
+ if (ret)
+ return ret;
+
+ if (val & BIT(offset))
+ return 1; /* high */
+
+ return 0; /* low */
+}
+
+static void ad24xx_gpio_set(struct gpio_chip *gc, unsigned int offset,
+ int value)
+{
+ struct ad24xx_gpio *adg = gpiochip_get_data(gc);
+ unsigned int reg = value ? A2B_GPIODATSET : A2B_GPIODATCLR;
+
+ regmap_write(adg->regmap, reg, BIT(offset));
+}
+
+static int ad24xx_gpio_set_direction(struct ad24xx_gpio *adg,
+ unsigned int offset,
+ unsigned int direction)
+{
+ unsigned int mask = BIT(offset);
+ unsigned int ival = direction ? BIT(offset) : 0;
+ int ret;
+
+ ret = regmap_update_bits(adg->regmap, A2B_GPIOIEN, mask, ival);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(adg->regmap, A2B_GPIOOEN, mask, ~ival);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ad24xx_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct ad24xx_gpio *adg = gpiochip_get_data(gc);
+
+ return ad24xx_gpio_set_direction(adg, offset, 1);
+}
+
+static int ad24xx_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int value)
+{
+ struct ad24xx_gpio *adg = gpiochip_get_data(gc);
+
+ /* For atomicity, write the output value before setting the direction */
+ ad24xx_gpio_set(gc, offset, value);
+
+ return ad24xx_gpio_set_direction(adg, offset, 0);
+}
+
+static int ad24xx_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
+ unsigned int child,
+ unsigned int child_type,
+ unsigned int *parent,
+ unsigned int *parent_type)
+{
+ *parent = child;
+ return 0;
+}
+
+static void ad24xx_gpio_irq_mask(struct irq_data *d)
+{
+ struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
+ struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ adg->irq_enable &= ~BIT(hwirq);
+ gpiochip_disable_irq(gpio_chip, hwirq);
+}
+
+static void ad24xx_gpio_irq_unmask(struct irq_data *d)
+{
+ struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
+ struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ gpiochip_disable_irq(gpio_chip, hwirq);
+ adg->irq_enable |= BIT(hwirq);
+}
+
+static int ad24xx_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+ struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
+ struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
+ irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ adg->irq_invert &= ~BIT(hwirq);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ adg->irq_invert |= BIT(hwirq);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void ad24xx_gpio_irq_bus_lock(struct irq_data *d)
+{
+ struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
+ struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
+
+ mutex_lock(&adg->mutex);
+}
+
+static void ad24xx_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+ struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
+ struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
+ int ret;
+
+ ret = regmap_write(adg->regmap, A2B_PINTINV, adg->irq_invert);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(adg->regmap, A2B_PINTEN, adg->irq_enable);
+ if (ret)
+ goto out;
+
+out:
+ mutex_unlock(&adg->mutex);
+
+ if (ret)
+ dev_err(adg->dev,
+ "failed to update interrupt configuration: %d\n", ret);
+}
+
+static const struct irq_chip ad24xx_gpio_irq_chip = {
+ .name = "ad24xx-gpio",
+ .flags = IRQCHIP_IMMUTABLE,
+ .irq_mask = ad24xx_gpio_irq_mask,
+ .irq_unmask = ad24xx_gpio_irq_unmask,
+ .irq_set_type = ad24xx_gpio_irq_set_type,
+ .irq_bus_lock = ad24xx_gpio_irq_bus_lock,
+ .irq_bus_sync_unlock = ad24xx_gpio_irq_bus_sync_unlock,
+ GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static const struct regmap_config ad24xx_gpio_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int ad24xx_gpio_probe(struct device *dev)
+{
+ struct a2b_func *func = to_a2b_func(dev);
+ struct a2b_node *node = func->node;
+ struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
+ struct gpio_chip *gpio_chip;
+ struct gpio_irq_chip *irq_chip;
+ struct irq_domain *parent_domain;
+ struct ad24xx_gpio *adg;
+ struct device_node *np;
+ int ret;
+
+ adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL);
+ if (!adg)
+ return -ENOMEM;
+
+ adg->regmap =
+ devm_regmap_init_a2b_func(func, &ad24xx_gpio_regmap_config);
+ if (IS_ERR(adg->regmap))
+ return PTR_ERR(adg->regmap);
+
+ adg->dev = dev;
+ adg->func = func;
+ adg->node = node;
+ mutex_init(&adg->mutex);
+
+ np = of_irq_find_parent(dev->of_node);
+ if (!np)
+ return -ENOENT;
+
+ parent_domain = irq_find_host(np);
+ of_node_put(np);
+ if (!parent_domain)
+ return -ENOENT;
+
+ gpio_chip = &adg->gpio_chip;
+ gpio_chip->label = dev_name(dev);
+ gpio_chip->parent = dev;
+ gpio_chip->fwnode = fwnode;
+ gpio_chip->owner = THIS_MODULE;
+ gpio_chip->get_direction = ad24xx_gpio_get_direction;
+ gpio_chip->direction_input = ad24xx_gpio_direction_input;
+ gpio_chip->direction_output = ad24xx_gpio_direction_output;
+ gpio_chip->get = ad24xx_gpio_get;
+ gpio_chip->set = ad24xx_gpio_set;
+ gpio_chip->base = -1;
+ gpio_chip->ngpio = node->chip_info->max_gpios;
+ gpio_chip->can_sleep = true;
+
+ irq_chip = &gpio_chip->irq;
+ gpio_irq_chip_set_chip(irq_chip, &ad24xx_gpio_irq_chip);
+ irq_chip->fwnode = fwnode;
+ irq_chip->parent_domain = parent_domain;
+ irq_chip->child_to_parent_hwirq = ad24xx_gpio_child_to_parent_hwirq;
+ irq_chip->handler = handle_bad_irq;
+ irq_chip->default_type = IRQ_TYPE_NONE;
+
+ /* Initialize all GPIOs as inputs for high impedance state */
+ ret = regmap_write(adg->regmap, A2B_GPIOIEN, 0xFF);
+ if (ret)
+ return ret;
+
+ ret = devm_gpiochip_add_data(dev, gpio_chip, adg);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id ad24xx_gpio_of_match_table[] = {
+ { .compatible = "adi,ad2401-gpio" },
+ { .compatible = "adi,ad2402-gpio" },
+ { .compatible = "adi,ad2403-gpio" },
+ { .compatible = "adi,ad2410-gpio" },
+ { .compatible = "adi,ad2420-gpio" },
+ { .compatible = "adi,ad2421-gpio" },
+ { .compatible = "adi,ad2422-gpio" },
+ { .compatible = "adi,ad2425-gpio" },
+ { .compatible = "adi,ad2426-gpio" },
+ { .compatible = "adi,ad2427-gpio" },
+ { .compatible = "adi,ad2428-gpio" },
+ { .compatible = "adi,ad2429-gpio" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_gpio_of_match_table);
+
+static struct a2b_driver ad24xx_gpio_driver = {
+ .driver = {
+ .name = "ad24xx-gpio",
+ .of_match_table = ad24xx_gpio_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = ad24xx_gpio_probe,
+};
+module_a2b_driver(ad24xx_gpio_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("AD24xx GPIO driver");
+MODULE_LICENSE("GPL");

--
2.44.0


2024-05-17 13:00:22

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 05/13] a2b: add AD24xx node driver

From: Alvin Šipraga <[email protected]>

This A2B node driver supports controlling both main and subordinate
AD24xx nodes. As well as implementing the required ops for an A2B node
driver, it also registers peripheral functions available on this series
of A2B transceivers: GPIO, codec, clock, and I2C controller. The
implementation of those functions is handled in discrete A2B drivers
placed in the relevant subsystems.

The core node op symbols are also exported to support the implementation
of more bespoke node drivers, such as for hardware which requires
additional hand-holding to properly integrate with the driver model. A
supporting header file is also added with prototypes for these
functions.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 14 +
drivers/a2b/Makefile | 3 +
drivers/a2b/ad24xx-node.c | 887 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/a2b/ad24xx-node.h | 42 +++
4 files changed, 946 insertions(+)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 120b1d491623..1f6d836463f3 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -18,11 +18,25 @@ config A2B_AD24XX_I2C
tristate "Analog Devices Inc. AD24xx I2C interface support"
depends on I2C
select REGMAP_I2C
+ select A2B_AD24XX_NODE
help
Say Y here to enable I2C interface support for AD24xx A2B transceiver
chips from Analog Devices Inc. Supported models include AD240x, AD241x,
and AD242x.

+ Selecting this option will also force AD24xx node support, which is
+ required to operate the chip as a main node.
+
+ If unsure, say N.
+
+config A2B_AD24XX_NODE
+ tristate "Analog Devices Inc. AD24xx node support"
+ select REGMAP_A2B
+ help
+ Say Y here to enable support for AD24xx A2B transceiver nodes. This
+ applies to both main nodes and subordinate nodes. Supported models
+ include AD240x, AD241x, and AD242x.
+
If unsure, say N.

endif # A2B
diff --git a/drivers/a2b/Makefile b/drivers/a2b/Makefile
index 07241524645c..171ffa237943 100644
--- a/drivers/a2b/Makefile
+++ b/drivers/a2b/Makefile
@@ -7,3 +7,6 @@ obj-$(CONFIG_A2B) += a2b.o

# Interface drivers
obj-$(CONFIG_A2B_AD24XX_I2C) += ad24xx-i2c.o
+
+# Node drivers
+obj-$(CONFIG_A2B_AD24XX_NODE) += ad24xx-node.o
diff --git a/drivers/a2b/ad24xx-node.c b/drivers/a2b/ad24xx-node.c
new file mode 100644
index 000000000000..c5716391936d
--- /dev/null
+++ b/drivers/a2b/ad24xx-node.c
@@ -0,0 +1,887 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD24xx A2B transceiver node driver
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ *
+ * Analog Devices Inc. documentation cited in some of the comments below:
+ *
+ * [1] AD2420(W)/6(W)/7(W)/8(W)/9(W) Automotive Audio Bus A2B Transceiver
+ * Technical Reference, Revision 1.1, October 2019, Part Number 82-100138-01
+ *
+ * [2] Datasheet for AD2420(W)/AD2426(W)/AD2427(W)/AD2428(W)/AD2429(W) Rev. C,
+ * July 2021
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+
+#include "ad24xx-node.h"
+
+struct ad24xx_node {
+ struct device *dev;
+ struct a2b_node *node;
+ struct regmap *regmap;
+ struct irq_domain *irqdomain;
+ int irq;
+ struct completion running_completion;
+ struct completion discovery_completion;
+ struct a2b_func *func_gpio;
+ struct a2b_func *func_codec;
+ struct a2b_func *func_clk;
+ struct a2b_func *func_i2c;
+};
+
+#define A2B_CHIP_CAPS_AD242X \
+ (A2B_CHIP_CAP_REDUCED_RATE | A2B_CHIP_CAP_CLKOUT | \
+ A2B_CHIP_CAP_BUS_MONITOR | A2B_CHIP_CAP_SUSTAIN | \
+ A2B_CHIP_CAP_DATA_RX_MASK | A2B_CHIP_CAP_GPIO_DISTANCE | \
+ A2B_CHIP_CAP_MAILBOX)
+
+const struct a2b_chip_info ad24xx_chip_info[] = {
+ [A2B_AD2401] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_PDM,
+ .max_gpios = 7,
+ },
+ [A2B_AD2402] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_PDM,
+ .max_gpios = 7,
+ },
+ [A2B_AD2403] = {
+ .caps = A2B_CHIP_CAP_MAIN |
+ A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_I2S,
+ .max_subs = 8,
+ .max_gpios = 7,
+ },
+ [A2B_AD2410] = {
+ .caps = A2B_CHIP_CAP_MAIN |
+ A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_I2S |
+ A2B_CHIP_CAP_PDM,
+ .max_subs = 8,
+ .max_gpios = 7,
+ },
+ [A2B_AD2420] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_gpios = 8,
+ },
+ [A2B_AD2421] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_gpios = 8,
+ },
+ [A2B_AD2422] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_gpios = 8,
+ },
+ [A2B_AD2425] = {
+ .caps = A2B_CHIP_CAP_MAIN |
+ A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_I2S |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_subs = 10,
+ .max_gpios = 8,
+ },
+ [A2B_AD2426] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_gpios = 8,
+ },
+ [A2B_AD2427] = {
+ .caps = A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_gpios = 8,
+ },
+ [A2B_AD2428] = {
+ .caps = A2B_CHIP_CAP_MAIN |
+ A2B_CHIP_CAP_A_SIDE |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_I2S |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_subs = 10,
+ .max_gpios = 8,
+ },
+ [A2B_AD2429] = {
+ .caps = A2B_CHIP_CAP_MAIN |
+ A2B_CHIP_CAP_B_SIDE |
+ A2B_CHIP_CAP_I2S |
+ A2B_CHIP_CAP_PDM |
+ A2B_CHIP_CAPS_AD242X,
+ .max_subs = 2,
+ .max_gpios = 8,
+ },
+};
+EXPORT_SYMBOL_GPL(ad24xx_chip_info);
+
+static int of_a2b_parse_tdm_slot_size(struct device_node *np,
+ enum a2b_tdm_slot_size *tdm_slot_size)
+{
+ u32 slot_size;
+ int ret;
+
+ ret = of_property_read_u32(np, "adi,tdm-slot-size", &slot_size);
+ if (ret)
+ return ret;
+
+ if (slot_size == 16)
+ *tdm_slot_size = A2B_TDMSS_16;
+ else if (slot_size == 32)
+ *tdm_slot_size = A2B_TDMSS_32;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int of_a2b_parse_tdm_mode(struct device_node *np,
+ enum a2b_tdm_mode *tdm_mode)
+{
+ u32 mode;
+ int ret;
+
+ ret = of_property_read_u32(np, "adi,tdm-mode", &mode);
+ if (ret)
+ return ret;
+
+ if (mode == 2)
+ *tdm_mode = A2B_TDMMODE_2;
+ else if (mode == 4)
+ *tdm_mode = A2B_TDMMODE_4;
+ else if (mode == 8)
+ *tdm_mode = A2B_TDMMODE_8;
+ else if (mode == 12)
+ *tdm_mode = A2B_TDMMODE_12;
+ else if (mode == 16)
+ *tdm_mode = A2B_TDMMODE_16;
+ else if (mode == 20)
+ *tdm_mode = A2B_TDMMODE_20;
+ else if (mode == 24)
+ *tdm_mode = A2B_TDMMODE_24;
+ else if (mode == 32)
+ *tdm_mode = A2B_TDMMODE_32;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct irq_chip ad24xx_node_irq_chip = {
+ .name = "ad24xx-node",
+};
+
+static int ad24xx_node_irqdomain_map(struct irq_domain *irqdomain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_data(irq, irqdomain->host_data);
+ irq_set_chip_and_handler(irq, &ad24xx_node_irq_chip, handle_simple_irq);
+ irq_set_nested_thread(irq, 1);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static void ad24xx_node_irqdomain_unmap(struct irq_domain *irqdomain,
+ unsigned int irq)
+{
+ irq_set_nested_thread(irq, 0);
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static int ad24xx_node_irqdomain_alloc(struct irq_domain *irqdomain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *data)
+{
+ struct ad24xx_node *adn = irqdomain->host_data;
+ struct irq_fwspec *fwspec = data;
+ irq_hw_number_t hwirq = fwspec->param[0];
+
+ if (nr_irqs != 1)
+ return -EINVAL;
+
+ if (hwirq > AD24XX_MAX_GPIOS)
+ return -EINVAL;
+
+ return irq_domain_set_hwirq_and_chip(irqdomain, virq, hwirq,
+ &ad24xx_node_irq_chip, adn);
+}
+
+static const struct irq_domain_ops ad24xx_node_irqdomain_ops = {
+ .alloc = ad24xx_node_irqdomain_alloc,
+ .free = irq_domain_free_irqs_common,
+ .map = ad24xx_node_irqdomain_map,
+ .unmap = ad24xx_node_irqdomain_unmap,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static void devm_ad24xx_node_release_irqdomain(void *data)
+{
+ struct irq_domain *irqdomain = data;
+ int virq;
+ int i;
+
+ for (i = 0; i < A2B_MAX_NODES; i++) {
+ virq = irq_find_mapping(irqdomain, i);
+ if (virq)
+ irq_dispose_mapping(virq);
+ }
+
+ irq_domain_remove(irqdomain);
+}
+
+static irqreturn_t ad24xx_node_irq_handler(int irq, void *data)
+{
+ struct ad24xx_node *adn = data;
+ struct a2b_node *node = adn->node;
+ struct device *dev = adn->dev;
+ unsigned int inttype;
+ unsigned int virq;
+ int ret;
+
+ ret = a2b_node_get_inttype(node, &inttype);
+ if (ret) {
+ dev_err_ratelimited(adn->dev,
+ "failed to get interrupt type: %d\n", ret);
+ return IRQ_NONE;
+ }
+
+ dev_dbg_ratelimited(dev, "received interrupt of type %d\n", inttype);
+
+ switch (inttype) {
+ case A2B_INTTYPE_HDCNTERR:
+ case A2B_INTTYPE_DDERR:
+ case A2B_INTTYPE_CRCERR:
+ case A2B_INTTYPE_DPERR:
+ case A2B_INTTYPE_BECOVF:
+ case A2B_INTTYPE_SRFERR:
+ case A2B_INTTYPE_SRFCRCERR:
+ case A2B_INTTYPE_PWRERR_0:
+ case A2B_INTTYPE_PWRERR_1:
+ case A2B_INTTYPE_PWRERR_2:
+ case A2B_INTTYPE_PWRERR_3:
+ case A2B_INTTYPE_PWRERR_4:
+ case A2B_INTTYPE_PWRERR_5:
+ case A2B_INTTYPE_I2CERR:
+ case A2B_INTTYPE_ICRCERR:
+ case A2B_INTTYPE_PWRERR_6:
+ case A2B_INTTYPE_PWRERR_7:
+ case A2B_INTTYPE_IRQMSGERR:
+ case A2B_INTTYPE_STARTUPERR:
+ case A2B_INTTYPE_SLVINTTYPERR:
+ /* Error IRQ */
+ a2b_node_report_error(node, inttype);
+ return IRQ_HANDLED;
+ case A2B_INTTYPE_IO0PND:
+ case A2B_INTTYPE_IO1PND:
+ case A2B_INTTYPE_IO2PND:
+ case A2B_INTTYPE_IO3PND:
+ case A2B_INTTYPE_IO4PND:
+ case A2B_INTTYPE_IO5PND:
+ case A2B_INTTYPE_IO6PND:
+ case A2B_INTTYPE_IO7PND:
+ /* GPIO IRQ */
+ virq = irq_find_mapping(adn->irqdomain,
+ inttype - A2B_INTTYPE_IO0PND);
+ if (virq)
+ handle_nested_irq(virq);
+ return IRQ_NONE;
+ case A2B_INTTYPE_DSCDONE:
+ /* Discovery done IRQ */
+ complete(&adn->discovery_completion);
+ return IRQ_HANDLED;
+ case A2B_INTTYPE_MBOX0FULL:
+ case A2B_INTTYPE_MBOX0EMPTY:
+ case A2B_INTTYPE_MBOX1FULL:
+ case A2B_INTTYPE_MBOX1EMPTY:
+ /* Mailbox IRQ - unimplemented */
+ dev_info(dev, "unhandled mailbox interrupt %d\n", inttype);
+ return IRQ_NONE;
+ case A2B_INTTYPE_STBYDONE:
+ /* Standby IRQ - unimplemented */
+ dev_info(dev, "unhandled standby interrupt %d\n", inttype);
+ return IRQ_NONE;
+ case A2B_INTTYPE_MSTR_RUNNING:
+ /* Master (main) running IRQ */
+ complete(&adn->running_completion);
+ return IRQ_HANDLED;
+ default:
+ dev_warn(dev, "unhandled unknown interrupt %d\n", inttype);
+ return IRQ_NONE;
+ }
+}
+
+int ad24xx_node_set_respcycs(struct a2b_node *node, unsigned int respcycs)
+{
+ struct ad24xx_node *adn = node->priv;
+ int ret;
+
+ dev_dbg(&node->dev, "set RESPCYCS %d\n", respcycs);
+
+ ret = regmap_write(adn->regmap, A2B_RESPCYCS, respcycs);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_set_respcycs);
+
+int ad24xx_node_set_switching(struct a2b_node *node, bool enable,
+ enum a2b_swmode mode)
+{
+ struct ad24xx_node *adn = node->priv;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Use external switch mode 1 instead of 0. This indicates that the
+ * downstream node is not using A2B bus power and is not properly
+ * terminating the bias. See [1] section 7-11 "Switch Control Register"
+ * for more information.
+ */
+ if (node->swmode_1 && mode == A2B_SWMODE_0)
+ mode = A2B_SWMODE_1;
+
+ dev_dbg(&node->dev, "%s switching, mode %d\n",
+ enable ? "enable" : "disable", mode);
+
+ val = FIELD_PREP(A2B_SWCTL_ENSW_MASK, enable) |
+ FIELD_PREP(A2B_SWCTL_MODE_MASK, mode);
+
+ ret = regmap_write(adn->regmap, A2B_SWCTL, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_set_switching);
+
+int ad24xx_node_discover(struct a2b_node *node, unsigned int respcycs)
+{
+ struct ad24xx_node *adn = node->priv;
+ int ret;
+ long timeout;
+
+ ret = regmap_write(adn->regmap, A2B_DISCVRY, respcycs);
+ if (ret)
+ return ret;
+
+ timeout = wait_for_completion_interruptible_timeout(
+ &adn->discovery_completion, msecs_to_jiffies(350));
+ reinit_completion(&adn->discovery_completion);
+ if (timeout < 0)
+ return timeout;
+ else if (timeout == 0) {
+ /*
+ * On discovery timeout it is necessary to manually end the
+ * discovery process by setting the ENDDSC bit. Empirically, the
+ * following issues were observed when failing to do so:
+ *
+ * - the A2B_DISCSTAT.DSCACT bit will remain indefinitely set;
+ * - the main node will fail to report a bus drop error
+ * properly; namely, it will signal SRFERRs but only set its
+ * LAST bit when switching is disabled;
+ * - subsequent attempts to rediscover the first subordinate
+ * node will succeed (insofar as a DSCDONE interrupt will
+ * arrive), but I2C access to the node's registers over the
+ * BUS client will always fail.
+ */
+ ret = regmap_set_bits(adn->regmap, A2B_CONTROL,
+ A2B_CONTROL_ENDDSC_MASK);
+ if (ret)
+ return ret;
+
+ return 1;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_discover);
+
+int ad24xx_node_new_structure(struct a2b_node *node,
+ const struct a2b_slot_config *slot_config,
+ bool dn_enable, bool up_enable)
+{
+ struct ad24xx_node *adn = node->priv;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Synchronize A2B slot sizes and formats with all downstream nodes. The
+ * A2B_SLOTFMT register is main only and with auto-broadcast, meaning
+ * that the written value is automatically propagated to all downstream
+ * subordinate nodes.
+ */
+ val = FIELD_PREP(A2B_SLOTFMT_DNSIZE_MASK,
+ slot_config->size[A2B_DIR_DOWN]) |
+ FIELD_PREP(A2B_SLOTFMT_DNFMT_MASK,
+ slot_config->format[A2B_DIR_DOWN]) |
+ FIELD_PREP(A2B_SLOTFMT_UPSIZE_MASK,
+ slot_config->size[A2B_DIR_UP]) |
+ FIELD_PREP(A2B_SLOTFMT_UPFMT_MASK,
+ slot_config->format[A2B_DIR_UP]);
+
+ ret = regmap_write(adn->regmap, A2B_SLOTFMT, val);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP(A2B_DATCTL_DNS_MASK, dn_enable) |
+ FIELD_PREP(A2B_DATCTL_UPS_MASK, up_enable);
+
+ ret = regmap_write(adn->regmap, A2B_DATCTL, val);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(adn->regmap, A2B_CONTROL,
+ A2B_CONTROL_NEWSTRCT_MASK);
+ if (ret)
+ return ret;
+
+ /*
+ * A new structure is applied within 5 superframe cycles unless
+ * communication errors create delays, cf. [1] section 7-24 "Control
+ * Register". Nominally this is about 100 us, so add a little extra to
+ * account for any potential errors.
+ */
+ usleep_range(200, 400);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_new_structure);
+
+int ad24xx_node_is_last(struct a2b_node *node)
+{
+ struct ad24xx_node *adn = node->priv;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(adn->regmap, A2B_NODE, &val);
+ if (ret)
+ return ret;
+
+ return val & A2B_NODE_LAST_MASK ? 1 : 0;
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_is_last);
+
+static int ad24xx_node_setup_pincfg(struct ad24xx_node *adn)
+{
+ struct device_node *np = adn->dev->of_node;
+ unsigned int val = 0;
+ unsigned int drvstr = 1; /* Chip default is high drive strength */
+ bool irqinv;
+ bool irqts;
+
+ of_property_read_u32(np, "adi,drive-strength", &drvstr);
+ irqinv = of_property_present(np, "adi,invert-interrupt");
+ irqts = of_property_present(np, "adi,tristate-interrupt");
+
+ val |= FIELD_PREP(A2B_PINCFG_DRVSTR_MASK, drvstr);
+ val |= FIELD_PREP(A2B_PINCFG_IRQINV_MASK, irqinv);
+ val |= FIELD_PREP(A2B_PINCFG_IRQTS_MASK, irqts);
+
+ return regmap_write(adn->regmap, A2B_PINCFG, val);
+}
+
+static int ad24xx_node_setup_i2sgcfg(struct ad24xx_node *adn)
+{
+ struct a2b_node *node = adn->node;
+ unsigned int val = 0;
+
+ val |= FIELD_PREP(A2B_I2SGCFG_TDMMODE_MASK, node->tdm_mode);
+ val |= FIELD_PREP(A2B_I2SGCFG_RXONDTX1_MASK, node->rx_on_dtx1);
+ val |= FIELD_PREP(A2B_I2SGCFG_TDMSS_MASK, node->tdm_slot_size);
+ val |= FIELD_PREP(A2B_I2SGCFG_ALT_MASK, node->alternating_sync);
+ val |= FIELD_PREP(A2B_I2SGCFG_EARLY_MASK, node->early_sync);
+ val |= FIELD_PREP(A2B_I2SGCFG_INV_MASK, node->invert_sync);
+
+ return regmap_write(adn->regmap, A2B_I2SGCFG, val);
+}
+
+static bool ad24xx_node_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case A2B_INTTYPE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ad24xx_node_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .precious_reg = ad24xx_node_precious_reg,
+ .max_register = A2B_REG_MAX,
+};
+
+int ad24xx_node_setup(struct a2b_node *node)
+{
+ struct device *dev = &node->dev;
+ struct device_node *np = dev->of_node;
+ struct ad24xx_node *adn;
+ long timeout;
+ int ret;
+
+ adn = devm_kzalloc(dev, sizeof(*adn), GFP_KERNEL);
+ if (!adn)
+ return -ENOMEM;
+
+ adn->regmap =
+ devm_regmap_init_a2b_node(node, &ad24xx_node_regmap_config);
+ if (IS_ERR(adn->regmap))
+ return PTR_ERR(adn->regmap);
+
+ ret = of_a2b_parse_tdm_mode(np, &node->tdm_mode);
+ if (ret)
+ return -EINVAL;
+
+ ret = of_a2b_parse_tdm_slot_size(np, &node->tdm_slot_size);
+ if (ret)
+ return -EINVAL;
+
+ if (of_property_present(np, "adi,invert-sync"))
+ node->invert_sync = 1;
+ if (of_property_present(np, "adi,early-sync"))
+ node->early_sync = 1;
+ if (of_property_present(np, "adi,alternating-sync"))
+ node->alternating_sync = 1;
+ if (of_property_present(np, "adi,rx-on-dtx1"))
+ node->rx_on_dtx1 = 1;
+ if (of_property_present(np, "adi,a2b-external-switch-mode-1"))
+ node->swmode_1 = 1;
+
+ node->priv = adn;
+
+ adn->dev = dev;
+ adn->node = node;
+ init_completion(&adn->running_completion);
+ init_completion(&adn->discovery_completion);
+
+ /* Identify */
+ ret = regmap_read(adn->regmap, A2B_VENDOR, &node->vendor);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(adn->regmap, A2B_PRODUCT, &node->product);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(adn->regmap, A2B_VERSION, &node->version);
+ if (ret)
+ return ret;
+
+ /* IRQ domain for GPIOs */
+ adn->irqdomain = irq_domain_add_linear(adn->dev->of_node,
+ AD24XX_MAX_GPIOS,
+ &ad24xx_node_irqdomain_ops, adn);
+ if (!adn->irqdomain)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(
+ adn->dev, devm_ad24xx_node_release_irqdomain, adn->irqdomain);
+ if (ret)
+ return ret;
+
+ /* IRQ */
+ adn->irq = of_irq_get(adn->dev->of_node, 0);
+ if (adn->irq <= 0)
+ return -EINVAL;
+
+ ret = devm_request_threaded_irq(adn->dev, adn->irq, NULL,
+ ad24xx_node_irq_handler, IRQF_ONESHOT,
+ "ad24xx-node", adn);
+ if (ret)
+ return ret;
+
+ /*
+ * Perform a software reset - but only on the main node, as doing this
+ * on subordinate nodes will require them to be re-discovered.
+ */
+ if (is_a2b_main(node)) {
+ ret = regmap_set_bits(adn->regmap, A2B_CONTROL,
+ A2B_CONTROL_SOFTRST_MASK);
+ if (ret)
+ return ret;
+ }
+
+ /* Pin configuration */
+ ret = ad24xx_node_setup_pincfg(adn);
+ if (ret)
+ return ret;
+
+ /* Enable interrupts */
+ ret = regmap_write(adn->regmap, A2B_INTMSK0, 0xFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(adn->regmap, A2B_INTMSK1, 0xFF);
+ if (ret)
+ return ret;
+
+ if (is_a2b_main(node)) {
+ /*
+ * Enable master (main) bit and wait for the transceiver to lock
+ * its PLL to the received SYNC signal.
+ */
+ ret = regmap_set_bits(adn->regmap, A2B_CONTROL,
+ A2B_CONTROL_MSTR_MASK);
+ if (ret)
+ return ret;
+
+ /*
+ * Per the datasheet [2] Table 3, "Clock and Reset Timing (A2B
+ * Master)", the typical PLL Lock Time t_PLK is 7.5 ms. Wait 30
+ * ms to be on the safe side and avoid spurious timeouts.
+ */
+ timeout = wait_for_completion_interruptible_timeout(
+ &adn->running_completion, msecs_to_jiffies(30));
+ reinit_completion(&adn->running_completion);
+ if (timeout < 0)
+ return timeout;
+ else if (timeout == 0)
+ return -ETIMEDOUT;
+
+ /*
+ * Enable main-node-only interrupts, ...
+ *
+ * but NOT I2C Error interrupts, as we should expect the error
+ * to be reported via the I2C adapter associated with the BUS
+ * client of the main node. This prevents many spurious
+ * interrupts during e.g. i2cdetect -r.
+ */
+ ret = regmap_write(adn->regmap, A2B_INTMSK2, 0x0D);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Set the global I2S configuration. For main nodes, the Technical
+ * Reference [1] is clear that this register must be set before
+ * discovery and must not be modified thereafter. For subordinate nodes
+ * there is no such restriction.
+ */
+ ret = ad24xx_node_setup_i2sgcfg(adn);
+ if (ret)
+ return ret;
+
+ /* Register optional transceiver functions with the core */
+ np = of_get_child_by_name(node->dev.of_node, "gpio");
+ if (np)
+ adn->func_gpio = a2b_node_of_add_func(node, np);
+ of_node_put(np);
+ if (IS_ERR(adn->func_gpio))
+ return PTR_ERR(adn->func_gpio);
+
+ np = of_get_child_by_name(node->dev.of_node, "codec");
+ if (np)
+ adn->func_codec = a2b_node_of_add_func(node, np);
+ of_node_put(np);
+ if (IS_ERR(adn->func_codec)) {
+ ret = PTR_ERR(adn->func_codec);
+ goto err_codec;
+ }
+
+ np = of_get_child_by_name(node->dev.of_node, "clock");
+ if (np)
+ adn->func_clk = a2b_node_of_add_func(node, np);
+ of_node_put(np);
+ if (IS_ERR(adn->func_clk)) {
+ ret = PTR_ERR(adn->func_clk);
+ goto err_clk;
+ }
+
+ np = of_get_child_by_name(node->dev.of_node, "i2c");
+ if (np)
+ adn->func_i2c = a2b_node_of_add_func(node, np);
+ of_node_put(np);
+ if (IS_ERR(adn->func_i2c)) {
+ ret = PTR_ERR(adn->func_i2c);
+ goto err_i2c;
+ }
+
+ return 0;
+
+ /* Unregister optional functions on error */
+err_i2c:
+ if (adn->func_clk)
+ device_unregister(&adn->func_clk->dev);
+err_clk:
+ if (adn->func_codec)
+ device_unregister(&adn->func_codec->dev);
+err_codec:
+ if (adn->func_gpio)
+ device_unregister(&adn->func_gpio->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_setup);
+
+void ad24xx_node_teardown(struct a2b_node *node)
+{
+ struct ad24xx_node *adn = node->priv;
+
+ if (adn->func_i2c)
+ device_unregister(&adn->func_i2c->dev);
+ if (adn->func_clk)
+ device_unregister(&adn->func_clk->dev);
+ if (adn->func_codec)
+ device_unregister(&adn->func_codec->dev);
+ if (adn->func_gpio)
+ device_unregister(&adn->func_gpio->dev);
+
+ /*
+ * Reset the switch control register to disable any switching. This
+ * might fail - particularly if this node is being torn down as a result
+ * of a bus drop. But if the driver is just being unbound from the node
+ * device, switching should be disabled so that on any rebind, the
+ * discovery process can continue from this node. Otherwise there is a
+ * possibility that the switching is never toggled off, which is a
+ * prerequisite for rediscovery.
+ */
+ regmap_write(adn->regmap, A2B_SWCTL, 0x00);
+
+ /*
+ * Similarly, in case only an unbind is occurring, mask and clear all
+ * pending interrupts to prevent spurious interrupts.
+ */
+ regmap_write(adn->regmap, A2B_INTMSK0, 0x00);
+ regmap_write(adn->regmap, A2B_INTMSK1, 0x00);
+ regmap_write(adn->regmap, A2B_INTPND0, 0xFF);
+ regmap_write(adn->regmap, A2B_INTPND1, 0xFF);
+
+ if (is_a2b_main(node)) {
+ regmap_write(adn->regmap, A2B_INTMSK2, 0x00);
+ regmap_write(adn->regmap, A2B_INTPND2, 0xFF);
+ }
+}
+EXPORT_SYMBOL_GPL(ad24xx_node_teardown);
+
+static struct a2b_node_ops ad24xx_sub_ops = {
+ .set_respcycs = ad24xx_node_set_respcycs,
+ .set_switching = ad24xx_node_set_switching,
+ .is_last = ad24xx_node_is_last,
+ .setup = ad24xx_node_setup,
+ .teardown = ad24xx_node_teardown,
+};
+
+static struct a2b_node_ops ad24xx_main_ops = {
+ .set_respcycs = ad24xx_node_set_respcycs,
+ .set_switching = ad24xx_node_set_switching,
+ .discover = ad24xx_node_discover,
+ .new_structure = ad24xx_node_new_structure,
+ .is_last = ad24xx_node_is_last,
+ .setup = ad24xx_node_setup,
+ .teardown = ad24xx_node_teardown,
+};
+
+static int ad24xx_node_probe(struct device *dev)
+{
+ struct a2b_node *node = to_a2b_node(dev);
+ int ret;
+
+ node->ops = is_a2b_main(node) ? &ad24xx_main_ops : &ad24xx_sub_ops;
+ node->chip_info = of_device_get_match_data(dev);
+
+ ret = a2b_register_node(node);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void ad24xx_node_remove(struct device *dev)
+{
+ struct a2b_node *node = to_a2b_node(dev);
+
+ a2b_unregister_node(node);
+}
+
+static const struct of_device_id ad24xx_node_of_match_table[] = {
+ {
+ .compatible = "adi,ad2401-node",
+ .data = &ad24xx_chip_info[A2B_AD2401],
+ },
+ {
+ .compatible = "adi,ad2402-node",
+ .data = &ad24xx_chip_info[A2B_AD2402],
+ },
+ {
+ .compatible = "adi,ad2403-node",
+ .data = &ad24xx_chip_info[A2B_AD2403],
+ },
+ {
+ .compatible = "adi,ad2410-node",
+ .data = &ad24xx_chip_info[A2B_AD2410],
+ },
+ {
+ .compatible = "adi,ad2420-node",
+ .data = &ad24xx_chip_info[A2B_AD2420],
+ },
+ {
+ .compatible = "adi,ad2421-node",
+ .data = &ad24xx_chip_info[A2B_AD2421],
+ },
+ {
+ .compatible = "adi,ad2422-node",
+ .data = &ad24xx_chip_info[A2B_AD2422],
+ },
+ {
+ .compatible = "adi,ad2425-node",
+ .data = &ad24xx_chip_info[A2B_AD2425],
+ },
+ {
+ .compatible = "adi,ad2426-node",
+ .data = &ad24xx_chip_info[A2B_AD2426],
+ },
+ {
+ .compatible = "adi,ad2427-node",
+ .data = &ad24xx_chip_info[A2B_AD2427],
+ },
+ {
+ .compatible = "adi,ad2428-node",
+ .data = &ad24xx_chip_info[A2B_AD2428],
+ },
+ {
+ .compatible = "adi,ad2429-node",
+ .data = &ad24xx_chip_info[A2B_AD2429],
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_node_of_match_table);
+
+static struct a2b_driver ad24xx_node_driver = {
+ .driver = {
+ .name = "ad24xx-node",
+ .of_match_table = ad24xx_node_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = ad24xx_node_probe,
+ .remove = ad24xx_node_remove,
+};
+module_a2b_driver(ad24xx_node_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("AD24xx A2B transceiver node driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/a2b/ad24xx-node.h b/drivers/a2b/ad24xx-node.h
new file mode 100644
index 000000000000..15591f0b1a51
--- /dev/null
+++ b/drivers/a2b/ad24xx-node.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AD24xx A2B transceiver node driver extension header
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ *
+ * Use this to derive your own custom A2B node driver.
+ */
+#ifndef _AD24XX_NODE_H
+#define _AD24XX_NODE_H
+
+#include <linux/a2b/a2b.h>
+
+enum ad24xx_chips {
+ A2B_AD2401,
+ A2B_AD2402,
+ A2B_AD2403,
+ A2B_AD2410,
+ A2B_AD2420,
+ A2B_AD2421,
+ A2B_AD2422,
+ A2B_AD2425,
+ A2B_AD2426,
+ A2B_AD2427,
+ A2B_AD2428,
+ A2B_AD2429,
+};
+
+extern const struct a2b_chip_info ad24xx_chip_info[];
+
+int ad24xx_node_set_respcycs(struct a2b_node *node, unsigned int respcycs);
+int ad24xx_node_set_switching(struct a2b_node *node, bool enable,
+ enum a2b_swmode mode);
+int ad24xx_node_discover(struct a2b_node *node, unsigned int respcycs);
+int ad24xx_node_new_structure(struct a2b_node *node,
+ const struct a2b_slot_config *slot_config,
+ bool dn_enable, bool up_enable);
+int ad24xx_node_is_last(struct a2b_node *node);
+int ad24xx_node_setup(struct a2b_node *node);
+void ad24xx_node_teardown(struct a2b_node *node);
+
+#endif /* _AD24XX_NODE_H */

--
2.44.0


2024-05-17 13:00:58

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 07/13] ASoC: codecs: add AD24xx codec driver

From: Alvin Šipraga <[email protected]>

This A2B driver adds support for the I2S/TDM interface found on AD24xx
A2B transceiver chips. The chips also support PDM, but this is not
currently implemented due to a lack of hardware to test with.

Configuration of A2B codecs takes place at runtime through manipulation
of kcontrols exported by this codec. The full semantics are far too
detailed to repeat in this commit message, and so it is suggested to
refer to the technical reference manual published by ADI:

[1] AD2420(W)/6(W)/7(W)/8(W)/9(W) Automotive Audio Bus A2B Transceiver
Technical Reference, Part Number 82-100138-01

Check out the section "Managing A2B System Data Flow". What follows is a
simplified description with Linux specifics.

A2B nodes are daisy-chained via unshielded twisted pair. An A2B bus
consists of a single "main" node connected to the SoC via I2C and TDM.
The other nodes are called "subordinate" nodes and also have TDM
interfaces. These nodes' TDM interfaces are typically connected to other
codecs. A2B enables a user to forward TDM slots captured on nodes' TDM
interfaces over the A2B bus to be retransmitted on other (possibly
multiple) nodes' TDM interfaces. There are various restrictions imposed
by the hardware, namely bandwidth, but to give an idea of the
capability: in a relatively simple case the bus enables synchronous
transmission of up to 32 channels of 32-bit PCM data between a main node
and a subordinate node.

In ASoC context, main nodes are always clock consumers and subordinate
nodes are always clock providers. All clocks are synchronized to the
FSYNC signal provided to the main node. The default state of the bus is
not to enable any transmission of audio data. Through I2C, the system
data flow can be modified to send TDM slots where they need to go. These
registers are exposed by the codec in the form of kcontrols.

The slot configuration - known in the technical documentation as a
"structure" - must be seen in the context of the entire A2B bus. For
this reason it is assumed that all nodes are part of the same sound
card. When kcontrols are modified it does not immediately result in a
change in structure; instead, the codecs use the hw_params and hw_free
ops to register and unregister their requested slots with the A2B driver
core. When all nodes on the bus have requested slots, a new structure is
applied. In the hw_free path, slots are freed and the bus can revert to
zero PCM data transmission.

Link: https://www.analog.com/media/en/technical-documentation/user-guides/ad242x-trm.pdf [1]
Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 4 +-
sound/soc/codecs/Kconfig | 5 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ad24xx-codec.c | 665 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 675 insertions(+), 1 deletion(-)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 8c894579e2fc..6ba5dc11c51d 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -8,7 +8,8 @@ menuconfig A2B
select OF
help
A2B (Automotive Audio Bus) is a digital audio and control bus from
- Analog Devices Inc.
+ Analog Devices Inc. that enables synchronous capture and playback of
+ PCM audio over distance.

If unsure, say N.

@@ -33,6 +34,7 @@ config A2B_AD24XX_NODE
tristate "Analog Devices Inc. AD24xx node support"
select REGMAP_A2B
imply GPIO_AD24XX
+ imply SND_SOC_AD24XX
help
Say Y here to enable support for AD24xx A2B transceiver nodes. This
applies to both main nodes and subordinate nodes. Supported models
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4afc43d3f71f..ae9460aed55c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -21,6 +21,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_AD193X_SPI
imply SND_SOC_AD193X_I2C
imply SND_SOC_AD1980
+ imply SND_SOC_AD24XX
imply SND_SOC_AD73311
imply SND_SOC_ADAU1372_I2C
imply SND_SOC_ADAU1372_SPI
@@ -431,6 +432,10 @@ config SND_SOC_AD1980
depends on SND_SOC_AC97_BUS
select REGMAP_AC97

+config SND_SOC_AD24XX
+ tristate "Analog Devices Inc. AD24xx codec"
+ depends on A2B_AD24XX_NODE
+
config SND_SOC_AD73311
tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index b4df22186e25..0f865d47385e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -7,6 +7,7 @@ snd-soc-ad193x-y := ad193x.o
snd-soc-ad193x-spi-y := ad193x-spi.o
snd-soc-ad193x-i2c-y := ad193x-i2c.o
snd-soc-ad1980-y := ad1980.o
+snd-soc-ad24xx-y := ad24xx-codec.o
snd-soc-ad73311-y := ad73311.o
snd-soc-adau-utils-y := adau-utils.o
snd-soc-adau1372-y := adau1372.o
@@ -403,6 +404,7 @@ obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o
obj-$(CONFIG_SND_SOC_AD193X_SPI) += snd-soc-ad193x-spi.o
obj-$(CONFIG_SND_SOC_AD193X_I2C) += snd-soc-ad193x-i2c.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
+obj-$(CONFIG_SND_SOC_AD24XX) += snd-soc-ad24xx.o
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
obj-$(CONFIG_SND_SOC_ADAU_UTILS) += snd-soc-adau-utils.o
obj-$(CONFIG_SND_SOC_ADAU1372) += snd-soc-adau1372.o
diff --git a/sound/soc/codecs/ad24xx-codec.c b/sound/soc/codecs/ad24xx-codec.c
new file mode 100644
index 000000000000..56ee32effc01
--- /dev/null
+++ b/sound/soc/codecs/ad24xx-codec.c
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD24xx codec driver
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ *
+ * Analog Devices Inc. documentation cited in some of the comments below:
+ *
+ * [1] AD2420(W)/6(W)/7(W)/8(W)/9(W) Automotive Audio Bus A2B Transceiver
+ * Technical Reference, Revision 1.1, October 2019, Part Number 82-100138-01
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+
+#define AD24XX_RATES_SUB_48 \
+ (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000)
+#define AD24XX_RATES_SUB_44_1 \
+ (SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_176400)
+#define AD24XX_RATES_MAIN_48 SNDRV_PCM_RATE_48000
+#define AD24XX_RATES_MAIN_44_1 SNDRV_PCM_RATE_44100
+
+struct ad24xx_codec {
+ struct device *dev;
+ struct a2b_func *func;
+ struct a2b_node *node;
+ struct regmap *regmap;
+ struct snd_soc_dai_driver *dai_drv;
+ struct a2b_slot_config slot_config;
+};
+
+static const char *const ad24xx_codec_slot_format_text[] = {
+ "Normal Slot Format",
+ "Alternate Slot Format",
+};
+
+static const char *const ad24xx_codec_slot_size_text[] = {
+ "8 bits", "12 bits", "16 bits", "20 bits",
+ "24 bits", "28 bits", "32 bits",
+};
+
+static SOC_ENUM_SINGLE_VIRT_DECL(ad24xx_codec_dn_slot_size_enum,
+ ad24xx_codec_slot_size_text);
+static SOC_ENUM_SINGLE_VIRT_DECL(ad24xx_codec_dn_slot_format_enum,
+ ad24xx_codec_slot_format_text);
+static SOC_ENUM_SINGLE_VIRT_DECL(ad24xx_codec_up_slot_size_enum,
+ ad24xx_codec_slot_size_text);
+static SOC_ENUM_SINGLE_VIRT_DECL(ad24xx_codec_up_slot_format_enum,
+ ad24xx_codec_slot_format_text);
+
+static int ad24xx_codec_slot_config_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
+ struct a2b_slot_config *slot_config = &adc->slot_config;
+ const struct soc_enum *priv = (void *)kcontrol->private_value;
+ unsigned int *val = &ucontrol->value.enumerated.item[0];
+
+ if (priv == &ad24xx_codec_dn_slot_size_enum)
+ *val = slot_config->size[A2B_DIR_DOWN];
+ else if (priv == &ad24xx_codec_dn_slot_format_enum)
+ *val = slot_config->format[A2B_DIR_DOWN];
+ else if (priv == &ad24xx_codec_up_slot_size_enum)
+ *val = slot_config->size[A2B_DIR_UP];
+ else if (priv == &ad24xx_codec_up_slot_format_enum)
+ *val = slot_config->format[A2B_DIR_UP];
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static int ad24xx_codec_slot_config_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component =
+ snd_soc_kcontrol_component(kcontrol);
+ struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
+ struct a2b_slot_config *slot_config = &adc->slot_config;
+ const struct soc_enum *priv = (void *)kcontrol->private_value;
+ unsigned int val = ucontrol->value.enumerated.item[0];
+ enum a2b_direction direction =
+ (priv == &ad24xx_codec_up_slot_size_enum ||
+ priv == &ad24xx_codec_up_slot_format_enum) ?
+ A2B_DIR_UP :
+ A2B_DIR_DOWN;
+
+ if (priv == &ad24xx_codec_up_slot_size_enum ||
+ priv == &ad24xx_codec_dn_slot_size_enum) {
+ if (val >= ARRAY_SIZE(ad24xx_codec_slot_size_text))
+ return -EINVAL;
+ slot_config->size[direction] = val;
+ } else if (priv == &ad24xx_codec_up_slot_format_enum ||
+ priv == &ad24xx_codec_dn_slot_format_enum) {
+ if (val >= ARRAY_SIZE(ad24xx_codec_slot_format_text))
+ return -EINVAL;
+ slot_config->format[direction] = val;
+ } else
+ return -ENOENT;
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new ad24xx_codec_controls_main[] = {
+ SOC_SINGLE("Downstream Slots", A2B_DNSLOTS, 0, 32, 0),
+ SOC_SINGLE("Upstream Slots", A2B_UPSLOTS, 0, 32, 0),
+ SOC_ENUM_EXT("Downstream Slot Size", ad24xx_codec_dn_slot_size_enum,
+ ad24xx_codec_slot_config_get,
+ ad24xx_codec_slot_config_put),
+ SOC_ENUM_EXT("Downstream Slot Format", ad24xx_codec_dn_slot_format_enum,
+ ad24xx_codec_slot_config_get,
+ ad24xx_codec_slot_config_put),
+ SOC_ENUM_EXT("Upstream Slot Size", ad24xx_codec_up_slot_size_enum,
+ ad24xx_codec_slot_config_get,
+ ad24xx_codec_slot_config_put),
+ SOC_ENUM_EXT("Upstream Slot Format", ad24xx_codec_up_slot_format_enum,
+ ad24xx_codec_slot_config_get,
+ ad24xx_codec_slot_config_put),
+};
+
+static const struct snd_kcontrol_new ad24xx_codec_controls_sub[] = {
+ SOC_SINGLE("Broadcast Downstream Slots", A2B_BCDNSLOTS, 0, 32, 0),
+ SOC_SINGLE("Downstream Slots Targeted", A2B_LDNSLOTS, 0, 32, 0),
+ SOC_SINGLE("Upstream Slots Generated", A2B_LUPSLOTS, 0, 32, 0),
+ SOC_SINGLE("Downstream Slots", A2B_DNSLOTS, 0, 32, 0),
+ SOC_SINGLE("Upstream Slots", A2B_UPSLOTS, 0, 32, 0),
+};
+
+static const struct snd_kcontrol_new ad24xx_codec_controls_data_rx_mask[] = {
+ SOC_SINGLE("Downstream Broadcast Mask Enable", A2B_LDNSLOTS, 7, 1, 0),
+ SND_SOC_BYTES("Upstream Data RX Mask", A2B_UPMASK0, 4),
+ SOC_SINGLE("Local Upstream Channel Offset", A2B_UPOFFSET, 0, 31, 0),
+ SND_SOC_BYTES("Downstream Data RX Mask", A2B_DNMASK0, 4),
+ SOC_SINGLE("Local Downstream Channel Offset", A2B_DNOFFSET, 0, 31, 0),
+};
+
+#define SND_SOC_DAPM_ENCODER(wname, stname, wreg, wshift, winvert) \
+{ .id = snd_soc_dapm_encoder, .name = wname, .sname = stname, \
+ SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
+
+#define SND_SOC_DAPM_DECODER(wname, stname, wreg, wshift, winvert) \
+{ .id = snd_soc_dapm_decoder, .name = wname, .sname = stname, \
+ SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), }
+
+static const struct snd_soc_dapm_widget ad24xx_codec_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX0", NULL, 0, A2B_I2SCFG, 4, 0),
+ SND_SOC_DAPM_AIF_IN("RX1", NULL, 0, A2B_I2SCFG, 5, 0),
+ SND_SOC_DAPM_AIF_OUT("TX0", NULL, 0, A2B_I2SCFG, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX1", NULL, 0, A2B_I2SCFG, 1, 0),
+ SND_SOC_DAPM_ENCODER("ENC", NULL, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_DECODER("DEC", NULL, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route ad24xx_codec_dapm_routes_main[] = {
+ { "I2S Capture", NULL, "DEC" },
+ { "TX0", NULL, "I2S Capture" },
+ { "TX1", NULL, "I2S Capture" },
+ { "I2S Playback", NULL, "RX0" },
+ { "I2S Playback", NULL, "RX1" },
+ { "ENC", NULL, "I2S Playback" },
+};
+
+static const struct snd_soc_dapm_route ad24xx_codec_dapm_routes_sub[] = {
+ { "ENC", NULL, "I2S Capture" },
+ { "I2S Capture", NULL, "RX0" },
+ { "I2S Capture", NULL, "RX1" },
+ { "TX0", NULL, "I2S Playback" },
+ { "TX1", NULL, "I2S Playback" },
+ { "I2S Playback", NULL, "DEC" },
+};
+
+static int ad24xx_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
+ bool bclk_invert;
+ unsigned int val;
+ int ret;
+
+ /* Main node must be BCLK/FSYNC consumer, subordinate node provider */
+ if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) !=
+ (is_a2b_main(adc->node) ? SND_SOC_DAIFMT_CBC_CFC :
+ SND_SOC_DAIFMT_CBP_CFP))
+ return -EINVAL;
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ if (adc->node->invert_sync)
+ return -EINVAL;
+ bclk_invert = false;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ if (!adc->node->invert_sync)
+ return -EINVAL;
+ bclk_invert = false;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ if (adc->node->invert_sync)
+ return -EINVAL;
+ bclk_invert = true;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ if (!adc->node->invert_sync)
+ return -EINVAL;
+ bclk_invert = true;
+ break;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ if (!adc->node->alternating_sync || !adc->node->early_sync)
+ return -EINVAL;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ if (adc->node->alternating_sync || !adc->node->early_sync)
+ return -EINVAL;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ if (adc->node->alternating_sync || adc->node->early_sync)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val = bclk_invert ? A2B_I2SCFG_RXBCLKINV_MASK :
+ A2B_I2SCFG_TXBCLKINV_MASK;
+ ret = regmap_update_bits(
+ adc->regmap, A2B_I2SCFG,
+ A2B_I2SCFG_TXBCLKINV_MASK | A2B_I2SCFG_RXBCLKINV_MASK, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ad24xx_codec_calc_a_dnslots(struct ad24xx_codec *adc)
+{
+ struct a2b_node *node = adc->node;
+ unsigned int dnslots;
+ unsigned int dnmasken;
+ unsigned int ldnslots;
+ unsigned int bcdnslots;
+ unsigned int dnmaskrx;
+ __le32 dnmask;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Calculate the number of downstream slots to be received by this
+ * node's A-side transceiver. For main nodes this is trivially zero
+ * because the A-side is inactive. Following [1] section 3-18
+ * "Downstream Data Slots", for subordinate nodes the calculation
+ * depends on whether the A2B_LDNSLOTS.DNMASKEN bit is set:
+ *
+ * DNMASKEN=0 => A2B_BCDNSLOTS + A2B_DNSLOTS + A2B_LDNSLOTS
+ * DNMASKEN=1 => max(A2B_DNSLOTS, dnmaskrx)
+ *
+ * where dnmaskrx is the most significant bit of the A2B_DNMASK{0,3}
+ * mask.
+ */
+
+ if (is_a2b_main(node))
+ return 0;
+
+ ret = regmap_read(adc->regmap, A2B_DNSLOTS, &val);
+ if (ret)
+ return ret;
+
+ dnslots = FIELD_GET(A2B_DNSLOTS_DNSLOTS_MASK, val);
+
+ ret = regmap_read(adc->regmap, A2B_LDNSLOTS, &val);
+ if (ret)
+ return ret;
+
+ ldnslots = FIELD_GET(A2B_LDNSLOTS_LDNSLOTS_MASK, val);
+ dnmasken = FIELD_GET(A2B_LDNSLOTS_DNMASKEN_MASK, val);
+
+ if (!dnmasken) {
+ ret = regmap_read(adc->regmap, A2B_BCDNSLOTS, &val);
+ if (ret)
+ return ret;
+
+ bcdnslots = FIELD_GET(A2B_BCDNSLOTS_BCDNSLOTS_MASK, val);
+
+ return bcdnslots + dnslots + ldnslots;
+ }
+
+ ret = regmap_bulk_read(adc->regmap, A2B_DNMASK0, &dnmask, 4);
+ if (ret)
+ return ret;
+
+ dnmaskrx = fls(le32_to_cpu(dnmask));
+
+ return max(dnslots, dnmaskrx);
+}
+
+static int ad24xx_codec_calc_b_dnslots(struct ad24xx_codec *adc)
+{
+ struct a2b_node *node = adc->node;
+ unsigned int dnslots;
+ unsigned int dnmasken;
+ unsigned int ldnslots;
+ unsigned int bcdnslots;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Calculate the number of downstream slots to be transmitted by this
+ * node's B-side transceiver. Following [1] section 3-18 "Downstream
+ * Data Slots", for main nodes the number is A2B_DNSLOTS. For
+ * subordinate nodes the calculation depends on whether the
+ * A2B_LDNSLOTS.DNMASKEN bit is set:
+ *
+ * DNMASKEN=0 => A2B_BCDNSLOTS + A2B_DNSLOTS
+ * DNMASKEN=1 => A2B_DNSLOTS + A2B_LDNSLOTS
+ */
+
+ ret = regmap_read(adc->regmap, A2B_DNSLOTS, &val);
+ if (ret)
+ return ret;
+
+ dnslots = FIELD_GET(A2B_DNSLOTS_DNSLOTS_MASK, val);
+
+ if (is_a2b_main(node))
+ return dnslots;
+
+ ret = regmap_read(adc->regmap, A2B_LDNSLOTS, &val);
+ if (ret)
+ return ret;
+
+ ldnslots = FIELD_GET(A2B_LDNSLOTS_LDNSLOTS_MASK, val);
+ dnmasken = FIELD_GET(A2B_LDNSLOTS_DNMASKEN_MASK, val);
+
+ if (dnmasken)
+ return dnslots + ldnslots;
+
+ ret = regmap_read(adc->regmap, A2B_BCDNSLOTS, &val);
+ if (ret)
+ return ret;
+
+ bcdnslots = FIELD_GET(A2B_BCDNSLOTS_BCDNSLOTS_MASK, val);
+
+ return bcdnslots + dnslots;
+}
+
+static unsigned int ad24xx_codec_calc_a_upslots(struct ad24xx_codec *adc)
+{
+ struct a2b_node *node = adc->node;
+ unsigned int upslots;
+ unsigned int lupslots;
+ unsigned int val;
+ int ret;
+
+ /*
+ * Calculate the number of upstream slots to be transmitted by this
+ * node's A-side transceiver. According to [1] section 3-20 "Upstream
+ * Data Slots", this is A2B_UPSLOTS + A2B_LUPSLOTS for subordinate
+ * nodes. For the main node it is trivially always zero, as its A-side
+ * is inactive.
+ */
+
+ if (is_a2b_main(node))
+ return 0;
+
+ ret = regmap_read(adc->regmap, A2B_UPSLOTS, &val);
+ if (ret)
+ return ret;
+
+ upslots = FIELD_GET(A2B_UPSLOTS_UPSLOTS_MASK, val);
+
+ ret = regmap_read(adc->regmap, A2B_LUPSLOTS, &val);
+ if (ret)
+ return ret;
+
+ lupslots = FIELD_GET(A2B_LUPSLOTS_LUPSLOTS_MASK, val);
+
+ return upslots + lupslots;
+}
+
+static unsigned int ad24xx_codec_calc_b_upslots(struct ad24xx_codec *adc)
+{
+ struct a2b_node *node = adc->node;
+ unsigned int upslots;
+ unsigned int upmaskrx;
+ unsigned int upmask;
+ unsigned int val;
+ u8 buf[4];
+ int ret;
+
+ /*
+ * Calculate the number of upstream slots to be received by this node's
+ * B-side transceiver. This is, cf. [1] section 3-20, max(A2B_UPSLOTS,
+ * upmaskrx), where upmaskrx is the most significant bit of the
+ * A2B_UPMASK{0,3} mask. For main nodes it is simply the value of
+ * A2B_UPSLOTS, as they have no upstream data RX mask to configure.
+ */
+
+ ret = regmap_read(adc->regmap, A2B_UPSLOTS, &val);
+ if (ret)
+ return ret;
+
+ upslots = FIELD_GET(A2B_UPSLOTS_UPSLOTS_MASK, val);
+
+ if (is_a2b_main(node))
+ return upslots;
+
+ ret = regmap_bulk_read(adc->regmap, A2B_UPMASK0, buf, 4);
+ if (ret)
+ return ret;
+
+ upmask = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ upmaskrx = fls(upmask);
+
+ return max(upslots, upmaskrx);
+}
+
+static int ad24xx_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
+ unsigned int rate = params_rate(params);
+ struct a2b_slot_req slot_req = {
+ .a_dnslots = ad24xx_codec_calc_a_dnslots(adc),
+ .a_upslots = ad24xx_codec_calc_a_upslots(adc),
+ .b_dnslots = ad24xx_codec_calc_b_dnslots(adc),
+ .b_upslots = ad24xx_codec_calc_b_upslots(adc),
+ .slot_config = adc->slot_config, /* ignored for subordinates */
+ };
+ enum a2b_superframe_freq sff = adc->node->bus->sff;
+ int ret;
+
+ /* Configure I2S/TDM rate */
+ if (is_a2b_main(adc->node)) {
+ /*
+ * The I2S rate of the main node DAIs is fixed at the superframe
+ * frequency (SFF) and cannot change.
+ */
+ if (!((rate == 48000 && sff == A2B_SFF_48000) ||
+ (rate == 44100 && sff == A2B_SFF_44100)))
+ return -EINVAL;
+ } else {
+ /*
+ * The I2S rate of subordinate nodes can be set to (SFF * x)
+ * for x in { 0.25, 0.5, 1, 2, 4 }.
+ */
+ unsigned int sff_rate = sff == A2B_SFF_48000 ? 48000 : 44100;
+ unsigned int val = 0;
+
+ if (rate == sff_rate / 4)
+ val |= FIELD_PREP(A2B_I2SRATE_I2SRATE_MASK, 2);
+ else if (rate == sff_rate / 2)
+ val |= FIELD_PREP(A2B_I2SRATE_I2SRATE_MASK, 1);
+ else if (rate == sff_rate)
+ val |= FIELD_PREP(A2B_I2SRATE_I2SRATE_MASK, 0);
+ /* A2B_I2SRRATE.RRDIV support is not implemented */
+ else if (rate == sff_rate * 2)
+ val |= FIELD_PREP(A2B_I2SRATE_I2SRATE_MASK, 5);
+ else if (rate == sff_rate * 4)
+ val |= FIELD_PREP(A2B_I2SRATE_I2SRATE_MASK, 6);
+ else
+ return -EINVAL;
+
+ ret = regmap_update_bits(adc->regmap, A2B_I2SRATE,
+ A2B_I2SRATE_I2SRATE_MASK, val);
+ if (ret)
+ return ret;
+ }
+
+
+ /* Finally, request slots */
+ ret = a2b_node_request_slots(adc->node, &slot_req);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ad24xx_codec_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ ret = a2b_node_free_slots(adc->node);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops ad24xx_codec_dai_ops = {
+ .set_fmt = ad24xx_codec_set_fmt,
+ .hw_params = ad24xx_codec_hw_params,
+ .hw_free = ad24xx_codec_hw_free,
+};
+
+enum ad24xx_codec_dai {
+ AD24XX_DAI_I2S,
+};
+
+static const struct snd_soc_dai_driver ad24xx_codec_dai_drv[] = {
+ [AD24XX_DAI_I2S] = {
+ .name = "ad24xx-i2s",
+ .playback = {
+ .stream_name = "I2S Playback",
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .capture = {
+ .stream_name = "I2S Capture",
+ .channels_min = 1,
+ .channels_max = 32,
+ },
+ .ops = &ad24xx_codec_dai_ops,
+ .symmetric_rate = 1,
+ },
+};
+
+static int ad24xx_codec_component_probe(struct snd_soc_component *component)
+{
+ struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
+ struct a2b_node *node = adc->node;
+ int ret;
+
+ snd_soc_component_init_regmap(component, adc->regmap);
+
+ if (is_a2b_sub(node) &&
+ (node->chip_info->caps & A2B_CHIP_CAP_DATA_RX_MASK)) {
+ ret = snd_soc_add_component_controls(
+ component, ad24xx_codec_controls_data_rx_mask,
+ ARRAY_SIZE(ad24xx_codec_controls_data_rx_mask));
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_component_driver ad24xx_codec_component_drv_main = {
+ .probe = ad24xx_codec_component_probe,
+ .controls = ad24xx_codec_controls_main,
+ .num_controls = ARRAY_SIZE(ad24xx_codec_controls_main),
+ .dapm_widgets = ad24xx_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ad24xx_codec_dapm_widgets),
+ .dapm_routes = ad24xx_codec_dapm_routes_main,
+ .num_dapm_routes = ARRAY_SIZE(ad24xx_codec_dapm_routes_main),
+ .endianness = 1,
+};
+
+static const struct snd_soc_component_driver ad24xx_codec_component_drv_sub = {
+ .probe = ad24xx_codec_component_probe,
+ .controls = ad24xx_codec_controls_sub,
+ .num_controls = ARRAY_SIZE(ad24xx_codec_controls_sub),
+ .dapm_widgets = ad24xx_codec_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ad24xx_codec_dapm_widgets),
+ .dapm_routes = ad24xx_codec_dapm_routes_sub,
+ .num_dapm_routes = ARRAY_SIZE(ad24xx_codec_dapm_routes_sub),
+ .endianness = 1,
+};
+
+static const struct regmap_config ad24xx_codec_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int ad24xx_codec_probe(struct device *dev)
+{
+ struct a2b_func *func = to_a2b_func(dev);
+ const struct snd_soc_component_driver *drv;
+ struct snd_soc_dai_driver *i2s_dai;
+ struct ad24xx_codec *adc;
+ int ret;
+
+ adc = devm_kzalloc(dev, sizeof(*adc), GFP_KERNEL);
+ if (!adc)
+ return -ENOMEM;
+
+ adc->dev = dev;
+ adc->func = func;
+ adc->node = func->node;
+ dev_set_drvdata(dev, adc);
+
+ adc->regmap =
+ devm_regmap_init_a2b_func(func, &ad24xx_codec_regmap_config);
+ if (IS_ERR(adc->regmap))
+ return PTR_ERR(adc->regmap);
+
+ adc->dai_drv = devm_kmemdup(dev, ad24xx_codec_dai_drv,
+ sizeof(ad24xx_codec_dai_drv), GFP_KERNEL);
+ if (!adc->dai_drv)
+ return -ENOMEM;
+
+ i2s_dai = &adc->dai_drv[AD24XX_DAI_I2S];
+
+ if (adc->node->tdm_slot_size == A2B_TDMSS_32)
+ i2s_dai->playback.formats = i2s_dai->capture.formats =
+ SNDRV_PCM_FMTBIT_S32_LE;
+ else
+ i2s_dai->playback.formats = i2s_dai->capture.formats =
+ SNDRV_PCM_FMTBIT_S16_LE;
+
+ if (is_a2b_main(adc->node)) {
+ if (adc->node->bus->sff == A2B_SFF_48000)
+ i2s_dai->playback.rates = i2s_dai->capture.rates =
+ AD24XX_RATES_MAIN_48;
+ else
+ i2s_dai->playback.rates = i2s_dai->capture.rates =
+ AD24XX_RATES_MAIN_44_1;
+ } else {
+ if (adc->node->bus->sff == A2B_SFF_48000)
+ i2s_dai->playback.rates = i2s_dai->capture.rates =
+ AD24XX_RATES_SUB_48;
+ else
+ i2s_dai->playback.rates = i2s_dai->capture.rates =
+ AD24XX_RATES_SUB_44_1;
+ }
+
+ if (is_a2b_main(adc->node))
+ drv = &ad24xx_codec_component_drv_main;
+ else
+ drv = &ad24xx_codec_component_drv_sub;
+
+ ret = devm_snd_soc_register_component(dev, drv, adc->dai_drv,
+ ARRAY_SIZE(ad24xx_codec_dai_drv));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id ad24xx_codec_of_match_table[] = {
+ { .compatible = "adi,ad2403-codec" },
+ { .compatible = "adi,ad2410-codec" },
+ { .compatible = "adi,ad2425-codec" },
+ { .compatible = "adi,ad2428-codec" },
+ { .compatible = "adi,ad2429-codec" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_codec_of_match_table);
+
+static struct a2b_driver ad24xx_codec_driver = {
+ .driver = {
+ .name = "ad24xx-codec",
+ .of_match_table = ad24xx_codec_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = ad24xx_codec_probe,
+};
+module_a2b_driver(ad24xx_codec_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("AD24xx codec driver");
+MODULE_LICENSE("GPL");

--
2.44.0


2024-05-17 13:00:58

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 04/13] a2b: add AD24xx I2C interface driver

From: Alvin Šipraga <[email protected]>

AD24xx series chips (AD240x, AD241x, AD242x) are controlled over I2C.
Add an A2B interface driver for I2C which registers an A2B node with the
A2B core and implements the relevant interface ops.

The motivation for abstracting away the interface and node control in
the driver model is because future generations of A2B transceivers are
expected to support both SPI and I2C as control interfaces.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 15 +
drivers/a2b/Makefile | 3 +
drivers/a2b/ad24xx-i2c.c | 532 +++++++++++++++++++++++++++
include/linux/a2b/ad24xx.h | 892 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1442 insertions(+)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 4aaef2ea4460..120b1d491623 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -11,3 +11,18 @@ menuconfig A2B
Analog Devices Inc.

If unsure, say N.
+
+if A2B
+
+config A2B_AD24XX_I2C
+ tristate "Analog Devices Inc. AD24xx I2C interface support"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here to enable I2C interface support for AD24xx A2B transceiver
+ chips from Analog Devices Inc. Supported models include AD240x, AD241x,
+ and AD242x.
+
+ If unsure, say N.
+
+endif # A2B
diff --git a/drivers/a2b/Makefile b/drivers/a2b/Makefile
index 40c9821f61ee..07241524645c 100644
--- a/drivers/a2b/Makefile
+++ b/drivers/a2b/Makefile
@@ -4,3 +4,6 @@
#

obj-$(CONFIG_A2B) += a2b.o
+
+# Interface drivers
+obj-$(CONFIG_A2B_AD24XX_I2C) += ad24xx-i2c.o
diff --git a/drivers/a2b/ad24xx-i2c.c b/drivers/a2b/ad24xx-i2c.c
new file mode 100644
index 000000000000..227d0391adf1
--- /dev/null
+++ b/drivers/a2b/ad24xx-i2c.c
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I2C interface driver for AD24xx A2B transceivers
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+struct ad24xx_i2c {
+ struct device *dev;
+ struct i2c_client *base_client;
+ struct i2c_client *bus_client;
+ struct regmap *base_regmap;
+ struct regmap *bus_regmap;
+ struct a2b_bus a2b_bus;
+ struct mutex mutex;
+ unsigned int irqs_enabled;
+ struct irq_domain *irqdomain;
+ int irq;
+ struct clk *sync_clk;
+};
+
+#define to_ad24xx_i2c(iface) container_of(iface, struct ad24xx_i2c, a2b_bus)
+
+static bool ad24xx_i2c_private_reg(unsigned int reg)
+{
+ /*
+ * "Private" registers which are owned by this interface driver should
+ * not be accessed by the constituent A2B drivers.
+ */
+ switch (reg) {
+ case A2B_CHIP:
+ case A2B_NODEADR:
+ case A2B_INTSRC:
+ case A2B_INTTYPE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int __ad24xx_i2c_read(struct a2b_bus *a2b_bus,
+ const struct a2b_node *node, unsigned int reg,
+ unsigned int *val)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+ unsigned int nodeadr;
+ int ret;
+
+ if (ad24xx_i2c_private_reg(reg))
+ return -EACCES;
+
+ /* Main node access */
+ if (is_a2b_main(node))
+ return regmap_read(ad->base_regmap, reg, val);
+
+ /* Sub node access */
+ nodeadr = FIELD_PREP(A2B_NODEADR_NODE_MASK, node->addr - 1);
+
+ ret = regmap_write(ad->base_regmap, A2B_NODEADR, nodeadr);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(ad->bus_regmap, reg, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ad24xx_i2c_read(struct a2b_bus *a2b_bus, const struct a2b_node *node,
+ unsigned int reg, unsigned int *val)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+ int ret;
+
+ mutex_lock(&ad->mutex);
+ ret = __ad24xx_i2c_read(a2b_bus, node, reg, val);
+ mutex_unlock(&ad->mutex);
+ return ret;
+}
+
+static int __ad24xx_i2c_write(struct a2b_bus *a2b_bus,
+ const struct a2b_node *node, unsigned int reg,
+ unsigned int val)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+ unsigned int nodeadr;
+ int ret;
+
+ if (ad24xx_i2c_private_reg(reg))
+ return -EACCES;
+
+ /* Main node access */
+ if (is_a2b_main(node))
+ return regmap_write(ad->base_regmap, reg, val);
+
+ /* Sub node access */
+ nodeadr = FIELD_PREP(A2B_NODEADR_NODE_MASK, node->addr - 1);
+
+ ret = regmap_write(ad->base_regmap, A2B_NODEADR, nodeadr);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(ad->bus_regmap, reg, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ad24xx_i2c_write(struct a2b_bus *a2b_bus,
+ const struct a2b_node *node, unsigned int reg,
+ unsigned int val)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+ int ret;
+
+ mutex_lock(&ad->mutex);
+ ret = __ad24xx_i2c_write(a2b_bus, node, reg, val);
+ mutex_unlock(&ad->mutex);
+ return ret;
+}
+
+static int ad24xx_i2c_xfer(struct a2b_bus *a2b_bus, const struct a2b_node *node,
+ struct i2c_msg *msgs, int num)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+ struct i2c_msg msgs2[2];
+ unsigned int nodeadr;
+ int ret;
+ int i;
+
+ /* Mains only have one I2C interface and it operates in slave mode */
+ if (is_a2b_main(node))
+ return -EINVAL;
+
+ /*
+ * Enforce some basic assumptions this function makes about the
+ * transfer. If this proves insufficient, some more complex logic will
+ * be needed.
+ */
+ if (num > 2 || (num == 2 && msgs[0].addr != msgs[1].addr))
+ return -EOPNOTSUPP;
+
+ /* Modify the messages to use the I2C address of the BUS client */
+ for (i = 0; i < num; i++) {
+ msgs2[i] = msgs[i];
+ msgs2[i].addr = ad->bus_client->addr;
+ }
+
+ mutex_lock(&ad->mutex);
+
+ /* Set I2C peripheral address in subordinate node */
+ nodeadr = FIELD_PREP(A2B_NODEADR_NODE_MASK, node->addr - 1);
+
+ ret = regmap_write(ad->base_regmap, A2B_NODEADR, nodeadr);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(ad->bus_regmap, A2B_CHIP, msgs[0].addr);
+ if (ret)
+ goto out;
+
+ /* Set peripheral bit */
+ nodeadr |= FIELD_PREP(A2B_NODEADR_PERI_MASK, 1);
+
+ ret = regmap_write(ad->base_regmap, A2B_NODEADR, nodeadr);
+ if (ret)
+ goto out;
+
+ ret = i2c_transfer(ad->bus_client->adapter, msgs2, num);
+ if (ret < 0)
+ goto out;
+
+out:
+ mutex_unlock(&ad->mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return num;
+}
+
+static int ad24xx_i2c_get_inttype(struct a2b_bus *a2b_bus,
+ unsigned int *val)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+ int ret;
+
+ mutex_lock(&ad->mutex);
+ ret = regmap_read(ad->base_regmap, A2B_INTTYPE, val);
+ mutex_unlock(&ad->mutex);
+
+ return ret;
+}
+
+static struct clk *ad24xx_i2c_get_sync_clk(struct a2b_bus *a2b_bus)
+{
+ struct ad24xx_i2c *ad = to_ad24xx_i2c(a2b_bus);
+
+ return ad->sync_clk;
+}
+
+struct a2b_bus_ops ad24xx_i2c_a2b_bus_ops = {
+ .read = ad24xx_i2c_read,
+ .write = ad24xx_i2c_write,
+ .i2c_xfer = ad24xx_i2c_xfer,
+ .get_inttype = ad24xx_i2c_get_inttype,
+ .get_sync_clk = ad24xx_i2c_get_sync_clk,
+};
+
+static irqreturn_t ad24xx_i2c_irq_handler(int irq, void *data)
+{
+ struct ad24xx_i2c *ad = data;
+ bool handled = false;
+ unsigned long hwirq;
+ unsigned int val;
+ unsigned int virq;
+ int ret;
+
+ /*
+ * The transceiver asserts the IRQ line as long as there are pending
+ * interrupts. Process them all here so that the interrupt can be
+ * configured with an edge trigger.
+ */
+ while (true) {
+ mutex_lock(&ad->mutex);
+ ret = regmap_read(ad->base_regmap, A2B_INTSRC, &val);
+ mutex_unlock(&ad->mutex);
+ if (ret) {
+ dev_err_ratelimited(
+ ad->dev,
+ "failed to read interrupt source: %d\n", ret);
+ break;
+ }
+
+ if (val & A2B_INTSRC_MSTINT_MASK)
+ hwirq = 0;
+ else if (val & A2B_INTSRC_SLVINT_MASK)
+ hwirq = (val & A2B_INTSRC_INODE_MASK) + 1;
+ else
+ break;
+
+ /*
+ * Pending interrupts are only cleared when reading the
+ * interrupt type. Normally this is done in the corresponding
+ * node's interrupt handler, but in case the interrupt is
+ * disabled, it has to be read here.
+ */
+ if (!(BIT(hwirq) & ad->irqs_enabled)) {
+ ret = ad24xx_i2c_get_inttype(&ad->a2b_bus, &val);
+ if (ret)
+ dev_err_ratelimited(
+ ad->dev,
+ "failed to read interrupt type: %d\n",
+ ret);
+ handled = true;
+ continue;
+ }
+
+ virq = irq_find_mapping(ad->irqdomain, hwirq);
+ if (!virq)
+ break;
+
+ handle_nested_irq(virq);
+ handled = true;
+ }
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static void ad24xx_i2c_irq_enable(struct irq_data *irq_data)
+{
+ struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
+ irq_hw_number_t hwirq = irq_data->hwirq;
+
+ ad->irqs_enabled |= BIT(hwirq);
+}
+
+static void ad24xx_i2c_irq_disable(struct irq_data *irq_data)
+{
+ struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
+ irq_hw_number_t hwirq = irq_data->hwirq;
+
+ ad->irqs_enabled &= ~BIT(hwirq);
+}
+
+static const struct irq_chip ad24xx_i2c_irq_chip = {
+ .name = "ad24xx-i2c",
+ .irq_enable = ad24xx_i2c_irq_enable,
+ .irq_disable = ad24xx_i2c_irq_disable,
+};
+
+static int ad24xx_i2c_irqdomain_map(struct irq_domain *irqdomain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_data(irq, irqdomain->host_data);
+ irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
+ irq_set_nested_thread(irq, 1);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static void ad24xx_i2c_irqdomain_unmap(struct irq_domain *irqdomain,
+ unsigned int irq)
+{
+ irq_set_nested_thread(irq, 0);
+ irq_set_chip_and_handler(irq, NULL, NULL);
+ irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops ad24xx_i2c_irqdomain_ops = {
+ .map = ad24xx_i2c_irqdomain_map,
+ .unmap = ad24xx_i2c_irqdomain_unmap,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static void devm_ad24xx_i2c_release_irqdomain(void *data)
+{
+ struct irq_domain *irqdomain = data;
+ int virq;
+ int i;
+
+ for (i = 0; i < A2B_MAX_NODES; i++) {
+ virq = irq_find_mapping(irqdomain, i);
+ if (virq)
+ irq_dispose_mapping(virq);
+ }
+
+ irq_domain_remove(irqdomain);
+}
+
+static int ad24xx_i2c_irq_setup(struct ad24xx_i2c *ad)
+{
+ u32 intsize;
+ int ret;
+
+ if (!of_property_read_bool(ad->dev->of_node, "interrupt-controller") ||
+ of_property_read_u32(ad->dev->of_node, "#interrupt-cells",
+ &intsize) ||
+ intsize != 1)
+ return -EINVAL;
+
+ ad->irqdomain = irq_domain_add_linear(ad->dev->of_node, A2B_MAX_NODES,
+ &ad24xx_i2c_irqdomain_ops, ad);
+ if (!ad->irqdomain)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(
+ ad->dev, devm_ad24xx_i2c_release_irqdomain, ad->irqdomain);
+ if (ret)
+ return ret;
+
+ ret = devm_request_threaded_irq(ad->dev, ad->irq, NULL,
+ ad24xx_i2c_irq_handler, IRQF_ONESHOT,
+ "ad24xx-i2c", ad);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int ad24xx_i2c_bus_setup(struct ad24xx_i2c *ad)
+{
+ struct device *dev = ad->dev;
+ unsigned long sff_rate;
+ int ret;
+
+ ad->a2b_bus.ops = &ad24xx_i2c_a2b_bus_ops;
+ ad->a2b_bus.parent = dev;
+ ad->a2b_bus.priv = ad;
+
+ sff_rate = clk_get_rate(ad->sync_clk);
+ if (sff_rate == 48000)
+ ad->a2b_bus.sff = A2B_SFF_48000;
+ else if (sff_rate == 44100)
+ ad->a2b_bus.sff = A2B_SFF_44100;
+ else
+ return -EINVAL;
+
+ ret = a2b_register_bus(&ad->a2b_bus);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct regmap_config ad24xx_i2c_base_regmap_config = {
+ .disable_locking = true,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_stride = 1,
+ .max_register = A2B_REG_MAX,
+};
+
+static const struct regmap_config ad24xx_i2c_bus_regmap_config = {
+ .disable_locking = true,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_stride = 1,
+ .max_register = A2B_REG_MAX,
+};
+
+static void ad24xx_i2c_remove(struct i2c_client *client)
+{
+ struct ad24xx_i2c *ad = i2c_get_clientdata(client);
+
+ a2b_unregister_bus(&ad->a2b_bus);
+}
+
+static int ad24xx_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device_node *np;
+ struct ad24xx_i2c *ad;
+ struct regmap_config *base_regmap_config;
+ struct regmap_config *bus_regmap_config;
+ u32 bus_addr;
+ int ret;
+ int i;
+
+ ad = devm_kzalloc(dev, sizeof(*ad), GFP_KERNEL);
+ if (!ad)
+ return -ENOMEM;
+
+ base_regmap_config = devm_kmemdup(dev, &ad24xx_i2c_base_regmap_config,
+ sizeof(*base_regmap_config),
+ GFP_KERNEL);
+ if (!base_regmap_config)
+ return -ENOMEM;
+
+ bus_regmap_config = devm_kmemdup(dev, &ad24xx_i2c_bus_regmap_config,
+ sizeof(*bus_regmap_config),
+ GFP_KERNEL);
+ if (!bus_regmap_config)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, ad);
+ ad->dev = dev;
+ ad->irq = client->irq;
+ ad->base_client = client;
+ mutex_init(&ad->mutex);
+
+ /* Optionally enable regulators for VIN or for out-of-band bus power */
+ ret = devm_regulator_get_enable_optional(dev, "vin");
+ if (ret && ret != -ENODEV)
+ return ret;
+
+ ret = devm_regulator_get_enable_optional(dev, "bus");
+ if (ret && ret != -ENODEV)
+ return ret;
+
+ ad->base_regmap =
+ devm_regmap_init_i2c(ad->base_client, base_regmap_config);
+ if (IS_ERR(ad->base_regmap))
+ return PTR_ERR(ad->base_regmap);
+
+ np = client->dev.of_node;
+ if (!np)
+ return -EINVAL;
+
+ i = of_property_match_string(np, "reg-names", "bus");
+ if (i < 0)
+ return -EINVAL;
+
+ ret = of_property_read_u32_index(np, "reg", i, &bus_addr);
+ if (ret)
+ return ret;
+
+ ad->bus_client =
+ devm_i2c_new_dummy_device(dev, client->adapter, bus_addr);
+ if (IS_ERR(ad->bus_client))
+ return PTR_ERR(ad->bus_client);
+
+ ad->bus_regmap =
+ devm_regmap_init_i2c(ad->bus_client, bus_regmap_config);
+ if (IS_ERR(ad->bus_regmap))
+ return PTR_ERR(ad->bus_regmap);
+
+ ad->sync_clk = devm_clk_get_enabled(dev, "sync");
+ if (IS_ERR(ad->sync_clk))
+ return PTR_ERR(ad->sync_clk);
+
+ ret = ad24xx_i2c_irq_setup(ad);
+ if (ret)
+ return ret;
+
+ ret = ad24xx_i2c_bus_setup(ad);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id ad24xx_i2c_of_match_table[] = {
+ { .compatible = "adi,ad2403" },
+ { .compatible = "adi,ad2410" },
+ { .compatible = "adi,ad2425" },
+ { .compatible = "adi,ad2428" },
+ { .compatible = "adi,ad2429" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_i2c_of_match_table);
+
+static const struct i2c_device_id ad24xx_i2c_id_table[] = {
+ { .name = "ad24xx", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ad24xx_i2c_id_table);
+
+static struct i2c_driver ad24xx_i2c_driver = {
+ .driver = {
+ .name = "ad24xx-i2c",
+ .of_match_table = ad24xx_i2c_of_match_table,
+ },
+ .probe = ad24xx_i2c_probe,
+ .remove = ad24xx_i2c_remove,
+ .id_table = ad24xx_i2c_id_table,
+};
+module_i2c_driver(ad24xx_i2c_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("AD24xx I2C driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/a2b/ad24xx.h b/include/linux/a2b/ad24xx.h
new file mode 100644
index 000000000000..846838e62c8a
--- /dev/null
+++ b/include/linux/a2b/ad24xx.h
@@ -0,0 +1,892 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AD24xx register map
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ */
+
+#ifndef _AD24XX_H
+#define _AD24XX_H
+
+#define AD24XX_MAX_GPIOS 8
+
+enum ad24xx_regs {
+ A2B_CHIP = 0x00, /* I2C Chip Address Register (sub only) */
+ A2B_NODEADR = 0x01, /* Node Address Register (main only) */
+ A2B_VENDOR = 0x02, /* Vendor ID Register */
+ A2B_PRODUCT = 0x03, /* Product ID Register */
+ A2B_VERSION = 0x04, /* Version ID Register */
+ A2B_CAPABILITY = 0x05, /* Capability ID Register */
+ A2B_SWCTL = 0x09, /* Switch Control Register */
+ A2B_BCDNSLOTS = 0x0A, /* Broadcast Downstream Slots Register (sub only) */
+ A2B_LDNSLOTS = 0x0B, /* Local Downstream Slots Register (sub only) */
+ A2B_LUPSLOTS = 0x0C, /* Local Upstream Slots Register (sub only) */
+ A2B_DNSLOTS = 0x0D, /* Downstream Slots Register */
+ A2B_UPSLOTS = 0x0E, /* Upstream Slots Register */
+ A2B_RESPCYCS = 0x0F, /* Response Cycles Register */
+ A2B_SLOTFMT = 0x10, /* Slot Format Register (main only, Auto-Broadcast) */
+ A2B_DATCTL = 0x11, /* Data Control Register (main only, Auto-Broadcast) */
+ A2B_CONTROL = 0x12, /* Control Register */
+ A2B_DISCVRY = 0x13, /* Discovery Register (main only) */
+ A2B_SWSTAT = 0x14, /* Switch Status Register */
+ A2B_INTSTAT = 0x15, /* Interrupt Status Register */
+ A2B_INTSRC = 0x16, /* Interrupt Source Register (main only) */
+ A2B_INTTYPE = 0x17, /* Interrupt Type Register (main only) */
+ A2B_INTPND0 = 0x18, /* Interrupt Pending 0 Register */
+ A2B_INTPND1 = 0x19, /* Interrupt Pending 1 Register */
+ A2B_INTPND2 = 0x1A, /* Interrupt Pending 2 Register (main only) */
+ A2B_INTMSK0 = 0x1B, /* Interrupt Mask 0 Register */
+ A2B_INTMSK1 = 0x1C, /* Interrupt Mask 1 Register */
+ A2B_INTMSK2 = 0x1D, /* Interrupt Mask 2 Register (main only) */
+ A2B_BECCTL = 0x1E, /* Bit Error Count Control Register */
+ A2B_BECNT = 0x1F, /* Bit Error Count Register */
+ A2B_TESTMODE = 0x20, /* Testmode Register */
+ A2B_ERRCNT0 = 0x21, /* PRBS Error Count Byte 0 Register */
+ A2B_ERRCNT1 = 0x22, /* PRBS Error Count Byte 1 Register */
+ A2B_ERRCNT2 = 0x23, /* PRBS Error Count Byte 2 Register */
+ A2B_ERRCNT3 = 0x24, /* PRBS Error Count Byte 3 Register */
+ A2B_NODE = 0x29, /* Node Register */
+ A2B_DISCSTAT = 0x2B, /* Discovery Status Register (main only) */
+ A2B_TXACTL = 0x2E, /* LVDSA TX Control Register */
+ A2B_TXBCTL = 0x30, /* LVDSB TX Control Register */
+ A2B_LINTTYPE = 0x3E, /* Local Interrupt Type (sub only) */
+ A2B_I2CCFG = 0x3F, /* I2C Configuration Register */
+ A2B_PLLCTL = 0x40, /* PLL Control Register */
+ A2B_I2SGCFG = 0x41, /* I2S Global Configuration Register */
+ A2B_I2SCFG = 0x42, /* I2S Configuration Register */
+ A2B_I2SRATE = 0x43, /* I2S Rate Register (sub only) */
+ A2B_I2STXOFFSET = 0x44, /* I2S Transmit Data Offset Register (main only) */
+ A2B_I2SRXOFFSET = 0x45, /* I2S Receive Data Offset Register (main only) */
+ A2B_SYNCOFFSET = 0x46, /* SYNC Offset Register (sub only) */
+ A2B_PDMCTL = 0x47, /* PDM Control Register */
+ A2B_ERRMGMT = 0x48, /* Error Management Register */
+ A2B_GPIODAT = 0x4A, /* GPIO Output Data Register */
+ A2B_GPIODATSET = 0x4B, /* GPIO Output Data Set Register */
+ A2B_GPIODATCLR = 0x4C, /* GPIO Output Data Clear Register */
+ A2B_GPIOOEN = 0x4D, /* GPIO Output Enable Register */
+ A2B_GPIOIEN = 0x4E, /* GPIO Input Enable Register */
+ A2B_GPIOIN = 0x4F, /* GPIO Input Value Register */
+ A2B_PINTEN = 0x50, /* Pin Interrupt Enable Register */
+ A2B_PINTINV = 0x51, /* Pin Interrupt Invert Register */
+ A2B_PINCFG = 0x52, /* Pin Configuration Register */
+ A2B_I2STEST = 0x53, /* I2S Test Register */
+ A2B_RAISE = 0x54, /* Raise Interrupt Register */
+ A2B_GENERR = 0x55, /* Generate Bus Error */
+ A2B_I2SRRATE = 0x56, /* I2S Reduced Rate Register (main only, Auto-Broadcast) */
+ A2B_I2SRRCTL = 0x57, /* I2S Reduced Rate Control Register */
+ A2B_I2SRRSOFFS = 0x58, /* I2S Reduced Rate SYNC Offset Register (sub only) */
+ A2B_CLK1CFG = 0x59, /* CLKOUT1 Configuration Register */
+ A2B_CLK2CFG = 0x5A, /* CLKOUT2 Configuration Register */
+ A2B_BMMCFG = 0x5B, /* Bus Monitor Mode Configuration Register */
+ A2B_SUSCFG = 0x5C, /* Sustain Configuration Register (sub only) */
+ A2B_PDMCTL2 = 0x5D, /* PDM Control 2 Register */
+ A2B_UPMASK0 = 0x60, /* Upstream Data RX Mask 0 Register (sub only) */
+ A2B_UPMASK1 = 0x61, /* Upstream Data RX Mask 1 Register (sub only) */
+ A2B_UPMASK2 = 0x62, /* Upstream Data RX Mask 2 Register (sub only) */
+ A2B_UPMASK3 = 0x63, /* Upstream Data RX Mask 3 Register (sub only) */
+ A2B_UPOFFSET = 0x64, /* Local Upstream Channel Offset Register (sub only) */
+ A2B_DNMASK0 = 0x65, /* Downstream Data RX Mask 0 Register (sub only) */
+ A2B_DNMASK1 = 0x66, /* Downstream Data RX Mask 1 Register (sub only) */
+ A2B_DNMASK2 = 0x67, /* Downstream Data RX Mask 2 Register (sub only) */
+ A2B_DNMASK3 = 0x68, /* Downstream Data RX Mask 3 Register (sub only) */
+ A2B_DNOFFSET = 0x69, /* Local Downstream Channel Offset Register (sub only) */
+ A2B_CHIPID0 = 0x6A, /* Chip ID Register 0 */
+ A2B_CHIPID1 = 0x6B, /* Chip ID Register 1 */
+ A2B_CHIPID2 = 0x6C, /* Chip ID Register 2 */
+ A2B_CHIPID3 = 0x6D, /* Chip ID Register 3 */
+ A2B_CHIPID4 = 0x6E, /* Chip ID Register 4 */
+ A2B_CHIPID5 = 0x6F, /* Chip ID Register 5 */
+ A2B_GPIODEN = 0x80, /* GPIO Over Distance Enable Register */
+ A2B_GPIOD0MSK = 0x81, /* GPIO Over Distance Mask 0 Register */
+ A2B_GPIOD1MSK = 0x82, /* GPIO Over Distance Mask 1 Register */
+ A2B_GPIOD2MSK = 0x83, /* GPIO Over Distance Mask 2 Register */
+ A2B_GPIOD3MSK = 0x84, /* GPIO Over Distance Mask 3 Register */
+ A2B_GPIOD4MSK = 0x85, /* GPIO Over Distance Mask 4 Register */
+ A2B_GPIOD5MSK = 0x86, /* GPIO Over Distance Mask 5 Register */
+ A2B_GPIOD6MSK = 0x87, /* GPIO Over Distance Mask 6 Register */
+ A2B_GPIOD7MSK = 0x88, /* GPIO Over Distance Mask 7 Register */
+ A2B_GPIODDAT = 0x89, /* GPIO Over Distance Data Register */
+ A2B_GPIODINV = 0x8A, /* GPIO Over Distance Invert Register */
+ A2B_MBOX0CTL = 0x90, /* Mailbox 0 Control Register (sub only) */
+ A2B_MBOX0STAT = 0x91, /* Mailbox 0 Status Register (sub only) */
+ A2B_MBOX0B0 = 0x92, /* Mailbox 0 Byte 0 Register (sub only) */
+ A2B_MBOX0B1 = 0x93, /* Mailbox 0 Byte 1 Register (sub only) */
+ A2B_MBOX0B2 = 0x94, /* Mailbox 0 Byte 2 Register (sub only) */
+ A2B_MBOX0B3 = 0x95, /* Mailbox 0 Byte 3 Register (sub only) */
+ A2B_MBOX1CTL = 0x96, /* Mailbox 1 Control Register (sub only) */
+ A2B_MBOX1STAT = 0x97, /* Mailbox 1 Status Register (sub only) */
+ A2B_MBOX1B0 = 0x98, /* Mailbox 1 Byte 0 Register (sub only) */
+ A2B_MBOX1B1 = 0x99, /* Mailbox 1 Byte 1 Register (sub only) */
+ A2B_MBOX1B2 = 0x9A, /* Mailbox 1 Byte 2 Register (sub only) */
+ A2B_MBOX1B3 = 0x9B, /* Mailbox 1 Byte 3 Register (sub only) */
+ A2B_REG_END,
+ A2B_REG_MAX = A2B_REG_END - 1,
+};
+
+#define A2B_CHIP_CHIPADR_MASK GENMASK(6, 0)
+#define A2B_NODEADR_NODE_MASK GENMASK(3, 0)
+#define A2B_NODEADR_PERI_MASK GENMASK(5, 5)
+#define A2B_NODEADR_BRCST_MASK GENMASK(7, 7)
+#define A2B_VENDOR_VENDOR_MASK GENMASK(7, 0)
+#define A2B_PRODUCT_PRODUCT_MASK GENMASK(7, 0)
+#define A2B_VERSION_VERSION_MASK GENMASK(7, 0)
+#define A2B_CAPABILITY_I2CAVAIL_MASK GENMASK(0, 0)
+#define A2B_SWCTL_ENSW_MASK GENMASK(0, 0)
+#define A2B_SWCTL_DIAGMODE_MASK GENMASK(3, 3)
+#define A2B_SWCTL_MODE_MASK GENMASK(5, 4)
+#define A2B_SWCTL_DISNXT_MASK GENMASK(6, 6)
+#define A2B_BCDNSLOTS_BCDNSLOTS_MASK GENMASK(5, 0)
+#define A2B_LDNSLOTS_LDNSLOTS_MASK GENMASK(5, 0)
+#define A2B_LDNSLOTS_DNMASKEN_MASK GENMASK(7, 7)
+#define A2B_LUPSLOTS_LUPSLOTS_MASK GENMASK(5, 0)
+#define A2B_DNSLOTS_DNSLOTS_MASK GENMASK(5, 0)
+#define A2B_UPSLOTS_UPSLOTS_MASK GENMASK(5, 0)
+#define A2B_RESPCYCS_RESPCYCS_MASK GENMASK(7, 0)
+#define A2B_SLOTFMT_DNSIZE_MASK GENMASK(2, 0)
+#define A2B_SLOTFMT_DNFMT_MASK GENMASK(3, 3)
+#define A2B_SLOTFMT_UPSIZE_MASK GENMASK(6, 4)
+#define A2B_SLOTFMT_UPFMT_MASK GENMASK(7, 7)
+#define A2B_DATCTL_DNS_MASK GENMASK(0, 0)
+#define A2B_DATCTL_UPS_MASK GENMASK(1, 1)
+#define A2B_DATCTL_ENDSNIFF_MASK GENMASK(5, 5)
+#define A2B_DATCTL_STANDBY_MASK GENMASK(7, 7)
+#define A2B_CONTROL_NEWSTRCT_MASK GENMASK(0, 0)
+#define A2B_CONTROL_ENDDSC_MASK GENMASK(1, 1)
+#define A2B_CONTROL_SOFTRST_MASK GENMASK(2, 2)
+#define A2B_CONTROL_SWBYP_MASK GENMASK(3, 3)
+#define A2B_CONTROL_XCVRBINV_MASK GENMASK(4, 4)
+#define A2B_CONTROL_MSTR_MASK GENMASK(7, 7)
+#define A2B_DISCVRY_DRESPCYC_MASK GENMASK(7, 0)
+#define A2B_SWSTAT_FIN_MASK GENMASK(0, 0)
+#define A2B_SWSTAT_FAULT_MASK GENMASK(1, 1)
+#define A2B_SWSTAT_FAULT_CODE_MASK GENMASK(6, 4)
+#define A2B_SWSTAT_FAULT_NLOC_MASK GENMASK(7, 7)
+#define A2B_INTSTAT_IRQ_MASK GENMASK(0, 0)
+#define A2B_INTSRC_INODE_MASK GENMASK(3, 0)
+#define A2B_INTSRC_SLVINT_MASK GENMASK(6, 6)
+#define A2B_INTSRC_MSTINT_MASK GENMASK(7, 7)
+#define A2B_INTTYPE_TYPE_MASK GENMASK(7, 0)
+#define A2B_INTPND0_HDCNTERR_MASK GENMASK(0, 0)
+#define A2B_INTPND0_DDERR_MASK GENMASK(1, 1)
+#define A2B_INTPND0_CRCERR_MASK GENMASK(2, 2)
+#define A2B_INTPND0_DPERR_MASK GENMASK(3, 3)
+#define A2B_INTPND0_PWRERR_MASK GENMASK(4, 4)
+#define A2B_INTPND0_BECOVF_MASK GENMASK(5, 5)
+#define A2B_INTPND0_SRFERR_MASK GENMASK(6, 6)
+#define A2B_INTPND0_SRFCRCERR_MASK GENMASK(7, 7)
+#define A2B_INTPND1_IO0PND_MASK GENMASK(0, 0)
+#define A2B_INTPND1_IO1PND_MASK GENMASK(1, 1)
+#define A2B_INTPND1_IO2PND_MASK GENMASK(2, 2)
+#define A2B_INTPND1_IO3PND_MASK GENMASK(3, 3)
+#define A2B_INTPND1_IO4PND_MASK GENMASK(4, 4)
+#define A2B_INTPND1_IO5PND_MASK GENMASK(5, 5)
+#define A2B_INTPND1_IO6PND_MASK GENMASK(6, 6)
+#define A2B_INTPND1_IO7PND_MASK GENMASK(7, 7)
+#define A2B_INTPND2_DSCDONE_MASK GENMASK(0, 0)
+#define A2B_INTPND2_I2CERR_MASK GENMASK(1, 1)
+#define A2B_INTPND2_ICRCERR_MASK GENMASK(2, 2)
+#define A2B_INTPND2_SLVIRQ_MASK GENMASK(3, 3)
+#define A2B_INTMSK0_HDEIEN_MASK GENMASK(0, 0)
+#define A2B_INTMSK0_DDEIEN_MASK GENMASK(1, 1)
+#define A2B_INTMSK0_CRCEIEN_MASK GENMASK(2, 2)
+#define A2B_INTMSK0_DPEIEN_MASK GENMASK(3, 3)
+#define A2B_INTMSK0_PWREIEN_MASK GENMASK(4, 4)
+#define A2B_INTMSK0_BECIEN_MASK GENMASK(5, 5)
+#define A2B_INTMSK0_SRFEIEN_MASK GENMASK(6, 6)
+#define A2B_INTMSK0_SRFCRCEIEN_MASK GENMASK(7, 7)
+#define A2B_INTMSK1_IO0IRQEN_MASK GENMASK(0, 0)
+#define A2B_INTMSK1_IO1IRQEN_MASK GENMASK(1, 1)
+#define A2B_INTMSK1_IO2IRQEN_MASK GENMASK(2, 2)
+#define A2B_INTMSK1_IO3IRQEN_MASK GENMASK(3, 3)
+#define A2B_INTMSK1_IO4IRQEN_MASK GENMASK(4, 4)
+#define A2B_INTMSK1_IO5IRQEN_MASK GENMASK(5, 5)
+#define A2B_INTMSK1_IO6IRQEN_MASK GENMASK(6, 6)
+#define A2B_INTMSK1_IO7IRQEN_MASK GENMASK(7, 7)
+#define A2B_INTMSK2_DSCDIEN_MASK GENMASK(0, 0)
+#define A2B_INTMSK2_I2CEIEN_MASK GENMASK(1, 1)
+#define A2B_INTMSK2_ICRCEIEN_MASK GENMASK(2, 2)
+#define A2B_INTMSK2_SLVIRQEN_MASK GENMASK(3, 3)
+#define A2B_BECCTL_ENHDCNT_MASK GENMASK(0, 0)
+#define A2B_BECCTL_ENDD_MASK GENMASK(1, 1)
+#define A2B_BECCTL_ENCRC_MASK GENMASK(2, 2)
+#define A2B_BECCTL_ENDP_MASK GENMASK(3, 3)
+#define A2B_BECCTL_ENICRC_MASK GENMASK(4, 4)
+#define A2B_BECCTL_THRESHLD_MASK GENMASK(7, 5)
+#define A2B_BECNT_BECNT_MASK GENMASK(7, 0)
+#define A2B_TESTMODE_PRBSUP_MASK GENMASK(0, 0)
+#define A2B_TESTMODE_PRBSDN_MASK GENMASK(1, 1)
+#define A2B_TESTMODE_PRBSN2N_MASK GENMASK(2, 2)
+#define A2B_TESTMODE_RXDPTH_MASK GENMASK(5, 4)
+#define A2B_ERRCNT0_ERRCNT_MASK GENMASK(7, 0)
+#define A2B_ERRCNT1_ERRCNT_MASK GENMASK(7, 0)
+#define A2B_ERRCNT2_ERRCNT_MASK GENMASK(7, 0)
+#define A2B_ERRCNT3_ERRCNT_MASK GENMASK(7, 0)
+#define A2B_NODE_NUMBER_MASK GENMASK(3, 0)
+#define A2B_NODE_DISCVD_MASK GENMASK(5, 5)
+#define A2B_NODE_NLAST_MASK GENMASK(6, 6)
+#define A2B_NODE_LAST_MASK GENMASK(7, 7)
+#define A2B_DISCSTAT_DNODE_MASK GENMASK(3, 0)
+#define A2B_DISCSTAT_DSCACT_MASK GENMASK(7, 7)
+#define A2B_TXACTL_TXALEVEL_MASK GENMASK(1, 0)
+#define A2B_TXACTL_TXAOVREN_MASK GENMASK(7, 7)
+#define A2B_TXBCTL_TXBLEVEL_MASK GENMASK(1, 0)
+#define A2B_TXBCTL_TXBOVREN_MASK GENMASK(7, 7)
+#define A2B_LINTTYPE_LTYPE_MASK GENMASK(7, 0)
+#define A2B_I2CCFG_DATARATE_MASK GENMASK(0, 0)
+#define A2B_I2CCFG_EACK_MASK GENMASK(1, 1)
+#define A2B_I2CCFG_FRAMERATE_MASK GENMASK(2, 2)
+#define A2B_PLLCTL_SSFREQ_MASK GENMASK(1, 0)
+#define A2B_PLLCTL_SSDEPTH_MASK GENMASK(3, 3)
+#define A2B_PLLCTL_SSMODE_MASK GENMASK(7, 6)
+#define A2B_I2SGCFG_TDMMODE_MASK GENMASK(2, 0)
+#define A2B_I2SGCFG_RXONDTX1_MASK GENMASK(3, 3)
+#define A2B_I2SGCFG_TDMSS_MASK GENMASK(4, 4)
+#define A2B_I2SGCFG_ALT_MASK GENMASK(5, 5)
+#define A2B_I2SGCFG_EARLY_MASK GENMASK(6, 6)
+#define A2B_I2SGCFG_INV_MASK GENMASK(7, 7)
+#define A2B_I2SCFG_TX0EN_MASK GENMASK(0, 0)
+#define A2B_I2SCFG_TX1EN_MASK GENMASK(1, 1)
+#define A2B_I2SCFG_TX2PINTL_MASK GENMASK(2, 2)
+#define A2B_I2SCFG_TXBCLKINV_MASK GENMASK(3, 3)
+#define A2B_I2SCFG_RX0EN_MASK GENMASK(4, 4)
+#define A2B_I2SCFG_RX1EN_MASK GENMASK(5, 5)
+#define A2B_I2SCFG_RX2PINTL_MASK GENMASK(6, 6)
+#define A2B_I2SCFG_RXBCLKINV_MASK GENMASK(7, 7)
+#define A2B_I2SRATE_I2SRATE_MASK GENMASK(2, 0)
+#define A2B_I2SRATE_BCLKRATE_MASK GENMASK(5, 3)
+#define A2B_I2SRATE_FRAMES_MASK GENMASK(5, 4)
+#define A2B_I2SRATE_REDUCE_MASK GENMASK(6, 6)
+#define A2B_I2SRATE_SHARE_MASK GENMASK(7, 7)
+#define A2B_I2STXOFFSET_TXOFFSET_MASK GENMASK(5, 0)
+#define A2B_I2STXOFFSET_TSAFTER_MASK GENMASK(6, 6)
+#define A2B_I2STXOFFSET_TSBEFORE_MASK GENMASK(7, 7)
+#define A2B_I2SRXOFFSET_RXOFFSET_MASK GENMASK(5, 0)
+#define A2B_SYNCOFFSET_SYNCOFFSET_MASK GENMASK(7, 0)
+#define A2B_PDMCTL_PDM0EN_MASK GENMASK(0, 0)
+#define A2B_PDMCTL_PDM0SLOTS_MASK GENMASK(1, 1)
+#define A2B_PDMCTL_PDM1EN_MASK GENMASK(2, 2)
+#define A2B_PDMCTL_PDM1SLOTS_MASK GENMASK(3, 3)
+#define A2B_PDMCTL_HPFEN_MASK GENMASK(4, 4)
+#define A2B_PDMCTL_PDMRATE_MASK GENMASK(6, 5)
+#define A2B_ERRMGMT_ERRLSB_MASK GENMASK(0, 0)
+#define A2B_ERRMGMT_ERRSIG_MASK GENMASK(1, 1)
+#define A2B_ERRMGMT_ERRSLOT_MASK GENMASK(2, 2)
+#define A2B_GPIODAT_IO0DAT_MASK GENMASK(0, 0)
+#define A2B_GPIODAT_IO1DAT_MASK GENMASK(1, 1)
+#define A2B_GPIODAT_IO2DAT_MASK GENMASK(2, 2)
+#define A2B_GPIODAT_IO3DAT_MASK GENMASK(3, 3)
+#define A2B_GPIODAT_IO4DAT_MASK GENMASK(4, 4)
+#define A2B_GPIODAT_IO5DAT_MASK GENMASK(5, 5)
+#define A2B_GPIODAT_IO6DAT_MASK GENMASK(6, 6)
+#define A2B_GPIODAT_IO7DAT_MASK GENMASK(7, 7)
+#define A2B_GPIODATSET_IO0DSET_MASK GENMASK(0, 0)
+#define A2B_GPIODATSET_IO1DSET_MASK GENMASK(1, 1)
+#define A2B_GPIODATSET_IO2DSET_MASK GENMASK(2, 2)
+#define A2B_GPIODATSET_IO3DSET_MASK GENMASK(3, 3)
+#define A2B_GPIODATSET_IO4DSET_MASK GENMASK(4, 4)
+#define A2B_GPIODATSET_IO5DSET_MASK GENMASK(5, 5)
+#define A2B_GPIODATSET_IO6DSET_MASK GENMASK(6, 6)
+#define A2B_GPIODATSET_IO7DSET_MASK GENMASK(7, 7)
+#define A2B_GPIODATCLR_IO0DCLR_MASK GENMASK(0, 0)
+#define A2B_GPIODATCLR_IO1DCLR_MASK GENMASK(1, 1)
+#define A2B_GPIODATCLR_IO2DCLR_MASK GENMASK(2, 2)
+#define A2B_GPIODATCLR_IO3DCLR_MASK GENMASK(3, 3)
+#define A2B_GPIODATCLR_IO4DCLR_MASK GENMASK(4, 4)
+#define A2B_GPIODATCLR_IO5DCLR_MASK GENMASK(5, 5)
+#define A2B_GPIODATCLR_IO6DCLR_MASK GENMASK(6, 6)
+#define A2B_GPIODATCLR_IO7DCLR_MASK GENMASK(7, 7)
+#define A2B_GPIOOEN_IO0OEN_MASK GENMASK(0, 0)
+#define A2B_GPIOOEN_IO1OEN_MASK GENMASK(1, 1)
+#define A2B_GPIOOEN_IO2OEN_MASK GENMASK(2, 2)
+#define A2B_GPIOOEN_IO3OEN_MASK GENMASK(3, 3)
+#define A2B_GPIOOEN_IO4OEN_MASK GENMASK(4, 4)
+#define A2B_GPIOOEN_IO5OEN_MASK GENMASK(5, 5)
+#define A2B_GPIOOEN_IO6OEN_MASK GENMASK(6, 6)
+#define A2B_GPIOOEN_IO7OEN_MASK GENMASK(7, 7)
+#define A2B_GPIOIEN_IO0IEN_MASK GENMASK(0, 0)
+#define A2B_GPIOIEN_IO1IEN_MASK GENMASK(1, 1)
+#define A2B_GPIOIEN_IO2IEN_MASK GENMASK(2, 2)
+#define A2B_GPIOIEN_IO3IEN_MASK GENMASK(3, 3)
+#define A2B_GPIOIEN_IO4IEN_MASK GENMASK(4, 4)
+#define A2B_GPIOIEN_IO5IEN_MASK GENMASK(5, 5)
+#define A2B_GPIOIEN_IO6IEN_MASK GENMASK(6, 6)
+#define A2B_GPIOIEN_IO7IEN_MASK GENMASK(7, 7)
+#define A2B_GPIOIN_IO0IN_MASK GENMASK(0, 0)
+#define A2B_GPIOIN_IO1IN_MASK GENMASK(1, 1)
+#define A2B_GPIOIN_IO2IN_MASK GENMASK(2, 2)
+#define A2B_GPIOIN_IO3IN_MASK GENMASK(3, 3)
+#define A2B_GPIOIN_IO4IN_MASK GENMASK(4, 4)
+#define A2B_GPIOIN_IO5IN_MASK GENMASK(5, 5)
+#define A2B_GPIOIN_IO6IN_MASK GENMASK(6, 6)
+#define A2B_GPIOIN_IO7IN_MASK GENMASK(7, 7)
+#define A2B_PINTEN_IO0IE_MASK GENMASK(0, 0)
+#define A2B_PINTEN_IO1IE_MASK GENMASK(1, 1)
+#define A2B_PINTEN_IO2IE_MASK GENMASK(2, 2)
+#define A2B_PINTEN_IO3IE_MASK GENMASK(3, 3)
+#define A2B_PINTEN_IO4IE_MASK GENMASK(4, 4)
+#define A2B_PINTEN_IO5IE_MASK GENMASK(5, 5)
+#define A2B_PINTEN_IO6IE_MASK GENMASK(6, 6)
+#define A2B_PINTEN_IO7IE_MASK GENMASK(7, 7)
+#define A2B_PINTINV_IO0INV_MASK GENMASK(0, 0)
+#define A2B_PINTINV_IO1INV_MASK GENMASK(1, 1)
+#define A2B_PINTINV_IO2INV_MASK GENMASK(2, 2)
+#define A2B_PINTINV_IO3INV_MASK GENMASK(3, 3)
+#define A2B_PINTINV_IO4INV_MASK GENMASK(4, 4)
+#define A2B_PINTINV_IO5INV_MASK GENMASK(5, 5)
+#define A2B_PINTINV_IO6INV_MASK GENMASK(6, 6)
+#define A2B_PINTINV_IO7INV_MASK GENMASK(7, 7)
+#define A2B_PINCFG_DRVSTR_MASK GENMASK(0, 0)
+#define A2B_PINCFG_IRQINV_MASK GENMASK(4, 4)
+#define A2B_PINCFG_IRQTS_MASK GENMASK(5, 5)
+#define A2B_I2STEST_PATTRN2TX_MASK GENMASK(0, 0)
+#define A2B_I2STEST_LOOPBK2TX_MASK GENMASK(1, 1)
+#define A2B_I2STEST_RX2LOOPBK_MASK GENMASK(2, 2)
+#define A2B_I2STEST_SELRX1_MASK GENMASK(3, 3)
+#define A2B_I2STEST_BUSLOOPBK_MASK GENMASK(4, 4)
+#define A2B_RAISE_RAISE_MASK GENMASK(7, 0)
+#define A2B_GENERR_GENHCERR_MASK GENMASK(0, 0)
+#define A2B_GENERR_GENDDERR_MASK GENMASK(1, 1)
+#define A2B_GENERR_GENCRCERR_MASK GENMASK(2, 2)
+#define A2B_GENERR_GENDPERR_MASK GENMASK(3, 3)
+#define A2B_GENERR_GENICRCERR_MASK GENMASK(4, 4)
+#define A2B_I2SRRATE_RRDIV_MASK GENMASK(5, 0)
+#define A2B_I2SRRATE_RBUS_MASK GENMASK(7, 7)
+#define A2B_I2SRRCTL_ENVLSB_MASK GENMASK(0, 0)
+#define A2B_I2SRRCTL_ENXBIT_MASK GENMASK(1, 1)
+#define A2B_I2SRRCTL_ENSTRB_MASK GENMASK(4, 4)
+#define A2B_I2SRRCTL_STRBDIR_MASK GENMASK(5, 5)
+#define A2B_I2SRRSOFFS_RRSOFFSET_MASK GENMASK(1, 0)
+#define A2B_CLK1CFG_CLK1DIV_MASK GENMASK(3, 0)
+#define A2B_CLK1CFG_CLK1PDIV_MASK GENMASK(5, 5)
+#define A2B_CLK1CFG_CLK1INV_MASK GENMASK(6, 6)
+#define A2B_CLK1CFG_CLK1EN_MASK GENMASK(7, 7)
+#define A2B_CLK2CFG_CLK2DIV_MASK GENMASK(3, 0)
+#define A2B_CLK2CFG_CLK2PDIV_MASK GENMASK(5, 5)
+#define A2B_CLK2CFG_CLK2INV_MASK GENMASK(6, 6)
+#define A2B_CLK2CFG_CLK2EN_MASK GENMASK(7, 7)
+#define A2B_BMMCFG_BMMEN_MASK GENMASK(0, 0)
+#define A2B_BMMCFG_BMMRXEN_MASK GENMASK(1, 1)
+#define A2B_BMMCFG_BMMNDSC_MASK GENMASK(2, 2)
+#define A2B_SUSCFG_SUSSEL_MASK GENMASK(2, 0)
+#define A2B_SUSCFG_SUSOE_MASK GENMASK(4, 4)
+#define A2B_SUSCFG_SUSDIS_MASK GENMASK(5, 5)
+#define A2B_PDMCTL2_PDMDEST_MASK GENMASK(1, 0)
+#define A2B_PDMCTL2_PDM0FFRST_MASK GENMASK(2, 2)
+#define A2B_PDMCTL2_PDM1FFRST_MASK GENMASK(3, 3)
+#define A2B_PDMCTL2_PDMALTCLK_MASK GENMASK(4, 4)
+#define A2B_PDMCTL2_PDMINVCLK_MASK GENMASK(5, 5)
+#define A2B_UPMASK0_RXUPSLOT00_MASK GENMASK(0, 0)
+#define A2B_UPMASK0_RXUPSLOT01_MASK GENMASK(1, 1)
+#define A2B_UPMASK0_RXUPSLOT02_MASK GENMASK(2, 2)
+#define A2B_UPMASK0_RXUPSLOT03_MASK GENMASK(3, 3)
+#define A2B_UPMASK0_RXUPSLOT04_MASK GENMASK(4, 4)
+#define A2B_UPMASK0_RXUPSLOT05_MASK GENMASK(5, 5)
+#define A2B_UPMASK0_RXUPSLOT06_MASK GENMASK(6, 6)
+#define A2B_UPMASK0_RXUPSLOT07_MASK GENMASK(7, 7)
+#define A2B_UPMASK1_RXUPSLOT08_MASK GENMASK(0, 0)
+#define A2B_UPMASK1_RXUPSLOT09_MASK GENMASK(1, 1)
+#define A2B_UPMASK1_RXUPSLOT10_MASK GENMASK(2, 2)
+#define A2B_UPMASK1_RXUPSLOT11_MASK GENMASK(3, 3)
+#define A2B_UPMASK1_RXUPSLOT12_MASK GENMASK(4, 4)
+#define A2B_UPMASK1_RXUPSLOT13_MASK GENMASK(5, 5)
+#define A2B_UPMASK1_RXUPSLOT14_MASK GENMASK(6, 6)
+#define A2B_UPMASK1_RXUPSLOT15_MASK GENMASK(7, 7)
+#define A2B_UPMASK2_RXUPSLOT16_MASK GENMASK(0, 0)
+#define A2B_UPMASK2_RXUPSLOT17_MASK GENMASK(1, 1)
+#define A2B_UPMASK2_RXUPSLOT18_MASK GENMASK(2, 2)
+#define A2B_UPMASK2_RXUPSLOT19_MASK GENMASK(3, 3)
+#define A2B_UPMASK2_RXUPSLOT20_MASK GENMASK(4, 4)
+#define A2B_UPMASK2_RXUPSLOT21_MASK GENMASK(5, 5)
+#define A2B_UPMASK2_RXUPSLOT22_MASK GENMASK(6, 6)
+#define A2B_UPMASK2_RXUPSLOT23_MASK GENMASK(7, 7)
+#define A2B_UPMASK3_RXUPSLOT24_MASK GENMASK(0, 0)
+#define A2B_UPMASK3_RXUPSLOT25_MASK GENMASK(1, 1)
+#define A2B_UPMASK3_RXUPSLOT26_MASK GENMASK(2, 2)
+#define A2B_UPMASK3_RXUPSLOT27_MASK GENMASK(3, 3)
+#define A2B_UPMASK3_RXUPSLOT28_MASK GENMASK(4, 4)
+#define A2B_UPMASK3_RXUPSLOT29_MASK GENMASK(5, 5)
+#define A2B_UPMASK3_RXUPSLOT30_MASK GENMASK(6, 6)
+#define A2B_UPMASK3_RXUPSLOT31_MASK GENMASK(7, 7)
+#define A2B_UPOFFSET_UPOFFSET_MASK GENMASK(4, 0)
+#define A2B_DNMASK0_RXDNSLOT00_MASK GENMASK(0, 0)
+#define A2B_DNMASK0_RXDNSLOT01_MASK GENMASK(1, 1)
+#define A2B_DNMASK0_RXDNSLOT02_MASK GENMASK(2, 2)
+#define A2B_DNMASK0_RXDNSLOT03_MASK GENMASK(3, 3)
+#define A2B_DNMASK0_RXDNSLOT04_MASK GENMASK(4, 4)
+#define A2B_DNMASK0_RXDNSLOT05_MASK GENMASK(5, 5)
+#define A2B_DNMASK0_RXDNSLOT06_MASK GENMASK(6, 6)
+#define A2B_DNMASK0_RXDNSLOT07_MASK GENMASK(7, 7)
+#define A2B_DNMASK1_RXDNSLOT08_MASK GENMASK(0, 0)
+#define A2B_DNMASK1_RXDNSLOT09_MASK GENMASK(1, 1)
+#define A2B_DNMASK1_RXDNSLOT10_MASK GENMASK(2, 2)
+#define A2B_DNMASK1_RXDNSLOT11_MASK GENMASK(3, 3)
+#define A2B_DNMASK1_RXDNSLOT12_MASK GENMASK(4, 4)
+#define A2B_DNMASK1_RXDNSLOT13_MASK GENMASK(5, 5)
+#define A2B_DNMASK1_RXDNSLOT14_MASK GENMASK(6, 6)
+#define A2B_DNMASK1_RXDNSLOT15_MASK GENMASK(7, 7)
+#define A2B_DNMASK2_RXDNSLOT16_MASK GENMASK(0, 0)
+#define A2B_DNMASK2_RXDNSLOT17_MASK GENMASK(1, 1)
+#define A2B_DNMASK2_RXDNSLOT18_MASK GENMASK(2, 2)
+#define A2B_DNMASK2_RXDNSLOT19_MASK GENMASK(3, 3)
+#define A2B_DNMASK2_RXDNSLOT20_MASK GENMASK(4, 4)
+#define A2B_DNMASK2_RXDNSLOT21_MASK GENMASK(5, 5)
+#define A2B_DNMASK2_RXDNSLOT22_MASK GENMASK(6, 6)
+#define A2B_DNMASK2_RXDNSLOT23_MASK GENMASK(7, 7)
+#define A2B_DNMASK3_RXDNSLOT24_MASK GENMASK(0, 0)
+#define A2B_DNMASK3_RXDNSLOT25_MASK GENMASK(1, 1)
+#define A2B_DNMASK3_RXDNSLOT26_MASK GENMASK(2, 2)
+#define A2B_DNMASK3_RXDNSLOT27_MASK GENMASK(3, 3)
+#define A2B_DNMASK3_RXDNSLOT28_MASK GENMASK(4, 4)
+#define A2B_DNMASK3_RXDNSLOT29_MASK GENMASK(5, 5)
+#define A2B_DNMASK3_RXDNSLOT30_MASK GENMASK(6, 6)
+#define A2B_DNMASK3_RXDNSLOT31_MASK GENMASK(7, 7)
+#define A2B_DNOFFSET_DNOFFSET_MASK GENMASK(4, 0)
+#define A2B_CHIPID0_CHIPID_MASK GENMASK(7, 0)
+#define A2B_CHIPID1_CHIPID_MASK GENMASK(7, 0)
+#define A2B_CHIPID2_CHIPID_MASK GENMASK(7, 0)
+#define A2B_CHIPID3_CHIPID_MASK GENMASK(7, 0)
+#define A2B_CHIPID4_CHIPID_MASK GENMASK(7, 0)
+#define A2B_CHIPID5_CHIPID_MASK GENMASK(7, 0)
+#define A2B_GPIODEN_IOD0EN_MASK GENMASK(0, 0)
+#define A2B_GPIODEN_IOD1EN_MASK GENMASK(1, 1)
+#define A2B_GPIODEN_IOD2EN_MASK GENMASK(2, 2)
+#define A2B_GPIODEN_IOD3EN_MASK GENMASK(3, 3)
+#define A2B_GPIODEN_IOD4EN_MASK GENMASK(4, 4)
+#define A2B_GPIODEN_IOD5EN_MASK GENMASK(5, 5)
+#define A2B_GPIODEN_IOD6EN_MASK GENMASK(6, 6)
+#define A2B_GPIODEN_IOD7EN_MASK GENMASK(7, 7)
+#define A2B_GPIOD0MSK_IOD0MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD1MSK_IOD1MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD2MSK_IOD2MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD3MSK_IOD3MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD4MSK_IOD4MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD5MSK_IOD5MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD6MSK_IOD6MSK_MASK GENMASK(7, 0)
+#define A2B_GPIOD7MSK_IOD7MSK_MASK GENMASK(7, 0)
+#define A2B_GPIODDAT_IOD0DAT_MASK GENMASK(0, 0)
+#define A2B_GPIODDAT_IOD1DAT_MASK GENMASK(1, 1)
+#define A2B_GPIODDAT_IOD2DAT_MASK GENMASK(2, 2)
+#define A2B_GPIODDAT_IOD3DAT_MASK GENMASK(3, 3)
+#define A2B_GPIODDAT_IOD4DAT_MASK GENMASK(4, 4)
+#define A2B_GPIODDAT_IOD5DAT_MASK GENMASK(5, 5)
+#define A2B_GPIODDAT_IOD6DAT_MASK GENMASK(6, 6)
+#define A2B_GPIODDAT_IOD7DAT_MASK GENMASK(7, 7)
+#define A2B_GPIODINV_IOD0INV_MASK GENMASK(0, 0)
+#define A2B_GPIODINV_IOD1INV_MASK GENMASK(1, 1)
+#define A2B_GPIODINV_IOD2INV_MASK GENMASK(2, 2)
+#define A2B_GPIODINV_IOD3INV_MASK GENMASK(3, 3)
+#define A2B_GPIODINV_IOD4INV_MASK GENMASK(4, 4)
+#define A2B_GPIODINV_IOD5INV_MASK GENMASK(5, 5)
+#define A2B_GPIODINV_IOD6INV_MASK GENMASK(6, 6)
+#define A2B_GPIODINV_IOD7INV_MASK GENMASK(7, 7)
+#define A2B_MBOX0CTL_MB0EN_MASK GENMASK(0, 0)
+#define A2B_MBOX0CTL_MB0DIR_MASK GENMASK(1, 1)
+#define A2B_MBOX0CTL_MB0EIEN_MASK GENMASK(2, 2)
+#define A2B_MBOX0CTL_MB0FIEN_MASK GENMASK(3, 3)
+#define A2B_MBOX0CTL_MB0LEN_MASK GENMASK(5, 4)
+#define A2B_MBOX0STAT_MB0FULL_MASK GENMASK(0, 0)
+#define A2B_MBOX0STAT_MB0EMPTY_MASK GENMASK(1, 1)
+#define A2B_MBOX0STAT_MB0FIRQ_MASK GENMASK(4, 4)
+#define A2B_MBOX0STAT_MB0EIRQ_MASK GENMASK(5, 5)
+#define A2B_MBOX0B0_MBOX0_MASK GENMASK(7, 0)
+#define A2B_MBOX0B1_MBOX0_MASK GENMASK(7, 0)
+#define A2B_MBOX0B2_MBOX0_MASK GENMASK(7, 0)
+#define A2B_MBOX0B3_MBOX0_MASK GENMASK(7, 0)
+#define A2B_MBOX1CTL_MB1EN_MASK GENMASK(0, 0)
+#define A2B_MBOX1CTL_MB1DIR_MASK GENMASK(1, 1)
+#define A2B_MBOX1CTL_MB1EIEN_MASK GENMASK(2, 2)
+#define A2B_MBOX1CTL_MB1FIEN_MASK GENMASK(3, 3)
+#define A2B_MBOX1CTL_MB1LEN_MASK GENMASK(5, 4)
+#define A2B_MBOX1STAT_MB1FULL_MASK GENMASK(0, 0)
+#define A2B_MBOX1STAT_MB1EMPTY_MASK GENMASK(1, 1)
+#define A2B_MBOX1STAT_MB1FIRQ_MASK GENMASK(4, 4)
+#define A2B_MBOX1STAT_MB1EIRQ_MASK GENMASK(5, 5)
+#define A2B_MBOX1B0_MBOX1_MASK GENMASK(7, 0)
+#define A2B_MBOX1B1_MBOX1_MASK GENMASK(7, 0)
+#define A2B_MBOX1B2_MBOX1_MASK GENMASK(7, 0)
+#define A2B_MBOX1B3_MBOX1_MASK GENMASK(7, 0)
+
+#define A2B_CHIP_CHIPADR_SHIFT 0
+#define A2B_NODEADR_NODE_SHIFT 0
+#define A2B_NODEADR_PERI_SHIFT 5
+#define A2B_NODEADR_BRCST_SHIFT 7
+#define A2B_VENDOR_VENDOR_SHIFT 0
+#define A2B_PRODUCT_PRODUCT_SHIFT 0
+#define A2B_VERSION_VERSION_SHIFT 0
+#define A2B_CAPABILITY_I2CAVAIL_SHIFT 0
+#define A2B_SWCTL_ENSW_SHIFT 0
+#define A2B_SWCTL_DIAGMODE_SHIFT 3
+#define A2B_SWCTL_MODE_SHIFT 4
+#define A2B_SWCTL_DISNXT_SHIFT 6
+#define A2B_BCDNSLOTS_BCDNSLOTS_SHIFT 0
+#define A2B_LDNSLOTS_LDNSLOTS_SHIFT 0
+#define A2B_LDNSLOTS_DNMASKEN_SHIFT 7
+#define A2B_LUPSLOTS_LUPSLOTS_SHIFT 0
+#define A2B_DNSLOTS_DNSLOTS_SHIFT 0
+#define A2B_UPSLOTS_UPSLOTS_SHIFT 0
+#define A2B_RESPCYCS_RESPCYCS_SHIFT 0
+#define A2B_SLOTFMT_DNSIZE_SHIFT 0
+#define A2B_SLOTFMT_DNFMT_SHIFT 3
+#define A2B_SLOTFMT_UPSIZE_SHIFT 4
+#define A2B_SLOTFMT_UPFMT_SHIFT 7
+#define A2B_DATCTL_DNS_SHIFT 0
+#define A2B_DATCTL_UPS_SHIFT 1
+#define A2B_DATCTL_ENDSNIFF_SHIFT 5
+#define A2B_DATCTL_STANDBY_SHIFT 7
+#define A2B_CONTROL_NEWSTRCT_SHIFT 0
+#define A2B_CONTROL_ENDDSC_SHIFT 1
+#define A2B_CONTROL_SOFTRST_SHIFT 2
+#define A2B_CONTROL_SWBYP_SHIFT 3
+#define A2B_CONTROL_XCVRBINV_SHIFT 4
+#define A2B_CONTROL_MSTR_SHIFT 7
+#define A2B_DISCVRY_DRESPCYC_SHIFT 0
+#define A2B_SWSTAT_FIN_SHIFT 0
+#define A2B_SWSTAT_FAULT_SHIFT 1
+#define A2B_SWSTAT_FAULT_CODE_SHIFT 4
+#define A2B_SWSTAT_FAULT_NLOC_SHIFT 7
+#define A2B_INTSTAT_IRQ_SHIFT 0
+#define A2B_INTSRC_INODE_SHIFT 0
+#define A2B_INTSRC_SLVINT_SHIFT 6
+#define A2B_INTSRC_MSTINT_SHIFT 7
+#define A2B_INTTYPE_TYPE_SHIFT 0
+#define A2B_INTPND0_HDCNTERR_SHIFT 0
+#define A2B_INTPND0_DDERR_SHIFT 1
+#define A2B_INTPND0_CRCERR_SHIFT 2
+#define A2B_INTPND0_DPERR_SHIFT 3
+#define A2B_INTPND0_PWRERR_SHIFT 4
+#define A2B_INTPND0_BECOVF_SHIFT 5
+#define A2B_INTPND0_SRFERR_SHIFT 6
+#define A2B_INTPND0_SRFCRCERR_SHIFT 7
+#define A2B_INTPND1_IO0PND_SHIFT 0
+#define A2B_INTPND1_IO1PND_SHIFT 1
+#define A2B_INTPND1_IO2PND_SHIFT 2
+#define A2B_INTPND1_IO3PND_SHIFT 3
+#define A2B_INTPND1_IO4PND_SHIFT 4
+#define A2B_INTPND1_IO5PND_SHIFT 5
+#define A2B_INTPND1_IO6PND_SHIFT 6
+#define A2B_INTPND1_IO7PND_SHIFT 7
+#define A2B_INTPND2_DSCDONE_SHIFT 0
+#define A2B_INTPND2_I2CERR_SHIFT 1
+#define A2B_INTPND2_ICRCERR_SHIFT 2
+#define A2B_INTPND2_SLVIRQ_SHIFT 3
+#define A2B_INTMSK0_HDEIEN_SHIFT 0
+#define A2B_INTMSK0_DDEIEN_SHIFT 1
+#define A2B_INTMSK0_CRCEIEN_SHIFT 2
+#define A2B_INTMSK0_DPEIEN_SHIFT 3
+#define A2B_INTMSK0_PWREIEN_SHIFT 4
+#define A2B_INTMSK0_BECIEN_SHIFT 5
+#define A2B_INTMSK0_SRFEIEN_SHIFT 6
+#define A2B_INTMSK0_SRFCRCEIEN_SHIFT 7
+#define A2B_INTMSK1_IO0IRQEN_SHIFT 0
+#define A2B_INTMSK1_IO1IRQEN_SHIFT 1
+#define A2B_INTMSK1_IO2IRQEN_SHIFT 2
+#define A2B_INTMSK1_IO3IRQEN_SHIFT 3
+#define A2B_INTMSK1_IO4IRQEN_SHIFT 4
+#define A2B_INTMSK1_IO5IRQEN_SHIFT 5
+#define A2B_INTMSK1_IO6IRQEN_SHIFT 6
+#define A2B_INTMSK1_IO7IRQEN_SHIFT 7
+#define A2B_INTMSK2_DSCDIEN_SHIFT 0
+#define A2B_INTMSK2_I2CEIEN_SHIFT 1
+#define A2B_INTMSK2_ICRCEIEN_SHIFT 2
+#define A2B_INTMSK2_SLVIRQEN_SHIFT 3
+#define A2B_BECCTL_ENHDCNT_SHIFT 0
+#define A2B_BECCTL_ENDD_SHIFT 1
+#define A2B_BECCTL_ENCRC_SHIFT 2
+#define A2B_BECCTL_ENDP_SHIFT 3
+#define A2B_BECCTL_ENICRC_SHIFT 4
+#define A2B_BECCTL_THRESHLD_SHIFT 5
+#define A2B_BECNT_BECNT_SHIFT 0
+#define A2B_TESTMODE_PRBSUP_SHIFT 0
+#define A2B_TESTMODE_PRBSDN_SHIFT 1
+#define A2B_TESTMODE_PRBSN2N_SHIFT 2
+#define A2B_TESTMODE_RXDPTH_SHIFT 4
+#define A2B_ERRCNT0_ERRCNT_SHIFT 0
+#define A2B_ERRCNT1_ERRCNT_SHIFT 0
+#define A2B_ERRCNT2_ERRCNT_SHIFT 0
+#define A2B_ERRCNT3_ERRCNT_SHIFT 0
+#define A2B_NODE_NUMBER_SHIFT 0
+#define A2B_NODE_DISCVD_SHIFT 5
+#define A2B_NODE_NLAST_SHIFT 6
+#define A2B_NODE_LAST_SHIFT 7
+#define A2B_DISCSTAT_DNODE_SHIFT 0
+#define A2B_DISCSTAT_DSCACT_SHIFT 7
+#define A2B_TXACTL_TXALEVEL_SHIFT 0
+#define A2B_TXACTL_TXAOVREN_SHIFT 7
+#define A2B_TXBCTL_TXBLEVEL_SHIFT 0
+#define A2B_TXBCTL_TXBOVREN_SHIFT 7
+#define A2B_LINTTYPE_LTYPE_SHIFT 0
+#define A2B_I2CCFG_DATARATE_SHIFT 0
+#define A2B_I2CCFG_EACK_SHIFT 1
+#define A2B_I2CCFG_FRAMERATE_SHIFT 2
+#define A2B_PLLCTL_SSFREQ_SHIFT 0
+#define A2B_PLLCTL_SSDEPTH_SHIFT 3
+#define A2B_PLLCTL_SSMODE_SHIFT 6
+#define A2B_I2SGCFG_TDMMODE_SHIFT 0
+#define A2B_I2SGCFG_RXONDTX1_SHIFT 3
+#define A2B_I2SGCFG_TDMSS_SHIFT 4
+#define A2B_I2SGCFG_ALT_SHIFT 5
+#define A2B_I2SGCFG_EARLY_SHIFT 6
+#define A2B_I2SGCFG_INV_SHIFT 7
+#define A2B_I2SCFG_TX0EN_SHIFT 0
+#define A2B_I2SCFG_TX1EN_SHIFT 1
+#define A2B_I2SCFG_TX2PINTL_SHIFT 2
+#define A2B_I2SCFG_TXBCLKINV_SHIFT 3
+#define A2B_I2SCFG_RX0EN_SHIFT 4
+#define A2B_I2SCFG_RX1EN_SHIFT 5
+#define A2B_I2SCFG_RX2PINTL_SHIFT 6
+#define A2B_I2SCFG_RXBCLKINV_SHIFT 7
+#define A2B_I2SRATE_I2SRATE_SHIFT 0
+#define A2B_I2SRATE_BCLKRATE_SHIFT 3
+#define A2B_I2SRATE_FRAMES_SHIFT 4
+#define A2B_I2SRATE_REDUCE_SHIFT 6
+#define A2B_I2SRATE_SHARE_SHIFT 7
+#define A2B_I2STXOFFSET_TXOFFSET_SHIFT 0
+#define A2B_I2STXOFFSET_TSAFTER_SHIFT 6
+#define A2B_I2STXOFFSET_TSBEFORE_SHIFT 7
+#define A2B_I2SRXOFFSET_RXOFFSET_SHIFT 0
+#define A2B_SYNCOFFSET_SYNCOFFSET_SHIFT 0
+#define A2B_PDMCTL_PDM0EN_SHIFT 0
+#define A2B_PDMCTL_PDM0SLOTS_SHIFT 1
+#define A2B_PDMCTL_PDM1EN_SHIFT 2
+#define A2B_PDMCTL_PDM1SLOTS_SHIFT 3
+#define A2B_PDMCTL_HPFEN_SHIFT 4
+#define A2B_PDMCTL_PDMRATE_SHIFT 5
+#define A2B_ERRMGMT_ERRLSB_SHIFT 0
+#define A2B_ERRMGMT_ERRSIG_SHIFT 1
+#define A2B_ERRMGMT_ERRSLOT_SHIFT 2
+#define A2B_GPIODAT_IO0DAT_SHIFT 0
+#define A2B_GPIODAT_IO1DAT_SHIFT 1
+#define A2B_GPIODAT_IO2DAT_SHIFT 2
+#define A2B_GPIODAT_IO3DAT_SHIFT 3
+#define A2B_GPIODAT_IO4DAT_SHIFT 4
+#define A2B_GPIODAT_IO5DAT_SHIFT 5
+#define A2B_GPIODAT_IO6DAT_SHIFT 6
+#define A2B_GPIODAT_IO7DAT_SHIFT 7
+#define A2B_GPIODATSET_IO0DSET_SHIFT 0
+#define A2B_GPIODATSET_IO1DSET_SHIFT 1
+#define A2B_GPIODATSET_IO2DSET_SHIFT 2
+#define A2B_GPIODATSET_IO3DSET_SHIFT 3
+#define A2B_GPIODATSET_IO4DSET_SHIFT 4
+#define A2B_GPIODATSET_IO5DSET_SHIFT 5
+#define A2B_GPIODATSET_IO6DSET_SHIFT 6
+#define A2B_GPIODATSET_IO7DSET_SHIFT 7
+#define A2B_GPIODATCLR_IO0DCLR_SHIFT 0
+#define A2B_GPIODATCLR_IO1DCLR_SHIFT 1
+#define A2B_GPIODATCLR_IO2DCLR_SHIFT 2
+#define A2B_GPIODATCLR_IO3DCLR_SHIFT 3
+#define A2B_GPIODATCLR_IO4DCLR_SHIFT 4
+#define A2B_GPIODATCLR_IO5DCLR_SHIFT 5
+#define A2B_GPIODATCLR_IO6DCLR_SHIFT 6
+#define A2B_GPIODATCLR_IO7DCLR_SHIFT 7
+#define A2B_GPIOOEN_IO0OEN_SHIFT 0
+#define A2B_GPIOOEN_IO1OEN_SHIFT 1
+#define A2B_GPIOOEN_IO2OEN_SHIFT 2
+#define A2B_GPIOOEN_IO3OEN_SHIFT 3
+#define A2B_GPIOOEN_IO4OEN_SHIFT 4
+#define A2B_GPIOOEN_IO5OEN_SHIFT 5
+#define A2B_GPIOOEN_IO6OEN_SHIFT 6
+#define A2B_GPIOOEN_IO7OEN_SHIFT 7
+#define A2B_GPIOIEN_IO0IEN_SHIFT 0
+#define A2B_GPIOIEN_IO1IEN_SHIFT 1
+#define A2B_GPIOIEN_IO2IEN_SHIFT 2
+#define A2B_GPIOIEN_IO3IEN_SHIFT 3
+#define A2B_GPIOIEN_IO4IEN_SHIFT 4
+#define A2B_GPIOIEN_IO5IEN_SHIFT 5
+#define A2B_GPIOIEN_IO6IEN_SHIFT 6
+#define A2B_GPIOIEN_IO7IEN_SHIFT 7
+#define A2B_GPIOIN_IO0IN_SHIFT 0
+#define A2B_GPIOIN_IO1IN_SHIFT 1
+#define A2B_GPIOIN_IO2IN_SHIFT 2
+#define A2B_GPIOIN_IO3IN_SHIFT 3
+#define A2B_GPIOIN_IO4IN_SHIFT 4
+#define A2B_GPIOIN_IO5IN_SHIFT 5
+#define A2B_GPIOIN_IO6IN_SHIFT 6
+#define A2B_GPIOIN_IO7IN_SHIFT 7
+#define A2B_PINTEN_IO0IE_SHIFT 0
+#define A2B_PINTEN_IO1IE_SHIFT 1
+#define A2B_PINTEN_IO2IE_SHIFT 2
+#define A2B_PINTEN_IO3IE_SHIFT 3
+#define A2B_PINTEN_IO4IE_SHIFT 4
+#define A2B_PINTEN_IO5IE_SHIFT 5
+#define A2B_PINTEN_IO6IE_SHIFT 6
+#define A2B_PINTEN_IO7IE_SHIFT 7
+#define A2B_PINTINV_IO0INV_SHIFT 0
+#define A2B_PINTINV_IO1INV_SHIFT 1
+#define A2B_PINTINV_IO2INV_SHIFT 2
+#define A2B_PINTINV_IO3INV_SHIFT 3
+#define A2B_PINTINV_IO4INV_SHIFT 4
+#define A2B_PINTINV_IO5INV_SHIFT 5
+#define A2B_PINTINV_IO6INV_SHIFT 6
+#define A2B_PINTINV_IO7INV_SHIFT 7
+#define A2B_PINCFG_DRVSTR_SHIFT 0
+#define A2B_PINCFG_IRQINV_SHIFT 4
+#define A2B_PINCFG_IRQTS_SHIFT 5
+#define A2B_I2STEST_PATTRN2TX_SHIFT 0
+#define A2B_I2STEST_LOOPBK2TX_SHIFT 1
+#define A2B_I2STEST_RX2LOOPBK_SHIFT 2
+#define A2B_I2STEST_SELRX1_SHIFT 3
+#define A2B_I2STEST_BUSLOOPBK_SHIFT 4
+#define A2B_RAISE_RAISE_SHIFT 0
+#define A2B_GENERR_GENHCERR_SHIFT 0
+#define A2B_GENERR_GENDDERR_SHIFT 1
+#define A2B_GENERR_GENCRCERR_SHIFT 2
+#define A2B_GENERR_GENDPERR_SHIFT 3
+#define A2B_GENERR_GENICRCERR_SHIFT 4
+#define A2B_I2SRRATE_RRDIV_SHIFT 0
+#define A2B_I2SRRATE_RBUS_SHIFT 7
+#define A2B_I2SRRCTL_ENVLSB_SHIFT 0
+#define A2B_I2SRRCTL_ENXBIT_SHIFT 1
+#define A2B_I2SRRCTL_ENSTRB_SHIFT 4
+#define A2B_I2SRRCTL_STRBDIR_SHIFT 5
+#define A2B_I2SRRSOFFS_RRSOFFSET_SHIFT 0
+#define A2B_CLK1CFG_CLK1DIV_SHIFT 0
+#define A2B_CLK1CFG_CLK1PDIV_SHIFT 5
+#define A2B_CLK1CFG_CLK1INV_SHIFT 6
+#define A2B_CLK1CFG_CLK1EN_SHIFT 7
+#define A2B_CLK2CFG_CLK2DIV_SHIFT 0
+#define A2B_CLK2CFG_CLK2PDIV_SHIFT 5
+#define A2B_CLK2CFG_CLK2INV_SHIFT 6
+#define A2B_CLK2CFG_CLK2EN_SHIFT 7
+#define A2B_BMMCFG_BMMEN_SHIFT 0
+#define A2B_BMMCFG_BMMRXEN_SHIFT 1
+#define A2B_BMMCFG_BMMNDSC_SHIFT 2
+#define A2B_SUSCFG_SUSSEL_SHIFT 0
+#define A2B_SUSCFG_SUSOE_SHIFT 4
+#define A2B_SUSCFG_SUSDIS_SHIFT 5
+#define A2B_PDMCTL2_PDMDEST_SHIFT 0
+#define A2B_PDMCTL2_PDM0FFRST_SHIFT 2
+#define A2B_PDMCTL2_PDM1FFRST_SHIFT 3
+#define A2B_PDMCTL2_PDMALTCLK_SHIFT 4
+#define A2B_PDMCTL2_PDMINVCLK_SHIFT 5
+#define A2B_UPMASK0_RXUPSLOT00_SHIFT 0
+#define A2B_UPMASK0_RXUPSLOT01_SHIFT 1
+#define A2B_UPMASK0_RXUPSLOT02_SHIFT 2
+#define A2B_UPMASK0_RXUPSLOT03_SHIFT 3
+#define A2B_UPMASK0_RXUPSLOT04_SHIFT 4
+#define A2B_UPMASK0_RXUPSLOT05_SHIFT 5
+#define A2B_UPMASK0_RXUPSLOT06_SHIFT 6
+#define A2B_UPMASK0_RXUPSLOT07_SHIFT 7
+#define A2B_UPMASK1_RXUPSLOT08_SHIFT 0
+#define A2B_UPMASK1_RXUPSLOT09_SHIFT 1
+#define A2B_UPMASK1_RXUPSLOT10_SHIFT 2
+#define A2B_UPMASK1_RXUPSLOT11_SHIFT 3
+#define A2B_UPMASK1_RXUPSLOT12_SHIFT 4
+#define A2B_UPMASK1_RXUPSLOT13_SHIFT 5
+#define A2B_UPMASK1_RXUPSLOT14_SHIFT 6
+#define A2B_UPMASK1_RXUPSLOT15_SHIFT 7
+#define A2B_UPMASK2_RXUPSLOT16_SHIFT 0
+#define A2B_UPMASK2_RXUPSLOT17_SHIFT 1
+#define A2B_UPMASK2_RXUPSLOT18_SHIFT 2
+#define A2B_UPMASK2_RXUPSLOT19_SHIFT 3
+#define A2B_UPMASK2_RXUPSLOT20_SHIFT 4
+#define A2B_UPMASK2_RXUPSLOT21_SHIFT 5
+#define A2B_UPMASK2_RXUPSLOT22_SHIFT 6
+#define A2B_UPMASK2_RXUPSLOT23_SHIFT 7
+#define A2B_UPMASK3_RXUPSLOT24_SHIFT 0
+#define A2B_UPMASK3_RXUPSLOT25_SHIFT 1
+#define A2B_UPMASK3_RXUPSLOT26_SHIFT 2
+#define A2B_UPMASK3_RXUPSLOT27_SHIFT 3
+#define A2B_UPMASK3_RXUPSLOT28_SHIFT 4
+#define A2B_UPMASK3_RXUPSLOT29_SHIFT 5
+#define A2B_UPMASK3_RXUPSLOT30_SHIFT 6
+#define A2B_UPMASK3_RXUPSLOT31_SHIFT 7
+#define A2B_UPOFFSET_UPOFFSET_SHIFT 0
+#define A2B_DNMASK0_RXDNSLOT00_SHIFT 0
+#define A2B_DNMASK0_RXDNSLOT01_SHIFT 1
+#define A2B_DNMASK0_RXDNSLOT02_SHIFT 2
+#define A2B_DNMASK0_RXDNSLOT03_SHIFT 3
+#define A2B_DNMASK0_RXDNSLOT04_SHIFT 4
+#define A2B_DNMASK0_RXDNSLOT05_SHIFT 5
+#define A2B_DNMASK0_RXDNSLOT06_SHIFT 6
+#define A2B_DNMASK0_RXDNSLOT07_SHIFT 7
+#define A2B_DNMASK1_RXDNSLOT08_SHIFT 0
+#define A2B_DNMASK1_RXDNSLOT09_SHIFT 1
+#define A2B_DNMASK1_RXDNSLOT10_SHIFT 2
+#define A2B_DNMASK1_RXDNSLOT11_SHIFT 3
+#define A2B_DNMASK1_RXDNSLOT12_SHIFT 4
+#define A2B_DNMASK1_RXDNSLOT13_SHIFT 5
+#define A2B_DNMASK1_RXDNSLOT14_SHIFT 6
+#define A2B_DNMASK1_RXDNSLOT15_SHIFT 7
+#define A2B_DNMASK2_RXDNSLOT16_SHIFT 0
+#define A2B_DNMASK2_RXDNSLOT17_SHIFT 1
+#define A2B_DNMASK2_RXDNSLOT18_SHIFT 2
+#define A2B_DNMASK2_RXDNSLOT19_SHIFT 3
+#define A2B_DNMASK2_RXDNSLOT20_SHIFT 4
+#define A2B_DNMASK2_RXDNSLOT21_SHIFT 5
+#define A2B_DNMASK2_RXDNSLOT22_SHIFT 6
+#define A2B_DNMASK2_RXDNSLOT23_SHIFT 7
+#define A2B_DNMASK3_RXDNSLOT24_SHIFT 0
+#define A2B_DNMASK3_RXDNSLOT25_SHIFT 1
+#define A2B_DNMASK3_RXDNSLOT26_SHIFT 2
+#define A2B_DNMASK3_RXDNSLOT27_SHIFT 3
+#define A2B_DNMASK3_RXDNSLOT28_SHIFT 4
+#define A2B_DNMASK3_RXDNSLOT29_SHIFT 5
+#define A2B_DNMASK3_RXDNSLOT30_SHIFT 6
+#define A2B_DNMASK3_RXDNSLOT31_SHIFT 7
+#define A2B_DNOFFSET_DNOFFSET_SHIFT 0
+#define A2B_CHIPID0_CHIPID_SHIFT 0
+#define A2B_CHIPID1_CHIPID_SHIFT 0
+#define A2B_CHIPID2_CHIPID_SHIFT 0
+#define A2B_CHIPID3_CHIPID_SHIFT 0
+#define A2B_CHIPID4_CHIPID_SHIFT 0
+#define A2B_CHIPID5_CHIPID_SHIFT 0
+#define A2B_GPIODEN_IOD0EN_SHIFT 0
+#define A2B_GPIODEN_IOD1EN_SHIFT 1
+#define A2B_GPIODEN_IOD2EN_SHIFT 2
+#define A2B_GPIODEN_IOD3EN_SHIFT 3
+#define A2B_GPIODEN_IOD4EN_SHIFT 4
+#define A2B_GPIODEN_IOD5EN_SHIFT 5
+#define A2B_GPIODEN_IOD6EN_SHIFT 6
+#define A2B_GPIODEN_IOD7EN_SHIFT 7
+#define A2B_GPIOD0MSK_IOD0MSK_SHIFT 0
+#define A2B_GPIOD1MSK_IOD1MSK_SHIFT 0
+#define A2B_GPIOD2MSK_IOD2MSK_SHIFT 0
+#define A2B_GPIOD3MSK_IOD3MSK_SHIFT 0
+#define A2B_GPIOD4MSK_IOD4MSK_SHIFT 0
+#define A2B_GPIOD5MSK_IOD5MSK_SHIFT 0
+#define A2B_GPIOD6MSK_IOD6MSK_SHIFT 0
+#define A2B_GPIOD7MSK_IOD7MSK_SHIFT 0
+#define A2B_GPIODDAT_IOD0DAT_SHIFT 0
+#define A2B_GPIODDAT_IOD1DAT_SHIFT 1
+#define A2B_GPIODDAT_IOD2DAT_SHIFT 2
+#define A2B_GPIODDAT_IOD3DAT_SHIFT 3
+#define A2B_GPIODDAT_IOD4DAT_SHIFT 4
+#define A2B_GPIODDAT_IOD5DAT_SHIFT 5
+#define A2B_GPIODDAT_IOD6DAT_SHIFT 6
+#define A2B_GPIODDAT_IOD7DAT_SHIFT 7
+#define A2B_GPIODINV_IOD0INV_SHIFT 0
+#define A2B_GPIODINV_IOD1INV_SHIFT 1
+#define A2B_GPIODINV_IOD2INV_SHIFT 2
+#define A2B_GPIODINV_IOD3INV_SHIFT 3
+#define A2B_GPIODINV_IOD4INV_SHIFT 4
+#define A2B_GPIODINV_IOD5INV_SHIFT 5
+#define A2B_GPIODINV_IOD6INV_SHIFT 6
+#define A2B_GPIODINV_IOD7INV_SHIFT 7
+#define A2B_MBOX0CTL_MB0EN_SHIFT 0
+#define A2B_MBOX0CTL_MB0DIR_SHIFT 1
+#define A2B_MBOX0CTL_MB0EIEN_SHIFT 2
+#define A2B_MBOX0CTL_MB0FIEN_SHIFT 3
+#define A2B_MBOX0CTL_MB0LEN_SHIFT 4
+#define A2B_MBOX0STAT_MB0FULL_SHIFT 0
+#define A2B_MBOX0STAT_MB0EMPTY_SHIFT 1
+#define A2B_MBOX0STAT_MB0FIRQ_SHIFT 4
+#define A2B_MBOX0STAT_MB0EIRQ_SHIFT 5
+#define A2B_MBOX0B0_MBOX0_SHIFT 0
+#define A2B_MBOX0B1_MBOX0_SHIFT 0
+#define A2B_MBOX0B2_MBOX0_SHIFT 0
+#define A2B_MBOX0B3_MBOX0_SHIFT 0
+#define A2B_MBOX1CTL_MB1EN_SHIFT 0
+#define A2B_MBOX1CTL_MB1DIR_SHIFT 1
+#define A2B_MBOX1CTL_MB1EIEN_SHIFT 2
+#define A2B_MBOX1CTL_MB1FIEN_SHIFT 3
+#define A2B_MBOX1CTL_MB1LEN_SHIFT 4
+#define A2B_MBOX1STAT_MB1FULL_SHIFT 0
+#define A2B_MBOX1STAT_MB1EMPTY_SHIFT 1
+#define A2B_MBOX1STAT_MB1FIRQ_SHIFT 4
+#define A2B_MBOX1STAT_MB1EIRQ_SHIFT 5
+#define A2B_MBOX1B0_MBOX1_SHIFT 0
+#define A2B_MBOX1B1_MBOX1_SHIFT 0
+#define A2B_MBOX1B2_MBOX1_SHIFT 0
+#define A2B_MBOX1B3_MBOX1_SHIFT 0
+
+#endif /* _AD24XX_H */

--
2.44.0


2024-05-17 13:14:53

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 08/13] clk: add AD24xx clock driver

From: Alvin Šipraga <[email protected]>

Analog Devices Inc. AD24xx series A2B transceivers support muxing IO
pins to a CLKOUT function. The clock supports division of the internal
PLL of the transceiver.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 1 +
drivers/clk/Kconfig | 7 +
drivers/clk/Makefile | 1 +
drivers/clk/clk-ad24xx.c | 341 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 350 insertions(+)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 6ba5dc11c51d..08acf5728023 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -35,6 +35,7 @@ config A2B_AD24XX_NODE
select REGMAP_A2B
imply GPIO_AD24XX
imply SND_SOC_AD24XX
+ imply COMMON_CLK_AD24XX
help
Say Y here to enable support for AD24xx A2B transceiver nodes. This
applies to both main nodes and subordinate nodes. Supported models
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 3e9099504fad..a3d54b077e68 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -257,6 +257,13 @@ config COMMON_CLK_LAN966X
LAN966X SoC. GCK generates and supplies clock to various peripherals
within the SoC.

+config COMMON_CLK_AD24XX
+ bool "Clock driver for Analog Devices Inc. AD24xx"
+ depends on A2B_AD24XX_NODE
+ help
+ This driver supports the clock output functionality of AD24xx series
+ A2B transceiver chips.
+
config COMMON_CLK_ASPEED
bool "Clock driver for Aspeed BMC SoCs"
depends on ARCH_ASPEED || COMPILE_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 4abe16c8ccdf..cf5c867bf71a 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_COMMON_CLK_FIXED_MMIO) += clk-fixed-mmio.o
obj-$(CONFIG_COMMON_CLK_FSL_FLEXSPI) += clk-fsl-flexspi.o
obj-$(CONFIG_COMMON_CLK_FSL_SAI) += clk-fsl-sai.o
obj-$(CONFIG_COMMON_CLK_GEMINI) += clk-gemini.o
+obj-$(CONFIG_COMMON_CLK_AD24XX) += clk-ad24xx.o
obj-$(CONFIG_COMMON_CLK_ASPEED) += clk-aspeed.o
obj-$(CONFIG_MACH_ASPEED_G6) += clk-ast2600.o
obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
diff --git a/drivers/clk/clk-ad24xx.c b/drivers/clk/clk-ad24xx.c
new file mode 100644
index 000000000000..ed227c317faa
--- /dev/null
+++ b/drivers/clk/clk-ad24xx.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD24xx clock driver
+ *
+ * Copyright (c) 2023 Alvin Šipraga <[email protected]>
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define AD24XX_NUM_CLKS 2
+
+/* Define some safe macros to make the code more readable */
+#define A2B_CLKCFG(_idx) (!(_idx) ? A2B_CLK1CFG : A2B_CLK2CFG)
+
+#define A2B_CLKCFG_DIV_SHIFT A2B_CLK1CFG_CLK1DIV_SHIFT
+#define A2B_CLKCFG_PDIV_SHIFT A2B_CLK1CFG_CLK1PDIV_SHIFT
+
+#define A2B_CLKCFG_DIV_MASK A2B_CLK1CFG_CLK1DIV_MASK
+#define A2B_CLKCFG_PDIV_MASK A2B_CLK1CFG_CLK1PDIV_MASK
+#define A2B_CLKCFG_INV_MASK A2B_CLK1CFG_CLK1INV_MASK
+#define A2B_CLKCFG_EN_MASK A2B_CLK1CFG_CLK1EN_MASK
+
+static_assert(A2B_CLK1CFG_CLK1DIV_MASK == A2B_CLK2CFG_CLK2DIV_MASK);
+static_assert(A2B_CLK1CFG_CLK1PDIV_MASK == A2B_CLK2CFG_CLK2PDIV_MASK);
+static_assert(A2B_CLK1CFG_CLK1INV_MASK == A2B_CLK2CFG_CLK2INV_MASK);
+static_assert(A2B_CLK1CFG_CLK1EN_MASK == A2B_CLK2CFG_CLK2EN_MASK);
+
+struct ad24xx_clkout {
+ struct clk_hw hw;
+ unsigned int idx;
+ bool registered;
+};
+
+struct ad24xx_clk {
+ struct device *dev;
+ struct a2b_func *func;
+ struct a2b_node *node;
+ struct regmap *regmap;
+ struct clk_hw *pll_hw;
+ struct ad24xx_clkout clkouts[AD24XX_NUM_CLKS];
+};
+
+static struct ad24xx_clkout *to_ad24xx_clkout(struct clk_hw *hw)
+{
+ return container_of(hw, struct ad24xx_clkout, hw);
+}
+
+static struct ad24xx_clk *to_ad24xx_clk(struct ad24xx_clkout *clkout)
+{
+ return container_of(clkout, struct ad24xx_clk, clkouts[clkout->idx]);
+}
+
+/*
+ * A CLKOUT signal is derived from the PLL frequency (2048 * SFF), going through
+ * a pre-divide step and a divide step.
+ *
+ * The pre-divide is either 2 or 32. The divisor is between 1 and 16.
+ *
+ * The pre-divide register PDIV is 1 bit and selects between 2 (0) or 32 (1).
+ * The divide register DIV is 4 bit and the resultant divisor is 2 * (DIV + 1).
+ */
+
+#define VAL(_pdiv, _div) \
+ (((_pdiv) << A2B_CLKCFG_PDIV_SHIFT) | ((_div) << A2B_CLKCFG_DIV_SHIFT))
+#define DIV(_div) (2 * ((_div) + 1))
+
+/* In total there are 6 bits to the value, with the 4th bit going unused */
+#define AD24XX_CLK_DIV_WIDTH 6
+static const struct clk_div_table ad24xx_clk_div_table[] = {
+ { VAL(0, 0), 2 * DIV(0) }, { VAL(0, 1), 2 * DIV(1) },
+ { VAL(0, 2), 2 * DIV(2) }, { VAL(0, 3), 2 * DIV(3) },
+ { VAL(0, 4), 2 * DIV(4) }, { VAL(0, 5), 2 * DIV(5) },
+ { VAL(0, 6), 2 * DIV(6) }, { VAL(0, 7), 2 * DIV(7) },
+ { VAL(0, 8), 2 * DIV(8) }, { VAL(0, 9), 2 * DIV(9) },
+ { VAL(0, 10), 2 * DIV(10) }, { VAL(0, 11), 2 * DIV(11) },
+ { VAL(0, 12), 2 * DIV(12) }, { VAL(0, 13), 2 * DIV(13) },
+ { VAL(0, 14), 2 * DIV(14) }, { VAL(0, 15), 2 * DIV(15) },
+ { VAL(1, 0), 32 * DIV(0) }, { VAL(1, 1), 32 * DIV(1) },
+ { VAL(1, 2), 32 * DIV(2) }, { VAL(1, 3), 32 * DIV(3) },
+ { VAL(1, 4), 32 * DIV(4) }, { VAL(1, 5), 32 * DIV(5) },
+ { VAL(1, 6), 32 * DIV(6) }, { VAL(1, 7), 32 * DIV(7) },
+ { VAL(1, 8), 32 * DIV(8) }, { VAL(1, 9), 32 * DIV(9) },
+ { VAL(1, 10), 32 * DIV(10) }, { VAL(1, 11), 32 * DIV(11) },
+ { VAL(1, 12), 32 * DIV(12) }, { VAL(1, 13), 32 * DIV(13) },
+ { VAL(1, 14), 32 * DIV(14) }, { VAL(1, 15), 32 * DIV(15) },
+ { /* sentinel */ }
+};
+
+static int ad24xx_clk_prepare(struct clk_hw *hw)
+{
+ struct ad24xx_clkout *clkout = to_ad24xx_clkout(hw);
+ struct ad24xx_clk *adclk = to_ad24xx_clk(clkout);
+ unsigned int idx = clkout->idx;
+
+ return regmap_update_bits(adclk->regmap, A2B_CLKCFG(idx),
+ A2B_CLKCFG_EN_MASK,
+ FIELD_PREP(A2B_CLKCFG_EN_MASK, 1));
+}
+
+static void ad24xx_clk_unprepare(struct clk_hw *hw)
+{
+ struct ad24xx_clkout *clkout = to_ad24xx_clkout(hw);
+ struct ad24xx_clk *adclk = to_ad24xx_clk(clkout);
+ unsigned int idx = clkout->idx;
+
+ regmap_update_bits(adclk->regmap, A2B_CLKCFG(idx), A2B_CLKCFG_EN_MASK,
+ FIELD_PREP(A2B_CLKCFG_EN_MASK, 0));
+}
+
+static unsigned long ad24xx_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct ad24xx_clkout *clkout = to_ad24xx_clkout(hw);
+ struct ad24xx_clk *adclk = to_ad24xx_clk(clkout);
+ unsigned int idx = clkout->idx;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(adclk->regmap, A2B_CLKCFG(idx), &val);
+ if (ret)
+ return 0;
+
+ val &= A2B_CLKCFG_PDIV_MASK | A2B_CLKCFG_DIV_MASK;
+
+ return divider_recalc_rate(hw, parent_rate, val, ad24xx_clk_div_table,
+ 0, AD24XX_CLK_DIV_WIDTH);
+}
+
+static long ad24xx_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return divider_round_rate(hw, rate, parent_rate, ad24xx_clk_div_table,
+ AD24XX_CLK_DIV_WIDTH, 0);
+}
+
+static int ad24xx_clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ return divider_determine_rate(hw, req, ad24xx_clk_div_table,
+ AD24XX_CLK_DIV_WIDTH, 0);
+}
+
+static int ad24xx_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct ad24xx_clkout *clkout = to_ad24xx_clkout(hw);
+ struct ad24xx_clk *adclk = to_ad24xx_clk(clkout);
+ unsigned int idx = clkout->idx;
+ int val;
+
+ val = divider_get_val(rate, parent_rate, ad24xx_clk_div_table,
+ AD24XX_CLK_DIV_WIDTH, 0);
+ if (val < 0)
+ return val;
+
+ return regmap_update_bits(adclk->regmap, A2B_CLKCFG(idx),
+ A2B_CLKCFG_PDIV_MASK | A2B_CLKCFG_DIV_MASK,
+ val);
+}
+
+static int ad24xx_clk_get_phase(struct clk_hw *hw)
+{
+ struct ad24xx_clkout *clkout = to_ad24xx_clkout(hw);
+ struct ad24xx_clk *adclk = to_ad24xx_clk(clkout);
+ unsigned int idx = clkout->idx;
+ unsigned int val;
+ bool invert;
+ int ret;
+
+ ret = regmap_read(adclk->regmap, A2B_CLKCFG(idx), &val);
+ if (ret)
+ return ret;
+
+ invert = FIELD_GET(A2B_CLKCFG_INV_MASK, val);
+
+ return invert ? 180 : 0;
+}
+
+static int ad24xx_clk_set_phase(struct clk_hw *hw, int degrees)
+{
+ struct ad24xx_clkout *clkout = to_ad24xx_clkout(hw);
+ struct ad24xx_clk *adclk = to_ad24xx_clk(clkout);
+ unsigned int idx = clkout->idx;
+ bool invert = !!degrees;
+
+ if (degrees != 0 && degrees != 180)
+ return -EINVAL;
+
+ return regmap_update_bits(adclk->regmap, A2B_CLKCFG(idx),
+ A2B_CLKCFG_INV_MASK,
+ FIELD_PREP(A2B_CLKCFG_INV_MASK, invert));
+}
+
+static const struct clk_ops ad24xx_clk_ops = {
+ .prepare = ad24xx_clk_prepare,
+ .unprepare = ad24xx_clk_unprepare,
+ .recalc_rate = ad24xx_clk_recalc_rate,
+ .round_rate = ad24xx_clk_round_rate,
+ .determine_rate = ad24xx_clk_determine_rate,
+ .set_rate = ad24xx_clk_set_rate,
+ .get_phase = ad24xx_clk_get_phase,
+ .set_phase = ad24xx_clk_set_phase,
+};
+
+static const struct regmap_config ad24xx_clk_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static struct clk_hw *ad24xx_clk_of_get(struct of_phandle_args *clkspec, void *data)
+{
+ struct ad24xx_clk *adclk = data;
+ unsigned int idx = clkspec->args[0];
+
+ if (idx >= AD24XX_NUM_CLKS)
+ return ERR_PTR(-EINVAL);
+
+ if (!adclk->clkouts[idx].registered)
+ return ERR_PTR(-ENOENT);
+
+ return &adclk->clkouts[idx].hw;
+}
+
+static int ad24xx_clk_probe(struct device *dev)
+{
+ struct a2b_func *func = to_a2b_func(dev);
+ struct a2b_node *node = func->node;
+ struct device_node *np = dev->of_node;
+ char *pll_name;
+ const char *sync_clk_name;
+ struct ad24xx_clk *adclk;
+ int num_clks;
+ int ret;
+ int i;
+
+ /*
+ * Older series AD240x and AD241x chips have a single discrete
+ * A2B_CLKCFG register that behaves differently to the A2B_CLKnCFG
+ * registers of the later AD242x series. This driver only supports the
+ * latter right now.
+ */
+ if (!(node->chip_info->caps & A2B_CHIP_CAP_CLKOUT))
+ return -ENODEV;
+
+ adclk = devm_kzalloc(dev, sizeof(*adclk), GFP_KERNEL);
+ if (!adclk)
+ return -ENOMEM;
+
+ adclk->regmap =
+ devm_regmap_init_a2b_func(func, &ad24xx_clk_regmap_config);
+ if (IS_ERR(adclk->regmap))
+ return PTR_ERR(adclk->regmap);
+
+ adclk->dev = dev;
+ adclk->func = func;
+ adclk->node = node;
+ dev_set_drvdata(dev, adclk);
+
+ num_clks = of_property_count_strings(np, "clock-output-names");
+ if (num_clks < 0 || num_clks > AD24XX_NUM_CLKS)
+ return -EINVAL;
+
+ /*
+ * Register the PLL internally to use it as the parent of the CLKOUTs.
+ * The PLL runs at 2048 times the SYNC clock rate.
+ */
+ pll_name =
+ devm_kasprintf(dev, GFP_KERNEL, "%s_pll", dev_name(&node->dev));
+ if (!pll_name)
+ return -ENOMEM;
+ sync_clk_name = __clk_get_name(a2b_node_get_sync_clk(func->node));
+ adclk->pll_hw = devm_clk_hw_register_fixed_factor(
+ dev, pll_name, sync_clk_name, 0, 2048, 1);
+ if (IS_ERR(adclk->pll_hw))
+ return PTR_ERR(adclk->pll_hw);
+
+ for (i = 0; i < num_clks; i++) {
+ struct clk_init_data init = { };
+ const char *parent_names = clk_hw_get_name(adclk->pll_hw);
+ unsigned int idx = i;
+
+ /* Clock outputs can be skipped with the clock-indices property */
+ of_property_read_u32_index(np, "clock-indices", i, &idx);
+ if (idx > AD24XX_NUM_CLKS)
+ return -EINVAL;
+
+ ret = of_property_read_string_index(np, "clock-output-names", i,
+ &init.name);
+ if (ret)
+ return ret;
+
+ init.ops = &ad24xx_clk_ops;
+ init.parent_names = &parent_names;
+ init.num_parents = 1;
+
+ adclk->clkouts[idx].hw.init = &init;
+ adclk->clkouts[idx].idx = idx;
+ adclk->clkouts[idx].registered = true;
+
+ ret = devm_clk_hw_register(dev, &adclk->clkouts[idx].hw);
+ if (ret)
+ return ret;
+ }
+
+ ret = devm_of_clk_add_hw_provider(dev, ad24xx_clk_of_get, adclk);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id ad24xx_clk_of_match_table[] = {
+ { .compatible = "adi,ad2420-clk" },
+ { .compatible = "adi,ad2421-clk" },
+ { .compatible = "adi,ad2422-clk" },
+ { .compatible = "adi,ad2425-clk" },
+ { .compatible = "adi,ad2426-clk" },
+ { .compatible = "adi,ad2427-clk" },
+ { .compatible = "adi,ad2428-clk" },
+ { .compatible = "adi,ad2429-clk" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_clk_of_match_table);
+
+static struct a2b_driver ad24xx_clk_driver = {
+ .driver = {
+ .name = "ad24xx-clk",
+ .of_match_table = ad24xx_clk_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = ad24xx_clk_probe,
+};
+module_a2b_driver(ad24xx_clk_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("AD24xx CLK driver");
+MODULE_LICENSE("GPL");

--
2.44.0


2024-05-17 13:15:30

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 09/13] i2c: add AD24xx I2C controller driver

From: Alvin Šipraga <[email protected]>

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 1 +
drivers/clk/Kconfig | 2 +-
drivers/i2c/busses/Kconfig | 7 +++
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-ad24xx.c | 121 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index 08acf5728023..e3c38520a90a 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -36,6 +36,7 @@ config A2B_AD24XX_NODE
imply GPIO_AD24XX
imply SND_SOC_AD24XX
imply COMMON_CLK_AD24XX
+ imply I2C_AD24XX
help
Say Y here to enable support for AD24xx A2B transceiver nodes. This
applies to both main nodes and subordinate nodes. Supported models
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index a3d54b077e68..460762f44434 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -258,7 +258,7 @@ config COMMON_CLK_LAN966X
within the SoC.

config COMMON_CLK_AD24XX
- bool "Clock driver for Analog Devices Inc. AD24xx"
+ tristate "Clock driver for Analog Devices Inc. AD24xx"
depends on A2B_AD24XX_NODE
help
This driver supports the clock output functionality of AD24xx series
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index fe6e8a1bb607..d1f303bd7c90 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1387,6 +1387,13 @@ config I2C_ACORN

If you don't know, say Y.

+config I2C_AD24XX
+ tristate "Analog Devices Inc. AD24xx I2C controller support"
+ depends on A2B_AD24XX_NODE
+ help
+ Say yes if you want to support the I2C controller function of AD24xx
+ A2B transceiver chips.
+
config I2C_ELEKTOR
tristate "Elektor ISA card"
depends on ISA && HAS_IOPORT_MAP && BROKEN_ON_SMP
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3d65934f5eb4..892a32b02267 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_I2C_VIPERBOARD) += i2c-viperboard.o

# Other I2C/SMBus bus drivers
obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o
+obj-$(CONFIG_I2C_AD24XX) += i2c-ad24xx.o
obj-$(CONFIG_I2C_BCM_KONA) += i2c-bcm-kona.o
obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o
obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o
diff --git a/drivers/i2c/busses/i2c-ad24xx.c b/drivers/i2c/busses/i2c-ad24xx.c
new file mode 100644
index 000000000000..ad9657df25fb
--- /dev/null
+++ b/drivers/i2c/busses/i2c-ad24xx.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD24xx I2C controller (master) driver
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ */
+
+#include <linux/a2b/a2b.h>
+#include <linux/a2b/ad24xx.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+
+struct ad24xx_i2c_adapter {
+ struct device *dev;
+ struct a2b_func *func;
+ struct a2b_node *node;
+ struct i2c_adapter adap;
+};
+
+static int ad24xx_i2c_adapter_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct ad24xx_i2c_adapter *ada = i2c_get_adapdata(adap);
+ struct a2b_node *node = ada->node;
+
+ return a2b_node_i2c_xfer(node, msgs, num);
+}
+
+static u32 ad24xx_i2c_adapter_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_adapter_quirks ad24xx_i2c_adapter_quirks = {
+ .flags = I2C_AQ_COMB | I2C_AQ_COMB_SAME_ADDR,
+};
+
+static const struct i2c_algorithm ad24xx_i2c_adapter_algo = {
+ .master_xfer = ad24xx_i2c_adapter_xfer,
+ .functionality = ad24xx_i2c_adapter_functionality,
+};
+
+static int ad24xx_i2c_adapter_probe(struct device *dev)
+{
+ struct a2b_func *func = to_a2b_func(dev);
+ struct device_node *np = dev->of_node;
+ struct ad24xx_i2c_adapter *ada;
+ unsigned int val = 0;
+ u32 bus_speed;
+ int ret;
+
+ ada = devm_kzalloc(dev, sizeof(*ada), GFP_KERNEL);
+ if (!ada)
+ return -ENOMEM;
+
+ ada->dev = dev;
+ ada->func = func;
+ ada->node = func->node;
+
+ ada->adap.owner = THIS_MODULE;
+ ada->adap.algo = &ad24xx_i2c_adapter_algo;
+ ada->adap.dev.parent = dev;
+ ada->adap.dev.of_node = dev->of_node;
+ ada->adap.quirks = &ad24xx_i2c_adapter_quirks;
+ strscpy(ada->adap.name, dev_name(dev), sizeof(ada->adap.name));
+ i2c_set_adapdata(&ada->adap, ada);
+
+ ret = of_property_read_u32(np, "clock-frequency", &bus_speed);
+ if (ret)
+ bus_speed = I2C_MAX_STANDARD_MODE_FREQ;
+
+ if (bus_speed != I2C_MAX_STANDARD_MODE_FREQ &&
+ bus_speed != I2C_MAX_FAST_MODE_FREQ)
+ return -EINVAL;
+
+ val |= FIELD_PREP(A2B_I2CCFG_DATARATE_MASK,
+ bus_speed == I2C_MAX_FAST_MODE_FREQ ? 1 : 0);
+ val |= FIELD_PREP(A2B_I2CCFG_FRAMERATE_MASK,
+ func->node->bus->sff == A2B_SFF_44100 ? 1 : 0);
+
+ ret = a2b_node_write(func->node, A2B_I2CCFG, val);
+ if (ret)
+ return ret;
+
+ ret = devm_i2c_add_adapter(dev, &ada->adap);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id ad24xx_i2c_adapter_of_match_table[] = {
+ { .compatible = "adi,ad2401-i2c" },
+ { .compatible = "adi,ad2402-i2c" },
+ { .compatible = "adi,ad2403-i2c" },
+ { .compatible = "adi,ad2410-i2c" },
+ { .compatible = "adi,ad2420-i2c" },
+ { .compatible = "adi,ad2421-i2c" },
+ { .compatible = "adi,ad2422-i2c" },
+ { .compatible = "adi,ad2425-i2c" },
+ { .compatible = "adi,ad2426-i2c" },
+ { .compatible = "adi,ad2427-i2c" },
+ { .compatible = "adi,ad2428-i2c" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ad24xx_i2c_adapter_of_match_table);
+
+static struct a2b_driver ad24xx_i2c_adapter_driver = {
+ .driver = {
+ .name = "ad24xx-i2c-adapter",
+ .of_match_table = ad24xx_i2c_adapter_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = ad24xx_i2c_adapter_probe,
+};
+module_a2b_driver(ad24xx_i2c_adapter_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("AD24xx I2C controller driver");
+MODULE_LICENSE("GPL");

--
2.44.0


2024-05-17 13:16:59

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 11/13] dt-bindings: a2b: add compatible string for Beosound Shape node

From: Alvin Šipraga <[email protected]>

The Beosound Shape has the same device tree bindings as an AD2425, so it
is sufficient to just add an entry to the compatible enum.

Signed-off-by: Alvin Šipraga <[email protected]>
---
Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml | 1 +
1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
index dcda15e8032a..bea29f88d535 100644
--- a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
+++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
@@ -81,6 +81,7 @@ patternProperties:
- adi,ad2427-node
- adi,ad2428-node
- adi,ad2429-node
+ - beo,shape-node

reg:
maxItems: 1

--
2.44.0


2024-05-17 13:17:05

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 10/13] dt-bindings: vendor-prefixes: add Bang & Olufsen a/s

From: Alvin Šipraga <[email protected]>

Bang & Olufsen a/s is a Danish designer and manufacturer of high-end
consumer audio and home entertainment products.

The vendor prefix 'beo,' follows from the ubiquitous product naming
scheme, e.g. Beosound Balance, Beolab 28.

https://www.bang-olufsen.com/

Signed-off-by: Alvin Šipraga <[email protected]>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index fbf47f0bacf1..470ed53de8f1 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -208,6 +208,8 @@ patternProperties:
description: Compass Electronics Group, LLC
"^beagle,.*":
description: BeagleBoard.org Foundation
+ "^beo,.*":
+ description: Bang & Olufsen a/s
"^belling,.*":
description: Shanghai Belling Co., Ltd.
"^bhf,.*":

--
2.44.0


2024-05-17 13:17:54

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 13/13] MAINTAINERS: add maintainership for A2B drivers

From: Alvin Šipraga <[email protected]>

Add all relevant A2B driver files to the MAINTAINERS file and mark
myself as maintainer.

Signed-off-by: Alvin Šipraga <[email protected]>
---
MAINTAINERS | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 90754a451bcf..c2019c78b77c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3501,6 +3501,18 @@ F: kernel/audit*
F: lib/*audit.c
K: \baudit_[a-z_0-9]\+\b

+AUTOMOTIVE AUDIO BUS (A2B) DRIVERS
+M: Alvin Šipraga <[email protected]>
+S: Supported
+F: Documentation/devicetree/bindings/a2b/
+F: drivers/a2b/
+F: drivers/base/regmap/regmap-a2b.c
+F: drivers/clk/clk-ad24xx.c
+F: drivers/gpio/gpio-ad24xx.c
+F: drivers/i2c/busses/i2c-ad24xx.c
+F: include/linux/a2b/
+F: sound/soc/codecs/ad24xx-codec.c
+
AUXILIARY BUS DRIVER
M: Greg Kroah-Hartman <[email protected]>
R: Dave Ertman <[email protected]>

--
2.44.0


2024-05-17 13:20:46

by Alvin Šipraga

[permalink] [raw]
Subject: [PATCH 12/13] a2b: add Beosound Shape node driver

From: Alvin Šipraga <[email protected]>

Bang & Olufsen Beosound Shapes are amplifier speakers connected over
A2B. They have an on-board microcontroller with non-volatile firmware
which can be updated over a firmware update protocol (DFU).

Due to hardware peculiarities, the update of the microcontroller will
reset the A2B transceiver on the Shape board, causing an A2B bus drop.
This custom A2B node driver therefore handles the firmware update in a
serial fashion in order to ensure an error-free enumeration of the A2B
bus.

Signed-off-by: Alvin Šipraga <[email protected]>
---
drivers/a2b/Kconfig | 13 +
drivers/a2b/Makefile | 1 +
drivers/a2b/beo-shape-node.c | 584 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 598 insertions(+)

diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
index e3c38520a90a..7a8009c13672 100644
--- a/drivers/a2b/Kconfig
+++ b/drivers/a2b/Kconfig
@@ -44,4 +44,17 @@ config A2B_AD24XX_NODE

If unsure, say N.

+config A2B_BEO_SHAPE_NODE
+ tristate "Bang & Olufsen Beosound Shape node support"
+ depends on A2B_AD24XX_NODE
+ help
+ The Beosound Shape is an A2B-connected amplifier speaker. As a piece of
+ hardware it is functionally similar to any board with an AD2425, but
+ this driver handles firmware update of the on-board microcontroller in
+ a way that is agreeable to the A2B driver model.
+
+ Beosound Shapes are always subordinate A2B nodes.
+
+ If unsure, say N.
+
endif # A2B
diff --git a/drivers/a2b/Makefile b/drivers/a2b/Makefile
index 171ffa237943..abeeb76c4e8c 100644
--- a/drivers/a2b/Makefile
+++ b/drivers/a2b/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_A2B_AD24XX_I2C) += ad24xx-i2c.o

# Node drivers
obj-$(CONFIG_A2B_AD24XX_NODE) += ad24xx-node.o
+obj-$(CONFIG_A2B_BEO_SHAPE_NODE) += beo-shape-node.o
diff --git a/drivers/a2b/beo-shape-node.c b/drivers/a2b/beo-shape-node.c
new file mode 100644
index 000000000000..54184cd667df
--- /dev/null
+++ b/drivers/a2b/beo-shape-node.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Beosound Shape A2B transceiver node driver
+ *
+ * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
+ *
+ * This is basically an AD2425 driver. But in order to flash the STM32
+ * microcontroller on the Beosound Shape, some help is needed on the part of the
+ * A2B node driver.
+ *
+ * Here is a simplified block diagram of the problem this driver is dealing
+ * with:
+ *
+ * ┌───────────┐
+ * ┌───────│ regulator │
+ * │ └──────▲────┘
+ * │ 5V │ GPIO enable
+ * ┌──────┐ A2B ┌───▼──┐ I2C ┌───────┐
+ * │ A2B │/\/\/\│ A2B │───────│ STM32 │
+ * │ main │\/\/\/│ sub │ │ MCU │
+ * └──────┘ └──────┘ └───────┘
+ *
+ * The Shape's MCU is an STM32F072. It has a bootloader. The bootloader can
+ * either enter firmware update (DFU) mode, or jump to the Bang & Olufsen
+ * application code (APP). DFU mode is a proprietary implementation and does not
+ * refer to the standard STM32 bootloader mode. DFU mode allows for the APP
+ * code to be updated.
+ *
+ * Whether the bootloader enters DFU or APP mode depends on a flag kept in the
+ * MCU's non-volatile flash memory. The MCU can be moved into DFU or APP mode by
+ * issuing a command which sets the flag to DFU (resp. APP) mode and then
+ * performs a software reset. The MCU responds over I2C in both modes, but the
+ * commands are in general different. The command to read the flag is the same
+ * for both modes, which allows the driver to determine the current state.
+ *
+ * When the MCU undergoes software reset, its GPIOs enter their default state
+ * and this causes the A2B transceiver on the board to lose power due to a
+ * hardware pull-down on the GPIO enable line of its supply regulator. This A2B
+ * node driver supervises the process to ensure that the A2B discovery process
+ * only continues when all currently discovered nodes have had their MCU
+ * firmware updated.
+ *
+ * An obvious question is why not let an MCU-specific I2C driver handle the
+ * firmware update. The answer lies in the issue of device probe order and
+ * topology: suppose that an I2C driver flashed the MCU instead. Then what is
+ * likely to happen is that further downstream nodes also get discovered and
+ * potentially probed in between one of the transitions between APP/DFU
+ * mode. This process is wasted as at some point there will be a bus drop and
+ * all those new devices must also be cleaned up. Worse yet is if further
+ * downstream MCU I2C drivers begin flashing as well, leading to a big mess of
+ * devices coming and going during boot. By blocking the creation of a2b_func
+ * devices and discovery of further nodes until this MCU reset flip-flopping is
+ * complete, the chaos is kept to a minimum.
+ *
+ * After the firmware is up-to-date, the driver reverts to the standard
+ * behaviour of the generic ad24xx-node driver.
+ *
+ * The firmware is split into 2048 byte sectors, and each sector has 16
+ * blocks. Each block is written with a single I2C command. After each block
+ * write command, an ACK must be read back successfully to continue with the
+ * next block write. The MCU must only be put into APP mode when all blocks have
+ * successfully been written - doing otherwise will cause the bootloader's
+ * checksum verification to fail and it will then unconditionally fall into the
+ * standard STM32 bootloader every time.
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+
+#include "ad24xx-node.h"
+
+/* The MCU answers on this I2C address */
+#define MCU_ADDRESS 0x65
+
+/* Firmware properties */
+#define FW_ADDR 0x08004000
+#define FW_SIZE 0x1B800
+#define FW_BLKSZ 128
+#define FW_SECSZ 2048
+#define FW_BLKS_PER_SEC (FW_SECSZ / FW_BLKSZ)
+#define FW_SECTORS (FW_SIZE / FW_SECSZ)
+#define FW_VER32_ADDR 0x0801F7F8
+#define FW_VER32_OFFSET (FW_VER32_ADDR - FW_ADDR)
+
+#define FW_VER32_0 0xFF000000
+#define FW_VER32_1 0x00FF0000
+#define FW_VER32_2 0x0000FF00
+#define FW_VER32_3 0x000000FF
+#define FW_VER32_TO_FW_VER(fw_ver32) \
+ (FIELD_GET(FW_VER32_0, (fw_ver32)) * 1000 + \
+ FIELD_GET(FW_VER32_1, (fw_ver32)) * 100 + \
+ FIELD_GET(FW_VER32_2, (fw_ver32)) * 10 + \
+ FIELD_GET(FW_VER32_3, (fw_ver32)) * 1)
+#define FW_VER32_FIELDS(fw_ver32) \
+ FIELD_GET(FW_VER32_0, (fw_ver32)), \
+ FIELD_GET(FW_VER32_1, (fw_ver32)), \
+ FIELD_GET(FW_VER32_2, (fw_ver32)), \
+ FIELD_GET(FW_VER32_3, (fw_ver32))
+#define FW_VER32(fw_ver32) FW_VER32_FIELDS(fw_ver32)
+#define FW_VER32_FIELDS_FMT "%u.%u.%u.%u"
+#define FW_VER32_FMT FW_VER32_FIELDS_FMT
+
+#define FW_VER_FIELDS(fw_ver) \
+ (((fw_ver) % 10000) / 1000), \
+ (((fw_ver) % 1000) / 100), \
+ (((fw_ver) % 100) / 10), \
+ (((fw_ver) % 10))
+#define FW_VER(fw_ver) FW_VER_FIELDS(fw_ver)
+#define FW_VER_FIELDS_FMT "%u.%u.%u.%u"
+#define FW_VER_FMT FW_VER_FIELDS_FMT
+
+/* The DFU flag indicates whether or not the MCU is in DFU mode or not */
+#define FLAG_APP_MODE 0x00
+#define FLAG_DFU_MODE 0xDD
+
+/* DFU constants */
+#define DFU_ACK 0xAA
+#define DFU_NACK 0xBB
+
+/* Read commands in APP mode */
+#define APP_READ_DFU_FLAG 0x00
+#define APP_READ_ITEM_NO 0x01
+#define APP_READ_TYPE_NO 0x02
+#define APP_READ_SERIAL_NO 0x03
+#define APP_READ_HW_VER 0x04
+#define APP_READ_BTL_VER 0x05
+#define APP_READ_APP_VER 0x06
+#define APP_READ_DSP_VER 0x07
+#define APP_READ_NTC_VALUE 0x08
+#define APP_READ_DSP_DELAY 0x09
+#define APP_READ_DSP_GAIN 0x0A
+#define APP_READ_DSP_ROOMEQ 0x0B
+#define APP_READ_DSP_ROOMEQ2 0x0C
+
+/* Write commands in APP mode */
+#define APP_WRITE_ENTER_DFU_MODE 0x01
+
+/* Read commands in DFU mode */
+#define DFU_READ_DFU_FLAG APP_READ_DFU_FLAG
+#define DFU_READ_ACK 0x02
+
+/* Write commands in DFU mode */
+#define DFU_WRITE_BLOCK 0x01
+#define DFU_WRITE_ENTER_APP_MODE 0x02
+
+static unsigned int force_fwupd;
+module_param(force_fwupd, uint, 0644);
+MODULE_PARM_DESC(force_fwupd, "force firmware update ignoring version check");
+
+static int beo_shape_node_enter_app_mode(struct a2b_node *node)
+{
+ struct i2c_msg xfer[1];
+ u8 buf[2] = {
+ DFU_WRITE_ENTER_APP_MODE,
+ 0xFF - DFU_WRITE_ENTER_APP_MODE, /* checksum */
+ };
+ int ret;
+
+ xfer[0].addr = MCU_ADDRESS;
+ xfer[0].flags = 0;
+ xfer[0].len = 2;
+ xfer[0].buf = buf;
+
+ ret = a2b_node_i2c_xfer(node, xfer, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Wait for the A2B transceiver to lose power */
+ msleep(1000);
+
+ return 0;
+}
+
+static int beo_shape_node_enter_dfu_mode(struct a2b_node *node)
+{
+ struct i2c_msg xfer[1];
+ u8 reg = APP_WRITE_ENTER_DFU_MODE;
+ int ret;
+
+ xfer[0].addr = MCU_ADDRESS;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &reg;
+
+ ret = a2b_node_i2c_xfer(node, xfer, 1);
+ if (ret < 0)
+ return ret;
+
+ /* Wait for the A2B transceiver to lose power */
+ msleep(1000);
+
+ return 0;
+}
+
+static int beo_shape_node_read(struct a2b_node *node, u8 reg, u8 *buf, u16 len)
+{
+ struct i2c_msg xfer[2];
+ int ret;
+
+ xfer[0].addr = MCU_ADDRESS;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &reg;
+
+ xfer[1].addr = MCU_ADDRESS;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = len;
+ xfer[1].buf = buf;
+
+ ret = a2b_node_i2c_xfer(node, xfer, 2);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int beo_shape_node_read8(struct a2b_node *node, u8 reg, u8 *val)
+{
+ return beo_shape_node_read(node, reg, val, 1);
+}
+
+static int beo_shape_node_read16(struct a2b_node *node, u8 reg, u16 *val)
+{
+ int ret;
+
+ ret = beo_shape_node_read(node, reg, (u8 *)val, 2);
+ if (ret)
+ return ret;
+
+ *val = __le16_to_cpu(*val);
+
+ return 0;
+}
+
+static int beo_shape_node_read32(struct a2b_node *node, u8 reg, u32 *val)
+{
+ int ret;
+
+ ret = beo_shape_node_read(node, reg, (u8 *)val, 4);
+ if (ret)
+ return ret;
+
+ *val = __le32_to_cpu(*val);
+
+ return 0;
+}
+
+static int beo_shape_node_get_dfu_flag(struct a2b_node *node, u8 *flag)
+{
+ return beo_shape_node_read8(node, APP_READ_DFU_FLAG, flag);
+}
+
+static int beo_shape_node_get_app_ver(struct a2b_node *node, u16 *ver)
+{
+ return beo_shape_node_read16(node, APP_READ_APP_VER, ver);
+}
+
+static int beo_shape_node_get_item_no(struct a2b_node *node, u32 *item_no)
+{
+ return beo_shape_node_read32(node, APP_READ_ITEM_NO, item_no);
+}
+
+static int beo_shape_node_get_type_no(struct a2b_node *node, u32 *type_no)
+{
+ return beo_shape_node_read32(node, APP_READ_TYPE_NO, type_no);
+}
+
+static int beo_shape_node_get_serial_no(struct a2b_node *node, u32 *serial_no)
+{
+ return beo_shape_node_read32(node, APP_READ_SERIAL_NO, serial_no);
+}
+
+static int beo_shape_node_get_hw_ver(struct a2b_node *node, u32 *hw_ver)
+{
+ return beo_shape_node_read32(node, APP_READ_HW_VER, hw_ver);
+}
+
+static const char *beo_shape_node_hw_ver_string(u32 hw_ver)
+{
+ const char *hw_string[] = { "unknown", "ES1", "ES2", "ES3",
+ "EVT1", "EVT2", "DVT1", "DVT2",
+ "PVT", "MP1", "MP2" };
+ if (hw_ver >= ARRAY_SIZE(hw_string))
+ return "unknown";
+
+ return hw_string[hw_ver];
+}
+
+static int beo_shape_node_write_fw_blk(struct a2b_node *node,
+ const struct firmware *fw, u8 sec,
+ u8 blk)
+{
+ u32 offset = (sec * FW_SECSZ) + (blk * FW_BLKSZ);
+ union {
+ struct {
+ u8 cmd;
+ u8 data[FW_BLKSZ];
+ u8 sec;
+ u8 blk;
+ u8 csum;
+ };
+ u8 raw[FW_BLKSZ + 4];
+ } buf;
+ struct i2c_msg xfer[1];
+ unsigned int retries = 3;
+ u8 ack = 0;
+ int ret;
+ int i;
+
+ buf.cmd = DFU_WRITE_BLOCK;
+ memcpy(buf.data, fw->data + offset, FW_BLKSZ);
+ buf.sec = sec;
+ buf.blk = blk;
+ buf.csum = 0;
+
+ for (i = 0; i < sizeof(buf) - 1; i++)
+ buf.csum += buf.raw[i];
+ buf.csum = 0xFF - buf.csum;
+
+ xfer[0].addr = MCU_ADDRESS;
+ xfer[0].flags = 0;
+ xfer[0].len = sizeof(buf);
+ xfer[0].buf = buf.raw;
+
+retry:
+ ret = a2b_node_i2c_xfer(node, xfer, 1);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * These sleeps are stolen from the firmware code. They might be too
+ * generous. But issuing a DFU_READ_ACK command too early will clobber
+ * the I2C RX buffer in the MCU while it is reading from that buffer to
+ * write a block. So the sleeps are crucial.
+ */
+ if (blk == FW_BLKS_PER_SEC - 1)
+ msleep(100);
+ else
+ msleep(3);
+
+ /*
+ * An ACK indicates that the checksum at the end of the previous
+ * DFU_WRITE_BLOCK command was correct on the receiving (MCU) end.
+ */
+ ret = beo_shape_node_read8(node, DFU_READ_ACK, &ack);
+ if (ret)
+ return ret;
+
+ if (ack != DFU_ACK) {
+ if (--retries > 0)
+ goto retry;
+
+ dev_err_ratelimited(&node->dev,
+ "got NACK on write of sec %d blk %d\n", sec,
+ blk);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int beo_shape_node_write_fw(struct a2b_node *node,
+ const struct firmware *fw)
+{
+ u8 sec, blk;
+ int ret;
+
+ for (sec = 0; sec < FW_SECTORS; sec++) {
+ for (blk = 0; blk < FW_BLKS_PER_SEC; blk++) {
+ ret = beo_shape_node_write_fw_blk(node, fw, sec, blk);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /*
+ * The firmware might silently ignore (but still ACK) subsequent
+ * commands for some reason... give it a moment.
+ */
+ msleep(100);
+
+ return 0;
+}
+
+struct beo_shape_node {
+ bool resetting;
+};
+
+static int beo_shape_node_setup(struct a2b_node *node)
+{
+ struct beo_shape_node *shape;
+ const struct firmware *fw;
+ u32 fw_ver32;
+ u16 fw_ver;
+ int ret;
+ u8 flag;
+
+ if (node->priv)
+ shape = node->priv;
+ else {
+ shape = devm_kzalloc(&node->dev, sizeof(*shape), GFP_KERNEL);
+ if (!shape)
+ return -ENOMEM;
+
+ node->priv = shape;
+ }
+
+ /*
+ * A reset command was already sent to flip the MCU into APP or DFU
+ * mode. Nothing left to do until a bus drop. Just continue deferring
+ * probe.
+ */
+ if (shape->resetting)
+ return -EPROBE_DEFER;
+
+ ret = beo_shape_node_get_dfu_flag(node, &flag);
+ if (ret)
+ return ret;
+
+ ret = request_firmware(&fw, "beo/shape.bin", &node->dev);
+ if (ret)
+ return ret;
+
+ if (fw->size != FW_SIZE) {
+ ret = -EINVAL;
+ goto release_fw;
+ }
+
+ /*
+ * The firmware binary contains a 32 bit version field at a fixed
+ * offset. There is also a 16 bit representation of the version returned
+ * by the APP over I2C. The data is interchangeable so we convert to a
+ * 16 bit representation to test whether or not the Shape needs a
+ * firmware update.
+ */
+ fw_ver32 = *((u32 *)&fw->data[FW_VER32_OFFSET]);
+ fw_ver = FW_VER32_TO_FW_VER(fw_ver32);
+
+ if (flag != FLAG_DFU_MODE) {
+ u32 hw_ver = 0;
+ u32 type_no;
+ u32 item_no;
+ u32 serial_no;
+ u16 app_ver;
+
+ /*
+ * The APP firmware returns 0 on some read commands while it is
+ * still initializing. It doesn't send I2C NAKs. Due to this,
+ * the driver has to poll something to figure out when the
+ * firmware is actually ready. From what I can see, the HW
+ * revision is the last thing to get populated out of the
+ * miscellaneous read registers, and also not at all likely to
+ * be 0 thereafter. So let's use that. Give it up to 3 seconds.
+ */
+ ret = read_poll_timeout(beo_shape_node_get_hw_ver, ret,
+ (ret != 0 || hw_ver != 0), 100e3, 2e6,
+ true, node, &hw_ver);
+ if (ret)
+ goto release_fw;
+
+ ret = beo_shape_node_get_app_ver(node, &app_ver);
+ if (ret)
+ goto release_fw;
+
+ ret = beo_shape_node_get_type_no(node, &type_no);
+ if (ret)
+ goto release_fw;
+
+ ret = beo_shape_node_get_item_no(node, &item_no);
+ if (ret)
+ goto release_fw;
+
+ ret = beo_shape_node_get_serial_no(node, &serial_no);
+ if (ret)
+ goto release_fw;
+
+ dev_info(&node->dev,
+ "shape hw %u (%s) fw " FW_VER_FMT
+ " type %u item %u serial %u \n",
+ hw_ver, beo_shape_node_hw_ver_string(hw_ver),
+ FW_VER(app_ver), type_no, item_no, serial_no);
+
+ if (app_ver != fw_ver || (BIT(node->addr) & force_fwupd)) {
+ dev_info(&node->dev, "entering DFU mode\n");
+
+ /*
+ * Unset the bit now that we are updating this shape in
+ * order to avoid an infinite update loop
+ */
+ force_fwupd &= ~BIT(node->addr);
+
+ ret = beo_shape_node_enter_dfu_mode(node);
+ if (ret)
+ goto release_fw;
+
+ /* Expect a bus drop now */
+ shape->resetting = true;
+ ret = -EPROBE_DEFER;
+ goto release_fw;
+ }
+ } else {
+ dev_info(&node->dev, "writing fw " FW_VER32_FMT "\n",
+ FW_VER32(fw_ver32));
+
+ ret = beo_shape_node_write_fw(node, fw);
+ if (ret)
+ goto release_fw;
+
+ dev_info(&node->dev, "entering APP mode\n");
+
+ ret = beo_shape_node_enter_app_mode(node);
+ if (ret)
+ goto release_fw;
+
+ /* Expect a bus drop now */
+ shape->resetting = true;
+ ret = -EPROBE_DEFER;
+ goto release_fw;
+ }
+
+release_fw:
+ release_firmware(fw);
+
+ if (ret)
+ return ret;
+
+ return ad24xx_node_setup(node);
+}
+
+static struct a2b_node_ops beo_shape_node_ops = {
+ .set_respcycs = ad24xx_node_set_respcycs,
+ .set_switching = ad24xx_node_set_switching,
+ .is_last = ad24xx_node_is_last,
+ .setup = beo_shape_node_setup,
+ .teardown = ad24xx_node_teardown,
+};
+
+static int beo_shape_node_probe(struct device *dev)
+{
+ struct a2b_node *node = to_a2b_node(dev);
+ int ret;
+
+ node->ops = &beo_shape_node_ops;
+ node->chip_info = of_device_get_match_data(dev);
+
+ ret = a2b_register_node(node);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void beo_shape_node_remove(struct device *dev)
+{
+ struct a2b_node *node = to_a2b_node(dev);
+
+ a2b_unregister_node(node);
+}
+
+static const struct of_device_id beo_shape_node_of_match_table[] = {
+ {
+ .compatible = "beo,shape-node",
+ .data = &ad24xx_chip_info[A2B_AD2425],
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, beo_shape_node_of_match_table);
+
+static struct a2b_driver beo_shape_node_driver = {
+ .driver = {
+ .name = "beo-shape-node",
+ .of_match_table = beo_shape_node_of_match_table,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+ .probe = beo_shape_node_probe,
+ .remove = beo_shape_node_remove,
+};
+module_a2b_driver(beo_shape_node_driver);
+
+MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
+MODULE_DESCRIPTION("Beosound Shape A2B transceiver node driver");
+MODULE_LICENSE("GPL");

--
2.44.0


2024-05-17 14:50:20

by Wolfram Sang

[permalink] [raw]
Subject: Re: [PATCH 04/13] a2b: add AD24xx I2C interface driver


> + /*
> + * Enforce some basic assumptions this function makes about the
> + * transfer. If this proves insufficient, some more complex logic will
> + * be needed.
> + */
> + if (num > 2 || (num == 2 && msgs[0].addr != msgs[1].addr))
> + return -EOPNOTSUPP;

As you populated 'ad24xx_i2c_adapter_quirks' in the I2C driver, you can
drop this. The I2C core will do the checks for you.


Attachments:
(No filename) (403.00 B)
signature.asc (849.00 B)
Download all attachments

2024-05-17 15:00:13

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 02/13] regmap: add A2B support

On Fri, May 17, 2024 at 02:58:00PM +0200, Alvin Šipraga wrote:

> +static int regmap_a2b_write(void *context, const void *data, size_t count)
> +{

> + for (i = 0; i < count - 1; i++) {
> + ret = bus->ops->write(bus, node, reg + i, d[i + 1]);
> + if (ret)
> + return ret;
> + }

Just force single_read and single_write (looks like you'll need to add
the hook for the bus there).

> +struct regmap *__devm_regmap_init_a2b_node(struct a2b_node *node,
> + const struct regmap_config *config,
> + struct lock_class_key *lock_key,
> + const char *lock_name)
> +{
> + return __devm_regmap_init(&node->dev, &regmap_a2b, node, config,
> + lock_key, lock_name);
> +}
> +EXPORT_SYMBOL_GPL(__devm_regmap_init_a2b_node);

Should there be validation of val_bits?


Attachments:
(No filename) (806.00 B)
signature.asc (499.00 B)
Download all attachments

2024-05-17 15:06:46

by Wolfram Sang

[permalink] [raw]
Subject: Re: [PATCH 00/13] Analog Devices Inc. Automotive Audio Bus (A2B) support


> On subordinate nodes the I2C interface functions in controller
> (master) mode, providing an additional I2C adapter to the host for
> each subordinate node connected to the A2B bus.

I am not sure I got this right? That would mean for an I2C adapter on a
subordinate node, that there might be targets connected to only that
subordinate node? How do its messages go to the host machine? Is I2C
encapsulated over I2C? I probably missed something.


Attachments:
(No filename) (465.00 B)
signature.asc (849.00 B)
Download all attachments

2024-05-17 15:30:03

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 07/13] ASoC: codecs: add AD24xx codec driver

On Fri, May 17, 2024 at 02:58:05PM +0200, Alvin Šipraga wrote:

> +++ b/sound/soc/codecs/ad24xx-codec.c
> @@ -0,0 +1,665 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * AD24xx codec driver

Please make the whole comment a C++ comment.

> +static const char *const ad24xx_codec_slot_size_text[] = {
> + "8 bits", "12 bits", "16 bits", "20 bits",
> + "24 bits", "28 bits", "32 bits",
> +};

Why is this configured by the user rather than via set_tdm_slot(), and
how would one usefully use this at runtime?

> +static int ad24xx_codec_slot_config_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{

> + } else if (priv == &ad24xx_codec_up_slot_format_enum ||
> + priv == &ad24xx_codec_dn_slot_format_enum) {
> + if (val >= ARRAY_SIZE(ad24xx_codec_slot_format_text))
> + return -EINVAL;
> + slot_config->format[direction] = val;
> + } else
> + return -ENOENT;

If one side has {} both sides should, see coding-style.rst.

> +
> + return 0;
> +}

This won't flag changes by returning 1 which will mean no events are
generated and break some UIs. Please show the output of the mixer-test
selftest on new submissions, it will check for this and other issues.

> + /* Main node must be BCLK/FSYNC consumer, subordinate node provider */
> + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) !=
> + (is_a2b_main(adc->node) ? SND_SOC_DAIFMT_CBC_CFC :
> + SND_SOC_DAIFMT_CBP_CFP))
> + return -EINVAL;

Please don't use the ternery operator like this, it just makes things
harder to read.

> + val = bclk_invert ? A2B_I2SCFG_RXBCLKINV_MASK :
> + A2B_I2SCFG_TXBCLKINV_MASK;

Similarly, please use normal conditional statements.

> +static int ad24xx_codec_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params,
> + struct snd_soc_dai *dai)

> +
> + /* Finally, request slots */
> + ret = a2b_node_request_slots(adc->node, &slot_req);
> + if (ret)
> + return ret;

Note that hw_params() can be called multiple times before starting the
audio stream, will this leak?

> + struct snd_soc_dai *dai)
> +{
> + struct snd_soc_component *component = dai->component;
> + struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
> + int ret;
> +
> + ret = a2b_node_free_slots(adc->node);
> + if (ret)
> + return ret;

What if we close without having called hw_params()?

> +static const struct snd_soc_dai_driver ad24xx_codec_dai_drv[] = {
> + [AD24XX_DAI_I2S] = {
> + .name = "ad24xx-i2s",
> + .playback = {
> + .stream_name = "I2S Playback",
> + .channels_min = 1,
> + .channels_max = 32,
> + },
> + .capture = {
> + .stream_name = "I2S Capture",
> + .channels_min = 1,
> + .channels_max = 32,
> + },
> + .ops = &ad24xx_codec_dai_ops,
> + .symmetric_rate = 1,
> + },
> +};

Why is this an array?

> +static const struct regmap_config ad24xx_codec_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .cache_type = REGCACHE_RBTREE,
> +};

New code should use _MAPLE unless there's a strong reason to use
something else.


Attachments:
(No filename) (3.09 kB)
signature.asc (499.00 B)
Download all attachments

2024-05-18 12:51:48

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core

Hi Alvin,

kernel test robot noticed the following build errors:

[auto build test ERROR on c75962170e49f24399141276ae119e6a879f36dc]

url: https://github.com/intel-lab-lkp/linux/commits/Alvin-ipraga/a2b-add-A2B-driver-core/20240517-211849
base: c75962170e49f24399141276ae119e6a879f36dc
patch link: https://lore.kernel.org/r/20240517-a2b-v1-1-b8647554c67b%40bang-olufsen.dk
patch subject: [PATCH 01/13] a2b: add A2B driver core
config: i386-allmodconfig (https://download.01.org/0day-ci/archive/20240518/[email protected]/config)
compiler: gcc-13 (Ubuntu 13.2.0-4ubuntu3) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240518/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All error/warnings (new ones prefixed by >>):

drivers/a2b/a2b.c: In function 'a2b_bus_of_add_node':
>> drivers/a2b/a2b.c:463:16: error: implicit declaration of function 'kzalloc' [-Werror=implicit-function-declaration]
463 | node = kzalloc(sizeof(*node), GFP_KERNEL);
| ^~~~~~~
>> drivers/a2b/a2b.c:463:14: warning: assignment to 'struct a2b_node *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
463 | node = kzalloc(sizeof(*node), GFP_KERNEL);
| ^
drivers/a2b/a2b.c: In function 'a2b_node_of_add_func':
>> drivers/a2b/a2b.c:1017:14: warning: assignment to 'struct a2b_func *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
1017 | func = kzalloc(sizeof(*func), GFP_KERNEL);
| ^
drivers/a2b/a2b.c: In function 'a2b_node_release':
>> drivers/a2b/a2b.c:1112:9: error: implicit declaration of function 'kfree' [-Werror=implicit-function-declaration]
1112 | kfree(node);
| ^~~~~
cc1: some warnings being treated as errors


vim +/kzalloc +463 drivers/a2b/a2b.c

444
445 static int a2b_bus_of_add_node(struct a2b_bus *bus, struct device_node *np,
446 unsigned int addr)
447 {
448 struct a2b_node *node;
449 int ret = 0;
450
451 if (!bus || !np)
452 return -EINVAL;
453
454 if (addr >= A2B_MAX_NODES)
455 return -EINVAL;
456
457 if (!of_device_is_available(np))
458 return -ENODEV;
459
460 if (of_node_test_and_set_flag(np, OF_POPULATED))
461 return -EBUSY;
462
> 463 node = kzalloc(sizeof(*node), GFP_KERNEL);
464 if (IS_ERR(node))
465 return -ENOMEM;
466
467 node->dev.bus = &a2b_bus;
468 node->dev.type = &a2b_node_type;
469 node->dev.parent = &bus->dev;
470 node->dev.of_node = np;
471 node->dev.fwnode = of_fwnode_handle(np);
472 dev_set_name(&node->dev, "a2b-%d.%d", bus->id, addr);
473
474 node->bus = bus;
475 node->addr = addr;
476
477 /*
478 * Register the node device. Note that due to asynchronous probing,
479 * there is no guarantee that the node driver's probe function has been
480 * called just yet. The synchronization point is a2b_register_node(),
481 * which should be called unconditionally by node drivers.
482 */
483 ret = device_register(&node->dev);
484 if (ret)
485 goto err_put_device;
486
487 return 0;
488
489 err_put_device:
490 put_device(&node->dev);
491
492 return ret;
493 }
494

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2024-05-18 12:57:36

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 04/13] a2b: add AD24xx I2C interface driver

Hi Alvin,

kernel test robot noticed the following build errors:

[auto build test ERROR on c75962170e49f24399141276ae119e6a879f36dc]

url: https://github.com/intel-lab-lkp/linux/commits/Alvin-ipraga/a2b-add-A2B-driver-core/20240517-211849
base: c75962170e49f24399141276ae119e6a879f36dc
patch link: https://lore.kernel.org/r/20240517-a2b-v1-4-b8647554c67b%40bang-olufsen.dk
patch subject: [PATCH 04/13] a2b: add AD24xx I2C interface driver
config: x86_64-allyesconfig (https://download.01.org/0day-ci/archive/20240518/[email protected]/config)
compiler: clang version 18.1.5 (https://github.com/llvm/llvm-project 617a15a9eac96088ae5e9134248d8236e34b91b1)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240518/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

>> drivers/a2b/ad24xx-i2c.c:272:3: error: call to undeclared function 'handle_nested_irq'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
272 | handle_nested_irq(virq);
| ^
>> drivers/a2b/ad24xx-i2c.c:281:26: error: call to undeclared function 'irq_data_get_irq_chip_data'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
281 | struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
| ^
>> drivers/a2b/ad24xx-i2c.c:281:21: error: incompatible integer to pointer conversion initializing 'struct ad24xx_i2c *' with an expression of type 'int' [-Wint-conversion]
281 | struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/a2b/ad24xx-i2c.c:282:34: error: incomplete definition of type 'struct irq_data'
282 | irq_hw_number_t hwirq = irq_data->hwirq;
| ~~~~~~~~^
include/linux/irqdomain.h:44:8: note: forward declaration of 'struct irq_data'
44 | struct irq_data;
| ^
drivers/a2b/ad24xx-i2c.c:289:26: error: call to undeclared function 'irq_data_get_irq_chip_data'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
289 | struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
| ^
drivers/a2b/ad24xx-i2c.c:289:21: error: incompatible integer to pointer conversion initializing 'struct ad24xx_i2c *' with an expression of type 'int' [-Wint-conversion]
289 | struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:290:34: error: incomplete definition of type 'struct irq_data'
290 | irq_hw_number_t hwirq = irq_data->hwirq;
| ~~~~~~~~^
include/linux/irqdomain.h:44:8: note: forward declaration of 'struct irq_data'
44 | struct irq_data;
| ^
>> drivers/a2b/ad24xx-i2c.c:295:30: error: variable has incomplete type 'const struct irq_chip'
295 | static const struct irq_chip ad24xx_i2c_irq_chip = {
| ^
include/linux/irqdomain.h:43:8: note: forward declaration of 'struct irq_chip'
43 | struct irq_chip;
| ^
>> drivers/a2b/ad24xx-i2c.c:304:2: error: call to undeclared function 'irq_set_chip_data'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
304 | irq_set_chip_data(irq, irqdomain->host_data);
| ^
>> drivers/a2b/ad24xx-i2c.c:305:2: error: call to undeclared function 'irq_set_chip_and_handler'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
305 | irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
| ^
>> drivers/a2b/ad24xx-i2c.c:305:54: error: use of undeclared identifier 'handle_simple_irq'
305 | irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
| ^
>> drivers/a2b/ad24xx-i2c.c:306:2: error: call to undeclared function 'irq_set_nested_thread'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
306 | irq_set_nested_thread(irq, 1);
| ^
>> drivers/a2b/ad24xx-i2c.c:307:2: error: call to undeclared function 'irq_set_noprobe'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
307 | irq_set_noprobe(irq);
| ^
drivers/a2b/ad24xx-i2c.c:315:2: error: call to undeclared function 'irq_set_nested_thread'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
315 | irq_set_nested_thread(irq, 0);
| ^
drivers/a2b/ad24xx-i2c.c:316:2: error: call to undeclared function 'irq_set_chip_and_handler'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
316 | irq_set_chip_and_handler(irq, NULL, NULL);
| ^
drivers/a2b/ad24xx-i2c.c:317:2: error: call to undeclared function 'irq_set_chip_data'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
317 | irq_set_chip_data(irq, NULL);
| ^
16 errors generated.


vim +/handle_nested_irq +272 drivers/a2b/ad24xx-i2c.c

218
219 static irqreturn_t ad24xx_i2c_irq_handler(int irq, void *data)
220 {
221 struct ad24xx_i2c *ad = data;
222 bool handled = false;
223 unsigned long hwirq;
224 unsigned int val;
225 unsigned int virq;
226 int ret;
227
228 /*
229 * The transceiver asserts the IRQ line as long as there are pending
230 * interrupts. Process them all here so that the interrupt can be
231 * configured with an edge trigger.
232 */
233 while (true) {
234 mutex_lock(&ad->mutex);
235 ret = regmap_read(ad->base_regmap, A2B_INTSRC, &val);
236 mutex_unlock(&ad->mutex);
237 if (ret) {
238 dev_err_ratelimited(
239 ad->dev,
240 "failed to read interrupt source: %d\n", ret);
241 break;
242 }
243
244 if (val & A2B_INTSRC_MSTINT_MASK)
245 hwirq = 0;
246 else if (val & A2B_INTSRC_SLVINT_MASK)
247 hwirq = (val & A2B_INTSRC_INODE_MASK) + 1;
248 else
249 break;
250
251 /*
252 * Pending interrupts are only cleared when reading the
253 * interrupt type. Normally this is done in the corresponding
254 * node's interrupt handler, but in case the interrupt is
255 * disabled, it has to be read here.
256 */
257 if (!(BIT(hwirq) & ad->irqs_enabled)) {
258 ret = ad24xx_i2c_get_inttype(&ad->a2b_bus, &val);
259 if (ret)
260 dev_err_ratelimited(
261 ad->dev,
262 "failed to read interrupt type: %d\n",
263 ret);
264 handled = true;
265 continue;
266 }
267
268 virq = irq_find_mapping(ad->irqdomain, hwirq);
269 if (!virq)
270 break;
271
> 272 handle_nested_irq(virq);
273 handled = true;
274 }
275
276 return handled ? IRQ_HANDLED : IRQ_NONE;
277 }
278
279 static void ad24xx_i2c_irq_enable(struct irq_data *irq_data)
280 {
> 281 struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
> 282 irq_hw_number_t hwirq = irq_data->hwirq;
283
284 ad->irqs_enabled |= BIT(hwirq);
285 }
286
287 static void ad24xx_i2c_irq_disable(struct irq_data *irq_data)
288 {
289 struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
290 irq_hw_number_t hwirq = irq_data->hwirq;
291
292 ad->irqs_enabled &= ~BIT(hwirq);
293 }
294
> 295 static const struct irq_chip ad24xx_i2c_irq_chip = {
296 .name = "ad24xx-i2c",
297 .irq_enable = ad24xx_i2c_irq_enable,
298 .irq_disable = ad24xx_i2c_irq_disable,
299 };
300
301 static int ad24xx_i2c_irqdomain_map(struct irq_domain *irqdomain,
302 unsigned int irq, irq_hw_number_t hwirq)
303 {
> 304 irq_set_chip_data(irq, irqdomain->host_data);
> 305 irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
> 306 irq_set_nested_thread(irq, 1);
> 307 irq_set_noprobe(irq);
308
309 return 0;
310 }
311

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2024-05-18 15:03:16

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 04/13] a2b: add AD24xx I2C interface driver

Hi Alvin,

kernel test robot noticed the following build errors:

[auto build test ERROR on c75962170e49f24399141276ae119e6a879f36dc]

url: https://github.com/intel-lab-lkp/linux/commits/Alvin-ipraga/a2b-add-A2B-driver-core/20240517-211849
base: c75962170e49f24399141276ae119e6a879f36dc
patch link: https://lore.kernel.org/r/20240517-a2b-v1-4-b8647554c67b%40bang-olufsen.dk
patch subject: [PATCH 04/13] a2b: add AD24xx I2C interface driver
config: i386-allmodconfig (https://download.01.org/0day-ci/archive/20240518/[email protected]/config)
compiler: gcc-13 (Ubuntu 13.2.0-4ubuntu3) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240518/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All error/warnings (new ones prefixed by >>):

drivers/a2b/ad24xx-i2c.c: In function 'ad24xx_i2c_irq_handler':
>> drivers/a2b/ad24xx-i2c.c:272:17: error: implicit declaration of function 'handle_nested_irq' [-Werror=implicit-function-declaration]
272 | handle_nested_irq(virq);
| ^~~~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c: In function 'ad24xx_i2c_irq_enable':
>> drivers/a2b/ad24xx-i2c.c:281:33: error: implicit declaration of function 'irq_data_get_irq_chip_data'; did you mean 'irq_domain_get_irq_data'? [-Werror=implicit-function-declaration]
281 | struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
| irq_domain_get_irq_data
>> drivers/a2b/ad24xx-i2c.c:281:33: warning: initialization of 'struct ad24xx_i2c *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
>> drivers/a2b/ad24xx-i2c.c:282:41: error: invalid use of undefined type 'struct irq_data'
282 | irq_hw_number_t hwirq = irq_data->hwirq;
| ^~
drivers/a2b/ad24xx-i2c.c: In function 'ad24xx_i2c_irq_disable':
drivers/a2b/ad24xx-i2c.c:289:33: warning: initialization of 'struct ad24xx_i2c *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
289 | struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:290:41: error: invalid use of undefined type 'struct irq_data'
290 | irq_hw_number_t hwirq = irq_data->hwirq;
| ^~
drivers/a2b/ad24xx-i2c.c: At top level:
>> drivers/a2b/ad24xx-i2c.c:295:21: error: variable 'ad24xx_i2c_irq_chip' has initializer but incomplete type
295 | static const struct irq_chip ad24xx_i2c_irq_chip = {
| ^~~~~~~~
>> drivers/a2b/ad24xx-i2c.c:296:10: error: 'const struct irq_chip' has no member named 'name'
296 | .name = "ad24xx-i2c",
| ^~~~
>> drivers/a2b/ad24xx-i2c.c:296:17: warning: excess elements in struct initializer
296 | .name = "ad24xx-i2c",
| ^~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:296:17: note: (near initialization for 'ad24xx_i2c_irq_chip')
>> drivers/a2b/ad24xx-i2c.c:297:10: error: 'const struct irq_chip' has no member named 'irq_enable'
297 | .irq_enable = ad24xx_i2c_irq_enable,
| ^~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:297:23: warning: excess elements in struct initializer
297 | .irq_enable = ad24xx_i2c_irq_enable,
| ^~~~~~~~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:297:23: note: (near initialization for 'ad24xx_i2c_irq_chip')
>> drivers/a2b/ad24xx-i2c.c:298:10: error: 'const struct irq_chip' has no member named 'irq_disable'
298 | .irq_disable = ad24xx_i2c_irq_disable,
| ^~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:298:24: warning: excess elements in struct initializer
298 | .irq_disable = ad24xx_i2c_irq_disable,
| ^~~~~~~~~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:298:24: note: (near initialization for 'ad24xx_i2c_irq_chip')
drivers/a2b/ad24xx-i2c.c: In function 'ad24xx_i2c_irqdomain_map':
>> drivers/a2b/ad24xx-i2c.c:304:9: error: implicit declaration of function 'irq_set_chip_data' [-Werror=implicit-function-declaration]
304 | irq_set_chip_data(irq, irqdomain->host_data);
| ^~~~~~~~~~~~~~~~~
>> drivers/a2b/ad24xx-i2c.c:305:9: error: implicit declaration of function 'irq_set_chip_and_handler' [-Werror=implicit-function-declaration]
305 | irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
| ^~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/a2b/ad24xx-i2c.c:305:61: error: 'handle_simple_irq' undeclared (first use in this function)
305 | irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
| ^~~~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c:305:61: note: each undeclared identifier is reported only once for each function it appears in
>> drivers/a2b/ad24xx-i2c.c:306:9: error: implicit declaration of function 'irq_set_nested_thread' [-Werror=implicit-function-declaration]
306 | irq_set_nested_thread(irq, 1);
| ^~~~~~~~~~~~~~~~~~~~~
>> drivers/a2b/ad24xx-i2c.c:307:9: error: implicit declaration of function 'irq_set_noprobe' [-Werror=implicit-function-declaration]
307 | irq_set_noprobe(irq);
| ^~~~~~~~~~~~~~~
drivers/a2b/ad24xx-i2c.c: At top level:
>> drivers/a2b/ad24xx-i2c.c:295:30: error: storage size of 'ad24xx_i2c_irq_chip' isn't known
295 | static const struct irq_chip ad24xx_i2c_irq_chip = {
| ^~~~~~~~~~~~~~~~~~~
cc1: some warnings being treated as errors


vim +/handle_nested_irq +272 drivers/a2b/ad24xx-i2c.c

218
219 static irqreturn_t ad24xx_i2c_irq_handler(int irq, void *data)
220 {
221 struct ad24xx_i2c *ad = data;
222 bool handled = false;
223 unsigned long hwirq;
224 unsigned int val;
225 unsigned int virq;
226 int ret;
227
228 /*
229 * The transceiver asserts the IRQ line as long as there are pending
230 * interrupts. Process them all here so that the interrupt can be
231 * configured with an edge trigger.
232 */
233 while (true) {
234 mutex_lock(&ad->mutex);
235 ret = regmap_read(ad->base_regmap, A2B_INTSRC, &val);
236 mutex_unlock(&ad->mutex);
237 if (ret) {
238 dev_err_ratelimited(
239 ad->dev,
240 "failed to read interrupt source: %d\n", ret);
241 break;
242 }
243
244 if (val & A2B_INTSRC_MSTINT_MASK)
245 hwirq = 0;
246 else if (val & A2B_INTSRC_SLVINT_MASK)
247 hwirq = (val & A2B_INTSRC_INODE_MASK) + 1;
248 else
249 break;
250
251 /*
252 * Pending interrupts are only cleared when reading the
253 * interrupt type. Normally this is done in the corresponding
254 * node's interrupt handler, but in case the interrupt is
255 * disabled, it has to be read here.
256 */
257 if (!(BIT(hwirq) & ad->irqs_enabled)) {
258 ret = ad24xx_i2c_get_inttype(&ad->a2b_bus, &val);
259 if (ret)
260 dev_err_ratelimited(
261 ad->dev,
262 "failed to read interrupt type: %d\n",
263 ret);
264 handled = true;
265 continue;
266 }
267
268 virq = irq_find_mapping(ad->irqdomain, hwirq);
269 if (!virq)
270 break;
271
> 272 handle_nested_irq(virq);
273 handled = true;
274 }
275
276 return handled ? IRQ_HANDLED : IRQ_NONE;
277 }
278
279 static void ad24xx_i2c_irq_enable(struct irq_data *irq_data)
280 {
> 281 struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
> 282 irq_hw_number_t hwirq = irq_data->hwirq;
283
284 ad->irqs_enabled |= BIT(hwirq);
285 }
286
287 static void ad24xx_i2c_irq_disable(struct irq_data *irq_data)
288 {
289 struct ad24xx_i2c *ad = irq_data_get_irq_chip_data(irq_data);
290 irq_hw_number_t hwirq = irq_data->hwirq;
291
292 ad->irqs_enabled &= ~BIT(hwirq);
293 }
294
> 295 static const struct irq_chip ad24xx_i2c_irq_chip = {
> 296 .name = "ad24xx-i2c",
> 297 .irq_enable = ad24xx_i2c_irq_enable,
> 298 .irq_disable = ad24xx_i2c_irq_disable,
299 };
300
301 static int ad24xx_i2c_irqdomain_map(struct irq_domain *irqdomain,
302 unsigned int irq, irq_hw_number_t hwirq)
303 {
> 304 irq_set_chip_data(irq, irqdomain->host_data);
> 305 irq_set_chip_and_handler(irq, &ad24xx_i2c_irq_chip, handle_simple_irq);
> 306 irq_set_nested_thread(irq, 1);
> 307 irq_set_noprobe(irq);
308
309 return 0;
310 }
311

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2024-05-19 06:45:20

by Markus Elfring

[permalink] [raw]
Subject: Re: [PATCH 00/13] Analog Devices Inc. Automotive Audio Bus (A2B) support


> A2B buses consist of a seires of daisy-chained A2B transceiver ICs known

> This is the main feature of A2B, whence the name Audio Bus. Each node


I suggest to avoid a few typos also in such a detailed cover letter.

Regards,
Markus

2024-05-19 07:16:14

by Markus Elfring

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core


> +++ b/drivers/a2b/a2b.c
> @@ -0,0 +1,1252 @@

> +#define __a2b_bus_for_each_node(__bus, __node, __i) \
> + for (__i = 0; __i < A2B_MAX_NODES && (__node = __bus->nodes[__i]); i++)


I suggest to reconsider the usage of double underscores for identifiers.
How do you think about to avoid that this software depends on undefined behaviour?
https://wiki.sei.cmu.edu/confluence/display/c/DCL37-C.+Do+not+declare+or+define+a+reserved+identifier

Regards,
Markus

2024-05-19 07:33:59

by Markus Elfring

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core


> +++ b/drivers/a2b/a2b.c
> @@ -0,0 +1,1252 @@

> +unsigned long a2b_bus_status(struct a2b_bus *bus)
> +{
> + unsigned long status;
> +
> + mutex_lock(&bus->mutex);
> + status = bus->status;
> + mutex_unlock(&bus->mutex);
> +
> + return status;
> +}


How do you think about to increase the application of scope-base resource management
also for such software components?
https://elixir.bootlin.com/linux/v6.9.1/source/include/linux/cleanup.h#L124

Regards,
Markus

2024-05-19 08:39:27

by Markus Elfring

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core


> +++ b/drivers/a2b/a2b.c
> @@ -0,0 +1,1252 @@

> +static int a2b_bus_of_add_node(struct a2b_bus *bus, struct device_node *np,
> + unsigned int addr)
> +{

> + node = kzalloc(sizeof(*node), GFP_KERNEL);
> + if (IS_ERR(node))
> + return -ENOMEM;

Please improve the distinction for checks according to the handling of error/null pointers.



> + ret = device_register(&node->dev);
> + if (ret)
> + goto err_put_device;
> +
> + return 0;
> +
> +err_put_device:
> + put_device(&node->dev);
> +
> + return ret;
> +}

Did you overlook to release the node memory after a failed function call
at such a source code place?

Regards,
Markus

2024-05-19 11:19:34

by Markus Elfring

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core


> +++ b/drivers/a2b/a2b.c
> @@ -0,0 +1,1252 @@

> +static int a2b_node_uevent(const struct device *dev,
> + struct kobj_uevent_env *env)
> +{

> + if (add_uevent_var(env, "A2B_NODE_VENDOR=%02x", node->vendor))
> + return -ENOMEM;

> + if (add_uevent_var(env, "A2B_NODE_VERSION=%02x", node->version))
> + return -ENOMEM;



I suggest to merge three if statements into one in a branch of
this function implementation.

Regards,
Markus

2024-05-19 11:40:48

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 03/13] dt-bindings: a2b: Analog Devices AD24xx devices

On 17/05/2024 14:58, Alvin Šipraga wrote:
> From: Alvin Šipraga <[email protected]>
>
> Add device tree bindings for the AD24xx series A2B transceiver chips,
> including their functional blocks.
>
> Signed-off-by: Alvin Šipraga <[email protected]>
> ---
> .../devicetree/bindings/a2b/adi,ad24xx-clk.yaml | 53 +++++

What is a2b and why clock bindings are not in clock?

> .../devicetree/bindings/a2b/adi,ad24xx-codec.yaml | 52 +++++
> .../devicetree/bindings/a2b/adi,ad24xx-gpio.yaml | 76 +++++++
> .../devicetree/bindings/a2b/adi,ad24xx-i2c.yaml | 55 +++++
> .../devicetree/bindings/a2b/adi,ad24xx.yaml | 253 +++++++++++++++++++++

Sorry, all this looks weirdly placed.

> 5 files changed, 489 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml
> new file mode 100644
> index 000000000000..819efaa6a3f9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml
> @@ -0,0 +1,53 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/a2b/adi,ad24xx-clk.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices Inc. AD24xx clock functional block
> +
> +maintainers:
> + - Alvin Šipraga <[email protected]>
> +
> +allOf:
> + - $ref: /schemas/clock/clock.yaml

Drop. There is no single binding doing this, which is usually a hint you
do something not correct.

> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad2420-clk
> + - adi,ad2421-clk
> + - adi,ad2422-clk
> + - adi,ad2425-clk
> + - adi,ad2426-clk
> + - adi,ad2427-clk
> + - adi,ad2428-clk
> + - adi,ad2429-clk
> +

This is just incomplete. See other bindings how clock controller is written.

> +required:
> + - compatible
> + - clock-output-names
> +
> +unevaluatedProperties: false

additionalProperties: false
> +
> +examples:
> + - |
> + a2b {
> + #address-cells = <1>;
> + #size-cells = <0>;

Not related, drop entire node.

> +
> + node@1 {
> + compatible = "adi,ad2425-node";

Not related, drop entire node.

> + reg = <1>;
> + interrupts = <1>;
> + adi,tdm-mode = <16>;
> + adi,tdm-slot-size = <32>;
> +
> + clock {
> + compatible = "adi,ad2425-clk";
> + #clock-cells = <1>;
> + clock-indices = <1>;
> + clock-output-names = "A2B1 CLKOUT2";
> + };
> + };
> + };
> diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml
> new file mode 100644
> index 000000000000..eee12f1c810e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml
> @@ -0,0 +1,52 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/a2b/adi,ad24xx-codec.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices Inc. AD24xx I2S/TDM functional block
> +
> +maintainers:
> + - Alvin Šipraga <[email protected]>
> +
> +allOf:
> + - $ref: /schemas/sound/dai-common.yaml#

Why full path? It's the same directory, isn't it?

> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad2403-codec
> + - adi,ad2410-codec
> + - adi,ad2425-codec
> + - adi,ad2428-codec
> + - adi,ad2429-codec
> +
> + '#sound-dai-cells':
> + const: 0
> +
> +required:
> + - compatible
> + - '#sound-dai-cells'
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + a2b {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + node@2 {
> + compatible = "adi,ad2428-node";
> + reg = <2>;
> + interrupts = <2>;
> + adi,tdm-mode = <8>;
> + adi,tdm-slot-size = <32>;

Same comments. Limited review follows.


..


> +
> +required:
> + - compatible
> +
> +unevaluatedProperties: false

Sorry, but not. No resources, nothing here. Do not create bindings just
to instantiate drivers.


..

> diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> new file mode 100644
> index 000000000000..dcda15e8032a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> @@ -0,0 +1,253 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/a2b/adi,ad24xx.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices Inc. AD24xx Automotive Audio Bus A2B Transceiver
> +
> +description: |
> + AD24xx chips provide A2B bus functionality together with several peripheral

What is A2B?

> + functions, including GPIO, I2S/TDM, an I2C controller interface, and
> + programmable clock outputs.
> +
> +maintainers:
> + - Alvin Šipraga <[email protected]>
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad2403
> + - adi,ad2410
> + - adi,ad2425
> + - adi,ad2428
> + - adi,ad2429
> +

reg: is second property.

> + reg-names:
> + items:
> + - const: base
> + - const: bus
> +
> + reg:
> + items:
> + - description: Normal I2C address of the chip
> + - description: Auxiliary BUS_ADDR I2C address of the chip
> +
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0
> +
> + clock-names:
> + items:
> + - const: sync

Again misordered. -names always follow main entry. Anyway, clock-names
for just one entry is not really useful.

> +
> + clocks:
> + items:
> + - description: SYNC input pin clock source
> +
> + vin-supply:
> + description: Optional regulator for supply voltage to VIN pin
> +
> + bus-supply:
> + description: Optional regulator for out-of-band supply voltage to
> + subodrinate nodes' VIN pins
> +
> + interrupts: true

??? This must be specific.

> +
> + interrupt-controller: true
> +
> + '#interrupt-cells':
> + const: 1
> +
> +patternProperties:
> + '^node@[0-9]+$':
> + type: object
> + unevaluatedProperties: false

Why? This must be additionalProperties: false, or I miss something...

> +
> + properties:
> + compatible:
> + enum:
> + - adi,ad2401-node
> + - adi,ad2402-node
> + - adi,ad2403-node
> + - adi,ad2410-node
> + - adi,ad2420-node
> + - adi,ad2421-node
> + - adi,ad2422-node
> + - adi,ad2425-node
> + - adi,ad2426-node
> + - adi,ad2427-node
> + - adi,ad2428-node
> + - adi,ad2429-node
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + interrupt-controller: true
> +
> + '#interrupt-cells':
> + const: 1
> +
> + gpio:
> + $ref: adi,ad24xx-gpio.yaml#
> +
> + codec:
> + $ref: adi,ad24xx-codec.yaml#
> +
> + i2c:
> + $ref: adi,ad24xx-i2c.yaml#
> +
> + clock:
> + $ref: adi,ad24xx-clk.yaml#
> +
> + adi,tdm-mode:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: TDM mode

Please do not add descriptions which are copies of property name. You
basically said ZERO here. Say something useful...

> + enum: [2, 4, 8, 12, 16, 20, 24, 32]
> +
> + adi,tdm-slot-size:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: TDM slot size
> + enum: [16, 32]
> +
> + adi,invert-sync:
> + description: Falling edge of SYNC pin indicates the start of an audio
> + frame, as opposed to rising edge.
> + type: boolean
> +
> + adi,early-sync:
> + description: The SYNC pin changes one cycle before the MSB of the first
> + data slot.
> + type: boolean
> +
> + adi,alternating-sync:
> + description: Drive SYNC pin during first half of I2S/TDM data
> + transmission rather than just pulsing it for once cycle.
> + type: boolean
> +
> + adi,rx-on-dtx1:
> + description: Use the DTX1 pin for I2S/TDM RX in place of the DRX1 pin.
> + type: boolean
> +
> + adi,a2b-external-switch-mode-1:
> + description: Use external switch mode 1 instead of 0 on the assumption
> + that the downstream node is not using A2B bus power.
> + type: boolean
> +
> + adi,drive-strength:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Configures drive strength low (0) or high (1, default).
> + enum: [0, 1]
> + default: 1
> +
> + adi,invert-interrupt:
> + description: Invert polarity of IRQ pin, making it active low.
> + type: boolean
> +
> + adi,tristate-interrupt:
> + description: Rather than always actively driving the IRQ pin, only drive
> + when the interrupt is active and otherwise set to tristate (high-Z).
> + type: boolean

It looks you put all children properties into parent node... With so
little explanation tricky to judge.

> +
> + required:
> + - compatible
> + - reg
> + - adi,tdm-mode
> + - adi,tdm-slot-size
> +
> + dependencies:
> + interrupt-controller: [ '#interrupt-cells' ]
> + '#interrupt-cells': [ interrupt-controller ]
> +
> +required:
> + - compatible
> + - reg-names
> + - reg
> + - clock-names
> + - clocks
> + - '#address-cells'
> + - '#size-cells'
> + - interrupts
> + - interrupt-controller
> + - '#interrupt-cells'
> + - node@0
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + sync_clk: sync-clock {

Drop, not related.

> + compatible = "fixed-clock";
> + #clock-cells = <0>;
> + clock-frequency = <48000>;
> + };
> +
> + i2c {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + a2b@68 {
> + compatible = "adi,ad2428";
> + reg-names = "base", "bus";
> + reg = <0x68>, <0x69>;


Please follow DTS coding style. Do not introduce entire different style
and order of properties. reg-names IS NEVER the second property.



Best regards,
Krzysztof


2024-05-19 11:41:05

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 10/13] dt-bindings: vendor-prefixes: add Bang & Olufsen a/s

On 17/05/2024 15:02, Alvin Šipraga wrote:
> From: Alvin Šipraga <[email protected]>
>
> Bang & Olufsen a/s is a Danish designer and manufacturer of high-end
> consumer audio and home entertainment products.
>
> The vendor prefix 'beo,' follows from the ubiquitous product naming
> scheme, e.g. Beosound Balance, Beolab 28.
>
> https://www.bang-olufsen.com/
>
> Signed-off-by: Alvin Šipraga <[email protected]>
> ---
> Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> index fbf47f0bacf1..470ed53de8f1 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> @@ -208,6 +208,8 @@ patternProperties:
> description: Compass Electronics Group, LLC
> "^beagle,.*":
> description: BeagleBoard.org Foundation
> + "^beo,.*":

Keep order.

Best regards,
Krzysztof


2024-05-19 11:42:12

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 11/13] dt-bindings: a2b: add compatible string for Beosound Shape node

On 17/05/2024 15:02, Alvin Šipraga wrote:
> From: Alvin Šipraga <[email protected]>
>
> The Beosound Shape has the same device tree bindings as an AD2425, so it
> is sufficient to just add an entry to the compatible enum.

? If it has the same, then devices are compatible but your binding did
not express it.

>
> Signed-off-by: Alvin Šipraga <[email protected]>
> ---
> Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> index dcda15e8032a..bea29f88d535 100644
> --- a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> @@ -81,6 +81,7 @@ patternProperties:
> - adi,ad2427-node
> - adi,ad2428-node
> - adi,ad2429-node
> + - beo,shape-node

You just added this binding. Add entire binding in one patch.

Best regards,
Krzysztof


2024-05-19 11:44:22

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 03/13] dt-bindings: a2b: Analog Devices AD24xx devices

On 19/05/2024 13:40, Krzysztof Kozlowski wrote:
> On 17/05/2024 14:58, Alvin Šipraga wrote:
>> From: Alvin Šipraga <[email protected]>
>>
>> Add device tree bindings for the AD24xx series A2B transceiver chips,
>> including their functional blocks.
>>
>> Signed-off-by: Alvin Šipraga <[email protected]>
>> ---
>> .../devicetree/bindings/a2b/adi,ad24xx-clk.yaml | 53 +++++
>
> What is a2b and why clock bindings are not in clock?

Just in case you reply "but I have cover letter", so no, it does not
matter really. This is the patch describing hardware, so here you
describe hardware. Not in cover letter which often is ignored. Many
people, including myself, skip cover letters.

Provide *hardware* description here.

Best regards,
Krzysztof


2024-05-21 06:27:38

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 02/13] regmap: add A2B support

On Fri, May 17, 2024 at 03:42:31PM GMT, Mark Brown wrote:
> On Fri, May 17, 2024 at 02:58:00PM +0200, Alvin Šipraga wrote:
>
> > +static int regmap_a2b_write(void *context, const void *data, size_t count)
> > +{
>
> > + for (i = 0; i < count - 1; i++) {
> > + ret = bus->ops->write(bus, node, reg + i, d[i + 1]);
> > + if (ret)
> > + return ret;
> > + }
>
> Just force single_read and single_write

Ah OK, i.e. return -EINVAL if (!config->use_single_read ||
!config->use_single_write) in the init functions below?

> (looks like you'll need to add
> the hook for the bus there).

I am not sure what you mean by this, can you elaborate?

>
> > +struct regmap *__devm_regmap_init_a2b_node(struct a2b_node *node,
> > + const struct regmap_config *config,
> > + struct lock_class_key *lock_key,
> > + const char *lock_name)
> > +{
> > + return __devm_regmap_init(&node->dev, &regmap_a2b, node, config,
> > + lock_key, lock_name);
> > +}
> > +EXPORT_SYMBOL_GPL(__devm_regmap_init_a2b_node);
>
> Should there be validation of val_bits?

Yes, I can and should enforce reg/val bits to always be 8.

2024-05-21 06:46:40

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 07/13] ASoC: codecs: add AD24xx codec driver

On Fri, May 17, 2024 at 04:03:50PM GMT, Mark Brown wrote:
> On Fri, May 17, 2024 at 02:58:05PM +0200, Alvin Šipraga wrote:
>
> > +++ b/sound/soc/codecs/ad24xx-codec.c
> > @@ -0,0 +1,665 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * AD24xx codec driver
>
> Please make the whole comment a C++ comment.

OK

>
> > +static const char *const ad24xx_codec_slot_size_text[] = {
> > + "8 bits", "12 bits", "16 bits", "20 bits",
> > + "24 bits", "28 bits", "32 bits",
> > +};
>
> Why is this configured by the user rather than via set_tdm_slot(), and
> how would one usefully use this at runtime?

This configures the slot size of A2B data slots, not the slot size on
the TDM interface. Typically one would expect it to be the same, so your
question is valid. But it is not a strict requirement as far as the A2B
bus and hardware is concerned.

To give a concrete example, the TDM interface might run with a TDM slot
size of 32 bits, but the PCM data is in reality 24 bits padded to 32
bits. In this case, A2B bus bandwidth can be saved by configuring the
"{Up,Down}stream Slot Size" kcontrol to "24 bits".

More detailed information can be found in the manual in [1] section 3-22
"I2S/TDM Port Programming Concepts", where an analogous example is
given.

>
> > +static int ad24xx_codec_slot_config_put(struct snd_kcontrol *kcontrol,
> > + struct snd_ctl_elem_value *ucontrol)
> > +{
>
> > + } else if (priv == &ad24xx_codec_up_slot_format_enum ||
> > + priv == &ad24xx_codec_dn_slot_format_enum) {
> > + if (val >= ARRAY_SIZE(ad24xx_codec_slot_format_text))
> > + return -EINVAL;
> > + slot_config->format[direction] = val;
> > + } else
> > + return -ENOENT;
>
> If one side has {} both sides should, see coding-style.rst.

OK

>
> > +
> > + return 0;
> > +}
>
> This won't flag changes by returning 1 which will mean no events are
> generated and break some UIs. Please show the output of the mixer-test
> selftest on new submissions, it will check for this and other issues.

OK, I will have a go. Thanks!

>
> > + /* Main node must be BCLK/FSYNC consumer, subordinate node provider */
> > + if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) !=
> > + (is_a2b_main(adc->node) ? SND_SOC_DAIFMT_CBC_CFC :
> > + SND_SOC_DAIFMT_CBP_CFP))
> > + return -EINVAL;
>
> Please don't use the ternery operator like this, it just makes things
> harder to read.
>
> > + val = bclk_invert ? A2B_I2SCFG_RXBCLKINV_MASK :
> > + A2B_I2SCFG_TXBCLKINV_MASK;
>
> Similarly, please use normal conditional statements.

OK to both.

>
> > +static int ad24xx_codec_hw_params(struct snd_pcm_substream *substream,
> > + struct snd_pcm_hw_params *params,
> > + struct snd_soc_dai *dai)
>
> > +
> > + /* Finally, request slots */
> > + ret = a2b_node_request_slots(adc->node, &slot_req);
> > + if (ret)
> > + return ret;
>
> Note that hw_params() can be called multiple times before starting the
> audio stream, will this leak?

I will take another look before sending v2.

>
> > + struct snd_soc_dai *dai)
> > +{
> > + struct snd_soc_component *component = dai->component;
> > + struct ad24xx_codec *adc = snd_soc_component_get_drvdata(component);
> > + int ret;
> > +
> > + ret = a2b_node_free_slots(adc->node);
> > + if (ret)
> > + return ret;
>
> What if we close without having called hw_params()?

Ditto.

>
> > +static const struct snd_soc_dai_driver ad24xx_codec_dai_drv[] = {
> > + [AD24XX_DAI_I2S] = {
> > + .name = "ad24xx-i2s",
> > + .playback = {
> > + .stream_name = "I2S Playback",
> > + .channels_min = 1,
> > + .channels_max = 32,
> > + },
> > + .capture = {
> > + .stream_name = "I2S Capture",
> > + .channels_min = 1,
> > + .channels_max = 32,
> > + },
> > + .ops = &ad24xx_codec_dai_ops,
> > + .symmetric_rate = 1,
> > + },
> > +};
>
> Why is this an array?

It needn't be, will flatten it.

>
> > +static const struct regmap_config ad24xx_codec_regmap_config = {
> > + .reg_bits = 8,
> > + .val_bits = 8,
> > + .cache_type = REGCACHE_RBTREE,
> > +};
>
> New code should use _MAPLE unless there's a strong reason to use
> something else.

OK

2024-05-21 07:08:31

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 00/13] Analog Devices Inc. Automotive Audio Bus (A2B) support

On Fri, May 17, 2024 at 04:57:57PM GMT, Wolfram Sang wrote:
>
> > On subordinate nodes the I2C interface functions in controller
> > (master) mode, providing an additional I2C adapter to the host for
> > each subordinate node connected to the A2B bus.
>
> I am not sure I got this right? That would mean for an I2C adapter on a
> subordinate node, that there might be targets connected to only that
> subordinate node? How do its messages go to the host machine? Is I2C
> encapsulated over I2C? I probably missed something.
>

Yes, each subordinate node exposes a whole discrete I2C segment, so you
can have two targets with the same address connected to two different
subordinate nodes respectively. Hence a new i2c_adapter for each
subordinate node.

The way it works is that the main node responds on two I2C addresses,
typically 0x68 and 0x69, labelled "BASE" and "BUS" respectively. BASE
always addresses the register map of the main node. BUS is a "proxy" and
addresses either the register map of a subordinate node, or a given
peripheral connected to the targetted subordinate node. This is
configured in some register fields of the main/subordinate nodes.

When addressing a target connected to the I2C controller of a
subordinate node, a register field of the given subordinate node must be
populated with the target's I2C address. The hardware will then proxy
I2C messages towards the main node's BUS address over the A2B bus
through to the selected subordinate node's I2C controller interface, and
will rewrite the address (BUS, 0x69) to whatever target address was
programmed into that subordinate node. The I2C interface of the main
node performs clock stretching so that the transfer is synchronous and
transparent to the host.

You can see the implementation details in the ad24xx-i2c.c "interface
driver":

(a) main/subordinate node register map access: __ad24xx_i2c_read() and
__ad24xx_i2c_write()

(b) subordinate node I2C target access: __ad24xx_i2c_xfer()

A more detailed description of the logic can be found in [1] section 3-1
"I2C Port Programming Concepts":

[1] https://www.analog.com/media/en/technical-documentation/user-guides/ad242x-trm.pdf

Hope that clears it up.

2024-05-21 07:08:59

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 07/13] ASoC: codecs: add AD24xx codec driver

On Tue, May 21, 2024 at 08:46:19AM GMT, Alvin Šipraga wrote:
> More detailed information can be found in the manual in [1] section 3-22
> "I2S/TDM Port Programming Concepts", where an analogous example is
> given.

Forgot to give the link:

[1] https://www.analog.com/media/en/technical-documentation/user-guides/ad242x-trm.pdf

2024-05-21 07:12:01

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core

On Sun, May 19, 2024 at 10:38:25AM GMT, Markus Elfring wrote:
> [Some people who received this message don't often get email from [email protected]. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>
> …
> > +++ b/drivers/a2b/a2b.c
> > @@ -0,0 +1,1252 @@
> …
> > +static int a2b_bus_of_add_node(struct a2b_bus *bus, struct device_node *np,
> > + unsigned int addr)
> > +{
> …
> > + node = kzalloc(sizeof(*node), GFP_KERNEL);
> > + if (IS_ERR(node))
> > + return -ENOMEM;
>
> Please improve the distinction for checks according to the handling of error/null pointers.

Right, I think it returns NULL on error. Thanks!

>
>
> …
> > + ret = device_register(&node->dev);
> > + if (ret)
> > + goto err_put_device;
> > +
> > + return 0;
> > +
> > +err_put_device:
> > + put_device(&node->dev);
> > +
> > + return ret;
> > +}
>
> Did you overlook to release the node memory after a failed function call
> at such a source code place?

I think this is correct, per the comment to device_register():

| * NOTE: _Never_ directly free @dev after calling this function, even
| * if it returned an error! Always use put_device() to give up the
| * reference initialized in this function instead.

or?

2024-05-21 07:13:05

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 11/13] dt-bindings: a2b: add compatible string for Beosound Shape node

On Sun, May 19, 2024 at 01:41:48PM GMT, Krzysztof Kozlowski wrote:
> On 17/05/2024 15:02, Alvin Šipraga wrote:
> > From: Alvin Šipraga <[email protected]>
> >
> > The Beosound Shape has the same device tree bindings as an AD2425, so it
> > is sufficient to just add an entry to the compatible enum.
>
> ? If it has the same, then devices are compatible but your binding did
> not express it.

OK, you're basically saying I should add it all in one patch?

>
> >
> > Signed-off-by: Alvin Šipraga <[email protected]>
> > ---
> > Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml | 1 +
> > 1 file changed, 1 insertion(+)
> >
> > diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> > index dcda15e8032a..bea29f88d535 100644
> > --- a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> > +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> > @@ -81,6 +81,7 @@ patternProperties:
> > - adi,ad2427-node
> > - adi,ad2428-node
> > - adi,ad2429-node
> > + - beo,shape-node
>
> You just added this binding. Add entire binding in one patch.

i.e. this?

Thanks

2024-05-21 07:24:25

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 03/13] dt-bindings: a2b: Analog Devices AD24xx devices

On Sun, May 19, 2024 at 01:40:25PM GMT, Krzysztof Kozlowski wrote:
> On 17/05/2024 14:58, Alvin Šipraga wrote:
> > From: Alvin Šipraga <[email protected]>
> >
> > Add device tree bindings for the AD24xx series A2B transceiver chips,
> > including their functional blocks.
> >
> > Signed-off-by: Alvin Šipraga <[email protected]>
> > ---
> > .../devicetree/bindings/a2b/adi,ad24xx-clk.yaml | 53 +++++
>
> What is a2b and why clock bindings are not in clock?
>
> > .../devicetree/bindings/a2b/adi,ad24xx-codec.yaml | 52 +++++
> > .../devicetree/bindings/a2b/adi,ad24xx-gpio.yaml | 76 +++++++
> > .../devicetree/bindings/a2b/adi,ad24xx-i2c.yaml | 55 +++++
> > .../devicetree/bindings/a2b/adi,ad24xx.yaml | 253 +++++++++++++++++++++
>
> Sorry, all this looks weirdly placed.

Alright, I'll move the bindings to their respective directories. Wasn't
sure what is preferred.

>
> > 5 files changed, 489 insertions(+)
> >
> > diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml
> > new file mode 100644
> > index 000000000000..819efaa6a3f9
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-clk.yaml
> > @@ -0,0 +1,53 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/a2b/adi,ad24xx-clk.yaml
> > +$schema: http://devicetree.org/meta-schemas/core.yaml
> > +
> > +title: Analog Devices Inc. AD24xx clock functional block
> > +
> > +maintainers:
> > + - Alvin Šipraga <[email protected]>
> > +
> > +allOf:
> > + - $ref: /schemas/clock/clock.yaml
>
> Drop. There is no single binding doing this, which is usually a hint you
> do something not correct.
>
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - adi,ad2420-clk
> > + - adi,ad2421-clk
> > + - adi,ad2422-clk
> > + - adi,ad2425-clk
> > + - adi,ad2426-clk
> > + - adi,ad2427-clk
> > + - adi,ad2428-clk
> > + - adi,ad2429-clk
> > +
>
> This is just incomplete. See other bindings how clock controller is written.

OK, will review.

>
> > +required:
> > + - compatible
> > + - clock-output-names
> > +
> > +unevaluatedProperties: false
>
> additionalProperties: false

OK

> > +
> > +examples:
> > + - |
> > + a2b {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
>
> Not related, drop entire node.

OK

>
> > +
> > + node@1 {
> > + compatible = "adi,ad2425-node";
>
> Not related, drop entire node.

OK

>
> > + reg = <1>;
> > + interrupts = <1>;
> > + adi,tdm-mode = <16>;
> > + adi,tdm-slot-size = <32>;
> > +
> > + clock {
> > + compatible = "adi,ad2425-clk";
> > + #clock-cells = <1>;
> > + clock-indices = <1>;
> > + clock-output-names = "A2B1 CLKOUT2";
> > + };
> > + };
> > + };
> > diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml
> > new file mode 100644
> > index 000000000000..eee12f1c810e
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx-codec.yaml
> > @@ -0,0 +1,52 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/a2b/adi,ad24xx-codec.yaml
> > +$schema: http://devicetree.org/meta-schemas/core.yaml
> > +
> > +title: Analog Devices Inc. AD24xx I2S/TDM functional block
> > +
> > +maintainers:
> > + - Alvin Šipraga <[email protected]>
> > +
> > +allOf:
> > + - $ref: /schemas/sound/dai-common.yaml#
>
> Why full path? It's the same directory, isn't it?

In this case no, but when I move it into sound, yes. So your comment is
acknowledged and will be addressed in v2.

>
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - adi,ad2403-codec
> > + - adi,ad2410-codec
> > + - adi,ad2425-codec
> > + - adi,ad2428-codec
> > + - adi,ad2429-codec
> > +
> > + '#sound-dai-cells':
> > + const: 0
> > +
> > +required:
> > + - compatible
> > + - '#sound-dai-cells'
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > + - |
> > + a2b {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + node@2 {
> > + compatible = "adi,ad2428-node";
> > + reg = <2>;
> > + interrupts = <2>;
> > + adi,tdm-mode = <8>;
> > + adi,tdm-slot-size = <32>;
>
> Same comments. Limited review follows.

Ack

>
>
> ...
>
>
> > +
> > +required:
> > + - compatible
> > +
> > +unevaluatedProperties: false
>
> Sorry, but not. No resources, nothing here. Do not create bindings just
> to instantiate drivers.

Do you mean that there is no need to introduce a binding for this codec
if it has the same bindings as dai-common.yaml?

Basically that is the case, but #sound-dai-cells should be <0>. Is that
not enough?

I am OK to just drop the binding if you think so, but I would think that
the compatible string should be somewhere in the bindings. Could you
explain a little more what you mean?

>
>
> ...
>
> > diff --git a/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> > new file mode 100644
> > index 000000000000..dcda15e8032a
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/a2b/adi,ad24xx.yaml
> > @@ -0,0 +1,253 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/a2b/adi,ad24xx.yaml
> > +$schema: http://devicetree.org/meta-schemas/core.yaml
> > +
> > +title: Analog Devices Inc. AD24xx Automotive Audio Bus A2B Transceiver
> > +
> > +description: |
> > + AD24xx chips provide A2B bus functionality together with several peripheral
>
> What is A2B?

I will improve the description per your review comments.

>
> > + functions, including GPIO, I2S/TDM, an I2C controller interface, and
> > + programmable clock outputs.
> > +
> > +maintainers:
> > + - Alvin Šipraga <[email protected]>
> > +
> > +properties:
> > + compatible:
> > + enum:
> > + - adi,ad2403
> > + - adi,ad2410
> > + - adi,ad2425
> > + - adi,ad2428
> > + - adi,ad2429
> > +
>
> reg: is second property.

Ack

>
> > + reg-names:
> > + items:
> > + - const: base
> > + - const: bus
> > +
> > + reg:
> > + items:
> > + - description: Normal I2C address of the chip
> > + - description: Auxiliary BUS_ADDR I2C address of the chip
> > +
> > + '#address-cells':
> > + const: 1
> > +
> > + '#size-cells':
> > + const: 0
> > +
> > + clock-names:
> > + items:
> > + - const: sync
>
> Again misordered. -names always follow main entry. Anyway, clock-names
> for just one entry is not really useful.

Ack

>
> > +
> > + clocks:
> > + items:
> > + - description: SYNC input pin clock source
> > +
> > + vin-supply:
> > + description: Optional regulator for supply voltage to VIN pin
> > +
> > + bus-supply:
> > + description: Optional regulator for out-of-band supply voltage to
> > + subodrinate nodes' VIN pins
> > +
> > + interrupts: true
>
> ??? This must be specific.

Right, it should be:

maxItems: 1

That's specific, right?

>
> > +
> > + interrupt-controller: true
> > +
> > + '#interrupt-cells':
> > + const: 1
> > +
> > +patternProperties:
> > + '^node@[0-9]+$':
> > + type: object
> > + unevaluatedProperties: false
>
> Why? This must be additionalProperties: false, or I miss something...

I think you are right, but I will review this before sending v2. Thanks.

>
> > +
> > + properties:
> > + compatible:
> > + enum:
> > + - adi,ad2401-node
> > + - adi,ad2402-node
> > + - adi,ad2403-node
> > + - adi,ad2410-node
> > + - adi,ad2420-node
> > + - adi,ad2421-node
> > + - adi,ad2422-node
> > + - adi,ad2425-node
> > + - adi,ad2426-node
> > + - adi,ad2427-node
> > + - adi,ad2428-node
> > + - adi,ad2429-node
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > + interrupt-controller: true
> > +
> > + '#interrupt-cells':
> > + const: 1
> > +
> > + gpio:
> > + $ref: adi,ad24xx-gpio.yaml#
> > +
> > + codec:
> > + $ref: adi,ad24xx-codec.yaml#
> > +
> > + i2c:
> > + $ref: adi,ad24xx-i2c.yaml#
> > +
> > + clock:
> > + $ref: adi,ad24xx-clk.yaml#
> > +
> > + adi,tdm-mode:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: TDM mode
>
> Please do not add descriptions which are copies of property name. You
> basically said ZERO here. Say something useful...

Ack

>
> > + enum: [2, 4, 8, 12, 16, 20, 24, 32]
> > +
> > + adi,tdm-slot-size:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: TDM slot size
> > + enum: [16, 32]
> > +
> > + adi,invert-sync:
> > + description: Falling edge of SYNC pin indicates the start of an audio
> > + frame, as opposed to rising edge.
> > + type: boolean
> > +
> > + adi,early-sync:
> > + description: The SYNC pin changes one cycle before the MSB of the first
> > + data slot.
> > + type: boolean
> > +
> > + adi,alternating-sync:
> > + description: Drive SYNC pin during first half of I2S/TDM data
> > + transmission rather than just pulsing it for once cycle.
> > + type: boolean
> > +
> > + adi,rx-on-dtx1:
> > + description: Use the DTX1 pin for I2S/TDM RX in place of the DRX1 pin.
> > + type: boolean
> > +
> > + adi,a2b-external-switch-mode-1:
> > + description: Use external switch mode 1 instead of 0 on the assumption
> > + that the downstream node is not using A2B bus power.
> > + type: boolean
> > +
> > + adi,drive-strength:
> > + $ref: /schemas/types.yaml#/definitions/uint32
> > + description: Configures drive strength low (0) or high (1, default).
> > + enum: [0, 1]
> > + default: 1
> > +
> > + adi,invert-interrupt:
> > + description: Invert polarity of IRQ pin, making it active low.
> > + type: boolean
> > +
> > + adi,tristate-interrupt:
> > + description: Rather than always actively driving the IRQ pin, only drive
> > + when the interrupt is active and otherwise set to tristate (high-Z).
> > + type: boolean
>
> It looks you put all children properties into parent node... With so
> little explanation tricky to judge.

I will put some more information into the binding so that it is more
understandable without reading the cover letter. Hopefully things will
be clearer for the next review and you can reconsider.

>
> > +
> > + required:
> > + - compatible
> > + - reg
> > + - adi,tdm-mode
> > + - adi,tdm-slot-size
> > +
> > + dependencies:
> > + interrupt-controller: [ '#interrupt-cells' ]
> > + '#interrupt-cells': [ interrupt-controller ]
> > +
> > +required:
> > + - compatible
> > + - reg-names
> > + - reg
> > + - clock-names
> > + - clocks
> > + - '#address-cells'
> > + - '#size-cells'
> > + - interrupts
> > + - interrupt-controller
> > + - '#interrupt-cells'
> > + - node@0
> > +
> > +unevaluatedProperties: false
> > +
> > +examples:
> > + - |
> > + sync_clk: sync-clock {
>
> Drop, not related.

If the clock is required (as it is) then I have to reference some
phandle in the example, else the example will fail the check (missing
required property 'clocks'). That's why I put it here. Please advise.

>
> > + compatible = "fixed-clock";
> > + #clock-cells = <0>;
> > + clock-frequency = <48000>;
> > + };
> > +
> > + i2c {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + a2b@68 {
> > + compatible = "adi,ad2428";
> > + reg-names = "base", "bus";
> > + reg = <0x68>, <0x69>;
>
>
> Please follow DTS coding style. Do not introduce entire different style
> and order of properties. reg-names IS NEVER the second property.

OK

2024-05-21 07:34:21

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 11/13] dt-bindings: a2b: add compatible string for Beosound Shape node

On 21/05/2024 09:12, Alvin Šipraga wrote:
> On Sun, May 19, 2024 at 01:41:48PM GMT, Krzysztof Kozlowski wrote:
>> On 17/05/2024 15:02, Alvin Šipraga wrote:
>>> From: Alvin Šipraga <[email protected]>
>>>
>>> The Beosound Shape has the same device tree bindings as an AD2425, so it
>>> is sufficient to just add an entry to the compatible enum.
>>
>> ? If it has the same, then devices are compatible but your binding did
>> not express it.
>
> OK, you're basically saying I should add it all in one patch?

No, I said that your commit msg suggests they are compatible. I don't
fully understand what you wanted to say by "same device tree bindings".

But anyway, make it one patch.

Best regards,
Krzysztof


2024-05-21 07:34:32

by Markus Elfring

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core

>> …
>>> +++ b/drivers/a2b/a2b.c
>>> @@ -0,0 +1,1252 @@
>> …
>>> +static int a2b_bus_of_add_node(struct a2b_bus *bus, struct device_node *np,
>>> + unsigned int addr)
>>> +{
>> …
>>> + node = kzalloc(sizeof(*node), GFP_KERNEL);
>>> + if (IS_ERR(node))
>>> + return -ENOMEM;
>>
>> Please improve the distinction for checks according to the handling of error/null pointers.
>
> Right, I think it returns NULL on error.

Do you see possibilities to reduce “confusion” about the properties of such a programming interface
any further?
https://elixir.bootlin.com/linux/v6.9.1/A/ident/kzalloc

Regards,
Markus

2024-05-21 07:37:49

by Greg Kroah-Hartman

[permalink] [raw]
Subject: Re: [PATCH 01/13] a2b: add A2B driver core

On Tue, May 21, 2024 at 09:33:51AM +0200, Markus Elfring wrote:
> >> …
> >>> +++ b/drivers/a2b/a2b.c
> >>> @@ -0,0 +1,1252 @@
> >> …
> >>> +static int a2b_bus_of_add_node(struct a2b_bus *bus, struct device_node *np,
> >>> + unsigned int addr)
> >>> +{
> >> …
> >>> + node = kzalloc(sizeof(*node), GFP_KERNEL);
> >>> + if (IS_ERR(node))
> >>> + return -ENOMEM;
> >>
> >> Please improve the distinction for checks according to the handling of error/null pointers.
> >
> > Right, I think it returns NULL on error.
>
> Do you see possibilities to reduce “confusion” about the properties of such a programming interface
> any further?
> https://elixir.bootlin.com/linux/v6.9.1/A/ident/kzalloc


Hi,

This is the semi-friendly patch-bot of Greg Kroah-Hartman.

Markus, you seem to have sent a nonsensical or otherwise pointless
review comment to a patch submission on a Linux kernel developer mailing
list. I strongly suggest that you not do this anymore. Please do not
bother developers who are actively working to produce patches and
features with comments that, in the end, are a waste of time.

Patch submitter, please ignore Markus's suggestion; you do not need to
follow it at all. The person/bot/AI that sent it is being ignored by
almost all Linux kernel maintainers for having a persistent pattern of
behavior of producing distracting and pointless commentary, and
inability to adapt to feedback. Please feel free to also ignore emails
from them.

thanks,

greg k-h's patch email bot

2024-05-21 07:56:17

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH 03/13] dt-bindings: a2b: Analog Devices AD24xx devices

On 21/05/2024 09:24, Alvin Šipraga wrote:

>>> +
>>> +required:
>>> + - compatible
>>> +
>>> +unevaluatedProperties: false
>>
>> Sorry, but not. No resources, nothing here. Do not create bindings just
>> to instantiate drivers.
>
> Do you mean that there is no need to introduce a binding for this codec
> if it has the same bindings as dai-common.yaml?

No, I said you do not have absolutely any resources here, so your
binding is empty. There is no need for such binding. You just want to
treat DT as way to instantiate drivers, which is a no-go.

>
> Basically that is the case, but #sound-dai-cells should be <0>. Is that
> not enough?
>
> I am OK to just drop the binding if you think so, but I would think that
> the compatible string should be somewhere in the bindings. Could you
> explain a little more what you mean?

Why do you need compatible? Which piece of hardware, with its own
resources, is being described here?

Just put dai-cells in parent node.


..


>>> +
>>> +examples:
>>> + - |
>>> + sync_clk: sync-clock {
>>
>> Drop, not related.
>
> If the clock is required (as it is) then I have to reference some
> phandle in the example,

Why?

> else the example will fail the check (missing
> required property 'clocks'). That's why I put it here. Please advise.

Let me answer indirectly: do you see any binding doing this? No. There
is almost none, so this should be a hint that it is not needed.



Best regards,
Krzysztof


2024-05-21 08:31:43

by Alvin Šipraga

[permalink] [raw]
Subject: Re: [PATCH 04/13] a2b: add AD24xx I2C interface driver

On Fri, May 17, 2024 at 04:49:20PM GMT, Wolfram Sang wrote:
>
> > + /*
> > + * Enforce some basic assumptions this function makes about the
> > + * transfer. If this proves insufficient, some more complex logic will
> > + * be needed.
> > + */
> > + if (num > 2 || (num == 2 && msgs[0].addr != msgs[1].addr))
> > + return -EOPNOTSUPP;
>
> As you populated 'ad24xx_i2c_adapter_quirks' in the I2C driver, you can
> drop this. The I2C core will do the checks for you.
>

The i2c_xfer function here is also available as a general A2B API, see
a2b.h:

int a2b_node_i2c_xfer(struct a2b_node *node, struct i2c_msg *msgs, int num);

This is used by the beo-shape-node.c driver submitted later in this
series to perform a firmware update of a more peculiar A2B hardware.
In this case it doesn't factor through the codepath you mention, hence
this check.

It's conceivable that there will be other such cases in the future as
well. ADI for example prescribes a specific EEPROM address where device
identification data can be stored with a well-defined format. In the
event that the driver should support some kind of device type detection,
it will also have to perform some I2C transfers out-of-band like this.

2024-05-21 10:43:37

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 02/13] regmap: add A2B support

On Tue, May 21, 2024 at 06:27:19AM +0000, Alvin Šipraga wrote:
> On Fri, May 17, 2024 at 03:42:31PM GMT, Mark Brown wrote:

> > (looks like you'll need to add
> > the hook for the bus there).

> I am not sure what you mean by this, can you elaborate?

I didn't check but I don't think we have flags at the bus level.


Attachments:
(No filename) (327.00 B)
signature.asc (499.00 B)
Download all attachments

2024-05-21 10:50:04

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 07/13] ASoC: codecs: add AD24xx codec driver

On Tue, May 21, 2024 at 06:46:21AM +0000, Alvin Šipraga wrote:
> On Fri, May 17, 2024 at 04:03:50PM GMT, Mark Brown wrote:
> > On Fri, May 17, 2024 at 02:58:05PM +0200, Alvin Šipraga wrote:

> > > +static const char *const ad24xx_codec_slot_size_text[] = {
> > > + "8 bits", "12 bits", "16 bits", "20 bits",
> > > + "24 bits", "28 bits", "32 bits",
> > > +};
> >
> > Why is this configured by the user rather than via set_tdm_slot(), and
> > how would one usefully use this at runtime?
>
> This configures the slot size of A2B data slots, not the slot size on
> the TDM interface. Typically one would expect it to be the same, so your
> question is valid. But it is not a strict requirement as far as the A2B
> bus and hardware is concerned.
>
> To give a concrete example, the TDM interface might run with a TDM slot
> size of 32 bits, but the PCM data is in reality 24 bits padded to 32
> bits. In this case, A2B bus bandwidth can be saved by configuring the
> "{Up,Down}stream Slot Size" kcontrol to "24 bits".
>
> More detailed information can be found in the manual in [1] section 3-22
> "I2S/TDM Port Programming Concepts", where an analogous example is
> given.

That still doesn't sound like something that should be configured
dynamically by the user. Based on that description it sounds like it's
just the sample size so should cope from hw_params.


Attachments:
(No filename) (1.36 kB)
signature.asc (499.00 B)
Download all attachments

2024-05-22 20:17:27

by Bartosz Golaszewski

[permalink] [raw]
Subject: Re: [PATCH 06/13] gpio: add AD24xx GPIO driver

On Fri, May 17, 2024 at 2:58 PM Alvin Šipraga <[email protected]> wrote:
>
> From: Alvin Šipraga <[email protected]>
>
> This driver adds GPIO function support for AD24xx A2B transceiver chips.
> When a GPIO is requested, the relevant pin is automatically muxed to
> GPIO mode. The device tree property gpio-reserved-ranges can be used to
> protect certain pins which are reserved for other functionality such as
> I2S/TDM data.
>
> Signed-off-by: Alvin Šipraga <[email protected]>
> ---
> drivers/a2b/Kconfig | 1 +
> drivers/gpio/Kconfig | 6 +
> drivers/gpio/Makefile | 1 +
> drivers/gpio/gpio-ad24xx.c | 302 +++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 310 insertions(+)
>
> diff --git a/drivers/a2b/Kconfig b/drivers/a2b/Kconfig
> index 1f6d836463f3..8c894579e2fc 100644
> --- a/drivers/a2b/Kconfig
> +++ b/drivers/a2b/Kconfig
> @@ -32,6 +32,7 @@ config A2B_AD24XX_I2C
> config A2B_AD24XX_NODE
> tristate "Analog Devices Inc. AD24xx node support"
> select REGMAP_A2B
> + imply GPIO_AD24XX
> help
> Say Y here to enable support for AD24xx A2B transceiver nodes. This
> applies to both main nodes and subordinate nodes. Supported models
> diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
> index 3dbddec07028..72bd0d88d6b3 100644
> --- a/drivers/gpio/Kconfig
> +++ b/drivers/gpio/Kconfig
> @@ -1241,6 +1241,12 @@ config GPIO_ALTERA_A10SR
> includes reads of pushbuttons and DIP switches as well
> as writes to LEDs.
>
> +config GPIO_AD24XX
> + tristate "Analog Devies Inc. AD24xx GPIO support"
> + depends on A2B_AD24XX_NODE
> + help
> + Say Y here to enable GPIO support for AD24xx A2B transceivers.
> +
> config GPIO_ARIZONA
> tristate "Wolfson Microelectronics Arizona class devices"
> depends on MFD_ARIZONA
> diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
> index e2a53013780e..f625bb140143 100644
> --- a/drivers/gpio/Makefile
> +++ b/drivers/gpio/Makefile
> @@ -24,6 +24,7 @@ obj-$(CONFIG_GPIO_104_IDI_48) += gpio-104-idi-48.o
> obj-$(CONFIG_GPIO_104_IDIO_16) += gpio-104-idio-16.o
> obj-$(CONFIG_GPIO_74X164) += gpio-74x164.o
> obj-$(CONFIG_GPIO_74XX_MMIO) += gpio-74xx-mmio.o
> +obj-$(CONFIG_GPIO_AD24XX) += gpio-ad24xx.o
> obj-$(CONFIG_GPIO_ADNP) += gpio-adnp.o
> obj-$(CONFIG_GPIO_ADP5520) += gpio-adp5520.o
> obj-$(CONFIG_GPIO_AGGREGATOR) += gpio-aggregator.o
> diff --git a/drivers/gpio/gpio-ad24xx.c b/drivers/gpio/gpio-ad24xx.c
> new file mode 100644
> index 000000000000..097ea9e2d629
> --- /dev/null
> +++ b/drivers/gpio/gpio-ad24xx.c
> @@ -0,0 +1,302 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * AD24xx GPIO driver
> + *
> + * Copyright (c) 2023-2024 Alvin Šipraga <[email protected]>
> + */
> +
> +#include <linux/a2b/a2b.h>
> +#include <linux/a2b/ad24xx.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/regmap.h>
> +
> +struct ad24xx_gpio {
> + struct device *dev;

You only use this once to emit a log message. You should probably drop
it and use the parent pointer in gpio_chip.

Otherwise looks pretty good to me.

With the above addressed:

Acked-by: Bartosz Golaszewski <[email protected]>

Bart

> + struct a2b_func *func;
> + struct a2b_node *node;
> + struct regmap *regmap;
> + int irqs[AD24XX_MAX_GPIOS];
> + struct gpio_chip gpio_chip;
> + struct irq_chip irq_chip;
> + struct mutex mutex;
> + unsigned int irq_invert : AD24XX_MAX_GPIOS;
> + unsigned int irq_enable : AD24XX_MAX_GPIOS;
> +};
> +
> +static int ad24xx_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(adg->regmap, A2B_GPIOOEN, &val);
> + if (ret)
> + return ret;
> +
> + if (val & BIT(offset))
> + return 0; /* output */
> +
> + return 1; /* input */
> +}
> +
> +static int ad24xx_gpio_get(struct gpio_chip *gc, unsigned int offset)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(adg->regmap, A2B_GPIOIN, &val);
> + if (ret)
> + return ret;
> +
> + if (val & BIT(offset))
> + return 1; /* high */
> +
> + return 0; /* low */
> +}
> +
> +static void ad24xx_gpio_set(struct gpio_chip *gc, unsigned int offset,
> + int value)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> + unsigned int reg = value ? A2B_GPIODATSET : A2B_GPIODATCLR;
> +
> + regmap_write(adg->regmap, reg, BIT(offset));
> +}
> +
> +static int ad24xx_gpio_set_direction(struct ad24xx_gpio *adg,
> + unsigned int offset,
> + unsigned int direction)
> +{
> + unsigned int mask = BIT(offset);
> + unsigned int ival = direction ? BIT(offset) : 0;
> + int ret;
> +
> + ret = regmap_update_bits(adg->regmap, A2B_GPIOIEN, mask, ival);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(adg->regmap, A2B_GPIOOEN, mask, ~ival);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static int ad24xx_gpio_direction_input(struct gpio_chip *gc,
> + unsigned int offset)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> +
> + return ad24xx_gpio_set_direction(adg, offset, 1);
> +}
> +
> +static int ad24xx_gpio_direction_output(struct gpio_chip *gc,
> + unsigned int offset, int value)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> +
> + /* For atomicity, write the output value before setting the direction */
> + ad24xx_gpio_set(gc, offset, value);
> +
> + return ad24xx_gpio_set_direction(adg, offset, 0);
> +}
> +
> +static int ad24xx_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
> + unsigned int child,
> + unsigned int child_type,
> + unsigned int *parent,
> + unsigned int *parent_type)
> +{
> + *parent = child;
> + return 0;
> +}
> +
> +static void ad24xx_gpio_irq_mask(struct irq_data *d)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> +
> + adg->irq_enable &= ~BIT(hwirq);
> + gpiochip_disable_irq(gpio_chip, hwirq);
> +}
> +
> +static void ad24xx_gpio_irq_unmask(struct irq_data *d)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> +
> + gpiochip_disable_irq(gpio_chip, hwirq);
> + adg->irq_enable |= BIT(hwirq);
> +}
> +
> +static int ad24xx_gpio_irq_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> +
> + switch (type) {
> + case IRQ_TYPE_EDGE_RISING:
> + adg->irq_invert &= ~BIT(hwirq);
> + break;
> + case IRQ_TYPE_EDGE_FALLING:
> + adg->irq_invert |= BIT(hwirq);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static void ad24xx_gpio_irq_bus_lock(struct irq_data *d)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> +
> + mutex_lock(&adg->mutex);
> +}
> +
> +static void ad24xx_gpio_irq_bus_sync_unlock(struct irq_data *d)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> + int ret;
> +
> + ret = regmap_write(adg->regmap, A2B_PINTINV, adg->irq_invert);
> + if (ret)
> + goto out;
> +
> + ret = regmap_write(adg->regmap, A2B_PINTEN, adg->irq_enable);
> + if (ret)
> + goto out;
> +
> +out:
> + mutex_unlock(&adg->mutex);
> +
> + if (ret)
> + dev_err(adg->dev,
> + "failed to update interrupt configuration: %d\n", ret);
> +}
> +
> +static const struct irq_chip ad24xx_gpio_irq_chip = {
> + .name = "ad24xx-gpio",
> + .flags = IRQCHIP_IMMUTABLE,
> + .irq_mask = ad24xx_gpio_irq_mask,
> + .irq_unmask = ad24xx_gpio_irq_unmask,
> + .irq_set_type = ad24xx_gpio_irq_set_type,
> + .irq_bus_lock = ad24xx_gpio_irq_bus_lock,
> + .irq_bus_sync_unlock = ad24xx_gpio_irq_bus_sync_unlock,
> + GPIOCHIP_IRQ_RESOURCE_HELPERS,
> +};
> +
> +static const struct regmap_config ad24xx_gpio_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> +};
> +
> +static int ad24xx_gpio_probe(struct device *dev)
> +{
> + struct a2b_func *func = to_a2b_func(dev);
> + struct a2b_node *node = func->node;
> + struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);
> + struct gpio_chip *gpio_chip;
> + struct gpio_irq_chip *irq_chip;
> + struct irq_domain *parent_domain;
> + struct ad24xx_gpio *adg;
> + struct device_node *np;
> + int ret;
> +
> + adg = devm_kzalloc(dev, sizeof(*adg), GFP_KERNEL);
> + if (!adg)
> + return -ENOMEM;
> +
> + adg->regmap =
> + devm_regmap_init_a2b_func(func, &ad24xx_gpio_regmap_config);
> + if (IS_ERR(adg->regmap))
> + return PTR_ERR(adg->regmap);
> +
> + adg->dev = dev;
> + adg->func = func;
> + adg->node = node;
> + mutex_init(&adg->mutex);
> +
> + np = of_irq_find_parent(dev->of_node);
> + if (!np)
> + return -ENOENT;
> +
> + parent_domain = irq_find_host(np);
> + of_node_put(np);
> + if (!parent_domain)
> + return -ENOENT;
> +
> + gpio_chip = &adg->gpio_chip;
> + gpio_chip->label = dev_name(dev);
> + gpio_chip->parent = dev;
> + gpio_chip->fwnode = fwnode;
> + gpio_chip->owner = THIS_MODULE;
> + gpio_chip->get_direction = ad24xx_gpio_get_direction;
> + gpio_chip->direction_input = ad24xx_gpio_direction_input;
> + gpio_chip->direction_output = ad24xx_gpio_direction_output;
> + gpio_chip->get = ad24xx_gpio_get;
> + gpio_chip->set = ad24xx_gpio_set;
> + gpio_chip->base = -1;
> + gpio_chip->ngpio = node->chip_info->max_gpios;
> + gpio_chip->can_sleep = true;
> +
> + irq_chip = &gpio_chip->irq;
> + gpio_irq_chip_set_chip(irq_chip, &ad24xx_gpio_irq_chip);
> + irq_chip->fwnode = fwnode;
> + irq_chip->parent_domain = parent_domain;
> + irq_chip->child_to_parent_hwirq = ad24xx_gpio_child_to_parent_hwirq;
> + irq_chip->handler = handle_bad_irq;
> + irq_chip->default_type = IRQ_TYPE_NONE;
> +
> + /* Initialize all GPIOs as inputs for high impedance state */
> + ret = regmap_write(adg->regmap, A2B_GPIOIEN, 0xFF);
> + if (ret)
> + return ret;
> +
> + ret = devm_gpiochip_add_data(dev, gpio_chip, adg);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static const struct of_device_id ad24xx_gpio_of_match_table[] = {
> + { .compatible = "adi,ad2401-gpio" },
> + { .compatible = "adi,ad2402-gpio" },
> + { .compatible = "adi,ad2403-gpio" },
> + { .compatible = "adi,ad2410-gpio" },
> + { .compatible = "adi,ad2420-gpio" },
> + { .compatible = "adi,ad2421-gpio" },
> + { .compatible = "adi,ad2422-gpio" },
> + { .compatible = "adi,ad2425-gpio" },
> + { .compatible = "adi,ad2426-gpio" },
> + { .compatible = "adi,ad2427-gpio" },
> + { .compatible = "adi,ad2428-gpio" },
> + { .compatible = "adi,ad2429-gpio" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ad24xx_gpio_of_match_table);
> +
> +static struct a2b_driver ad24xx_gpio_driver = {
> + .driver = {
> + .name = "ad24xx-gpio",
> + .of_match_table = ad24xx_gpio_of_match_table,
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> + .probe = ad24xx_gpio_probe,
> +};
> +module_a2b_driver(ad24xx_gpio_driver);
> +
> +MODULE_AUTHOR("Alvin Šipraga <[email protected]>");
> +MODULE_DESCRIPTION("AD24xx GPIO driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.44.0
>

2024-05-28 12:14:07

by Linus Walleij

[permalink] [raw]
Subject: Re: [PATCH 06/13] gpio: add AD24xx GPIO driver

Hi Alvin,

thanks for your patch!

On Fri, May 17, 2024 at 2:58 PM Alvin Šipraga <[email protected]> wrote:

> From: Alvin Šipraga <[email protected]>
>
> This driver adds GPIO function support for AD24xx A2B transceiver chips.
> When a GPIO is requested, the relevant pin is automatically muxed to
> GPIO mode. The device tree property gpio-reserved-ranges can be used to
> protect certain pins which are reserved for other functionality such as
> I2S/TDM data.
>
> Signed-off-by: Alvin Šipraga <[email protected]>
(...)

> config A2B_AD24XX_NODE
> tristate "Analog Devices Inc. AD24xx node support"
> select REGMAP_A2B
> + imply GPIO_AD24XX

Maybe it should even be select, if it's hard to think about a case
where this is not desired?

> +config GPIO_AD24XX
> + tristate "Analog Devies Inc. AD24xx GPIO support"
> + depends on A2B_AD24XX_NODE
> + help
> + Say Y here to enable GPIO support for AD24xx A2B transceivers.
> +
> config GPIO_ARIZONA
> tristate "Wolfson Microelectronics Arizona class devices"
> depends on MFD_ARIZONA

This is grouped with the MFD devices but as I understand it A2B is a
completely new bus type? Is MFD even always selected when A2B is
in use?

To me it's fine to add a new submenu for A2B devices, if there will be
more of them.

> +static int ad24xx_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(adg->regmap, A2B_GPIOOEN, &val);
> + if (ret)
> + return ret;
> +
> + if (val & BIT(offset))
> + return 0; /* output */
> +
> + return 1; /* input */

Please use GPIO_LINE_DIRECTION_OUT/GPIO_LINE_DIRECTION_IN
instead of 0/1 here?

Then you don't need the comments because it's evident.

> +static int ad24xx_gpio_get(struct gpio_chip *gc, unsigned int offset)
> +{
> + struct ad24xx_gpio *adg = gpiochip_get_data(gc);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(adg->regmap, A2B_GPIOIN, &val);
> + if (ret)
> + return ret;
> +
> + if (val & BIT(offset))
> + return 1; /* high */
> +
> + return 0; /* low */

Just

return !!(val & BIT(offset));

> +static int ad24xx_gpio_child_to_parent_hwirq(struct gpio_chip *gc,
> + unsigned int child,
> + unsigned int child_type,
> + unsigned int *parent,
> + unsigned int *parent_type)
> +{
> + *parent = child;
> + return 0;
> +}

This deserves a comment, is IRQ 0 the singular parent of
everything? Then it doesn't seem very hierarchical but rather
cascaded don't you think?

> +static int ad24xx_gpio_irq_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> + irq_hw_number_t hwirq = irqd_to_hwirq(d);
> +
> + switch (type) {
> + case IRQ_TYPE_EDGE_RISING:
> + adg->irq_invert &= ~BIT(hwirq);
> + break;
> + case IRQ_TYPE_EDGE_FALLING:
> + adg->irq_invert |= BIT(hwirq);
> + break;
> + default:
> + return -EINVAL;
> + }

No need for the "toggling trick" for supporting IRQ on both edges?
Implementing that hack (which is in several drivers) will be nice to
have for e.g. pushbuttons.

> +static void ad24xx_gpio_irq_bus_lock(struct irq_data *d)
> +{
> + struct gpio_chip *gpio_chip = irq_data_get_irq_chip_data(d);
> + struct ad24xx_gpio *adg = gpiochip_get_data(gpio_chip);
> +
> + mutex_lock(&adg->mutex);
> +}

Is this mutex needed since there is already a mutex or spinlock
in the regmap? Isn't this the case for A2B?

Yours,
Linus Walleij

2024-05-28 21:42:28

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH 06/13] gpio: add AD24xx GPIO driver

Fri, May 17, 2024 at 02:58:04PM +0200, Alvin Šipraga kirjoitti:
> From: Alvin Šipraga <[email protected]>
>
> This driver adds GPIO function support for AD24xx A2B transceiver chips.

"Add GPIO..."

> When a GPIO is requested, the relevant pin is automatically muxed to
> GPIO mode. The device tree property gpio-reserved-ranges can be used to
> protect certain pins which are reserved for other functionality such as
> I2S/TDM data.

Why this doesn't use gpio-regmap?

..

> +config GPIO_AD24XX
> + tristate "Analog Devies Inc. AD24xx GPIO support"
> + depends on A2B_AD24XX_NODE
> + help
> + Say Y here to enable GPIO support for AD24xx A2B transceivers.

checkpatch probably complain about too short help text. You may extend it by
explaining how module will be called.

..

> +#include <linux/a2b/a2b.h>
> +#include <linux/a2b/ad24xx.h>

This seems to me not so generic as below...

+ bits.h
+ device.h
+ err.h

> +#include <linux/gpio/driver.h>

> +#include <linux/interrupt.h>

+ mod_devicetable.h

> +#include <linux/module.h>

+ mutex.h

> +#include <linux/of_irq.h>

Please, can we avoid OF in a new code?

> +#include <linux/regmap.h>

..hence move that group here and put a blank line before.

..

> +struct ad24xx_gpio {
> + struct device *dev;
> + struct a2b_func *func;
> + struct a2b_node *node;
> + struct regmap *regmap;
> + int irqs[AD24XX_MAX_GPIOS];

> + struct gpio_chip gpio_chip;

If you move this to be the first member, you might get less code being
generated at compile time.

> + struct irq_chip irq_chip;

Should not be here, but static.

> + struct mutex mutex;
> + unsigned int irq_invert : AD24XX_MAX_GPIOS;
> + unsigned int irq_enable : AD24XX_MAX_GPIOS;
> +};

..

> + if (ret)
> + dev_err(adg->dev,
> + "failed to update interrupt configuration: %d\n", ret);

Why and how is this useful?

..

> + struct fwnode_handle *fwnode = of_node_to_fwnode(dev->of_node);

First of all it uses a wrong API (custom to IRQ core), second why do you need
this?

..

> + struct device_node *np;

> + np = of_irq_find_parent(dev->of_node);
> + if (!np)
> + return -ENOENT;
> +
> + parent_domain = irq_find_host(np);
> + of_node_put(np);
> + if (!parent_domain)
> + return -ENOENT;

Why is this magic needed?

..

> + ret = devm_gpiochip_add_data(dev, gpio_chip, adg);
> + if (ret)
> + return ret;
> +
> + return 0;

return devm_gpiochip_add_data(...);

--
With Best Regards,
Andy Shevchenko



2024-06-04 00:10:40

by Stephen Boyd

[permalink] [raw]
Subject: Re: [PATCH 08/13] clk: add AD24xx clock driver

Quoting Alvin Šipraga (2024-05-17 06:02:15)
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 3e9099504fad..a3d54b077e68 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -257,6 +257,13 @@ config COMMON_CLK_LAN966X
> LAN966X SoC. GCK generates and supplies clock to various peripherals
> within the SoC.
>
> +config COMMON_CLK_AD24XX
> + bool "Clock driver for Analog Devices Inc. AD24xx"

tristate

> + depends on A2B_AD24XX_NODE

Please make it be COMPILE_TESTed as well?

> + help
> + This driver supports the clock output functionality of AD24xx series
> + A2B transceiver chips.
> +
> config COMMON_CLK_ASPEED
> bool "Clock driver for Aspeed BMC SoCs"
> depends on ARCH_ASPEED || COMPILE_TEST
> diff --git a/drivers/clk/clk-ad24xx.c b/drivers/clk/clk-ad24xx.c
> new file mode 100644
> index 000000000000..ed227c317faa
> --- /dev/null
> +++ b/drivers/clk/clk-ad24xx.c
> @@ -0,0 +1,341 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * AD24xx clock driver
> + *
> + * Copyright (c) 2023 Alvin Šipraga <[email protected]>
> + */
> +
> +#include <linux/a2b/a2b.h>
> +#include <linux/a2b/ad24xx.h>
> +#include <linux/clk-provider.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>

Include header for static_assert() at least. There's probably more that
are needed, please check.

> +
> +#define AD24XX_NUM_CLKS 2
> +
> +/* Define some safe macros to make the code more readable */
> +#define A2B_CLKCFG(_idx) (!(_idx) ? A2B_CLK1CFG : A2B_CLK2CFG)
> +
> +#define A2B_CLKCFG_DIV_SHIFT A2B_CLK1CFG_CLK1DIV_SHIFT
> +#define A2B_CLKCFG_PDIV_SHIFT A2B_CLK1CFG_CLK1PDIV_SHIFT
> +
> +#define A2B_CLKCFG_DIV_MASK A2B_CLK1CFG_CLK1DIV_MASK
> +#define A2B_CLKCFG_PDIV_MASK A2B_CLK1CFG_CLK1PDIV_MASK
> +#define A2B_CLKCFG_INV_MASK A2B_CLK1CFG_CLK1INV_MASK
> +#define A2B_CLKCFG_EN_MASK A2B_CLK1CFG_CLK1EN_MASK
> +
> +static_assert(A2B_CLK1CFG_CLK1DIV_MASK == A2B_CLK2CFG_CLK2DIV_MASK);
> +static_assert(A2B_CLK1CFG_CLK1PDIV_MASK == A2B_CLK2CFG_CLK2PDIV_MASK);
> +static_assert(A2B_CLK1CFG_CLK1INV_MASK == A2B_CLK2CFG_CLK2INV_MASK);
> +static_assert(A2B_CLK1CFG_CLK1EN_MASK == A2B_CLK2CFG_CLK2EN_MASK);
> +
> +struct ad24xx_clkout {
> + struct clk_hw hw;
> + unsigned int idx;
> + bool registered;
> +};
> +
> +struct ad24xx_clk {
> + struct device *dev;

Is this used?

> + struct a2b_func *func;

Is this used?

> + struct a2b_node *node;

Is this used?

> + struct regmap *regmap;
> + struct clk_hw *pll_hw;

Is this used outside of probe?

> + struct ad24xx_clkout clkouts[AD24XX_NUM_CLKS];
> +};
> +
[..]
> +
> +static const struct regmap_config ad24xx_clk_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .cache_type = REGCACHE_RBTREE,

No max_register?

> +};
> +
> +static struct clk_hw *ad24xx_clk_of_get(struct of_phandle_args *clkspec, void *data)
> +{
> + struct ad24xx_clk *adclk = data;
> + unsigned int idx = clkspec->args[0];
> +
> + if (idx >= AD24XX_NUM_CLKS)
> + return ERR_PTR(-EINVAL);
> +
> + if (!adclk->clkouts[idx].registered)
> + return ERR_PTR(-ENOENT);
> +
> + return &adclk->clkouts[idx].hw;
> +}
> +
> +static int ad24xx_clk_probe(struct device *dev)
> +{
> + struct a2b_func *func = to_a2b_func(dev);
> + struct a2b_node *node = func->node;
> + struct device_node *np = dev->of_node;
> + char *pll_name;
> + const char *sync_clk_name;
> + struct ad24xx_clk *adclk;
> + int num_clks;
> + int ret;
> + int i;
> +
> + /*
> + * Older series AD240x and AD241x chips have a single discrete
> + * A2B_CLKCFG register that behaves differently to the A2B_CLKnCFG
> + * registers of the later AD242x series. This driver only supports the
> + * latter right now.
> + */
> + if (!(node->chip_info->caps & A2B_CHIP_CAP_CLKOUT))
> + return -ENODEV;

Maybe print a warning message to make it more obvious.

> +
> + adclk = devm_kzalloc(dev, sizeof(*adclk), GFP_KERNEL);
> + if (!adclk)
> + return -ENOMEM;
> +
> + adclk->regmap =
> + devm_regmap_init_a2b_func(func, &ad24xx_clk_regmap_config);

Put it on one line please .

> + if (IS_ERR(adclk->regmap))
> + return PTR_ERR(adclk->regmap);
> +
> + adclk->dev = dev;
> + adclk->func = func;
> + adclk->node = node;
> + dev_set_drvdata(dev, adclk);
> +
> + num_clks = of_property_count_strings(np, "clock-output-names");
> + if (num_clks < 0 || num_clks > AD24XX_NUM_CLKS)
> + return -EINVAL;

Please register all the clks provided by this chip.

> +
> + /*
> + * Register the PLL internally to use it as the parent of the CLKOUTs.
> + * The PLL runs at 2048 times the SYNC clock rate.
> + */
> + pll_name =
> + devm_kasprintf(dev, GFP_KERNEL, "%s_pll", dev_name(&node->dev));
> + if (!pll_name)
> + return -ENOMEM;
> + sync_clk_name = __clk_get_name(a2b_node_get_sync_clk(func->node));
> + adclk->pll_hw = devm_clk_hw_register_fixed_factor(
> + dev, pll_name, sync_clk_name, 0, 2048, 1);

I think this should be devm_clk_hw_register_fixed_factor_fwname().

> + if (IS_ERR(adclk->pll_hw))
> + return PTR_ERR(adclk->pll_hw);
> +
> + for (i = 0; i < num_clks; i++) {
> + struct clk_init_data init = { };
> + const char *parent_names = clk_hw_get_name(adclk->pll_hw);

Please use struct clk_parent_data instead of strings to describe
topology.

> + unsigned int idx = i;
> +
> + /* Clock outputs can be skipped with the clock-indices property */
> + of_property_read_u32_index(np, "clock-indices", i, &idx);
> + if (idx > AD24XX_NUM_CLKS)
> + return -EINVAL;
> +
> + ret = of_property_read_string_index(np, "clock-output-names", i,
> + &init.name);

The name should only be for debug purposes. Please don't use
clock-output-names DT property. If you need to make it unique perhaps
you can add in the device name or something like that?

> + if (ret)
> + return ret;
> +
> + init.ops = &ad24xx_clk_ops;
> + init.parent_names = &parent_names;
> + init.num_parents = 1;
> +
> + adclk->clkouts[idx].hw.init = &init;
> + adclk->clkouts[idx].idx = idx;
> + adclk->clkouts[idx].registered = true;
> +
> + ret = devm_clk_hw_register(dev, &adclk->clkouts[idx].hw);
> + if (ret)
> + return ret;
> + }
> +
> + ret = devm_of_clk_add_hw_provider(dev, ad24xx_clk_of_get, adclk);
> + if (ret)
> + return ret;
> +
> + return 0;

Please just return devm_of_clk_add_hw_provider(...) to prevent the
cleanup crews from sending a followup patch.

> +}
> +
> +static const struct of_device_id ad24xx_clk_of_match_table[] = {
> + { .compatible = "adi,ad2420-clk" },
> + { .compatible = "adi,ad2421-clk" },
> + { .compatible = "adi,ad2422-clk" },
> + { .compatible = "adi,ad2425-clk" },
> + { .compatible = "adi,ad2426-clk" },
> + { .compatible = "adi,ad2427-clk" },
> + { .compatible = "adi,ad2428-clk" },
> + { .compatible = "adi,ad2429-clk" },
> + { /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, ad24xx_clk_of_match_table);
> +
> +static struct a2b_driver ad24xx_clk_driver = {

I guess because this isn't a platform driver I can't merge this through
the clk tree? Is there any difference from the platform bus?

> + .driver = {
> + .name = "ad24xx-clk",
> + .of_match_table = ad24xx_clk_of_match_table,
> + .probe_type = PROBE_PREFER_ASYNCHRONOUS,
> + },
> + .probe = ad24xx_clk_probe,
> +};
> +module_a2b_driver(ad24xx_clk_driver);