From patchwork Fri May 17 12:57:59 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797540 Received: from out-179.mta1.migadu.com (out-179.mta1.migadu.com [95.215.58.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4ECAF4EB4A for ; Fri, 17 May 2024 12:58:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950731; cv=none; b=LJoSKVqUCVXwY4oF7xVoVTS8m2+aXrlALaCzo7jOvrpjFKKBevDwQZ6wBe6ZKj4pQiXta07MNk4xZe3rlcNQd4alV2FyifnFcYSxiz5tc8AlO8SsEoHSFahjdpv9FnCGb4r7BRyUD/o6XUSjGug++bVF2H8T42UBnFDTxFVT8Ng= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950731; c=relaxed/simple; bh=UUHB7B8cG+YTR4mWDwQBOaLx3bhWH/8bHKlhAHj0EnY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YDpSNSCii1fhyoVzTU5DlRED6dzFfmBNxxCQ9InxsKhavX9BhLQhnXIhHfdmVB+Ox2TakqhXHaKHE60zWyb1KAihwgIPwzK0kfp33MaKCKbI0u3y57WkQc1FFpunYj5x5iFemeotpKtkqOJpuIccIaHBeCC9KkdEJtwM5Zhemhs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=Io5jIG2G; arc=none smtp.client-ip=95.215.58.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="Io5jIG2G" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950725; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=hXyIP+ectjrW+Q2/Fz/6tuBReM/7W8qGyezkWsRCUvQ=; b=Io5jIG2GLVZ42Nmsqp7bv0gBQct5zRcpH8+vjIwruO6yqRnGmD2HP9YRex2hMs8ykdkzvh EqkSjs2I9DowyY6w7/Nn2e/hfaUZW8L+6X00BBfEWZyxe91gL8OOUj4je2L5/jxPAiIITv D4dtz89pp0INbgW+EKHfbPXD+hm97IcTlG8ar50jMhv8bAbkUT7Kzu9IFanr3lwBoE4LiI mOP2Rn0MLQi+6EZONLe8W7T96nEmjuOTlvL4Il9eGPAjcfSzHvf/M4QslQIZ+7dGABXFrD H/SFr+PGyi4NlBMP4NWEeJ3RnMixjH6qhog256Cucw3BfRvmd2/CAjGq3waoHA== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:57:59 +0200 Subject: [PATCH 01/13] a2b: add A2B driver core Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-1-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 + * + * 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 +#include +#include + +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 "); +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 + */ +#ifndef _A2B_H +#define _A2B_H + +#include +#include +#include + +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 */ From patchwork Fri May 17 12:58:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797738 Received: from out-184.mta1.migadu.com (out-184.mta1.migadu.com [95.215.58.184]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 262304F616 for ; Fri, 17 May 2024 12:58:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.184 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950735; cv=none; b=LMjRNf1qqt0FV41bqbSKOjkXhA0TVxN59RoG25t72LPT7FePnwDeYvRFDKSq9HyWC+jqaqfe4JLUNP2Tx51KFqmMJaZNglxS4suNqFGYQV47OI26bA3MOKvD7c99xRZHfH4RiEleJDvlpBF99zoD8NoAnjAJagmAaYKEj0tzwME= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950735; c=relaxed/simple; bh=zhEWBrawvMbizC57yXlsDphblOxGvShm0hH9a8UM0cE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=f+SG3l8Cwtd6LVF9l7lUO+8XU9BKNkOmEY6+WLGV66zcsJT+4cwpJTN8aE16oqV7hpA2MwtJxfYbwdLSjCHLQhxw2knVlHncovWBhW0HiHRJ7MmbpyrcaNi5gUoBWdH7fO39rwBUUrhoOAJK0SF2iW2D3WLkzwmvIFi6CNenO08= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=DistQRgE; arc=none smtp.client-ip=95.215.58.184 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="DistQRgE" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950727; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=JJGBX33aY0n80azEn4ipwIvrPzKLZvbe5lsXykY8yrk=; b=DistQRgEInbSJ+T5Tl0tJuLA4B6kO3oXQvYWjlwdenBXa7km9R5i1fL7dm/95XUweTxryY Wpwdrde35+5HfosjaYrQPLTGyTBy0HDzEyJS8XKZepBOfj2rTFSIWRRqK5Jirtbmd/+j8V nQYc66rjh/EGooc/jD1XX6QbN4yf5ZWVNNQ54iKgepTQFC7tpbbs0fD0X+fXzN2CzVvNO5 emNoSUEvq4+tsG/iI2hEoytfBqVheXUXpV+RHtDqq93so1bjkkBvBmCK4ZJE/BnZfvlVZF zN/WMJebSpHXB5KKNC9Mrm6e8Y+BLuLnHfw62japUz1BmUCsVxLncZKJa3b0FA== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:58:00 +0200 Subject: [PATCH 02/13] regmap: add A2B support Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-2-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga Add regmap support for A2B drivers. Signed-off-by: Alvin Šipraga --- 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 + +#include +#include + +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, ®map_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, ®map_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); From patchwork Fri May 17 12:58:01 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797539 Received: from out-178.mta1.migadu.com (out-178.mta1.migadu.com [95.215.58.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB9D34F887 for ; Fri, 17 May 2024 12:58:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950735; cv=none; b=HBGkjTDumFQQwRgN8/wEOfnYtAp1YMKWhff7jRMclfG+GDKElnQwW8A/aCxHBllJYtU9lAQuBLKTzrwYjOCcHOyXh6Q/QK4r0fjAkGX6ZKv8XT0IfpmncbT6skvqzUnXAlemo0RW/shzu0rqp6Z+rt/A94RfRDavfvs1H3HvdHw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950735; c=relaxed/simple; bh=E71ly8foJT0aHkZA0E4KWODwmYGz89/hKVx+xoBMyzo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tkjQpYeJnapfV3HLeDvEvzYfHr4pxvHAVk8eVNOeGrUhhmS2WTj5eVkBErYoPafDk97tdvSxhxEg14iYnYNnW4L8hQBg3hHsg44k6HHViGTttQSMopI0PqbEg1KDEBTLwBXCeuCnO04b5sux5a1viCJVLhu87nvUFTLXLIq1Wlg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=sYkG9chu; arc=none smtp.client-ip=95.215.58.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="sYkG9chu" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950728; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=tl+q/jQrGvH8Zg3A/f5QNM02CauYUnImoBTtV4Mgvtk=; b=sYkG9chuB9PKF8rVZgeB8KsuUrww9cfYSftHd3krpTT8OgsAmS+BozINb68ThHW63t3bEa tG68OtCV+sMYWeNd2Kh/+57/uOPojV7I9MHXRFQkqSuX94FGcFBYO1aSmMdN1c2PBYP7ac 7S5dHTxMwscKuxzg58pzJ1x+OxwNP5g6WrqeRTq6MNGtFQv91F/FhbGb97JU47Jo6AeATv VAEWUtNNSrwCNRCa9KbQ8FTkkLdpOJkn4QorlQ2O0m2tVvdqkoNRq+Nn3l6q08kwWee86A d2O1E9EFmK9KOdHoADcMxMvbDtIJSNy4kLMrRE/oCHw/2UqQ3zIOJJxbp7ivQQ== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:58:01 +0200 Subject: [PATCH 03/13] dt-bindings: a2b: Analog Devices AD24xx devices Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-3-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga Add device tree bindings for the AD24xx series A2B transceiver chips, including their functional blocks. Signed-off-by: Alvin Šipraga --- .../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 + +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 + +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 + +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 + +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 + +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>; + }; + }; + }; + }; + }; From patchwork Fri May 17 12:58:02 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797736 Received: from out-175.mta1.migadu.com (out-175.mta1.migadu.com [95.215.58.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C788250251 for ; Fri, 17 May 2024 12:58:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950738; cv=none; b=DXwNlqqixZNqStDahORIfE8zmvPd+meYl1JLSwmNNMrRu5ANHvYwNyHUI/seEU7uVZjQfcTraENfwi0RO2KYzyRgoeJ1MXGaFa3qJuZvHrzhrBrf3uuDMVvSUapcwVfoqbQd9lcLh/qER7BPeT9qlrnQd1nKe5/s3/S2MS8xka4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950738; c=relaxed/simple; bh=8vYRnccSlQYqlh3fxX5/Hisboqgnyr4rICOClMIM20U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=eLI+ZzoBIuRNUVqTBR1fSz6LWwCH1Wzr5tf6cGjZHoZr78lA2NCL7pvuZGg27dKjWIBxBVXgZWxD3FshaVIEOf9AREW5nT1cgHaoyHdWDP7CmOk3EA7SQEmkKmDDPYTIAbPCVRX6dnVzE4na0fiCosTX2Al5HlESZx1kerHV3cs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=A0C/+HAc; arc=none smtp.client-ip=95.215.58.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="A0C/+HAc" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950729; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=hFm2NKOLNS2+8TI+0VNTEyF5JacM3hETg+tusBEe8nU=; b=A0C/+HAc5wMRHsEa0T699iNczH9rVQuqtbmPYWpCAFb83KllhXwxDi9kUzpJSucX5MZ4ar zO+Viuu+SvaXiQyAwZO1z6CkywvjI1iueTGVLhDIfqb2sejYjj4ghOIjQHSCuOGYYlR+Nm uepk85w6H/Q8s7Cer3nhiYKfkZNL8gLcVZfjnsmEk48Q4ku+C8g59Uz/p9Qndbt0YKtw8R eEcJKnZXnwsMQJG5cLspewe+ULO54KJ36QFPmNe9jbwkZcUCG3QDxJYgHtpO5+TFG3ynwO OE49IL1Q2rKIrfbXsXOVk+CfXiEvcGLcU8kk4Zl0Spgl+XYNc/77q6SLJBWi+Q== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:58:02 +0200 Subject: [PATCH 04/13] a2b: add AD24xx I2C interface driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-4-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 + */ + +#include +#include +#include +#include +#include +#include + +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 "); +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 + */ + +#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 */ From patchwork Fri May 17 12:58:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797538 Received: from out-189.mta1.migadu.com (out-189.mta1.migadu.com [95.215.58.189]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B4D2551004 for ; Fri, 17 May 2024 12:58:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.189 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950737; cv=none; b=XZZcmxsS9dnXIS6SKRDQWpZ/oYsN/rp4Bo/cZ24sum/MP0KXZ0R/ax27chPg+PDAksR5uZ9PVbM7vHj67Xo9AsyGoAefcPeGiCqlJ3WmbvugzKKGenWtdUjFgpM8/U2CVgNDVEJchSEacLHTVsIccLS30JorQIzwQo04FzDvy7Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950737; c=relaxed/simple; bh=0d39N4GPWNqxF0dFVCzjorEh1XiWOcRr2ujB+nVTEXI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=VATcP32w7FzTNr2YeTsKsj5YplbDa+wLZx6o0+LWGUVXFTSMm2gTXQIm01rAC6BaB3fdS6nasQj9DsMlo03QA19x++w0dZ3PAGpDSLAHpGS24UoS7mc+G7+XTtqdIhzy0uq4L+NS1FrV9sFXhwnoMjZTWxbQ7AJF7msHcs4imiQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=wa81VU0r; arc=none smtp.client-ip=95.215.58.189 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="wa81VU0r" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950729; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=QZlgR///yHXui6cTc/6KXDaywf/me0E2mv4A04nqOmE=; b=wa81VU0r8x+nrJ00CtQUM/U0gA2ygVrJoSG7OFUWPcDKBmJmDF1WmMXJy8eZ56yxkiQiOr loKwswHpowGRP4aftRo64q2oWmia0Zao/1GYi5T9L07Tru6jMQQsLbJZlshZbO9ZoPj7l/ mrIudK5rQ8nWhdwuwz5dmXetu1FK1UQk3dn3FaahlwHXdqMtsoU4Z89rmLtS3XmYFNGzjt 14S4u6JB0utb+b+U8S4nUHRKgTFPPUuCxQEV2lsZ7Fxm4QQmtQ0vRlL4vK/0DyEQM+GTdn S9C5/ruov56mZIgKIjrhSbkNZXIFSXt/9SsSAvt0OgyFbKTxhI9mKhAUjzQF/w== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:58:03 +0200 Subject: [PATCH 05/13] a2b: add AD24xx node driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-5-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 "); +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 + * + * Use this to derive your own custom A2B node driver. + */ +#ifndef _AD24XX_NODE_H +#define _AD24XX_NODE_H + +#include + +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 */ From patchwork Fri May 17 12:58:04 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797737 Received: from out-179.mta1.migadu.com (out-179.mta1.migadu.com [95.215.58.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C508052F8C for ; Fri, 17 May 2024 12:58:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950736; cv=none; b=QGk6j44jyT95wnCwoXYM82JJ7peAFu2JUgaaPozfkntuRHDAfoMzEhwzVHbECoJ8Qch7Ddsb5S1DRmiZxaG1mS5qBSD6ZGyeUcovJOsdTdmgZSAvmaLYBewXfd5AaBs25QbUOXC0TVVc1+FVvjf11uhSXwYQ1Pbg9qJtoDRwARI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950736; c=relaxed/simple; bh=0XOMm+ge0CG1rMR477UJBe3byaxH6SnO+N8jfBfoTS4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=TV29b8QM7CB5DqUYNd9QkkJZcdOphMu59ZKJHYbhxZbxu+8x5t+OGnZkbjwXEYQp91DgtkiOwg6mlUBvVvNtkMl7uw27NAmLe/kh1ZnS2FECmo6AFfhAsZTC+XjjLP+fkoA9u2W7gEhcbCNqzkxaGpTqX9fvMJwfW6FLE3cBGsc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=t+hKGjVy; arc=none smtp.client-ip=95.215.58.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="t+hKGjVy" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950730; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BKi9dQbr0T+uVxmbTMIarChYf6vJF2b8+XfTd+iLTkI=; b=t+hKGjVy72mnGQfVQXGD2Ss4kQdbuvoKZ3LkaVJJ13ZBKIqKH7WJqRNu6/AhgLJ0aFQcWv /dOzAdAZOO4pstDh3a1PLPzzv+QEOuzuphNU0NalyGPOCSGUov2cD9yny9IFVWsoyBwv0W PIQi2eljSfZqvMIaEDLvb4CTWp/q8dahR8n1s0fjV1G1o3QD62Wdkf3phMsUNOSeONdsWq Px07v9/e5I9TSYJfPRf1yhRPnkfEyJrt01W/iacidEQPvZTAX6Jfao+DKfC3MvV9R/5lao LKVvunhbIn3a2YvQ+JPDl4ed9L/PEsaijBnFaeOtl5j5XhMWR1POGSqkx/2yAQ== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:58:04 +0200 Subject: [PATCH 06/13] gpio: add AD24xx GPIO driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-6-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 Acked-by: Bartosz Golaszewski --- 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("AD24xx GPIO driver"); +MODULE_LICENSE("GPL"); From patchwork Fri May 17 12:58:05 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797537 Received: from out-172.mta1.migadu.com (out-172.mta1.migadu.com [95.215.58.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D0BC4535D1 for ; Fri, 17 May 2024 12:58:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950738; cv=none; b=IT5TNFRZCfaM2eM9wFmdT905Pha+qY57bjqaglZ1DqrFkfWkz39YD6xZwcodJ0qZDDQ6sVcbiZY5x1JC2kHcId8HJGbC79CAs5x79+j32nRIIQSTGRmv+es54IeSZJsEBf+x14FLaQ7cZ2w0J8915PEpGNUzIfQsERBU0hy8hDM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715950738; c=relaxed/simple; bh=6bQE8tz005tpkjYXd8Krz8L0/70f7CCtMikQfPJxlQc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mgGi4nvBip2Zf8dntbSshH6tFfemLYAHibDmiRaozCY/+KnwgVIYx9D17cYyV/MrDcjHpMRgFs8qeXQpVI/1AjjmUWKKlOcawYfAmjAdH2D9M6Dpae0+o0IVFB5HQdKvXnFrs2k1Ev9hpsgOYkUwAYIRH3etoyNJJ3K2ZbPQZlU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=t+BQBTl8; arc=none smtp.client-ip=95.215.58.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="t+BQBTl8" X-Envelope-To: linux-i2c@vger.kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715950731; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=3Qq56lk4JZsEZ+C4jeNHc4OlySfRhJLqLZblzRlQEi4=; b=t+BQBTl8JR4T9tddRQ4lPoImalqHFRx32mz9imnyvVIZhu3dbQBdnmBR58B5Y9pbzFv1DP b4VOvWYlfCBDDW36NKBY4Py5JEY2JNrJr/98s0eDzcNu3jDAf8c8F8TMyuEViTM8HWZND/ 50CBIiiZrxyn2sjs7CD4lhLyQctBkv85kh6Cv/JQypVFTY4vWoM8sioUTIhx6Q6e2BA9XM kUYXGXK2wi80MW8+IiZdWW0aROme+F/7l7YNr8rVJzSe0ohCNbK4Y/Wx/qdoezt7om4D41 UbWWeszdNGgonx4h7TK6wMkIvuDPaITU+oVkEgxKIfV6W7NjxdqR65b6EyiHdg== X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: robh@kernel.org X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: perex@perex.cz X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: broonie@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: alsi@bang-olufsen.dk X-Envelope-To: rafael@kernel.org X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: tiwai@suse.com X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: sboyd@kernel.org X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: brgl@bgdev.pl X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 14:58:05 +0200 Subject: [PATCH 07/13] ASoC: codecs: add AD24xx codec driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-7-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 + * + * 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 +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("AD24xx codec driver"); +MODULE_LICENSE("GPL"); From patchwork Fri May 17 13:02:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797536 Received: from out-179.mta1.migadu.com (out-179.mta1.migadu.com [95.215.58.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CFAC4548EF for ; Fri, 17 May 2024 13:14:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951684; cv=none; b=lngm2AzHnoWUmi/r3LVtxokBZnI2lZ1IIhwr0apdr9kpjdQHrf2io9KS8ks/RanRMCiJkpNYCep/UKjjUdnPVYX99VG7/AWeZTuKzXYkuOJrZlv59EbAYzl6pfBxtO4X1BTldJGa0DbLN9xLGu/cuMHUXdLmOlJzMlA8crHr2ig= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951684; c=relaxed/simple; bh=+BQrjQj8fvQHF8fbbT0PBaakRFsdp/Ftmf8qaj4dRD4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FkH45x16UIBCoE1bRuL0Rm5sXcOKxeF8ZzC/KvR4gRG+RJKfw/GWy0rekFr/STTDvjuN3GwMv5ZVVrxrMMe0RRrNu8mLxvRMLNGWZIOZaX3jugP/3oYqdc0JGhHhfD6oSVz2E4UTQ2AXTHG8emOBqKBoojufgJt5Jqn73mvyG8w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=estYxcPJ; arc=none smtp.client-ip=95.215.58.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="estYxcPJ" X-Envelope-To: broonie@kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715951679; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=KGwqzFatac3SdyxB8/u13aV/YQxtNfiylkMZnmEN/dw=; b=estYxcPJm2tGc9LCnhQIwaTlvZ9mIP7xAf+KWFFcnH55yBaAN0jvXjWwUXf5WctD2UUHLY rHQLlsZNacEIrdY3BGMfskH9SDtgwbqBZu5sA9DR/87Jo3pBkcAriJ++4MLMzDdSm9ilX8 L5CXeSpw1ywI7vQIWJEOBMgW76R1shvA6OPqiQ8nXA7/pczqux9FhRBvc7lKzCxhN7p5Av lZ8Rch2B7ntAV7xZjYOQEnEqOyeY5zqFS+cLxWztfDBw3zrNOGqqIp7ViVs7NNDXsT3OFI PjOrv14b+lydrSVA09CBYGiZhkDs0TEZLZ6HEMxyFjrRQ8Foo1hzGZQ3Clnbew== X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: rafael@kernel.org X-Envelope-To: robh@kernel.org X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: brgl@bgdev.pl X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: perex@perex.cz X-Envelope-To: tiwai@suse.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: sboyd@kernel.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: linux-i2c@vger.kernel.org X-Envelope-To: alsi@bang-olufsen.dk X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 15:02:15 +0200 Subject: [PATCH 08/13] clk: add AD24xx clock driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-8-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 + */ + +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("AD24xx CLK driver"); +MODULE_LICENSE("GPL"); From patchwork Fri May 17 13:02:16 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797734 Received: from out-187.mta0.migadu.com (out-187.mta0.migadu.com [91.218.175.187]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8D8C2524BE for ; Fri, 17 May 2024 13:15:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.187 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951716; cv=none; b=BuFJy+H5HwHLQNXWNOO0Bfw0bD/ywRSj57vpGwN3HV8KBEdHz1PPPiVsKMqGlPiROveSwTIH8KlWFTymMZedH2DA672X9tPQ30kmpTCaZwh4FhSsu1VSP05h0do/jU3ED4VdszUyeTNiso/gpQ2Sz65rhzjN1TxJ64k6ME4Aqiw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951716; c=relaxed/simple; bh=RsFQ+OoWI70l8JgaWfMkVQEqy2Jyx6j6QbNtAz2F3Og=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Vm5NRkUEQkKq/2WKI2td+4O+qivZhghIgCAcoXcK9z6H62AwqOzukuv29R2DPPudn46Fd8BrGBFUFKq1mE8x4Pgiz2OPRyiMNfutHCiolMLPmGb4QlbQFjAQ7YuRHvau5Q9WPrtD9TzkQMCkrOcbqKK6T1Yp4nSXYllOw+lNmrA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=JDHKZcgV; arc=none smtp.client-ip=91.218.175.187 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="JDHKZcgV" X-Envelope-To: broonie@kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715951712; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=furiSTSuRDcRd2X5RjG7qZBiz4mVfyegHighaEnbVrw=; b=JDHKZcgVd02Cb1jDAPb5lrgjO495Vpvj9ji4ic/EfRVzYyJQNEjMkzTNbvvHUEtnaliot9 26zTd5DMIHj+qdqgSOoLzNOrGrBK99F4imlHxN9jhghXnM3Nf8IPXAk+Biawbp2ltvPraM e/jgiNIMiZp7oDb8hfl24NGNfL2OtOhHVDYENNXgJUIHz9kSHpusr4Thc7Xo6iV9m4Yd4I ijir9dYa/zPXHhAHbEV0iJm/d8Vo2BSww8dNlDV8dZY3PeEzwz2Vs/pzPuVEQPasS2A+QG CUyKyKvkMU0R2HdTOnnRDTO1sn/2WvgYyiL8eKWHCioYt6ytpxak/IwW6JWRYg== X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: rafael@kernel.org X-Envelope-To: robh@kernel.org X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: brgl@bgdev.pl X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: perex@perex.cz X-Envelope-To: tiwai@suse.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: sboyd@kernel.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: linux-i2c@vger.kernel.org X-Envelope-To: alsi@bang-olufsen.dk X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 15:02:16 +0200 Subject: [PATCH 09/13] i2c: add AD24xx I2C controller driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-9-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga Signed-off-by: Alvin Šipraga --- 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 + */ + +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("AD24xx I2C controller driver"); +MODULE_LICENSE("GPL"); From patchwork Fri May 17 13:02:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797535 Received: from out-188.mta0.migadu.com (out-188.mta0.migadu.com [91.218.175.188]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5147F535D1 for ; Fri, 17 May 2024 13:16:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.188 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951782; cv=none; b=NW1cI+PzQDoq/WdRFZ1RIkBgTLsnGcabsi+5eMY3o9dEROG28R+sIQFZGtgrBeLiza8RFbZ7WPpMTQfbPCv2lIkVc4GYgoKc4QAwoOuKy1He+bTxxPKXjgF9/igzB36gmBqqLfKapPE0izZ+mLm+lRORL0/8PRvP+NH6F6EtWJs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951782; c=relaxed/simple; bh=1yhPImTIxbRUJs4BEG3JtFs30Z11V54swjqaxOYSnE4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YQwMRBkxBxNclaK4QFfr4QGyemElos8eN4r/GuhbPtP7L/LbCd0uxywQBdWzt9NxkixwlpB8pmvSU3NSbF8FjeZlYU5ewfjP4ZF27xozWTl8bhIDM4tyTOpF2iZ3EFKsU7sx44iDSXUE/370PSzMg9q8K+t+wd6KN8KTbRaJ42Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=kkOf4K4L; arc=none smtp.client-ip=91.218.175.188 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="kkOf4K4L" X-Envelope-To: broonie@kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715951779; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=VxTCkbzQ0q0xLLZFecGhD5poDn8vUvNddwfmtWAlFos=; b=kkOf4K4L/SGmGiCesu1d/Kul9e18UUApGAWfr11Xr4C+jyF0YzVqrzkZDLTgr5A9qVS+vJ S89BH6vtX3wc25vEdy1k8ZOKs3m6GOPJPoBA/3ju11K16Oo0ckZwhPSEePi4m4Nqwq+FrR Z9NZDsJAUILZugZuXpeOsL4T7LBhd9j0DABt+Q1ITg21URDsAPCbUbCPW208RAMORP1va7 jd8uh9vfoax0vM6pl+HKqU27gwZDMBU9h8owM4Zkv1tWD3EPkr7UjpSLQpUsnJ7VQcRy0t Yrwv87/bBb6Yk31+YVlRcaJj//Qmcutcs4VNhEfceCDsB5TRzZ87E3VU/oqClw== X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: rafael@kernel.org X-Envelope-To: robh@kernel.org X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: brgl@bgdev.pl X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: perex@perex.cz X-Envelope-To: tiwai@suse.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: sboyd@kernel.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: linux-i2c@vger.kernel.org X-Envelope-To: alsi@bang-olufsen.dk X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 15:02:17 +0200 Subject: [PATCH 10/13] dt-bindings: vendor-prefixes: add Bang & Olufsen a/s Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-10-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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,.*": From patchwork Fri May 17 13:02:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797733 Received: from out-170.mta1.migadu.com (out-170.mta1.migadu.com [95.215.58.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5D36B51C59; Fri, 17 May 2024 13:16:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951796; cv=none; b=Q888Rv8Za5oU2jzchs3FFqqpodEuwrKMk3e4BmAObL7HSdWyFXpLlDHHDHTjxDy28Ib1WOLNsNPt15azIJxFLd73zuspv1MVF6EP450BjeHGoogQwujwpHL6+yU7bHSPBZczircslf3MBE7N7f9NxXt2Ylh+ZBw+oT+0sJKX0EI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951796; c=relaxed/simple; bh=LsL6/+HEsOXR+sC6ERUzXuGIUNVSxnK0ZFUkngyWS04=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HXZAwgfEYHLRrl5sQWaPDL4jGxfllgmzg1E1tq0Um7WHXVtMCU+6DzbEXNPnV3eNLeqN+fZCOebVpujJdKOH883S2IlO6YdIOhT6CfeHm/k1ujqxpMr8tJzc3Cf6XMu/MFMSYQM0lm017rdat4IfIsmT1d/CIjYASYtoKDe+dtI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=cjFZN1xx; arc=none smtp.client-ip=95.215.58.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="cjFZN1xx" X-Envelope-To: broonie@kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715951793; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=S8nebunxi/byJgjN/w1ZOJ1kbMfAR+jz+Tg2mpCFNWc=; b=cjFZN1xxWW6muiTPhuOb32VS0vyZ9LuOcO+zvrBNxNLG8U6VHm27MIdUPHm2YcY2WbBGR2 EPXY1WnZqphGg3038eoaj8eBRAmpv+o4sZdnmYCiGjObjOk5riT5sbiPQu+gwdjQyGQOUP +x04HHATCbTFUhmZbMVUyZ4ppJCA/gxOBbjUG83xphHhXrgMT7UWF6UwJNZGV2lukqWk2b NSQyfvYviWYGNJS1mO8eX3BsZ+Y7awd27gr4U7NQkyYlzSOs+TIcVYBRrWvjqqqnWsiiws ZTT79Kx2eK7Gcloo1V3MMM/2x8sXU9GB0+CYd9bTBIVMQ6k9WJ82ShBLgAJ4FA== X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: rafael@kernel.org X-Envelope-To: robh@kernel.org X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: brgl@bgdev.pl X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: perex@perex.cz X-Envelope-To: tiwai@suse.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: sboyd@kernel.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: linux-i2c@vger.kernel.org X-Envelope-To: alsi@bang-olufsen.dk X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 15:02:18 +0200 Subject: [PATCH 11/13] dt-bindings: a2b: add compatible string for Beosound Shape node Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-11-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 From patchwork Fri May 17 13:02:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797534 Received: from out-175.mta0.migadu.com (out-175.mta0.migadu.com [91.218.175.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 928505102F for ; Fri, 17 May 2024 13:16:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=91.218.175.175 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951807; cv=none; b=iaqE1pnLhZxZZKcqtYgUfOUDXTD+0XHBJ++XB51zemEqlL8bcrxYsnWhEk65iV+upIfj7xG+ZtEq7vSnsaqIEAXOxPUEDogXghoeQvGURWQvuP89JnOJCUL26NIvesSIOAM8j7UkvHnncmX6LmdDXL1CbklKuVHiQ9/KPfaC0Sc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951807; c=relaxed/simple; bh=pW5Asn4bjCcrEh6yAYVE0ztcjFoflcaLS8lMe2oOk5g=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GCy2e1T3YajIflSjULvjYXmEQMJghCe85nlc4KgNVrACdab/2R7fBdHHyfNq8H3y29aGkPSM5Xas/yVRIqkOBkcQlkyv+bXgYSaDaF9rhctSxiOxzIzkJ1lPdg+veYEqN2FxcNovxWVWgcmIKKhIeuNoi79d1EaOdxivdTaF7XM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=f+fR3s08; arc=none smtp.client-ip=91.218.175.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="f+fR3s08" X-Envelope-To: broonie@kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715951804; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=7lqePBNqA/MEOrkQ+ndQqHEh9JM7eWzf5/WXt1v0tTw=; b=f+fR3s08MhLko46kI0lSU5af4P3iEm//H7yw1suAVI4s+rW2cL8QtR0jlCY6SsDs1g36dD T0GC37oucZc6efmh9eFY4C3Xgzckc3lOrPGVaNCU7oO9/xUT6pAn0Nshr78A863l+LSE5M WI3iabPhN7ovX1JFY3ZBh62lvW76uaD4kZmzrSZ4pN0gv0UDMw6DFhRDTcdtmacRkVDlEp +5td6hI+lFaVy6PqT4N8NxTzhA1Al3NWgCGpjavVUpAIr2xGivU6bMdJDqIQlULJmzqmF4 wKdxz1UZgZcyz2K7QFUvl9VW6UR8y88u1ISwmEkcyTzXBDyK4PfhdcCwOEszcA== X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: rafael@kernel.org X-Envelope-To: robh@kernel.org X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: brgl@bgdev.pl X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: perex@perex.cz X-Envelope-To: tiwai@suse.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: sboyd@kernel.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: linux-i2c@vger.kernel.org X-Envelope-To: alsi@bang-olufsen.dk X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 15:02:19 +0200 Subject: [PATCH 12/13] a2b: add Beosound Shape node driver Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-12-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga 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 --- 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 + * + * 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 +#include +#include +#include + +#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 = ® + + 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 = ® + + 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 "); +MODULE_DESCRIPTION("Beosound Shape A2B transceiver node driver"); +MODULE_LICENSE("GPL"); From patchwork Fri May 17 13:02:20 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Alvin_=C5=A0ipraga?= X-Patchwork-Id: 797732 Received: from out-170.mta1.migadu.com (out-170.mta1.migadu.com [95.215.58.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1D11655E49 for ; Fri, 17 May 2024 13:16:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=95.215.58.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951819; cv=none; b=FC28+5pl6baC7dcafonWHqPp60KdPoRR+UDS6HrKhmLmn+AXRbdZBe6u5TpLqTRNRyaZyOJsl83AwMlGbklDk4dp28/A6i2DLfs4n4UFNPnLrsjl+pbLO6p8Iyb+tJ3gHv3r5oGJ9ZxIZC7NtjRgaMDu4dlFyzsNHppghXM8Zfo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1715951819; c=relaxed/simple; bh=aauTBVOT3XeIZIf4iPBR3drvWHeVdecOUmzx3NQ0Dn8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=c/DzxIAs23Nz1QgEjI1MDXyZ7mFyaSRTJh/dSeywQPn8EsaUt6F4ZjvD/qKuBFwdFjlJMi5lSUhBpSmG8mtalMJCPWXeQK9I7gl5115xlbLEbvkXH4WxqYD92C0NbrFCzesJxFDLoSKs0YKSXQagZpmCLaYV+zQ0jvCykhukLYI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk; spf=pass smtp.mailfrom=pqrs.dk; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b=Vub0Ksgh; arc=none smtp.client-ip=95.215.58.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pqrs.dk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pqrs.dk header.i=@pqrs.dk header.b="Vub0Ksgh" X-Envelope-To: broonie@kernel.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqrs.dk; s=key1; t=1715951816; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=aoT2lrcR+fSoon+5nFQKLzIon+tYudtRC6WweACFb2U=; b=Vub0Ksghb7U+GY2K5QmoEjBgJQsxJ2FhQH5cHdYuKBbWrLz/NH0WKLvjeeA6k6I0idG3mx JMAKohti18ob3V/hv0HJk5qO28wjy+aPzA37yZBk2MUKhE/K4ByWa4SuO6YN4hfjpydzbT 6i1MmJ3ahd99XJM+PH825HpB6VjQNkIYA4GY9hroEAg5nYjJpBMZrwVS7mxD8+SCCdmwUP UwqF0PER8BryHj1iovUY/KrusPIAIT8wKOpKy95vmenGVrhU1JUhQkz3MuZRg6ekcy0duI IRUwGczmtNM5q2z3K+qLV99AT1e4mr3lvbwVdI8pWtDTyFamdiCBlxp2QJaIIA== X-Envelope-To: gregkh@linuxfoundation.org X-Envelope-To: rafael@kernel.org X-Envelope-To: robh@kernel.org X-Envelope-To: krzk+dt@kernel.org X-Envelope-To: conor+dt@kernel.org X-Envelope-To: linus.walleij@linaro.org X-Envelope-To: brgl@bgdev.pl X-Envelope-To: lgirdwood@gmail.com X-Envelope-To: perex@perex.cz X-Envelope-To: tiwai@suse.com X-Envelope-To: mturquette@baylibre.com X-Envelope-To: sboyd@kernel.org X-Envelope-To: andi.shyti@kernel.org X-Envelope-To: saravanak@google.com X-Envelope-To: emas@bang-olufsen.dk X-Envelope-To: linux-kernel@vger.kernel.org X-Envelope-To: devicetree@vger.kernel.org X-Envelope-To: linux-gpio@vger.kernel.org X-Envelope-To: linux-sound@vger.kernel.org X-Envelope-To: linux-clk@vger.kernel.org X-Envelope-To: linux-i2c@vger.kernel.org X-Envelope-To: alsi@bang-olufsen.dk X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: =?utf-8?q?Alvin_=C5=A0ipraga?= Date: Fri, 17 May 2024 15:02:20 +0200 Subject: [PATCH 13/13] MAINTAINERS: add maintainership for A2B drivers Precedence: bulk X-Mailing-List: linux-i2c@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240517-a2b-v1-13-b8647554c67b@bang-olufsen.dk> References: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> In-Reply-To: <20240517-a2b-v1-0-b8647554c67b@bang-olufsen.dk> To: Mark Brown , Greg Kroah-Hartman , "Rafael J. Wysocki" , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Linus Walleij , Bartosz Golaszewski , Liam Girdwood , Jaroslav Kysela , Takashi Iwai , Michael Turquette , Stephen Boyd , Andi Shyti , Saravana Kannan Cc: Emil Svendsen , linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-sound@vger.kernel.org, linux-clk@vger.kernel.org, linux-i2c@vger.kernel.org, =?utf-8?q?Alvin_=C5=A0ipraga?= X-Migadu-Flow: FLOW_OUT From: Alvin Šipraga Add all relevant A2B driver files to the MAINTAINERS file and mark myself as maintainer. Signed-off-by: Alvin Šipraga --- 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 +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 R: Dave Ertman