From: Alvin Šipraga <alsi@xxxxxxxxxxxxxxx>
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 <alsi@xxxxxxxxxxxxxxx>
---
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 <alsi@xxxxxxxxxxxxxxx>
+ *
+ * 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 <alsi@xxxxxxxxxxxxxxx>");
+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 <alsi@xxxxxxxxxxxxxxx>
+ */
+#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
[Index of Archives]
[Pulseaudio]
[Linux Audio Users]
[ALSA Devel]
[Fedora Desktop]
[Fedora SELinux]
[Big List of Linux Books]
[Yosemite News]
[KDE Users]