From patchwork Sun Dec 15 20:53:00 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 1210008 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="n9h1vSh4"; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47bc7h4VZQz9sP3 for ; Mon, 16 Dec 2019 07:53:36 +1100 (AEDT) Received: from phobos.denx.de (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 6F61880929; Sun, 15 Dec 2019 21:53:17 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="n9h1vSh4"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 5D98280919; Sun, 15 Dec 2019 21:53:12 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FROM,SPF_HELO_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-wm1-x341.google.com (mail-wm1-x341.google.com [IPv6:2a00:1450:4864:20::341]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id A5DD5808B6 for ; Sun, 15 Dec 2019 21:53:08 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=olteanv@gmail.com Received: by mail-wm1-x341.google.com with SMTP id d139so3567228wmd.0 for ; Sun, 15 Dec 2019 12:53:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=RmO291dYQOcd2NAy01sfKFq+fNzuBQ79MWECJ6OiH0E=; b=n9h1vSh4m9pQ+RNZnm8hatWkH5B5rFzaaop2O/CsWpKxBkiuPLa/yo4QeWsftlF/QO JJxJKB3XP6bkXwQH329Q7jP4NUgcqH/sNBdc3bz0un+vnhq2BYSNTXgFrK+7ljGWjUyG CHlUHsG6qkIRg55PAzgkX2AVMNSvAK7crijz6YhxiYMLEykiVcFUjokKvT/dbHLFlPf5 K8xZjEbn3Tf5MN+pkj3EfJ+qD8MG1zl0iCmFtkHTwDtW6V6HKM72fL4YyMhD7ifugMIu 8BywDFHtmrgBmWZi5MhfTKdeexqURUKzJqR//gxuICYmfUJ2jgQEtqCTNwPV76AETD/G vh4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=RmO291dYQOcd2NAy01sfKFq+fNzuBQ79MWECJ6OiH0E=; b=IY7nnVtSayAQ3SGoud5bO7ALqXaJna4ZueoKdLOKptojcGQPkXpdyV6QMFac8K0F5E jeuVGxFykeoC62xfS0df1nxnaf0QvgEu4JqybelyNwl5UdCv2vrD1XPvUOnFRthwSmLQ YOb7p8szVXCh1Exjszy2fgS0uaiuZA17MbOwQDi4bUvRvgxHCuupH7mCUAJ2j/TzNf3A UptpXVKwKH1qoBO3jHygVJFR6GfFw7McjtNmhpk2QL/Mf7kdWyHH6xlwasVKzK7bzK32 pW5o9PRbvodsOIwZ5CMGMCwIiaeO/xzt2cEGNqFTNM7KniCeYeDYnqhB78TlL6CGbb2I TctA== X-Gm-Message-State: APjAAAWDerJBXU4p8SDaSIVmzvnmXC2VYgUzHD9PeyO6m3Tg5UbrhiGJ 524GwNZrYVA1/y9pBB0CTGo= X-Google-Smtp-Source: APXvYqyWPW9SrF0MGyAT7NMt1vhJNsGA5FtLfLYbniZBmthmk1X5GyAwzbTpeZcmq0htdJ1gKYnXqQ== X-Received: by 2002:a05:600c:2383:: with SMTP id m3mr27869326wma.32.1576443187926; Sun, 15 Dec 2019 12:53:07 -0800 (PST) Received: from localhost.localdomain ([86.121.29.241]) by smtp.gmail.com with ESMTPSA id g21sm19917028wrb.48.2019.12.15.12.53.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Dec 2019 12:53:07 -0800 (PST) From: Vladimir Oltean To: joe.hershberger@ni.com, u-boot@lists.denx.de Subject: [PATCH 1/3] lib: import packing API from Linux Date: Sun, 15 Dec 2019 22:53:00 +0200 Message-Id: <20191215205302.13325-2-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20191215205302.13325-1-olteanv@gmail.com> References: <20191215205302.13325-1-olteanv@gmail.com> X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.26 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: joseph.hershberger@ni.com, o.rempel@pengutronix.de, georg.waibel@sensor-technik.de Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.101.4 at phobos.denx.de X-Virus-Status: Clean This is needed to support the sja1105 DSA driver. Signed-off-by: Vladimir Oltean --- include/linux/packing.h | 49 ++++++++++ lib/Kconfig | 17 ++++ lib/Makefile | 1 + lib/packing.c | 208 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 include/linux/packing.h create mode 100644 lib/packing.c diff --git a/include/linux/packing.h b/include/linux/packing.h new file mode 100644 index 000000000000..54667735cc67 --- /dev/null +++ b/include/linux/packing.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 2016-2018, NXP Semiconductors + * Copyright (c) 2018-2019, Vladimir Oltean + */ +#ifndef _LINUX_PACKING_H +#define _LINUX_PACKING_H + +#include +#include + +#define QUIRK_MSB_ON_THE_RIGHT BIT(0) +#define QUIRK_LITTLE_ENDIAN BIT(1) +#define QUIRK_LSW32_IS_FIRST BIT(2) + +enum packing_op { + PACK, + UNPACK, +}; + +/** + * packing - Convert numbers (currently u64) between a packed and an unpacked + * format. Unpacked means laid out in memory in the CPU's native + * understanding of integers, while packed means anything else that + * requires translation. + * + * @pbuf: Pointer to a buffer holding the packed value. + * @uval: Pointer to an u64 holding the unpacked value. + * @startbit: The index (in logical notation, compensated for quirks) where + * the packed value starts within pbuf. Must be larger than, or + * equal to, endbit. + * @endbit: The index (in logical notation, compensated for quirks) where + * the packed value ends within pbuf. Must be smaller than, or equal + * to, startbit. + * @op: If PACK, then uval will be treated as const pointer and copied (packed) + * into pbuf, between startbit and endbit. + * If UNPACK, then pbuf will be treated as const pointer and the logical + * value between startbit and endbit will be copied (unpacked) to uval. + * @quirks: A bit mask of QUIRK_LITTLE_ENDIAN, QUIRK_LSW32_IS_FIRST and + * QUIRK_MSB_ON_THE_RIGHT. + * + * Return: 0 on success, EINVAL or ERANGE if called incorrectly. Assuming + * correct usage, return code may be discarded. + * If op is PACK, pbuf is modified. + * If op is UNPACK, uval is modified. + */ +int packing(void *pbuf, u64 *uval, int startbit, int endbit, size_t pbuflen, + enum packing_op op, u8 quirks); + +#endif diff --git a/lib/Kconfig b/lib/Kconfig index 965cf7bc039c..a828b80a40d3 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -33,6 +33,23 @@ config HAVE_PRIVATE_LIBGCC config LIB_UUID bool +config PACKING + bool "Generic bitfield packing and unpacking" + default n + help + This option provides the packing() helper function, which permits + converting bitfields between a CPU-usable representation and a + memory representation that can have any combination of these quirks: + - Is little endian (bytes are reversed within a 32-bit group) + - The least-significant 32-bit word comes first (within a 64-bit + group) + - The most significant bit of a byte is at its right (bit 0 of a + register description is numerically 2^7). + Drivers may use these helpers to match the bit indices as described + in the data sheets of the peripherals they are in control of. + + When in doubt, say N. + config PRINTF bool default y diff --git a/lib/Makefile b/lib/Makefile index 1fb650cd90d1..4f99bd47c21e 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -104,6 +104,7 @@ obj-y += hexdump.o obj-$(CONFIG_TRACE) += trace.o obj-$(CONFIG_LIB_UUID) += uuid.o obj-$(CONFIG_LIB_RAND) += rand.o +obj-$(CONFIG_PACKING) += packing.o obj-y += panic.o ifeq ($(CONFIG_$(SPL_TPL_)BUILD),y) diff --git a/lib/packing.c b/lib/packing.c new file mode 100644 index 000000000000..93af114ee1fe --- /dev/null +++ b/lib/packing.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* Copyright (c) 2016-2018, NXP Semiconductors + * Copyright (c) 2018-2019, Vladimir Oltean + */ +#include +#include +#include +#include + +static int get_le_offset(int offset) +{ + int closest_multiple_of_4; + + closest_multiple_of_4 = (offset / 4) * 4; + offset -= closest_multiple_of_4; + return closest_multiple_of_4 + (3 - offset); +} + +static int get_reverse_lsw32_offset(int offset, size_t len) +{ + int closest_multiple_of_4; + int word_index; + + word_index = offset / 4; + closest_multiple_of_4 = word_index * 4; + offset -= closest_multiple_of_4; + word_index = (len / 4) - word_index - 1; + return word_index * 4 + offset; +} + +static u64 bit_reverse(u64 val, unsigned int width) +{ + u64 new_val = 0; + unsigned int bit; + unsigned int i; + + for (i = 0; i < width; i++) { + bit = (val & (1 << i)) != 0; + new_val |= (bit << (width - i - 1)); + } + return new_val; +} + +static void adjust_for_msb_right_quirk(u64 *to_write, int *box_start_bit, + int *box_end_bit, u8 *box_mask) +{ + int box_bit_width = *box_start_bit - *box_end_bit + 1; + int new_box_start_bit, new_box_end_bit; + + *to_write >>= *box_end_bit; + *to_write = bit_reverse(*to_write, box_bit_width); + *to_write <<= *box_end_bit; + + new_box_end_bit = box_bit_width - *box_start_bit - 1; + new_box_start_bit = box_bit_width - *box_end_bit - 1; + *box_mask = GENMASK_ULL(new_box_start_bit, new_box_end_bit); + *box_start_bit = new_box_start_bit; + *box_end_bit = new_box_end_bit; +} + +/** + * packing - Convert numbers (currently u64) between a packed and an unpacked + * format. Unpacked means laid out in memory in the CPU's native + * understanding of integers, while packed means anything else that + * requires translation. + * + * @pbuf: Pointer to a buffer holding the packed value. + * @uval: Pointer to an u64 holding the unpacked value. + * @startbit: The index (in logical notation, compensated for quirks) where + * the packed value starts within pbuf. Must be larger than, or + * equal to, endbit. + * @endbit: The index (in logical notation, compensated for quirks) where + * the packed value ends within pbuf. Must be smaller than, or equal + * to, startbit. + * @op: If PACK, then uval will be treated as const pointer and copied (packed) + * into pbuf, between startbit and endbit. + * If UNPACK, then pbuf will be treated as const pointer and the logical + * value between startbit and endbit will be copied (unpacked) to uval. + * @quirks: A bit mask of QUIRK_LITTLE_ENDIAN, QUIRK_LSW32_IS_FIRST and + * QUIRK_MSB_ON_THE_RIGHT. + * + * Return: 0 on success, EINVAL or ERANGE if called incorrectly. Assuming + * correct usage, return code may be discarded. + * If op is PACK, pbuf is modified. + * If op is UNPACK, uval is modified. + */ +int packing(void *pbuf, u64 *uval, int startbit, int endbit, size_t pbuflen, + enum packing_op op, u8 quirks) +{ + /* Number of bits for storing "uval" + * also width of the field to access in the pbuf + */ + u64 value_width; + /* Logical byte indices corresponding to the + * start and end of the field. + */ + int plogical_first_u8, plogical_last_u8, box; + + /* startbit is expected to be larger than endbit */ + if (startbit < endbit) + /* Invalid function call */ + return -EINVAL; + + value_width = startbit - endbit + 1; + if (value_width > 64) + return -ERANGE; + + /* Check if "uval" fits in "value_width" bits. + * If value_width is 64, the check will fail, but any + * 64-bit uval will surely fit. + */ + if (op == PACK && value_width < 64 && (*uval >= (1ull << value_width))) + /* Cannot store "uval" inside "value_width" bits. + * Truncating "uval" is most certainly not desirable, + * so simply erroring out is appropriate. + */ + return -ERANGE; + + /* Initialize parameter */ + if (op == UNPACK) + *uval = 0; + + /* Iterate through an idealistic view of the pbuf as an u64 with + * no quirks, u8 by u8 (aligned at u8 boundaries), from high to low + * logical bit significance. "box" denotes the current logical u8. + */ + plogical_first_u8 = startbit / 8; + plogical_last_u8 = endbit / 8; + + for (box = plogical_first_u8; box >= plogical_last_u8; box--) { + /* Bit indices into the currently accessed 8-bit box */ + int box_start_bit, box_end_bit, box_addr; + u8 box_mask; + /* Corresponding bits from the unpacked u64 parameter */ + int proj_start_bit, proj_end_bit; + u64 proj_mask; + + /* This u8 may need to be accessed in its entirety + * (from bit 7 to bit 0), or not, depending on the + * input arguments startbit and endbit. + */ + if (box == plogical_first_u8) + box_start_bit = startbit % 8; + else + box_start_bit = 7; + if (box == plogical_last_u8) + box_end_bit = endbit % 8; + else + box_end_bit = 0; + + /* We have determined the box bit start and end. + * Now we calculate where this (masked) u8 box would fit + * in the unpacked (CPU-readable) u64 - the u8 box's + * projection onto the unpacked u64. Though the + * box is u8, the projection is u64 because it may fall + * anywhere within the unpacked u64. + */ + proj_start_bit = ((box * 8) + box_start_bit) - endbit; + proj_end_bit = ((box * 8) + box_end_bit) - endbit; + proj_mask = GENMASK_ULL(proj_start_bit, proj_end_bit); + box_mask = GENMASK_ULL(box_start_bit, box_end_bit); + + /* Determine the offset of the u8 box inside the pbuf, + * adjusted for quirks. The adjusted box_addr will be used for + * effective addressing inside the pbuf (so it's not + * logical any longer). + */ + box_addr = pbuflen - box - 1; + if (quirks & QUIRK_LITTLE_ENDIAN) + box_addr = get_le_offset(box_addr); + if (quirks & QUIRK_LSW32_IS_FIRST) + box_addr = get_reverse_lsw32_offset(box_addr, + pbuflen); + + if (op == UNPACK) { + u64 pval; + + /* Read from pbuf, write to uval */ + pval = ((u8 *)pbuf)[box_addr] & box_mask; + if (quirks & QUIRK_MSB_ON_THE_RIGHT) + adjust_for_msb_right_quirk(&pval, + &box_start_bit, + &box_end_bit, + &box_mask); + + pval >>= box_end_bit; + pval <<= proj_end_bit; + *uval &= ~proj_mask; + *uval |= pval; + } else { + u64 pval; + + /* Write to pbuf, read from uval */ + pval = (*uval) & proj_mask; + pval >>= proj_end_bit; + if (quirks & QUIRK_MSB_ON_THE_RIGHT) + adjust_for_msb_right_quirk(&pval, + &box_start_bit, + &box_end_bit, + &box_mask); + + pval <<= box_end_bit; + ((u8 *)pbuf)[box_addr] &= ~box_mask; + ((u8 *)pbuf)[box_addr] |= pval; + } + } + return 0; +} From patchwork Sun Dec 15 20:53:01 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 1210009 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=85.214.62.61; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Ek7GP4xV"; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47bc7p3QRNz9sP3 for ; Mon, 16 Dec 2019 07:53:42 +1100 (AEDT) Received: from phobos.denx.de (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 4162680E80; Sun, 15 Dec 2019 21:53:19 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Ek7GP4xV"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 7675380929; Sun, 15 Dec 2019 21:53:14 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, SPF_HELO_NONE, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-wr1-x441.google.com (mail-wr1-x441.google.com [IPv6:2a00:1450:4864:20::441]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id 5938A808E5 for ; Sun, 15 Dec 2019 21:53:09 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=olteanv@gmail.com Received: by mail-wr1-x441.google.com with SMTP id w15so4812642wru.4 for ; Sun, 15 Dec 2019 12:53:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=VCcdZog+kgOptXIpMd94LiH5IabpcdbhbetESUEct2o=; b=Ek7GP4xVORcoBuO5dXV1NUAQRaE8Hx4dzqNtcj7cehvvBPIZGzLSUbRIM8OEMrvA5s pU7/Egu8b+vyeWrahQJY4ufEV1/zGbIPPXAqby7q2jMf3HnP4aYYWTAWT5jIzTxwnpO3 dXS2B3kVTF1WVLzw86eP9xzpcPx7lVkwy41NURCHn2fIp4XRQXcfIWImn13lz9jVV+cJ GjXpovz8xPECcLD7U8PSyiezrScyo+QwhesSZptGaFwWHvUT0JPukvlWRxhRflBtJy5o d2Vg6WZK20A+WZSXLQGffo2vhpkALqlcFFm+aqt0FLKOwaRbGl/ZMoP6xlNqTbLmhIYn MN4A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=VCcdZog+kgOptXIpMd94LiH5IabpcdbhbetESUEct2o=; b=dC+BdAYAFxwyQRq/QEimjBAqnj11i2HoLo1OJSBzOdHZ+Ln6QywNDC6Ibvxz1jMzMv /13HhP6UM21hdRfvhWndQdWBVVh/WjE4u6F9wuzc5I2nP+sR1lQmiAyqE1VizhsqD04V SC4kMPOpVjBk9Ma9Ka9yDuh5j1OtXi+XJqYQLonJKSZFGLF60tu7UmhVHqpwy/ZFUMqx R4Uh8S0yc62enrh32hxjykY8cCpi0rEpq5E4BH4VJVeOXsS1QjPfW+q4AZANvShJ6uIt Qi1mVyXC9QAjTLjEu9DrgJqQqykynBn+THRGl8Y/al+nAW7bSdTaOcqkrchkAp0oW/6y U38A== X-Gm-Message-State: APjAAAXcw4sbRczzRgrZp9CidBlJD5KDemLsX+lVvHwk9t7ZcP1cqbE4 p0773m07o/J4XK7wF5ujWSQ= X-Google-Smtp-Source: APXvYqwE0YGdB9pHEt4lwG4R28f/QWMk7E6ghKFBM5V0sKz9DcD2lM/GMLbvJ2sIHRTJu9yTLWG27Q== X-Received: by 2002:adf:ea4f:: with SMTP id j15mr26909229wrn.356.1576443188987; Sun, 15 Dec 2019 12:53:08 -0800 (PST) Received: from localhost.localdomain ([86.121.29.241]) by smtp.gmail.com with ESMTPSA id g21sm19917028wrb.48.2019.12.15.12.53.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Dec 2019 12:53:08 -0800 (PST) From: Vladimir Oltean To: joe.hershberger@ni.com, u-boot@lists.denx.de Subject: [PATCH 2/3] include: import if_vlan.h from Linux Date: Sun, 15 Dec 2019 22:53:01 +0200 Message-Id: <20191215205302.13325-3-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20191215205302.13325-1-olteanv@gmail.com> References: <20191215205302.13325-1-olteanv@gmail.com> X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.26 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: joseph.hershberger@ni.com, o.rempel@pengutronix.de, georg.waibel@sensor-technik.de Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.101.4 at phobos.denx.de X-Virus-Status: Clean This is needed for the VLAN header structure. Signed-off-by: Vladimir Oltean --- include/linux/if_vlan.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 include/linux/if_vlan.h diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h new file mode 100644 index 000000000000..cbc82f4cc217 --- /dev/null +++ b/include/linux/if_vlan.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * VLAN An implementation of 802.1Q VLAN tagging. + * + * Authors: Ben Greear + */ +#ifndef _LINUX_IF_VLAN_H_ +#define _LINUX_IF_VLAN_H_ + +/** + * struct vlan_ethhdr - vlan ethernet header (ethhdr + vlan_hdr) + * @h_dest: destination ethernet address + * @h_source: source ethernet address + * @h_vlan_proto: ethernet protocol + * @h_vlan_TCI: priority and VLAN ID + * @h_vlan_encapsulated_proto: packet type ID or len + */ +struct vlan_ethhdr { + unsigned char h_dest[ETH_ALEN]; + unsigned char h_source[ETH_ALEN]; + __be16 h_vlan_proto; + __be16 h_vlan_TCI; + __be16 h_vlan_encapsulated_proto; +}; + +#endif /* !(_LINUX_IF_VLAN_H_) */ From patchwork Sun Dec 15 20:53:02 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladimir Oltean X-Patchwork-Id: 1210010 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="rvGKLAoa"; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47bc803d9wz9sP3 for ; Mon, 16 Dec 2019 07:53:52 +1100 (AEDT) Received: from phobos.denx.de (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id EA45A8148F; Sun, 15 Dec 2019 21:53:25 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="rvGKLAoa"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id D33A08149B; Sun, 15 Dec 2019 21:53:21 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU, FREEMAIL_FROM, SPF_HELO_NONE, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-wr1-x442.google.com (mail-wr1-x442.google.com [IPv6:2a00:1450:4864:20::442]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id C4FF880889 for ; Sun, 15 Dec 2019 21:53:11 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=olteanv@gmail.com Received: by mail-wr1-x442.google.com with SMTP id c14so4805550wrn.7 for ; Sun, 15 Dec 2019 12:53:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=uAgBi4eyOc9GUIKbMOcEnrsPEnD5nIlhcHuJozm/8QQ=; b=rvGKLAoaNs3rIvPgSftoYIzwa9EFVl7aL+JcCJXcvYL0cFYBnjBUOuBXoJxqhp7tbR ubgy+3ZafBhhtWE3P+VJ6yPNiIu+6ljXF8pOKsiGud1sNRvxGqH8KbqCmjjdrfavSboY 2wFnMBz09ur9falkdkrOSL/VmkZcLhw2YP09ydYf2A/4ZNbtEsU8yjU2jTtcAjVvorpU dDQHWYxCYp2H1LTL8EBDGCYhc93PmPV9K2hzG0jJWwjTWTXvO6zDt6/I6gYbZ//1CHe6 pgJIZK6ZmCI1j+xx5ZQ2iQ/Vi2C0ivuCRVFKO9ivqxzU1EbzoE9Ri6Qyjfo2Us7P4PJj jyVA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=uAgBi4eyOc9GUIKbMOcEnrsPEnD5nIlhcHuJozm/8QQ=; b=N01f2qoFWxszvvNTlaFFiHhkqcwn7mnznzF6JVgyL9jNQ0mKDuEXaXICDsSQiL6ufz 1EMAAXqItgK3Bs3v+ONN2iw4kT4dA3fykP7iQmhqIDgeQ+U3iWqZvhHdp20ueuz74LYB zDzEPAgPVcrxHGbqGITPH9HAkw1hMHHcJEI9kBiOqHPYf8ccjmfy0SmzuLdpOW3MiV38 ZsPVrm22m7/uximhPVgfX19YAJB2Vdc3/FiHmc9X6+lMDla5gKK67DY1+Lz2Rfg0+IWJ 3Spdgl9brOJ5pS3Qd7TAcyWs8xncj9OEVWn7ko010UzKHlHc1iLlnsN6lh5b5Kxw/jsu TFBg== X-Gm-Message-State: APjAAAXzFcR2ICHR5wpv+qEPnFJVup5heJL2Vyyr0nkPQ8oAzoSRZ8Ic vCQ64PQ1RrIME4QPfSQcOtA= X-Google-Smtp-Source: APXvYqxyGpDvjYiCqGzQkyMLJ0JQzGcUhVbsoXd6FC5k60Pej+OwLqq/NzHQvM1JxmxVbtYolVUuZQ== X-Received: by 2002:adf:dd46:: with SMTP id u6mr26787986wrm.13.1576443190202; Sun, 15 Dec 2019 12:53:10 -0800 (PST) Received: from localhost.localdomain ([86.121.29.241]) by smtp.gmail.com with ESMTPSA id g21sm19917028wrb.48.2019.12.15.12.53.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Dec 2019 12:53:09 -0800 (PST) From: Vladimir Oltean To: joe.hershberger@ni.com, u-boot@lists.denx.de Subject: [PATCH 3/3] net: add driver for NXP SJA1105 DSA L2 switch Date: Sun, 15 Dec 2019 22:53:02 +0200 Message-Id: <20191215205302.13325-4-olteanv@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20191215205302.13325-1-olteanv@gmail.com> References: <20191215205302.13325-1-olteanv@gmail.com> X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.26 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: joseph.hershberger@ni.com, o.rempel@pengutronix.de, georg.waibel@sensor-technik.de Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.101.4 at phobos.denx.de X-Virus-Status: Clean The SJA1105 driver is largely reused from Linux. Its programming model is that it is blank out of reset, and it waits for a static configuration stream over SPI, which contains all runtime parameters (it has no notion of "default values"). Keeping a binary array for the configuration stream would have meant that aspects such as the CPU port and the MAC speeds could have not been configured easily, and would have been static and board-dependent. Live-patching the binary array means recalculating the static config table CRCs, which is not a fun process. So we create an abstraction over the static config tables, using the packing API, same as in Linux. The tables are kept as C structures, and the binary configuration stream is constructed on-the-go, with CRC and all. All static config tables instantiated in this driver are mandatory. The hardware reference manual can be found at: https://www.nxp.com/docs/en/user-guide/UM10944.pdf For tagging, a simplified version of dsa_8021q from Linux is used. The VLAN EtherType is the same (0xdadb) but since we don't want switching in U-Boot, there is no reason to have a TX VLAN and an RX VLAN for each port. We just need the RX VLANs to act as the unique pvid of each front-panel port, to decode the switch port number. The RX VLAN is used for both RX and TX. The device tree bindings are the same as in Linux, sans the custom sja1105,role-mac and sja1105,role-phy which are not implemented yet since I saw no immediate need for them. Signed-off-by: Vladimir Oltean --- drivers/net/Kconfig | 17 + drivers/net/Makefile | 1 + drivers/net/sja1105.c | 2309 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2327 insertions(+) create mode 100644 drivers/net/sja1105.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index ff14e79453f2..7da091e6300d 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -381,6 +381,23 @@ config RTL8169 This driver supports Realtek 8169 series gigabit ethernet family of PCI/PCIe chipsets/adapters. +config SJA1105 + bool "NXP SJA1105 Ethernet switch family driver" + depends on DM_DSA && DM_SPI + select BITREVERSE + select PACKING + help + This is the driver for the NXP SJA1105 automotive Ethernet switch + family. These are 5-port devices and are managed over an SPI + interface. Probing is handled based on OF bindings. The driver + supports the following revisions: + - SJA1105E (Gen. 1, No TT-Ethernet) + - SJA1105T (Gen. 1, TT-Ethernet) + - SJA1105P (Gen. 2, No SGMII, No TT-Ethernet) + - SJA1105Q (Gen. 2, No SGMII, TT-Ethernet) + - SJA1105R (Gen. 2, SGMII, No TT-Ethernet) + - SJA1105S (Gen. 2, SGMII, TT-Ethernet) + config SMC911X bool "SMSC LAN911x and LAN921x controller driver" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 8be49f63352b..67204ef3f481 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_DNET) += dnet.o obj-$(CONFIG_E1000) += e1000.o obj-$(CONFIG_E1000_SPI) += e1000_spi.o obj-$(CONFIG_EEPRO100) += eepro100.o +obj-$(CONFIG_SJA1105) += sja1105.o obj-$(CONFIG_SUN4I_EMAC) += sunxi_emac.o obj-$(CONFIG_SUN8I_EMAC) += sun8i_emac.o obj-$(CONFIG_EP93XX) += ep93xx_eth.o diff --git a/drivers/net/sja1105.c b/drivers/net/sja1105.c new file mode 100644 index 000000000000..8e64845dcc06 --- /dev/null +++ b/drivers/net/sja1105.c @@ -0,0 +1,2309 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright 2016-2018, NXP Semiconductors + * Copyright 2018, Sensor-Technik Wiedemann GmbH + * Copyright 2018-2019, Vladimir Oltean + * + * Ported from Linux (drivers/net/dsa/sja1105/). + */ + +#include +#include +#include +#include +#include +#include + +#define ETHER_CRC32_POLY 0x04C11DB7 +#define ETH_P_SJA1105 0xdadb +#define SJA1105_NUM_PORTS 5 +#define SJA1105_NUM_TC 8 +#define SJA1105ET_FDB_BIN_SIZE 4 +#define SJA1105_SIZE_CGU_CMD 4 +#define SJA1105_SIZE_RESET_CMD 4 +#define SJA1105_SIZE_SPI_MSG_HEADER 4 +#define SJA1105_SIZE_SPI_MSG_MAXLEN (64 * 4) +#define SJA1105_SIZE_DEVICE_ID 4 +#define SJA1105_SIZE_TABLE_HEADER 12 +#define SJA1105_SIZE_L2_POLICING_ENTRY 8 +#define SJA1105_SIZE_VLAN_LOOKUP_ENTRY 8 +#define SJA1105_SIZE_L2_FORWARDING_ENTRY 8 +#define SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY 12 +#define SJA1105_SIZE_XMII_PARAMS_ENTRY 4 +#define SJA1105ET_SIZE_MAC_CONFIG_ENTRY 28 +#define SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY 4 +#define SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY 40 +#define SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY 32 +#define SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY 16 +#define SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY 44 + +#define SJA1105_MAX_L2_LOOKUP_COUNT 1024 +#define SJA1105_MAX_L2_POLICING_COUNT 45 +#define SJA1105_MAX_VLAN_LOOKUP_COUNT 4096 +#define SJA1105_MAX_L2_FORWARDING_COUNT 13 +#define SJA1105_MAX_MAC_CONFIG_COUNT 5 +#define SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT 1 +#define SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT 1 +#define SJA1105_MAX_GENERAL_PARAMS_COUNT 1 +#define SJA1105_MAX_XMII_PARAMS_COUNT 1 + +#define SJA1105_MAX_FRAME_MEMORY 929 + +#define SJA1105E_DEVICE_ID 0x9C00000Cull +#define SJA1105T_DEVICE_ID 0x9E00030Eull +#define SJA1105PR_DEVICE_ID 0xAF00030Eull +#define SJA1105QS_DEVICE_ID 0xAE00030Eull + +#define SJA1105ET_PART_NO 0x9A83 +#define SJA1105P_PART_NO 0x9A84 +#define SJA1105Q_PART_NO 0x9A85 +#define SJA1105R_PART_NO 0x9A86 +#define SJA1105S_PART_NO 0x9A87 + +#define DSA_8021Q_DIR_RX BIT(10) +#define DSA_8021Q_PORT_SHIFT 0 +#define DSA_8021Q_PORT_MASK GENMASK(3, 0) +#define DSA_8021Q_PORT(x) (((x) << DSA_8021Q_PORT_SHIFT) & \ + DSA_8021Q_PORT_MASK) + +#define SJA1105_RATE_MBPS(speed) (((speed) * 64000) / 1000) + +/* UM10944.pdf Page 11, Table 2. Configuration Blocks */ +enum { + BLKID_L2_POLICING = 0x06, + BLKID_VLAN_LOOKUP = 0x07, + BLKID_L2_FORWARDING = 0x08, + BLKID_MAC_CONFIG = 0x09, + BLKID_L2_LOOKUP_PARAMS = 0x0D, + BLKID_L2_FORWARDING_PARAMS = 0x0E, + BLKID_GENERAL_PARAMS = 0x11, + BLKID_XMII_PARAMS = 0x4E, +}; + +enum sja1105_blk_idx { + BLK_IDX_L2_POLICING = 0, + BLK_IDX_VLAN_LOOKUP, + BLK_IDX_L2_FORWARDING, + BLK_IDX_MAC_CONFIG, + BLK_IDX_L2_LOOKUP_PARAMS, + BLK_IDX_L2_FORWARDING_PARAMS, + BLK_IDX_GENERAL_PARAMS, + BLK_IDX_XMII_PARAMS, + BLK_IDX_MAX, +}; + +struct sja1105_general_params_entry { + u64 mac_fltres1; + u64 mac_fltres0; + u64 mac_flt1; + u64 mac_flt0; + u64 casc_port; + u64 host_port; + u64 mirr_port; + u64 tpid; + u64 tpid2; +}; + +struct sja1105_vlan_lookup_entry { + u64 vmemb_port; + u64 vlan_bc; + u64 tag_port; + u64 vlanid; +}; + +struct sja1105_l2_lookup_params_entry { + u64 maxaddrp[5]; + u64 start_dynspc; + u64 use_static; + u64 owr_dyn; + u64 dyn_tbsz; + u64 poly; +}; + +struct sja1105_l2_forwarding_entry { + u64 bc_domain; + u64 reach_port; + u64 fl_domain; +}; + +struct sja1105_l2_forwarding_params_entry { + u64 part_spc[8]; +}; + +struct sja1105_l2_policing_entry { + u64 sharindx; + u64 smax; + u64 rate; + u64 maxlen; + u64 partition; +}; + +struct sja1105_mac_config_entry { + u64 top[8]; + u64 base[8]; + u64 enabled[8]; + u64 speed; + u64 maxage; + u64 vlanprio; + u64 vlanid; + u64 dyn_learn; + u64 egress; + u64 ingress; +}; + +struct sja1105_xmii_params_entry { + u64 phy_mac[5]; + u64 xmii_mode[5]; +}; + +struct sja1105_table_header { + u64 block_id; + u64 len; + u64 crc; +}; + +struct sja1105_table_ops { + size_t (*packing)(void *buf, void *entry_ptr, enum packing_op op); + size_t unpacked_entry_size; + size_t packed_entry_size; + size_t max_entry_count; +}; + +struct sja1105_table { + const struct sja1105_table_ops *ops; + size_t entry_count; + void *entries; +}; + +struct sja1105_static_config { + u64 device_id; + struct sja1105_table tables[BLK_IDX_MAX]; +}; + +struct sja1105_private { + struct sja1105_static_config static_config; + bool rgmii_rx_delay[SJA1105_NUM_PORTS]; + bool rgmii_tx_delay[SJA1105_NUM_PORTS]; + int phy_modes[SJA1105_NUM_PORTS]; + u16 pvid[SJA1105_NUM_PORTS]; + const struct sja1105_info *info; + struct udevice *dev; +}; + +typedef enum { + SPI_READ = 0, + SPI_WRITE = 1, +} sja1105_spi_rw_mode_t; + +typedef enum { + XMII_MAC = 0, + XMII_PHY = 1, +} sja1105_mii_role_t; + +typedef enum { + XMII_MODE_MII = 0, + XMII_MODE_RMII = 1, + XMII_MODE_RGMII = 2, +} sja1105_phy_interface_t; + +typedef enum { + SJA1105_SPEED_10MBPS = 3, + SJA1105_SPEED_100MBPS = 2, + SJA1105_SPEED_1000MBPS = 1, +} sja1105_speed_t; + +/* Keeps the different addresses between E/T and P/Q/R/S */ +struct sja1105_regs { + u64 device_id; + u64 prod_id; + u64 status; + u64 port_control; + u64 rgu; + u64 config; + u64 rmii_pll1; + u64 pad_mii_tx[SJA1105_NUM_PORTS]; + u64 pad_mii_id[SJA1105_NUM_PORTS]; + u64 cgu_idiv[SJA1105_NUM_PORTS]; + u64 mii_tx_clk[SJA1105_NUM_PORTS]; + u64 mii_rx_clk[SJA1105_NUM_PORTS]; + u64 mii_ext_tx_clk[SJA1105_NUM_PORTS]; + u64 mii_ext_rx_clk[SJA1105_NUM_PORTS]; + u64 rgmii_tx_clk[SJA1105_NUM_PORTS]; + u64 rmii_ref_clk[SJA1105_NUM_PORTS]; + u64 rmii_ext_tx_clk[SJA1105_NUM_PORTS]; +}; + +struct sja1105_info { + u64 device_id; + u64 part_no; + const struct sja1105_table_ops *static_ops; + const struct sja1105_regs *regs; + int (*reset_cmd)(struct sja1105_private *priv); + int (*setup_rgmii_delay)(struct sja1105_private *priv, int port); + const char *name; +}; + +struct sja1105_chunk { + u8 *buf; + size_t len; + u64 reg_addr; +}; + +struct sja1105_spi_message { + u64 access; + u64 read_count; + u64 address; +}; + +struct sja1105_cfg_pad_mii_tx { + u64 d32_os; + u64 d32_ipud; + u64 d10_os; + u64 d10_ipud; + u64 ctrl_os; + u64 ctrl_ipud; + u64 clk_os; + u64 clk_ih; + u64 clk_ipud; +}; + +struct sja1105_cfg_pad_mii_id { + u64 rxc_stable_ovr; + u64 rxc_delay; + u64 rxc_bypass; + u64 rxc_pd; + u64 txc_stable_ovr; + u64 txc_delay; + u64 txc_bypass; + u64 txc_pd; +}; + +struct sja1105_cgu_idiv { + u64 clksrc; + u64 autoblock; + u64 idiv; + u64 pd; +}; + +struct sja1105_cgu_pll_ctrl { + u64 pllclksrc; + u64 msel; + u64 autoblock; + u64 psel; + u64 direct; + u64 fbsel; + u64 bypass; + u64 pd; +}; + +enum { + CLKSRC_MII0_TX_CLK = 0x00, + CLKSRC_MII0_RX_CLK = 0x01, + CLKSRC_MII1_TX_CLK = 0x02, + CLKSRC_MII1_RX_CLK = 0x03, + CLKSRC_MII2_TX_CLK = 0x04, + CLKSRC_MII2_RX_CLK = 0x05, + CLKSRC_MII3_TX_CLK = 0x06, + CLKSRC_MII3_RX_CLK = 0x07, + CLKSRC_MII4_TX_CLK = 0x08, + CLKSRC_MII4_RX_CLK = 0x09, + CLKSRC_PLL0 = 0x0B, + CLKSRC_PLL1 = 0x0E, + CLKSRC_IDIV0 = 0x11, + CLKSRC_IDIV1 = 0x12, + CLKSRC_IDIV2 = 0x13, + CLKSRC_IDIV3 = 0x14, + CLKSRC_IDIV4 = 0x15, +}; + +struct sja1105_cgu_mii_ctrl { + u64 clksrc; + u64 autoblock; + u64 pd; +}; + +static void sja1105_packing(void *buf, u64 *val, int start, int end, + size_t len, enum packing_op op) +{ + int rc; + + rc = packing(buf, val, start, end, len, op, QUIRK_LSW32_IS_FIRST); + if (likely(!rc)) + return; + + printf("Invalid use of packing API: start %d end %d returned %d\n", + start, end, rc); +} + +static u32 crc32_add(u32 crc, u8 byte) +{ + u32 byte32 = bitrev32(byte); + int i; + + for (i = 0; i < 8; i++) { + if ((crc ^ byte32) & BIT(31)) { + crc <<= 1; + crc ^= ETHER_CRC32_POLY; + } else { + crc <<= 1; + } + byte32 <<= 1; + } + return crc; +} + +/* Little-endian Ethernet CRC32 of data packed as big-endian u32 words */ +uint32_t sja1105_crc32(void *buf, size_t len) +{ + unsigned int i; + u64 chunk; + u32 crc; + + /* seed */ + crc = 0xFFFFFFFF; + for (i = 0; i < len; i += 4) { + sja1105_packing(buf + i, &chunk, 31, 0, 4, UNPACK); + crc = crc32_add(crc, chunk & 0xFF); + crc = crc32_add(crc, (chunk >> 8) & 0xFF); + crc = crc32_add(crc, (chunk >> 16) & 0xFF); + crc = crc32_add(crc, (chunk >> 24) & 0xFF); + } + return bitrev32(~crc); +} + +static void sja1105_spi_message_pack(void *buf, struct sja1105_spi_message *msg) +{ + const int size = SJA1105_SIZE_SPI_MSG_HEADER; + + memset(buf, 0, size); + + sja1105_packing(buf, &msg->access, 31, 31, size, PACK); + sja1105_packing(buf, &msg->read_count, 30, 25, size, PACK); + sja1105_packing(buf, &msg->address, 24, 4, size, PACK); +} + +static int sja1105_xfer_buf(const struct sja1105_private *priv, + sja1105_spi_rw_mode_t rw, u64 reg_addr, + u8 *buf, size_t len) +{ + struct udevice *dev = priv->dev; + struct sja1105_chunk chunk = { + .len = min_t(size_t, len, SJA1105_SIZE_SPI_MSG_MAXLEN), + .reg_addr = reg_addr, + .buf = buf, + }; + int num_chunks; + int rc, i; + + rc = dm_spi_claim_bus(dev); + if (rc) + return rc; + + num_chunks = DIV_ROUND_UP(len, SJA1105_SIZE_SPI_MSG_MAXLEN); + + for (i = 0; i < num_chunks; i++) { + u8 hdr_buf[SJA1105_SIZE_SPI_MSG_HEADER]; + struct sja1105_spi_message msg; + u8 *rx_buf = NULL; + u8 *tx_buf = NULL; + + /* Populate the transfer's header buffer */ + msg.address = chunk.reg_addr; + msg.access = rw; + if (rw == SPI_READ) + msg.read_count = chunk.len / 4; + else + /* Ignored */ + msg.read_count = 0; + sja1105_spi_message_pack(hdr_buf, &msg); + rc = dm_spi_xfer(dev, SJA1105_SIZE_SPI_MSG_HEADER * 8, hdr_buf, + NULL, SPI_XFER_BEGIN); + if (rc) + goto out; + + /* Populate the transfer's data buffer */ + if (rw == SPI_READ) + rx_buf = chunk.buf; + else + tx_buf = chunk.buf; + rc = dm_spi_xfer(dev, chunk.len * 8, tx_buf, rx_buf, + SPI_XFER_END); + if (rc) + goto out; + + /* Calculate next chunk */ + chunk.buf += chunk.len; + chunk.reg_addr += chunk.len / 4; + chunk.len = min_t(size_t, (ptrdiff_t)(buf + len - chunk.buf), + SJA1105_SIZE_SPI_MSG_MAXLEN); + } + +out: + dm_spi_release_bus(dev); + + return rc; +} + +static int sja1105et_reset_cmd(struct sja1105_private *priv) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_RESET_CMD] = {0}; + const int size = SJA1105_SIZE_RESET_CMD; + u64 cold_rst = 1; + + sja1105_packing(packed_buf, &cold_rst, 3, 3, size, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgu, packed_buf, + SJA1105_SIZE_RESET_CMD); +} + +static int sja1105pqrs_reset_cmd(struct sja1105_private *priv) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_RESET_CMD] = {0}; + const int size = SJA1105_SIZE_RESET_CMD; + u64 cold_rst = 1; + + sja1105_packing(packed_buf, &cold_rst, 2, 2, size, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgu, packed_buf, + SJA1105_SIZE_RESET_CMD); +} + +static size_t sja1105et_general_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY; + struct sja1105_general_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->mac_fltres1, 311, 264, size, op); + sja1105_packing(buf, &entry->mac_fltres0, 263, 216, size, op); + sja1105_packing(buf, &entry->mac_flt1, 215, 168, size, op); + sja1105_packing(buf, &entry->mac_flt0, 167, 120, size, op); + sja1105_packing(buf, &entry->casc_port, 115, 113, size, op); + sja1105_packing(buf, &entry->host_port, 112, 110, size, op); + sja1105_packing(buf, &entry->mirr_port, 109, 107, size, op); + sja1105_packing(buf, &entry->tpid, 42, 27, size, op); + sja1105_packing(buf, &entry->tpid2, 25, 10, size, op); + return size; +} + +static size_t +sja1105pqrs_general_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY; + struct sja1105_general_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->mac_fltres1, 343, 296, size, op); + sja1105_packing(buf, &entry->mac_fltres0, 295, 248, size, op); + sja1105_packing(buf, &entry->mac_flt1, 247, 200, size, op); + sja1105_packing(buf, &entry->mac_flt0, 199, 152, size, op); + sja1105_packing(buf, &entry->casc_port, 147, 145, size, op); + sja1105_packing(buf, &entry->host_port, 144, 142, size, op); + sja1105_packing(buf, &entry->mirr_port, 141, 139, size, op); + sja1105_packing(buf, &entry->tpid, 74, 59, size, op); + sja1105_packing(buf, &entry->tpid2, 57, 42, size, op); + return size; +} + +static size_t +sja1105_l2_forwarding_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY; + struct sja1105_l2_forwarding_params_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 13; i < 8; i++, offset += 10) + sja1105_packing(buf, &entry->part_spc[i], + offset + 9, offset + 0, size, op); + return size; +} + +static size_t sja1105_l2_forwarding_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_L2_FORWARDING_ENTRY; + struct sja1105_l2_forwarding_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->bc_domain, 63, 59, size, op); + sja1105_packing(buf, &entry->reach_port, 58, 54, size, op); + sja1105_packing(buf, &entry->fl_domain, 53, 49, size, op); + return size; +} + +static size_t +sja1105et_l2_lookup_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY; + struct sja1105_l2_lookup_params_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->dyn_tbsz, 16, 14, size, op); + sja1105_packing(buf, &entry->poly, 13, 6, size, op); + return size; +} + +static size_t +sja1105pqrs_l2_lookup_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY; + struct sja1105_l2_lookup_params_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 58; i < 5; i++, offset += 11) + sja1105_packing(buf, &entry->maxaddrp[i], + offset + 10, offset + 0, size, op); + sja1105_packing(buf, &entry->start_dynspc, 42, 33, size, op); + sja1105_packing(buf, &entry->use_static, 24, 24, size, op); + sja1105_packing(buf, &entry->owr_dyn, 23, 23, size, op); + return size; +} + +static size_t sja1105_l2_policing_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_L2_POLICING_ENTRY; + struct sja1105_l2_policing_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->sharindx, 63, 58, size, op); + sja1105_packing(buf, &entry->smax, 57, 42, size, op); + sja1105_packing(buf, &entry->rate, 41, 26, size, op); + sja1105_packing(buf, &entry->maxlen, 25, 15, size, op); + sja1105_packing(buf, &entry->partition, 14, 12, size, op); + return size; +} + +static size_t sja1105et_mac_config_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY; + struct sja1105_mac_config_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 72; i < 8; i++, offset += 19) { + sja1105_packing(buf, &entry->enabled[i], + offset + 0, offset + 0, size, op); + sja1105_packing(buf, &entry->base[i], + offset + 9, offset + 1, size, op); + sja1105_packing(buf, &entry->top[i], + offset + 18, offset + 10, size, op); + } + sja1105_packing(buf, &entry->speed, 66, 65, size, op); + sja1105_packing(buf, &entry->maxage, 32, 25, size, op); + sja1105_packing(buf, &entry->vlanprio, 24, 22, size, op); + sja1105_packing(buf, &entry->vlanid, 21, 10, size, op); + sja1105_packing(buf, &entry->dyn_learn, 3, 3, size, op); + sja1105_packing(buf, &entry->egress, 2, 2, size, op); + sja1105_packing(buf, &entry->ingress, 1, 1, size, op); + return size; +} + +static size_t sja1105pqrs_mac_config_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY; + struct sja1105_mac_config_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 104; i < 8; i++, offset += 19) { + sja1105_packing(buf, &entry->enabled[i], + offset + 0, offset + 0, size, op); + sja1105_packing(buf, &entry->base[i], + offset + 9, offset + 1, size, op); + sja1105_packing(buf, &entry->top[i], + offset + 18, offset + 10, size, op); + } + sja1105_packing(buf, &entry->speed, 98, 97, size, op); + sja1105_packing(buf, &entry->maxage, 64, 57, size, op); + sja1105_packing(buf, &entry->vlanprio, 56, 54, size, op); + sja1105_packing(buf, &entry->vlanid, 53, 42, size, op); + sja1105_packing(buf, &entry->dyn_learn, 33, 33, size, op); + sja1105_packing(buf, &entry->egress, 32, 32, size, op); + sja1105_packing(buf, &entry->ingress, 31, 31, size, op); + return size; +} + +static size_t sja1105_vlan_lookup_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY; + struct sja1105_vlan_lookup_entry *entry = entry_ptr; + + sja1105_packing(buf, &entry->vmemb_port, 53, 49, size, op); + sja1105_packing(buf, &entry->vlan_bc, 48, 44, size, op); + sja1105_packing(buf, &entry->tag_port, 43, 39, size, op); + sja1105_packing(buf, &entry->vlanid, 38, 27, size, op); + return size; +} + +static size_t sja1105_xmii_params_entry_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_XMII_PARAMS_ENTRY; + struct sja1105_xmii_params_entry *entry = entry_ptr; + int offset, i; + + for (i = 0, offset = 17; i < 5; i++, offset += 3) { + sja1105_packing(buf, &entry->xmii_mode[i], + offset + 1, offset + 0, size, op); + sja1105_packing(buf, &entry->phy_mac[i], + offset + 2, offset + 2, size, op); + } + return size; +} + +static size_t sja1105_table_header_packing(void *buf, void *entry_ptr, + enum packing_op op) +{ + const size_t size = SJA1105_SIZE_TABLE_HEADER; + struct sja1105_table_header *entry = entry_ptr; + + sja1105_packing(buf, &entry->block_id, 31, 24, size, op); + sja1105_packing(buf, &entry->len, 55, 32, size, op); + sja1105_packing(buf, &entry->crc, 95, 64, size, op); + return size; +} + +static void +sja1105_table_header_pack_with_crc(void *buf, struct sja1105_table_header *hdr) +{ + /* First pack the table as-is, then calculate the CRC, and + * finally put the proper CRC into the packed buffer + */ + memset(buf, 0, SJA1105_SIZE_TABLE_HEADER); + sja1105_table_header_packing(buf, hdr, PACK); + hdr->crc = sja1105_crc32(buf, SJA1105_SIZE_TABLE_HEADER - 4); + sja1105_packing(buf + SJA1105_SIZE_TABLE_HEADER - 4, &hdr->crc, + 31, 0, 4, PACK); +} + +static void sja1105_table_write_crc(u8 *table_start, u8 *crc_ptr) +{ + u64 computed_crc; + int len_bytes; + + len_bytes = (uintptr_t)(crc_ptr - table_start); + computed_crc = sja1105_crc32(table_start, len_bytes); + sja1105_packing(crc_ptr, &computed_crc, 31, 0, 4, PACK); +} + +/* The block IDs that the switches support are unfortunately sparse, so keep a + * mapping table to "block indices" and translate back and forth. + */ +static u64 blk_id_map[BLK_IDX_MAX] = { + [BLK_IDX_L2_POLICING] = BLKID_L2_POLICING, + [BLK_IDX_VLAN_LOOKUP] = BLKID_VLAN_LOOKUP, + [BLK_IDX_L2_FORWARDING] = BLKID_L2_FORWARDING, + [BLK_IDX_MAC_CONFIG] = BLKID_MAC_CONFIG, + [BLK_IDX_L2_LOOKUP_PARAMS] = BLKID_L2_LOOKUP_PARAMS, + [BLK_IDX_L2_FORWARDING_PARAMS] = BLKID_L2_FORWARDING_PARAMS, + [BLK_IDX_GENERAL_PARAMS] = BLKID_GENERAL_PARAMS, + [BLK_IDX_XMII_PARAMS] = BLKID_XMII_PARAMS, +}; + +static void +sja1105_static_config_pack(void *buf, struct sja1105_static_config *config) +{ + struct sja1105_table_header header = {0}; + enum sja1105_blk_idx i; + u8 *p = buf; + int j; + + sja1105_packing(p, &config->device_id, 31, 0, 4, PACK); + p += SJA1105_SIZE_DEVICE_ID; + + for (i = 0; i < BLK_IDX_MAX; i++) { + const struct sja1105_table *table; + u8 *table_start; + + table = &config->tables[i]; + if (!table->entry_count) + continue; + + header.block_id = blk_id_map[i]; + header.len = table->entry_count * + table->ops->packed_entry_size / 4; + sja1105_table_header_pack_with_crc(p, &header); + p += SJA1105_SIZE_TABLE_HEADER; + table_start = p; + for (j = 0; j < table->entry_count; j++) { + u8 *entry_ptr = table->entries; + + entry_ptr += j * table->ops->unpacked_entry_size; + memset(p, 0, table->ops->packed_entry_size); + table->ops->packing(p, entry_ptr, PACK); + p += table->ops->packed_entry_size; + } + sja1105_table_write_crc(table_start, p); + p += 4; + } + /* Final header: + * Block ID does not matter + * Length of 0 marks that header is final + * CRC will be replaced on-the-fly + */ + header.block_id = 0; + header.len = 0; + header.crc = 0xDEADBEEF; + memset(p, 0, SJA1105_SIZE_TABLE_HEADER); + sja1105_table_header_packing(p, &header, PACK); +} + +static size_t +sja1105_static_config_get_length(const struct sja1105_static_config *config) +{ + unsigned int header_count; + enum sja1105_blk_idx i; + unsigned int sum; + + /* Ending header */ + header_count = 1; + sum = SJA1105_SIZE_DEVICE_ID; + + /* Tables (headers and entries) */ + for (i = 0; i < BLK_IDX_MAX; i++) { + const struct sja1105_table *table; + + table = &config->tables[i]; + if (table->entry_count) + header_count++; + + sum += table->ops->packed_entry_size * table->entry_count; + } + /* Headers have an additional CRC at the end */ + sum += header_count * (SJA1105_SIZE_TABLE_HEADER + 4); + /* Last header does not have an extra CRC because there is no data */ + sum -= 4; + + return sum; +} + +/* Compatibility matrices */ +struct sja1105_table_ops sja1105et_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105et_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105ET_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105et_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105ET_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105et_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105ET_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +struct sja1105_table_ops sja1105pqrs_table_ops[BLK_IDX_MAX] = { + [BLK_IDX_L2_POLICING] = { + .packing = sja1105_l2_policing_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_policing_entry), + .packed_entry_size = SJA1105_SIZE_L2_POLICING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_POLICING_COUNT, + }, + [BLK_IDX_VLAN_LOOKUP] = { + .packing = sja1105_vlan_lookup_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_vlan_lookup_entry), + .packed_entry_size = SJA1105_SIZE_VLAN_LOOKUP_ENTRY, + .max_entry_count = SJA1105_MAX_VLAN_LOOKUP_COUNT, + }, + [BLK_IDX_L2_FORWARDING] = { + .packing = sja1105_l2_forwarding_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_COUNT, + }, + [BLK_IDX_MAC_CONFIG] = { + .packing = sja1105pqrs_mac_config_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_mac_config_entry), + .packed_entry_size = SJA1105PQRS_SIZE_MAC_CONFIG_ENTRY, + .max_entry_count = SJA1105_MAX_MAC_CONFIG_COUNT, + }, + [BLK_IDX_L2_LOOKUP_PARAMS] = { + .packing = sja1105pqrs_l2_lookup_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_lookup_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_L2_LOOKUP_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + }, + [BLK_IDX_L2_FORWARDING_PARAMS] = { + .packing = sja1105_l2_forwarding_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_l2_forwarding_params_entry), + .packed_entry_size = SJA1105_SIZE_L2_FORWARDING_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + }, + [BLK_IDX_GENERAL_PARAMS] = { + .packing = sja1105pqrs_general_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_general_params_entry), + .packed_entry_size = SJA1105PQRS_SIZE_GENERAL_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT, + }, + [BLK_IDX_XMII_PARAMS] = { + .packing = sja1105_xmii_params_entry_packing, + .unpacked_entry_size = sizeof(struct sja1105_xmii_params_entry), + .packed_entry_size = SJA1105_SIZE_XMII_PARAMS_ENTRY, + .max_entry_count = SJA1105_MAX_XMII_PARAMS_COUNT, + }, +}; + +static int +sja1105_static_config_init(struct sja1105_static_config *config, + const struct sja1105_table_ops *static_ops, + u64 device_id) +{ + enum sja1105_blk_idx i; + + *config = (struct sja1105_static_config) {0}; + + /* Transfer static_ops array from priv into per-table ops + * for handier access + */ + for (i = 0; i < BLK_IDX_MAX; i++) + config->tables[i].ops = &static_ops[i]; + + config->device_id = device_id; + return 0; +} + +static void sja1105_static_config_free(struct sja1105_static_config *config) +{ + enum sja1105_blk_idx i; + + for (i = 0; i < BLK_IDX_MAX; i++) { + if (config->tables[i].entry_count) { + free(config->tables[i].entries); + config->tables[i].entry_count = 0; + } + } +} + +static void sja1105_cgu_idiv_packing(void *buf, struct sja1105_cgu_idiv *idiv, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &idiv->clksrc, 28, 24, size, op); + sja1105_packing(buf, &idiv->autoblock, 11, 11, size, op); + sja1105_packing(buf, &idiv->idiv, 5, 2, size, op); + sja1105_packing(buf, &idiv->pd, 0, 0, size, op); +} + +static int sja1105_cgu_idiv_config(struct sja1105_private *priv, int port, + bool enabled, int factor) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_idiv idiv; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + + if (enabled && factor != 1 && factor != 10) + return -ERANGE; + + /* Payload for packed_buf */ + idiv.clksrc = 0x0A; /* 25MHz */ + idiv.autoblock = 1; /* Block clk automatically */ + idiv.idiv = factor - 1; /* Divide by 1 or 10 */ + idiv.pd = enabled ? 0 : 1; /* Power down? */ + sja1105_cgu_idiv_packing(packed_buf, &idiv, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->cgu_idiv[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static void +sja1105_cgu_mii_control_packing(void *buf, struct sja1105_cgu_mii_ctrl *cmd, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &cmd->clksrc, 28, 24, size, op); + sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op); + sja1105_packing(buf, &cmd->pd, 0, 0, size, op); +} + +static int sja1105_cgu_mii_tx_clk_config(struct sja1105_private *priv, + int port, sja1105_mii_role_t role) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_tx_clk; + const int mac_clk_sources[] = { + CLKSRC_MII0_TX_CLK, + CLKSRC_MII1_TX_CLK, + CLKSRC_MII2_TX_CLK, + CLKSRC_MII3_TX_CLK, + CLKSRC_MII4_TX_CLK, + }; + const int phy_clk_sources[] = { + CLKSRC_IDIV0, + CLKSRC_IDIV1, + CLKSRC_IDIV2, + CLKSRC_IDIV3, + CLKSRC_IDIV4, + }; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + int clksrc; + + if (role == XMII_MAC) + clksrc = mac_clk_sources[port]; + else + clksrc = phy_clk_sources[port]; + + /* Payload for packed_buf */ + mii_tx_clk.clksrc = clksrc; + mii_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_tx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_tx_clk, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_mii_rx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_rx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_MII0_RX_CLK, + CLKSRC_MII1_RX_CLK, + CLKSRC_MII2_RX_CLK, + CLKSRC_MII3_RX_CLK, + CLKSRC_MII4_RX_CLK, + }; + + /* Payload for packed_buf */ + mii_rx_clk.clksrc = clk_sources[port]; + mii_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_rx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_rx_clk, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_rx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_mii_ext_tx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_ext_tx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_IDIV0, + CLKSRC_IDIV1, + CLKSRC_IDIV2, + CLKSRC_IDIV3, + CLKSRC_IDIV4, + }; + + /* Payload for packed_buf */ + mii_ext_tx_clk.clksrc = clk_sources[port]; + mii_ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_ext_tx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_tx_clk, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_ext_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_mii_ext_rx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl mii_ext_rx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_IDIV0, + CLKSRC_IDIV1, + CLKSRC_IDIV2, + CLKSRC_IDIV3, + CLKSRC_IDIV4, + }; + + /* Payload for packed_buf */ + mii_ext_rx_clk.clksrc = clk_sources[port]; + mii_ext_rx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + mii_ext_rx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &mii_ext_rx_clk, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->mii_ext_rx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_mii_clocking_setup(struct sja1105_private *priv, int port, + sja1105_mii_role_t role) +{ + int rc; + + rc = sja1105_cgu_idiv_config(priv, port, (role == XMII_PHY), 1); + if (rc < 0) + return rc; + + rc = sja1105_cgu_mii_tx_clk_config(priv, port, role); + if (rc < 0) + return rc; + + rc = sja1105_cgu_mii_rx_clk_config(priv, port); + if (rc < 0) + return rc; + + if (role == XMII_PHY) { + rc = sja1105_cgu_mii_ext_tx_clk_config(priv, port); + if (rc < 0) + return rc; + + rc = sja1105_cgu_mii_ext_rx_clk_config(priv, port); + if (rc < 0) + return rc; + } + return 0; +} + +static void +sja1105_cgu_pll_control_packing(void *buf, struct sja1105_cgu_pll_ctrl *cmd, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &cmd->pllclksrc, 28, 24, size, op); + sja1105_packing(buf, &cmd->msel, 23, 16, size, op); + sja1105_packing(buf, &cmd->autoblock, 11, 11, size, op); + sja1105_packing(buf, &cmd->psel, 9, 8, size, op); + sja1105_packing(buf, &cmd->direct, 7, 7, size, op); + sja1105_packing(buf, &cmd->fbsel, 6, 6, size, op); + sja1105_packing(buf, &cmd->bypass, 1, 1, size, op); + sja1105_packing(buf, &cmd->pd, 0, 0, size, op); +} + +static int sja1105_cgu_rgmii_tx_clk_config(struct sja1105_private *priv, + int port, sja1105_speed_t speed) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl txc; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + int clksrc; + + if (speed == SJA1105_SPEED_1000MBPS) { + clksrc = CLKSRC_PLL0; + } else { + int clk_sources[] = {CLKSRC_IDIV0, CLKSRC_IDIV1, CLKSRC_IDIV2, + CLKSRC_IDIV3, CLKSRC_IDIV4}; + clksrc = clk_sources[port]; + } + + /* RGMII: 125MHz for 1000, 25MHz for 100, 2.5MHz for 10 */ + txc.clksrc = clksrc; + /* Autoblock clk while changing clksrc */ + txc.autoblock = 1; + /* Power Down off => enabled */ + txc.pd = 0; + sja1105_cgu_mii_control_packing(packed_buf, &txc, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->rgmii_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +/* AGU */ +static void +sja1105_cfg_pad_mii_tx_packing(void *buf, struct sja1105_cfg_pad_mii_tx *cmd, + enum packing_op op) +{ + const int size = 4; + + sja1105_packing(buf, &cmd->d32_os, 28, 27, size, op); + sja1105_packing(buf, &cmd->d32_ipud, 25, 24, size, op); + sja1105_packing(buf, &cmd->d10_os, 20, 19, size, op); + sja1105_packing(buf, &cmd->d10_ipud, 17, 16, size, op); + sja1105_packing(buf, &cmd->ctrl_os, 12, 11, size, op); + sja1105_packing(buf, &cmd->ctrl_ipud, 9, 8, size, op); + sja1105_packing(buf, &cmd->clk_os, 4, 3, size, op); + sja1105_packing(buf, &cmd->clk_ih, 2, 2, size, op); + sja1105_packing(buf, &cmd->clk_ipud, 1, 0, size, op); +} + +static int sja1105_rgmii_cfg_pad_tx_config(struct sja1105_private *priv, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cfg_pad_mii_tx pad_mii_tx; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + + /* Payload */ + pad_mii_tx.d32_os = 3; /* TXD[3:2] output stage: */ + /* high noise/high speed */ + pad_mii_tx.d10_os = 3; /* TXD[1:0] output stage: */ + /* high noise/high speed */ + pad_mii_tx.d32_ipud = 2; /* TXD[3:2] input stage: */ + /* plain input (default) */ + pad_mii_tx.d10_ipud = 2; /* TXD[1:0] input stage: */ + /* plain input (default) */ + pad_mii_tx.ctrl_os = 3; /* TX_CTL / TX_ER output stage */ + pad_mii_tx.ctrl_ipud = 2; /* TX_CTL / TX_ER input stage (default) */ + pad_mii_tx.clk_os = 3; /* TX_CLK output stage */ + pad_mii_tx.clk_ih = 0; /* TX_CLK input hysteresis (default) */ + pad_mii_tx.clk_ipud = 2; /* TX_CLK input stage (default) */ + sja1105_cfg_pad_mii_tx_packing(packed_buf, &pad_mii_tx, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_tx[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static void +sja1105_cfg_pad_mii_id_packing(void *buf, struct sja1105_cfg_pad_mii_id *cmd, + enum packing_op op) +{ + const int size = SJA1105_SIZE_CGU_CMD; + + sja1105_packing(buf, &cmd->rxc_stable_ovr, 15, 15, size, op); + sja1105_packing(buf, &cmd->rxc_delay, 14, 10, size, op); + sja1105_packing(buf, &cmd->rxc_bypass, 9, 9, size, op); + sja1105_packing(buf, &cmd->rxc_pd, 8, 8, size, op); + sja1105_packing(buf, &cmd->txc_stable_ovr, 7, 7, size, op); + sja1105_packing(buf, &cmd->txc_delay, 6, 2, size, op); + sja1105_packing(buf, &cmd->txc_bypass, 1, 1, size, op); + sja1105_packing(buf, &cmd->txc_pd, 0, 0, size, op); +} + +/* Valid range in degrees is an integer between 73.8 and 101.7 */ +static u64 sja1105_rgmii_delay(u64 phase) +{ + /* UM11040.pdf: The delay in degree phase is 73.8 + delay_tune * 0.9. + * To avoid floating point operations we'll multiply by 10 + * and get 1 decimal point precision. + */ + phase *= 10; + return (phase - 738) / 9; +} + +static int sja1105pqrs_setup_rgmii_delay(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cfg_pad_mii_id pad_mii_id = {0}; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + int rc; + + if (priv->rgmii_rx_delay[port]) + pad_mii_id.rxc_delay = sja1105_rgmii_delay(90); + if (priv->rgmii_tx_delay[port]) + pad_mii_id.txc_delay = sja1105_rgmii_delay(90); + + /* Stage 1: Turn the RGMII delay lines off. */ + pad_mii_id.rxc_bypass = 1; + pad_mii_id.rxc_pd = 1; + pad_mii_id.txc_bypass = 1; + pad_mii_id.txc_pd = 1; + sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK); + + rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port], + packed_buf, SJA1105_SIZE_CGU_CMD); + if (rc < 0) + return rc; + + /* Stage 2: Turn the RGMII delay lines on. */ + if (priv->rgmii_rx_delay[port]) { + pad_mii_id.rxc_bypass = 0; + pad_mii_id.rxc_pd = 0; + } + if (priv->rgmii_tx_delay[port]) { + pad_mii_id.txc_bypass = 0; + pad_mii_id.txc_pd = 0; + } + sja1105_cfg_pad_mii_id_packing(packed_buf, &pad_mii_id, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->pad_mii_id[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_rgmii_clocking_setup(struct sja1105_private *priv, int port, + sja1105_mii_role_t role) +{ + struct sja1105_mac_config_entry *mac; + sja1105_speed_t speed; + int rc; + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + speed = mac[port].speed; + + switch (speed) { + case SJA1105_SPEED_1000MBPS: + /* 1000Mbps, IDIV disabled (125 MHz) */ + rc = sja1105_cgu_idiv_config(priv, port, false, 1); + break; + case SJA1105_SPEED_100MBPS: + /* 100Mbps, IDIV enabled, divide by 1 (25 MHz) */ + rc = sja1105_cgu_idiv_config(priv, port, true, 1); + break; + case SJA1105_SPEED_10MBPS: + /* 10Mbps, IDIV enabled, divide by 10 (2.5 MHz) */ + rc = sja1105_cgu_idiv_config(priv, port, true, 10); + break; + default: + rc = -EINVAL; + } + + if (rc < 0) { + dev_err(dev, "Failed to configure idiv\n"); + return rc; + } + rc = sja1105_cgu_rgmii_tx_clk_config(priv, port, speed); + if (rc < 0) { + dev_err(dev, "Failed to configure RGMII Tx clock\n"); + return rc; + } + rc = sja1105_rgmii_cfg_pad_tx_config(priv, port); + if (rc < 0) { + dev_err(dev, "Failed to configure Tx pad registers\n"); + return rc; + } + if (!priv->info->setup_rgmii_delay) + return 0; + /* The role has no hardware effect for RGMII. However we use it as + * a proxy for this interface being a MAC-to-MAC connection, with + * the RGMII internal delays needing to be applied by us. + */ + if (role == XMII_MAC) + return 0; + + return priv->info->setup_rgmii_delay(priv, port); +} + +static int sja1105_cgu_rmii_ref_clk_config(struct sja1105_private *priv, + int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl ref_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + const int clk_sources[] = { + CLKSRC_MII0_TX_CLK, + CLKSRC_MII1_TX_CLK, + CLKSRC_MII2_TX_CLK, + CLKSRC_MII3_TX_CLK, + CLKSRC_MII4_TX_CLK, + }; + + /* Payload for packed_buf */ + ref_clk.clksrc = clk_sources[port]; + ref_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + ref_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &ref_clk, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_ref_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int +sja1105_cgu_rmii_ext_tx_clk_config(struct sja1105_private *priv, int port) +{ + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_cgu_mii_ctrl ext_tx_clk; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + + /* Payload for packed_buf */ + ext_tx_clk.clksrc = CLKSRC_PLL1; + ext_tx_clk.autoblock = 1; /* Autoblock clk while changing clksrc */ + ext_tx_clk.pd = 0; /* Power Down off => enabled */ + sja1105_cgu_mii_control_packing(packed_buf, &ext_tx_clk, PACK); + + return sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_ext_tx_clk[port], + packed_buf, SJA1105_SIZE_CGU_CMD); +} + +static int sja1105_cgu_rmii_pll_config(struct sja1105_private *priv) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_CGU_CMD] = {0}; + struct sja1105_cgu_pll_ctrl pll = {0}; + int rc; + + /* Step 1: PLL1 setup for 50Mhz */ + pll.pllclksrc = 0xA; + pll.msel = 0x1; + pll.autoblock = 0x1; + pll.psel = 0x1; + pll.direct = 0x0; + pll.fbsel = 0x1; + pll.bypass = 0x0; + pll.pd = 0x1; + + sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK); + rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_pll1, packed_buf, + SJA1105_SIZE_CGU_CMD); + if (rc < 0) + return rc; + + /* Step 2: Enable PLL1 */ + pll.pd = 0x0; + + sja1105_cgu_pll_control_packing(packed_buf, &pll, PACK); + rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->rmii_pll1, packed_buf, + SJA1105_SIZE_CGU_CMD); + return rc; +} + +static int sja1105_rmii_clocking_setup(struct sja1105_private *priv, int port, + sja1105_mii_role_t role) +{ + int rc; + + /* AH1601.pdf chapter 2.5.1. Sources */ + if (role == XMII_MAC) { + /* Configure and enable PLL1 for 50Mhz output */ + rc = sja1105_cgu_rmii_pll_config(priv); + if (rc < 0) + return rc; + } + /* Disable IDIV for this port */ + rc = sja1105_cgu_idiv_config(priv, port, false, 1); + if (rc < 0) + return rc; + /* Source to sink mappings */ + rc = sja1105_cgu_rmii_ref_clk_config(priv, port); + if (rc < 0) + return rc; + if (role == XMII_MAC) { + rc = sja1105_cgu_rmii_ext_tx_clk_config(priv, port); + if (rc < 0) + return rc; + } + return 0; +} + +static int sja1105_clocking_setup_port(struct sja1105_private *priv, int port) +{ + struct sja1105_xmii_params_entry *mii; + sja1105_phy_interface_t phy_mode; + sja1105_mii_role_t role; + int rc; + + mii = priv->static_config.tables[BLK_IDX_XMII_PARAMS].entries; + + /* RGMII etc */ + phy_mode = mii->xmii_mode[port]; + /* MAC or PHY, for applicable types (not RGMII) */ + role = mii->phy_mac[port]; + + switch (phy_mode) { + case XMII_MODE_MII: + rc = sja1105_mii_clocking_setup(priv, port, role); + break; + case XMII_MODE_RMII: + rc = sja1105_rmii_clocking_setup(priv, port, role); + break; + case XMII_MODE_RGMII: + rc = sja1105_rgmii_clocking_setup(priv, port, role); + break; + default: + return -EINVAL; + } + return rc; +} + +static int sja1105_clocking_setup(struct sja1105_private *priv) +{ + int port, rc; + + for (port = 0; port < SJA1105_NUM_PORTS; port++) { + rc = sja1105_clocking_setup_port(priv, port); + if (rc < 0) + return rc; + } + return 0; +} + +static struct sja1105_regs sja1105et_regs = { + .device_id = 0x0, + .prod_id = 0x100BC3, + .status = 0x1, + .port_control = 0x11, + .config = 0x020000, + .rgu = 0x100440, + /* UM10944.pdf, Table 86, ACU Register overview */ + .pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .rmii_pll1 = 0x10000A, + .cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F}, + /* UM10944.pdf, Table 78, CGU Register overview */ + .mii_tx_clk = {0x100013, 0x10001A, 0x100021, 0x100028, 0x10002F}, + .mii_rx_clk = {0x100014, 0x10001B, 0x100022, 0x100029, 0x100030}, + .mii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034}, + .mii_ext_rx_clk = {0x100019, 0x100020, 0x100027, 0x10002E, 0x100035}, + .rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032}, + .rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031}, + .rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034}, +}; + +static struct sja1105_regs sja1105pqrs_regs = { + .device_id = 0x0, + .prod_id = 0x100BC3, + .status = 0x1, + .port_control = 0x12, + .config = 0x020000, + .rgu = 0x100440, + /* UM10944.pdf, Table 86, ACU Register overview */ + .pad_mii_tx = {0x100800, 0x100802, 0x100804, 0x100806, 0x100808}, + .pad_mii_id = {0x100810, 0x100811, 0x100812, 0x100813, 0x100814}, + .rmii_pll1 = 0x10000A, + .cgu_idiv = {0x10000B, 0x10000C, 0x10000D, 0x10000E, 0x10000F}, + /* UM11040.pdf, Table 114 */ + .mii_tx_clk = {0x100013, 0x100019, 0x10001F, 0x100025, 0x10002B}, + .mii_rx_clk = {0x100014, 0x10001A, 0x100020, 0x100026, 0x10002C}, + .mii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F}, + .mii_ext_rx_clk = {0x100018, 0x10001E, 0x100024, 0x10002A, 0x100030}, + .rgmii_tx_clk = {0x100016, 0x10001C, 0x100022, 0x100028, 0x10002E}, + .rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D}, + .rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F}, +}; + +enum sja1105_switch_id { + SJA1105E = 0, + SJA1105T, + SJA1105P, + SJA1105Q, + SJA1105R, + SJA1105S, +}; + +struct sja1105_info sja1105_info[] = { + [SJA1105E] = { + .device_id = SJA1105E_DEVICE_ID, + .part_no = SJA1105ET_PART_NO, + .static_ops = sja1105et_table_ops, + .reset_cmd = sja1105et_reset_cmd, + .regs = &sja1105et_regs, + .name = "SJA1105E", + }, + [SJA1105T] = { + .device_id = SJA1105T_DEVICE_ID, + .part_no = SJA1105ET_PART_NO, + .static_ops = sja1105et_table_ops, + .reset_cmd = sja1105et_reset_cmd, + .regs = &sja1105et_regs, + .name = "SJA1105T", + }, + [SJA1105P] = { + .device_id = SJA1105PR_DEVICE_ID, + .part_no = SJA1105P_PART_NO, + .static_ops = sja1105pqrs_table_ops, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105P", + }, + [SJA1105Q] = { + .device_id = SJA1105QS_DEVICE_ID, + .part_no = SJA1105Q_PART_NO, + .static_ops = sja1105pqrs_table_ops, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105Q", + }, + [SJA1105R] = { + .device_id = SJA1105PR_DEVICE_ID, + .part_no = SJA1105R_PART_NO, + .static_ops = sja1105pqrs_table_ops, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105R", + }, + [SJA1105S] = { + .device_id = SJA1105QS_DEVICE_ID, + .part_no = SJA1105S_PART_NO, + .static_ops = sja1105pqrs_table_ops, + .setup_rgmii_delay = sja1105pqrs_setup_rgmii_delay, + .reset_cmd = sja1105pqrs_reset_cmd, + .regs = &sja1105pqrs_regs, + .name = "SJA1105S", + }, +}; + +static void +sja1105_port_allow_traffic(struct sja1105_l2_forwarding_entry *l2_fwd, + int from, int to) +{ + l2_fwd[from].bc_domain |= BIT(to); + l2_fwd[from].reach_port |= BIT(to); + l2_fwd[from].fl_domain |= BIT(to); +} + +static int sja1105_init_mac_settings(struct sja1105_private *priv) +{ + struct sja1105_mac_config_entry default_mac = { + /* Enable 1 priority queue on egress. */ + .top = {0x1FF, 0, 0, 0, 0, 0, 0}, + .base = {0x0, 0, 0, 0, 0, 0, 0, 0}, + .enabled = {1, 0, 0, 0, 0, 0, 0, 0}, + /* Default for all ports including the CPU. FIXME: may not work + * for all ports. + */ + .speed = SJA1105_SPEED_1000MBPS, + /* Disable aging for critical TTEthernet traffic */ + .maxage = 0xFF, + .dyn_learn = true, + .egress = true, + .ingress = true, + }; + struct sja1105_mac_config_entry *mac; + struct sja1105_table *table; + int port; + + table = &priv->static_config.tables[BLK_IDX_MAC_CONFIG]; + + table->entries = calloc(SJA1105_NUM_PORTS, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_NUM_PORTS; + + mac = table->entries; + + for (port = 0; port < SJA1105_NUM_PORTS; port++) { + mac[port] = default_mac; + /* Internal VLAN (pvid) to apply to untagged ingress */ + mac[port].vlanid = priv->pvid[port]; + } + + return 0; +} + +static int sja1105_init_mii_settings(struct sja1105_private *priv) +{ + struct dsa_perdev_platdata *platdata = priv->dev->platdata; + struct sja1105_xmii_params_entry *mii; + struct sja1105_table *table; + int port; + + table = &priv->static_config.tables[BLK_IDX_XMII_PARAMS]; + + table->entries = calloc(SJA1105_MAX_XMII_PARAMS_COUNT, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + /* Override table based on DT bindings */ + table->entry_count = SJA1105_MAX_XMII_PARAMS_COUNT; + + mii = table->entries; + + for (port = 0; port < SJA1105_NUM_PORTS; port++) { + struct phy_device *phy = platdata->port[port].phy; + + switch (priv->phy_modes[port]) { + case PHY_INTERFACE_MODE_MII: + mii->xmii_mode[port] = XMII_MODE_MII; + break; + case PHY_INTERFACE_MODE_RMII: + mii->xmii_mode[port] = XMII_MODE_RMII; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + mii->xmii_mode[port] = XMII_MODE_RGMII; + break; + default: + return -EINVAL; + } + + if (!phy || phy->phy_id == PHY_FIXED_ID) + mii->phy_mac[port] = XMII_PHY; + else + mii->phy_mac[port] = XMII_MAC; + } + return 0; +} + +static int sja1105_init_l2_lookup_params(struct sja1105_private *priv) +{ + struct sja1105_table *table; + u64 max_fdb_entries = SJA1105_MAX_L2_LOOKUP_COUNT / SJA1105_NUM_PORTS; + struct sja1105_l2_lookup_params_entry default_l2_lookup_params = { + /* All entries within a FDB bin are available for learning */ + .dyn_tbsz = SJA1105ET_FDB_BIN_SIZE, + /* And the P/Q/R/S equivalent setting: */ + .start_dynspc = 0, + .maxaddrp = {max_fdb_entries, max_fdb_entries, max_fdb_entries, + max_fdb_entries, max_fdb_entries, }, + /* 2^8 + 2^5 + 2^3 + 2^2 + 2^1 + 1 in Koopman notation */ + .poly = 0x97, + /* P/Q/R/S only */ + .use_static = true, + /* Dynamically learned FDB entries can overwrite other (older) + * dynamic FDB entries + */ + .owr_dyn = true, + }; + + table = &priv->static_config.tables[BLK_IDX_L2_LOOKUP_PARAMS]; + + table->entries = calloc(SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_LOOKUP_PARAMS_COUNT; + + /* This table only has a single entry */ + ((struct sja1105_l2_lookup_params_entry *)table->entries)[0] = + default_l2_lookup_params; + + return 0; +} + +static void sja1105_setup_tagging(struct sja1105_private *priv, int port) +{ + struct dsa_perdev_platdata *platdata = priv->dev->platdata; + struct sja1105_vlan_lookup_entry *vlan; + int cpu = platdata->cpu_port; + + /* The CPU port is implicitly configured by + * configuring the front-panel ports + */ + if (port == cpu) + return; + + vlan = priv->static_config.tables[BLK_IDX_VLAN_LOOKUP].entries; + + priv->pvid[port] = DSA_8021Q_DIR_RX | DSA_8021Q_PORT(port); + + vlan[port].vmemb_port = BIT(port) | BIT(cpu); + vlan[port].vlan_bc = BIT(port) | BIT(cpu); + vlan[port].tag_port = BIT(cpu); + vlan[port].vlanid = priv->pvid[port]; +} + +static int sja1105_init_vlan(struct sja1105_private *priv) +{ + struct sja1105_table *table; + int port; + + table = &priv->static_config.tables[BLK_IDX_VLAN_LOOKUP]; + + table->entries = calloc(SJA1105_NUM_PORTS, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_NUM_PORTS; + + for (port = 0; port < SJA1105_NUM_PORTS; port++) + sja1105_setup_tagging(priv, port); + + return 0; +} + +static int sja1105_init_l2_forwarding(struct sja1105_private *priv) +{ + struct dsa_perdev_platdata *platdata = priv->dev->platdata; + struct sja1105_l2_forwarding_entry *l2fwd; + int cpu = platdata->cpu_port; + struct sja1105_table *table; + int i; + + table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING]; + + table->entries = calloc(SJA1105_MAX_L2_FORWARDING_COUNT, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_FORWARDING_COUNT; + + l2fwd = table->entries; + + /* First 5 entries define the forwarding rules */ + for (i = 0; i < SJA1105_NUM_PORTS; i++) { + if (i == cpu) + continue; + + sja1105_port_allow_traffic(l2fwd, i, cpu); + sja1105_port_allow_traffic(l2fwd, cpu, i); + } + /* Next 8 entries define VLAN PCP mapping from ingress to egress. + * Leave them unpopulated (implicitly 0) but present. + */ + return 0; +} + +static int sja1105_init_l2_forwarding_params(struct sja1105_private *priv) +{ + struct sja1105_l2_forwarding_params_entry default_l2fwd_params = { + /* Use a single memory partition for all ingress queues */ + .part_spc = { SJA1105_MAX_FRAME_MEMORY, 0, 0, 0, 0, 0, 0, 0 }, + }; + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_L2_FORWARDING_PARAMS]; + + table->entries = calloc(SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_FORWARDING_PARAMS_COUNT; + + /* This table only has a single entry */ + ((struct sja1105_l2_forwarding_params_entry *)table->entries)[0] = + default_l2fwd_params; + + return 0; +} + +static int sja1105_init_general_params(struct sja1105_private *priv) +{ + struct sja1105_general_params_entry default_general_params = { + /* No frame trapping */ + .mac_fltres1 = 0x0, + .mac_flt1 = 0xffffffffffff, + .mac_fltres0 = 0x0, + .mac_flt0 = 0xffffffffffff, + .host_port = SJA1105_NUM_PORTS, + /* No mirroring => specify an out-of-range port value */ + .mirr_port = SJA1105_NUM_PORTS, + /* No link-local trapping => specify an out-of-range port value + */ + .casc_port = SJA1105_NUM_PORTS, + /* Force the switch to see all traffic as untagged. */ + .tpid = ETH_P_SJA1105, + .tpid2 = ETH_P_SJA1105, + }; + struct sja1105_table *table; + + table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS]; + + table->entries = calloc(SJA1105_MAX_GENERAL_PARAMS_COUNT, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_GENERAL_PARAMS_COUNT; + + /* This table only has a single entry */ + ((struct sja1105_general_params_entry *)table->entries)[0] = + default_general_params; + + return 0; +} + +static void sja1105_setup_policer(struct sja1105_l2_policing_entry *policing, + int index, int mtu) +{ + policing[index].sharindx = index; + policing[index].smax = 65535; /* Burst size in bytes */ + policing[index].rate = SJA1105_RATE_MBPS(1000); + policing[index].maxlen = mtu; + policing[index].partition = 0; +} + +static int sja1105_init_l2_policing(struct sja1105_private *priv) +{ + struct dsa_perdev_platdata *platdata = priv->dev->platdata; + struct sja1105_l2_policing_entry *policing; + int cpu = platdata->cpu_port; + struct sja1105_table *table; + int i, j, k; + + table = &priv->static_config.tables[BLK_IDX_L2_POLICING]; + + table->entries = calloc(SJA1105_MAX_L2_POLICING_COUNT, + table->ops->unpacked_entry_size); + if (!table->entries) + return -ENOMEM; + + table->entry_count = SJA1105_MAX_L2_POLICING_COUNT; + + policing = table->entries; + + /* k sweeps through all unicast policers (0-39). + * bcast sweeps through policers 40-44. + */ + for (i = 0, k = 0; i < SJA1105_NUM_PORTS; i++) { + int bcast = (SJA1105_NUM_PORTS * SJA1105_NUM_TC) + i; + int mtu = VLAN_ETH_FRAME_LEN + ETH_FCS_LEN; + + if (i == cpu) + mtu += VLAN_HLEN; + + for (j = 0; j < SJA1105_NUM_TC; j++, k++) + sja1105_setup_policer(policing, k, mtu); + + /* Set up this port's policer for broadcast traffic */ + sja1105_setup_policer(policing, bcast, mtu); + } + return 0; +} + +struct sja1105_status { + u64 configs; + u64 crcchkl; + u64 ids; + u64 crcchkg; +}; + +static void sja1105_status_unpack(void *buf, struct sja1105_status *status) +{ + sja1105_packing(buf, &status->configs, 31, 31, 4, UNPACK); + sja1105_packing(buf, &status->crcchkl, 30, 30, 4, UNPACK); + sja1105_packing(buf, &status->ids, 29, 29, 4, UNPACK); + sja1105_packing(buf, &status->crcchkg, 28, 28, 4, UNPACK); +} + +static int sja1105_status_get(struct sja1105_private *priv, + struct sja1105_status *status) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[4]; + int rc; + + rc = sja1105_xfer_buf(priv, SPI_READ, regs->status, packed_buf, 4); + if (rc < 0) + return rc; + + sja1105_status_unpack(packed_buf, status); + + return 0; +} + +/* Not const because unpacking priv->static_config into buffers and preparing + * for upload requires the recalculation of table CRCs and updating the + * structures with these. + */ +static int +static_config_buf_prepare_for_upload(struct sja1105_private *priv, + void *config_buf, int buf_len) +{ + struct sja1105_static_config *config = &priv->static_config; + struct sja1105_table_header final_header; + char *final_header_ptr; + int crc_len; + + /* Write Device ID and config tables to config_buf */ + sja1105_static_config_pack(config_buf, config); + /* Recalculate CRC of the last header (right now 0xDEADBEEF). + * Don't include the CRC field itself. + */ + crc_len = buf_len - 4; + /* Read the whole table header */ + final_header_ptr = config_buf + buf_len - SJA1105_SIZE_TABLE_HEADER; + sja1105_table_header_packing(final_header_ptr, &final_header, UNPACK); + /* Modify */ + final_header.crc = sja1105_crc32(config_buf, crc_len); + /* Rewrite */ + sja1105_table_header_packing(final_header_ptr, &final_header, PACK); + + return 0; +} + +static int sja1105_static_config_upload(struct sja1105_private *priv) +{ + struct sja1105_static_config *config = &priv->static_config; + const struct sja1105_regs *regs = priv->info->regs; + struct sja1105_status status; + u8 *config_buf; + int buf_len; + int rc; + + buf_len = sja1105_static_config_get_length(config); + config_buf = calloc(buf_len, sizeof(char)); + if (!config_buf) + return -ENOMEM; + + rc = static_config_buf_prepare_for_upload(priv, config_buf, buf_len); + if (rc < 0) { + printf("Invalid config, cannot upload\n"); + rc = -EINVAL; + goto out; + } + /* Put the SJA1105 in programming mode */ + rc = priv->info->reset_cmd(priv); + if (rc < 0) { + printf("Failed to reset switch\n"); + goto out; + } + /* Wait for the switch to come out of reset */ + udelay(1000); + /* Upload the static config to the device */ + rc = sja1105_xfer_buf(priv, SPI_WRITE, regs->config, + config_buf, buf_len); + if (rc < 0) { + printf("Failed to upload config\n"); + goto out; + } + /* Check that SJA1105 responded well to the config upload */ + rc = sja1105_status_get(priv, &status); + if (rc < 0) + goto out; + + if (status.ids == 1) { + printf("Mismatch between hardware and static config device id. " + "Wrote 0x%llx, wants 0x%llx\n", + config->device_id, priv->info->device_id); + rc = -EIO; + goto out; + } + if (status.crcchkl == 1 || status.crcchkg == 1) { + printf("Switch reported invalid CRC on static config\n"); + rc = -EIO; + goto out; + } + if (status.configs == 0) { + printf("Switch reported that config is invalid\n"); + rc = -EIO; + goto out; + } + +out: + free(config_buf); + return rc; +} + +static int sja1105_static_config_load(struct sja1105_private *priv) +{ + int rc; + + rc = sja1105_static_config_init(&priv->static_config, + priv->info->static_ops, + priv->info->device_id); + if (rc) + return rc; + + /* Build static configuration */ + rc = sja1105_init_vlan(priv); + if (rc < 0) + return rc; + rc = sja1105_init_mac_settings(priv); + if (rc < 0) + return rc; + rc = sja1105_init_mii_settings(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_lookup_params(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_forwarding(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_forwarding_params(priv); + if (rc < 0) + return rc; + rc = sja1105_init_l2_policing(priv); + if (rc < 0) + return rc; + rc = sja1105_init_general_params(priv); + if (rc < 0) + return rc; + + /* Send initial configuration to hardware via SPI */ + return sja1105_static_config_upload(priv); +} + +static int sja1105_static_config_reload(struct sja1105_private *priv) +{ + int rc; + + rc = sja1105_static_config_upload(priv); + if (rc < 0) { + printf("Failed to load static config: %d\n", rc); + return rc; + } + + /* Configure the CGU (PHY link modes and speeds) */ + rc = sja1105_clocking_setup(priv); + if (rc < 0) { + printf("Failed to configure MII clocking: %d\n", rc); + return rc; + } + + return 0; +} + +static int sja1105_port_enable(struct udevice *dev, int port, + struct phy_device *phy) +{ + struct sja1105_private *priv = dev_get_priv(dev); + struct sja1105_mac_config_entry *mac; + + if (!phy) + return 0; + + phy_startup(phy); + + mac = priv->static_config.tables[BLK_IDX_MAC_CONFIG].entries; + switch (phy->speed) { + case SPEED_1000: + mac[port].speed = SJA1105_SPEED_1000MBPS; + break; + case SPEED_100: + mac[port].speed = SJA1105_SPEED_100MBPS; + break; + case SPEED_10: + mac[port].speed = SJA1105_SPEED_10MBPS; + break; + default: + printf("Invalid speed %d\n", phy->speed); + return -EINVAL; + } + + return sja1105_static_config_reload(priv); +} + +static void sja1105_port_disable(struct udevice *dev, int port, + struct phy_device *phy) +{ + if (phy) + phy_shutdown(phy); +} + +static int sja1105_xmit(struct udevice *dev, int port, void *packet, int length) +{ + struct sja1105_private *priv = dev_get_priv(dev); + u8 *from = (u8 *)packet + VLAN_HLEN; + struct vlan_ethhdr *hdr = packet; + u8 *dest = (u8 *)packet; + + memmove(dest, from, 2 * ETH_ALEN); + hdr->h_vlan_proto = htons(ETH_P_SJA1105); + hdr->h_vlan_TCI = htons(priv->pvid[port]); + + return 0; +} + +static int sja1105_rcv(struct udevice *dev, int *port, void *packet, int length) +{ + struct vlan_ethhdr *hdr = packet; + u8 *dest = packet + VLAN_HLEN; + u8 *from = packet; + + if (ntohs(hdr->h_vlan_proto) != ETH_P_SJA1105) + return -EINVAL; + + *port = ntohs(hdr->h_vlan_TCI) & DSA_8021Q_PORT_MASK; + memmove(dest, from, 2 * ETH_ALEN); + + return 0; +} + +static const struct dsa_ops sja1105_dsa_ops = { + .port_enable = sja1105_port_enable, + .port_disable = sja1105_port_disable, + .xmit = sja1105_xmit, + .rcv = sja1105_rcv, +}; + +static int sja1105_init_port(struct sja1105_private *priv, int port) +{ + struct dsa_perdev_platdata *platdata = priv->dev->platdata; + int phy_mode = PHY_INTERFACE_MODE_NONE; + struct phy_device *phy; + const char *if_str; + + phy = platdata->port[port].phy; + if (phy) { + phy->supported &= PHY_GBIT_FEATURES; + phy->advertising &= PHY_GBIT_FEATURES; + phy_config(phy); + } + + if_str = ofnode_read_string(platdata->port[port].node, "phy-mode"); + if (if_str) + phy_mode = phy_get_interface_by_name(if_str); + + if (phy_mode != PHY_INTERFACE_MODE_RGMII_TXID && + phy_mode != PHY_INTERFACE_MODE_RGMII_RXID && + phy_mode != PHY_INTERFACE_MODE_RGMII_ID && + phy_mode != PHY_INTERFACE_MODE_RGMII && + phy_mode != PHY_INTERFACE_MODE_RMII && + phy_mode != PHY_INTERFACE_MODE_MII) { + printf("Unsupported PHY mode %s!\n", if_str); + return -EINVAL; + } + + /* Let the PHY handle the RGMII delays, if present. */ + if (!phy || phy->phy_id == PHY_FIXED_ID) { + if (phy_mode == PHY_INTERFACE_MODE_RGMII_RXID || + phy_mode == PHY_INTERFACE_MODE_RGMII_ID) + priv->rgmii_rx_delay[port] = true; + + if (phy_mode == PHY_INTERFACE_MODE_RGMII_TXID || + phy_mode == PHY_INTERFACE_MODE_RGMII_ID) + priv->rgmii_tx_delay[port] = true; + + if ((priv->rgmii_rx_delay[port] || + priv->rgmii_tx_delay[port]) && + !priv->info->setup_rgmii_delay) + return -EINVAL; + } + + priv->phy_modes[port] = phy_mode; + + return 0; +} + +static int sja1105_init(struct sja1105_private *priv) +{ + int port; + int rc; + + for (port = 0; port < SJA1105_NUM_PORTS; port++) { + rc = sja1105_init_port(priv, port); + if (rc < 0) { + printf("Failed to initialize port %d\n", port); + return rc; + } + } + + rc = sja1105_static_config_load(priv); + if (rc < 0) { + printf("Failed to load static config: %d\n", rc); + return rc; + } + + /* Configure the CGU (PHY link modes and speeds) */ + rc = sja1105_clocking_setup(priv); + if (rc < 0) { + printf("Failed to configure MII clocking: %d\n", rc); + return rc; + } + + return 0; +} + +static int sja1105_check_device_id(struct sja1105_private *priv) +{ + const struct sja1105_regs *regs = priv->info->regs; + u8 packed_buf[SJA1105_SIZE_DEVICE_ID] = {0}; + u64 device_id; + u64 part_no; + int rc; + + rc = sja1105_xfer_buf(priv, SPI_READ, regs->device_id, packed_buf, + SJA1105_SIZE_DEVICE_ID); + if (rc < 0) + return rc; + + sja1105_packing(packed_buf, &device_id, 31, 0, SJA1105_SIZE_DEVICE_ID, + UNPACK); + + if (device_id != priv->info->device_id) { + printf("Expected device ID 0x%llx but read 0x%llx\n", + priv->info->device_id, device_id); + return -ENODEV; + } + + rc = sja1105_xfer_buf(priv, SPI_READ, regs->prod_id, packed_buf, + SJA1105_SIZE_DEVICE_ID); + if (rc < 0) + return rc; + + sja1105_packing(packed_buf, &part_no, 19, 4, SJA1105_SIZE_DEVICE_ID, + UNPACK); + + if (part_no != priv->info->part_no) { + printf("Expected part number 0x%llx but read 0x%llx\n", + priv->info->part_no, part_no); + return -ENODEV; + } + + return 0; +} + +static int sja1105_probe(struct udevice *dev) +{ + enum sja1105_switch_id id = dev_get_driver_data(dev); + struct sja1105_private *priv = dev_get_priv(dev); + int rc; + + if (ofnode_valid(dev->node) && !ofnode_is_available(dev->node)) { + dev_dbg(dev, "switch disabled\n"); + return -ENODEV; + } + + priv->info = &sja1105_info[id]; + priv->dev = dev; + + rc = sja1105_check_device_id(priv); + if (rc < 0) { + dev_err(dev, "Device ID check failed: %d\n", rc); + return rc; + } + + return sja1105_init(priv); +} + +static int sja1105_remove(struct udevice *dev) +{ + struct sja1105_private *priv = dev->priv; + + sja1105_static_config_free(&priv->static_config); + + return 0; +} + +static int sja1105_bind(struct udevice *dev) +{ + struct dsa_perdev_platdata *pdata = dev->platdata; + + pdata->num_ports = SJA1105_NUM_PORTS; + pdata->headroom = VLAN_HLEN; + + return 0; +} + +static const struct udevice_id sja1105_ids[] = { + { .compatible = "nxp,sja1105e", .data = SJA1105E }, + { .compatible = "nxp,sja1105t", .data = SJA1105T }, + { .compatible = "nxp,sja1105p", .data = SJA1105P }, + { .compatible = "nxp,sja1105q", .data = SJA1105Q }, + { .compatible = "nxp,sja1105r", .data = SJA1105R }, + { .compatible = "nxp,sja1105s", .data = SJA1105S }, + { } +}; + +U_BOOT_DRIVER(sja1105) = { + .name = "sja1105", + .id = UCLASS_DSA, + .of_match = sja1105_ids, + .bind = sja1105_bind, + .probe = sja1105_probe, + .remove = sja1105_remove, + .ops = &sja1105_dsa_ops, + .priv_auto_alloc_size = sizeof(struct sja1105_private), + .platdata_auto_alloc_size = sizeof(struct dsa_perdev_platdata), +};