diff options
author | David Hendricks <dhendricks@fb.com> | 2018-03-09 14:30:38 -0800 |
---|---|---|
committer | Philipp Deppenwiese <zaolin.daisuki@gmail.com> | 2018-07-03 15:53:32 +0000 |
commit | 7d48ac5c7dfb52fc470bbad1013b4d460bc6a1e0 (patch) | |
tree | 42002ba1e86627339ff4a6cf38efb4b3f00033bb /src/vendorcode/cavium/bdk/libbdk-hal | |
parent | d837e660074e0621d63f59515f933c209441b653 (diff) | |
download | coreboot-7d48ac5c7dfb52fc470bbad1013b4d460bc6a1e0.tar.xz |
soc/cavium: Integrate BDK files into coreboot
* Make it compile.
* Fix whitespace errors.
* Fix printf formats.
* Add missing headers includes
* Guard headers with ifdefs
Compile DRAM init code in romstage.
Compile QLM, PCIe, RNG, PHY, GPIO, MDIO init code in ramstage.
Change-Id: I0a93219a14bfb6ebe41103a825d5032b11e7f2c6
Signed-off-by: David Hendricks <dhendricks@fb.com>
Reviewed-on: https://review.coreboot.org/25089
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Diffstat (limited to 'src/vendorcode/cavium/bdk/libbdk-hal')
25 files changed, 11949 insertions, 1736 deletions
diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-access.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-access.c new file mode 100644 index 0000000000..ebced00715 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-access.c @@ -0,0 +1,70 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-arch/bdk-csrs-uaa.h" +#include "libbdk-arch/bdk-csrs-rst.h" + +/** + * Perform a soft reset of the chip + * + * @return + */ +void bdk_reset_chip(bdk_node_t node) +{ + fflush(NULL); + + /* Wait for TX fifo to empty */ + while (1) + { + BDK_CSR_INIT(fr, node, BDK_UAAX_FR(0)); + if (fr.s.txfe) + break; + } + + /* RST_OCX is not cleared by a chip reset. Clear it now to avoid repeated + resets due to CCPI state changes during reset */ + BDK_CSR_WRITE(node, BDK_RST_OCX, 0); + BDK_CSR_READ(node, BDK_RST_OCX); + + bdk_rst_soft_rst_t rst_soft_rst; + rst_soft_rst.u = 0; + rst_soft_rst.s.soft_rst = 1; + BDK_CSR_WRITE(node, BDK_RST_SOFT_RST, rst_soft_rst.u); +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-clock.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-clock.c index f81285dffd..b8b0952de4 100644 --- a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-clock.c +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-clock.c @@ -37,123 +37,10 @@ * ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. ***********************license end**************************************/ #include <bdk.h> -#include "libbdk-arch/bdk-csrs-gti.h" -#include "libbdk-arch/bdk-csrs-ocx.h" - -/** - * Called in __bdk_init to setup the global timer - */ -void bdk_clock_setup(bdk_node_t node) -{ - const bdk_node_t local_node = bdk_numa_local(); - - /* Check if the counter was already setup */ - BDK_CSR_INIT(cntcr, node, BDK_GTI_CC_CNTCR); - if (cntcr.s.en) - return; - - /* Configure GTI to tick at BDK_GTI_RATE */ - uint64_t sclk = bdk_clock_get_rate(node, BDK_CLOCK_SCLK); - uint64_t inc = (BDK_GTI_RATE << 32) / sclk; - BDK_CSR_WRITE(node, BDK_GTI_CC_CNTRATE, inc); - BDK_CSR_WRITE(node, BDK_GTI_CTL_CNTFRQ, BDK_GTI_RATE); - cntcr.s.en = 1; - if (node != local_node) - { - /* Synchronize with local node. Very simple set of counter, will be - off a little */ - BDK_CSR_WRITE(node, BDK_GTI_CC_CNTCV, bdk_clock_get_count(BDK_CLOCK_TIME)); - } - /* Enable the counter */ - BDK_CSR_WRITE(node, BDK_GTI_CC_CNTCR, cntcr.u); - BDK_CSR_READ(node, BDK_GTI_CC_CNTCR); - - if (node != local_node) - { - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) - { - /* Assume the delay in each direction is the same, sync the counters */ - int64_t local1 = bdk_clock_get_count(BDK_CLOCK_TIME); - int64_t remote = BDK_CSR_READ(node, BDK_GTI_CC_CNTCV); - int64_t local2 = bdk_clock_get_count(BDK_CLOCK_TIME); - int64_t expected = (local1 + local2) / 2; - BDK_CSR_WRITE(node, BDK_GTI_CC_CNTADD, expected - remote); - BDK_TRACE(INIT, "N%d.GTI: Clock synchronization with master\n" - " expected: %ld, remote %ld\n" - " Counter correction: %ld\n", - node, expected, remote, expected - remote); - } - else - { - /* Due to errata TBD, we need to use OCX_PP_CMD to write - GTI_CC_CNTMB in order for timestamps to update. These constants - are the addresses we need for both local and remote GTI_CC_CNTMB */ - const uint64_t LOCAL_GTI_CC_CNTMB = bdk_numa_get_address(local_node, BDK_GTI_CC_CNTMB); - const uint64_t REMOTE_GTI_CC_CNTMB = bdk_numa_get_address(node, BDK_GTI_CC_CNTMB); - /* Build partial OCX_PP_CMD command used for writes. Address will - be filled later */ - BDK_CSR_DEFINE(pp_cmd, BDK_OCX_PP_CMD); - pp_cmd.u = 0; - pp_cmd.s.wr_mask = 0xff; - - const int NUM_AVERAGE = 16; /* Choose a power of two to avoid division */ - int64_t local_to_remote_sum = 0; - int64_t local_to_remote_min = 1000000; - int64_t local_to_remote_max = -1000000; - int64_t remote_to_local_sum = 0; - int64_t remote_to_local_min = 1000000; - int64_t remote_to_local_max = -1000000; - for (int loop = 0; loop < NUM_AVERAGE; loop++) - { - /* Perform a write to the remote GTI_CC_CNTMB to cause timestamp - update. We don't care about the value actually written */ - pp_cmd.s.addr = REMOTE_GTI_CC_CNTMB; - BDK_CSR_WRITE(local_node, BDK_OCX_PP_CMD, pp_cmd.u); - BDK_CSR_READ(local_node, BDK_OCX_PP_CMD); - - int64_t remote = BDK_CSR_READ(node, BDK_GTI_CC_CNTMBTS); - int64_t local = BDK_CSR_READ(local_node, BDK_GTI_CC_CNTMBTS); - int64_t delta = remote - local; - - local_to_remote_sum += delta; - if (delta < local_to_remote_min) - local_to_remote_min = delta; - if (delta > local_to_remote_max) - local_to_remote_max = delta; - - /* Perform a write to the local GTI_CC_CNTMB to cause timestamp - update. We don't care about the value actually written */ - pp_cmd.s.addr = LOCAL_GTI_CC_CNTMB; - BDK_CSR_WRITE(node, BDK_OCX_PP_CMD, pp_cmd.u); - BDK_CSR_READ(node, BDK_OCX_PP_CMD); - - remote = BDK_CSR_READ(node, BDK_GTI_CC_CNTMBTS); - local = BDK_CSR_READ(local_node, BDK_GTI_CC_CNTMBTS); - delta = local - remote; - - remote_to_local_sum += delta; - if (delta < remote_to_local_min) - remote_to_local_min = delta; - if (delta > remote_to_local_max) - remote_to_local_max = delta; - } - /* Calculate average, rounding to nearest */ - int64_t local_to_remote = (local_to_remote_sum + NUM_AVERAGE/2) / NUM_AVERAGE; - int64_t remote_to_local = (remote_to_local_sum + NUM_AVERAGE/2) / NUM_AVERAGE; - /* Calculate remote node offset */ - int64_t remote_offset = (remote_to_local - local_to_remote) / 2; - BDK_CSR_WRITE(node, BDK_GTI_CC_CNTADD, remote_offset); - BDK_TRACE(INIT, "N%d.GTI: Clock synchronization with master\n" - " local -> remote: min %ld, avg %ld, max %ld\n" - " remote -> local: min %ld, avg %ld, max %ld\n" - " Counter correction: %ld\n", - node, - local_to_remote_min, local_to_remote, local_to_remote_max, - remote_to_local_min, remote_to_local, remote_to_local_max, - remote_offset); - } - } -} +#include <libbdk-arch/bdk-csrs-gti.h> +#include <libbdk-arch/bdk-csrs-ocx.h> +#include <libbdk-hal/bdk-clock.h> +#include <libbdk-arch/bdk-csrs-rst.h> /** * Get cycle count based on the clock type. @@ -165,12 +52,6 @@ uint64_t __bdk_clock_get_count_slow(bdk_clock_t clock) { bdk_node_t node = bdk_numa_local(); BDK_CSR_INIT(rst_boot, node, BDK_RST_BOOT); - if (bdk_is_platform(BDK_PLATFORM_EMULATOR)) - { - /* Force RCLK and SCLK to be 1GHz on emulator */ - rst_boot.s.c_mul = 20; - rst_boot.s.pnr_mul = 20; - } uint64_t ref_cntr = BDK_CSR_READ(node, BDK_RST_REF_CNTR); switch(clock) { @@ -199,12 +80,6 @@ uint64_t __bdk_clock_get_rate_slow(bdk_node_t node, bdk_clock_t clock) const uint64_t REF_CLOCK = 50000000; BDK_CSR_INIT(mio_rst_boot, node, BDK_RST_BOOT); - if (bdk_is_platform(BDK_PLATFORM_EMULATOR)) - { - /* Force RCLK and SCLK to be 1GHz on emulator */ - mio_rst_boot.s.c_mul = 20; - mio_rst_boot.s.pnr_mul = 20; - } switch (clock) { case BDK_CLOCK_TIME: diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-config.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-config.c index d4b412d439..91f05d3ae7 100644 --- a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-config.c +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-config.c @@ -1,81 +1,265 @@ -/***********************license start*********************************** -* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights -* reserved. -* -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are -* met: -* -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* -* * Redistributions in binary form must reproduce the above -* copyright notice, this list of conditions and the following -* disclaimer in the documentation and/or other materials provided -* with the distribution. -* -* * Neither the name of Cavium Inc. nor the names of -* its contributors may be used to endorse or promote products -* derived from this software without specific prior written -* permission. -* -* This Software, including technical data, may be subject to U.S. export -* control laws, including the U.S. Export Administration Act and its -* associated regulations, and may be subject to export or import -* regulations in other countries. -* -* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" -* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR -* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT -* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY -* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT -* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES -* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR -* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, -* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK -* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. -***********************license end**************************************/ +/* + * This file is part of the coreboot project. + * + * Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights + * reserved. + * Copyright 2018-present Facebook, Inc. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * This file consists of data imported from bdk-config.c + */ + #include <bdk.h> -#include <stdarg.h> -#include <libfdt.h> -#include <unistd.h> -#include "libbdk-arch/bdk-csrs-mio_fus.h" -#include "libbdk-arch/bdk-csrs-fus.h" +#include <libbdk-hal/bdk-config.h> +#include <string.h> +#include <assert.h> +#include <lame_string.h> + +static struct bdk_devicetree_key_value *config_fdt; + +#if !defined(__PRE_RAM__) +static struct bdk_devicetree_key_value *bdk_config_duplicate( + const struct bdk_devicetree_key_value *old, + size_t free_space) +{ + struct bdk_devicetree_key_value *new; + size_t len = sizeof(struct bdk_devicetree_key_value) + free_space; + const struct bdk_devicetree_key_value *iter = old; + while (iter->key) { + iter++; + len += sizeof(struct bdk_devicetree_key_value); + } + new = malloc(len); + if (!new) + return NULL; -/* Set this define to override the trace the BDK uses. This is most - useful with trusted boot when the setup menus are not able to - configure the trace level. A possible example: */ -//#define BDK_TRACE_OVERRIDE (1ull << BDK_TRACE_ENABLE_INIT) -#define BDK_TRACE_OVERRIDE 0 + memcpy(new, old, len); + + return new; +} +#endif +/** + * Set the device tree used for configuration + * + * @param fdt Device tree to use. Memory is assumed to be from malloc() and bdk_config takes + * over ownership on success + * + * @return Zero on success, negative on failure + */ +int bdk_config_set_fdt(const struct bdk_devicetree_key_value *fdt) +{ +#if !defined(__PRE_RAM__) + config_fdt = bdk_config_duplicate(fdt, 0); +#else + config_fdt = (void *)fdt; +#endif + return 0; +} -typedef enum +/** + * Look up a configuration item in the environment and replace it. + * + * @param name + * + * @return + */ +static void set_value(const char *name, const char *val) { - BDK_CONFIG_TYPE_INT, - BDK_CONFIG_TYPE_STR, - BDK_CONFIG_TYPE_STR_LIST, - BDK_CONFIG_TYPE_BINARY, -} bdk_config_type_t; +#if !defined(__PRE_RAM__) + struct bdk_devicetree_key_value *iter; + char n[64]; + + strncpy(n, name, sizeof(n)); + n[sizeof(n)-1] = '\0'; + + iter = config_fdt; + while (iter->key) { + if (strcmp(iter->key, n) == 0) { + // we are leaking memory here... + iter->value = (const char *)strdup(val); + return; + } + iter++; + } + /* Not found: Create one */ + iter = bdk_config_duplicate(config_fdt, + sizeof(struct bdk_devicetree_key_value)); + if (!iter) + return; -typedef struct + free(config_fdt); + config_fdt = iter; + while (iter->key) { + iter++; + } + iter->key = (const char *)strdup(name); + iter->value = (const char *)strdup(val); + iter++; + iter->key = 0; + iter->value = 0; +#endif +} + +/** + * Look up a configuration item in the environment. + * + * @param name + * + * @return + */ +static const char *get_value(const char *name) { - const char *format; /* Printf style format string to create the item name */ - const bdk_config_type_t ctype;/* Type of this item */ - int64_t default_value; /* Default value when no present. String defaults are cast to pointers from this */ - const int64_t min_value;/* Minimum valid value for INT parameters. Unused for Strings */ - const int64_t max_value;/* Maximum valid value for INT parameters. Unused for Strings */ -} bdk_config_info_t; + const struct bdk_devicetree_key_value *iter; + char n[64]; -static void config_set_defaults(void); + strncpy(n, name, sizeof(n)); + n[sizeof(n)-1] = '\0'; -/* Tracing defaults to the level specified here before config files are loaded */ -uint64_t bdk_trace_enables = BDK_TRACE_OVERRIDE; + while (*n) { + iter = config_fdt; + while (iter->key) { + if (strcmp(iter->key, n) == 0) + return iter->value; + iter++; + } -/* Global variables that contain the config inside a FDT */ -static void *config_fdt; -static int config_node; + char *p = strrchr(n, '.'); + if (p) + *p = '\0'; + else + break; + } + return NULL; +} -static bdk_config_info_t config_info[__BDK_CONFIG_END] = { +/** + * Get an integer configuration item + * + * @param cfg_item Config item to get. If the item takes parameters (see bdk_config_t), then the + * parameters are listed following cfg_item. + * + * @return The value of the configuration item, or def_value if the item is not set + */ +int64_t bdk_config_get_int(bdk_config_t cfg_item, ...) +{ + char name[64]; + size_t count; + int64_t tmp; + + assert(cfg_item < __BDK_CONFIG_END); + + /* Make sure the correct access function was called */ + assert(config_info[cfg_item].ctype == BDK_CONFIG_TYPE_INT); + + if (!config_fdt) + return config_info[cfg_item].default_value; + + va_list args; + va_start(args, cfg_item); + vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); + va_end(args); + + const char *val = get_value(name); + if (!val) + return config_info[cfg_item].default_value; + +#if 0 + if ((val[0] == '0') && (val[1] == 'x')) + count = sscanf(val + 2, "%lx", &tmp); + else + count = sscanf(val, "%li", &tmp); +#endif + + if ((val[0] == '0') && (val[1] == 'x')) + count = str_to_hex(val + 2, &tmp); + else + count = str_to_int(val, &tmp); + if (count == 1) { + if ((tmp < config_info[cfg_item].min_value) || + (tmp > config_info[cfg_item].max_value)) { + printk(BIOS_WARNING, "Out of range for %s = %s, using " + "default\n", name, val); + return config_info[cfg_item].default_value; + } + return tmp; + } + + printk(BIOS_WARNING, "Failed to parse %s = %s, using default\n", + name, val); + return config_info[cfg_item].default_value; +} + +/** + * Set an integer configuration item. Note this only sets the item in memory, + * persistent storage is not updated. + * + * @param value Configuration item value + * @param cfg_item Config item to set. If the item takes parameters (see bdk_config_t), then the + * parameters are listed following cfg_item. + */ +void bdk_config_set_int(int64_t value, bdk_config_t cfg_item, ...) +{ + char name[64], val[32]; + + assert(cfg_item < __BDK_CONFIG_END); + + /* Make sure the correct access function was called */ + assert(config_info[cfg_item].ctype == BDK_CONFIG_TYPE_INT); + + if (!config_fdt) + return; + + va_list args; + va_start(args, cfg_item); + vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); + va_end(args); + + snprintf(val, sizeof(val), "0x%016llx", value); + set_value(name, val); +} + +/** + * Get a string configuration item + * + * @param cfg_item Config item to get. If the item takes parameters (see bdk_config_t), then the + * parameters are listed following cfg_item. + * + * @return The value of the configuration item, or def_value if the item is not set + */ +const char *bdk_config_get_str(bdk_config_t cfg_item, ...) +{ + char name[64]; + + /* Make sure the correct access function was called */ + assert(config_info[cfg_item].ctype == BDK_CONFIG_TYPE_STR); + + if (!config_fdt) + return (const char *)config_info[cfg_item].default_value; + + va_list args; + va_start(args, cfg_item); + vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); + + if (BDK_CONFIG_QLM_MODE == cfg_item) { + char name2[64]; + vsnprintf(name2, sizeof(name2)-1,"QLM-MODE.N%d.DLM%d" , args); + const char *val = get_value(name2); + if (val) + printk(BIOS_WARNING, "%s: QLM-MODE.N%%d.DLM%%d format " + "depricated. Please use QLM-MODE.N%%d.QLM%%d " + "instead\n", name2); + } + va_end(args); + + const char *val = get_value(name); + if (val) + return val; + else + return (const char *)config_info[cfg_item].default_value; +} + +bdk_config_info_t config_info[] = { /* Board manufacturing data */ [BDK_CONFIG_BOARD_MODEL] = { .format = "BOARD-MODEL", /* String, No parameters */ @@ -251,11 +435,11 @@ static bdk_config_info_t config_info[__BDK_CONFIG_END] = { .max_value = 0xffff, }, [BDK_CONFIG_PCIE_WIDTH] = { - .format = "PCIE-WIDTH.N%d.PORT%d", /* Parameters: Node, Port */ - .ctype = BDK_CONFIG_TYPE_INT, - .default_value = -1, /* Width override for PCIe links */ - .min_value = -1, - .max_value = 16, + .format = "PCIE-WIDTH.N%d.PORT%d", /* Parameters: Node, Port */ + .ctype = BDK_CONFIG_TYPE_INT, + .default_value = -1, /* Width override for PCIe links */ + .min_value = -1, + .max_value = 16, }, [BDK_CONFIG_PCIE_PHYSICAL_SLOT] = { .format = "PCIE-PHYSICAL-SLOT.N%d.PORT%d", /* Parameters: Node, Port */ @@ -264,6 +448,13 @@ static bdk_config_info_t config_info[__BDK_CONFIG_END] = { .min_value = -1, .max_value = 8191, }, + [BDK_CONFIG_PCIE_SKIP_LINK_TRAIN] = { + .format = "PCIE-SKIP-LINK-TRAIN.N%d.PORT%d", /* Parameters: Node, Port */ + .ctype = BDK_CONFIG_TYPE_INT, + .default_value = 0, /* Define which physical slot we connect to on the board */ + .min_value = 0, + .max_value = 1, + }, [BDK_CONFIG_PCIE_FLASH] = { .format = "PCIE-FLASH.N%d.PORT%d", /* Parameters: Node, Port */ .ctype = BDK_CONFIG_TYPE_STR_LIST, @@ -361,7 +552,6 @@ static bdk_config_info_t config_info[__BDK_CONFIG_END] = { .min_value = -1, .max_value = 40, }, - /* DRAM configuration options */ [BDK_CONFIG_DDR_SPEED] = { .format = "DDR-SPEED.N%d", /* Parameters: Node */ @@ -862,1085 +1052,3 @@ static bdk_config_info_t config_info[__BDK_CONFIG_END] = { }, }; -/** - * Look up a configuration item in the environment. - * - * @param name - * - * @return - */ -static const char *get_value(const char *name, int *blob_size) -{ - if (!config_fdt) - { - bdk_error("bdk-config asked for %s before configuration loaded\n", name); - return NULL; - } - - char n[64]; - strncpy(n, name, sizeof(n)); - n[sizeof(n)-1] = '\0'; - - while (*n) - { - const char *val = fdt_getprop(config_fdt, config_node, n, blob_size); - if (val) - return val; - - char *p = strrchr(n, '.'); - if (p) - *p = '\0'; - else - break; - } - return NULL; -} - -/** - * Get an integer configuration item - * - * @param cfg_item Config item to get. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - * - * @return The value of the configuration item, or def_value if the item is not set - */ -int64_t bdk_config_get_int(bdk_config_t cfg_item, ...) -{ - /* Make sure the correct access function was called */ - if (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_INT) - bdk_fatal("bdk_config_get_int() called for %s, not an int\n", - config_info[cfg_item].format); - - char name[64]; - va_list args; - va_start(args, cfg_item); - vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); - va_end(args); - - const char *val = get_value(name, NULL); - if (val) - { - int count; - int64_t tmp; - if ((val[0] == '0') && (val[1] == 'x')) - count = sscanf(val + 2, "%lx", &tmp); - else - count = sscanf(val, "%li", &tmp); - if (count == 1) - { - if ((tmp < config_info[cfg_item].min_value) || (tmp > config_info[cfg_item].max_value)) - { - bdk_warn("Out of range for %s = \"%s\", using default\n", name, val); - return config_info[cfg_item].default_value; - } - return tmp; - } - else - { - bdk_warn("Failed to parse %s = \"%s\", using default\n", name, val); - return config_info[cfg_item].default_value; - } - } - else - return config_info[cfg_item].default_value; -} - -/** - * Get a string configuration item - * - * @param cfg_item Config item to get. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - * - * @return The value of the configuration item, or def_value if the item is not set - */ -const char *bdk_config_get_str(bdk_config_t cfg_item, ...) -{ - /* Make sure the correct access function was called */ - if (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_STR) - bdk_fatal("bdk_config_get_str() called for %s, not a str\n", - config_info[cfg_item].format); - - char name[64]; - va_list args; - va_start(args, cfg_item); - vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); - - if (BDK_CONFIG_QLM_MODE == cfg_item) - { - char name2[64]; - vsnprintf(name2, sizeof(name2)-1,"QLM-MODE.N%d.DLM%d" , args); - const char *val = get_value(name2, NULL); - if (val) - bdk_warn("%s: QLM-MODE.N%%d.DLM%%d format depricated. Please use QLM-MODE.N%%d.QLM%%d instead\n", name2); - - } - va_end(args); - - const char *val = get_value(name, NULL); - if (val) - return val; - else - return (const char *)config_info[cfg_item].default_value; -} - -/** - * Get a binary blob - * - * @param blob_size Integer to receive the size of the blob - * @param cfg_item Config item to get. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - * - * @return The value of the configuration item, or def_value if the item is not set - */ -const void* bdk_config_get_blob(int *blob_size, bdk_config_t cfg_item, ...) -{ - char name[64]; - va_list args; - va_start(args, cfg_item); - vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); - va_end(args); - - const void *val = get_value(name, blob_size); - if (val) - return val; - else - return (const void *)config_info[cfg_item].default_value; -} - -/** - * Set an integer configuration item. Note this only sets the item in memory, - * persistent storage is not updated. The optional parameters for the setting are - * not supplied, meaning this function only changes the global default. - * - * @param value Configuration item value - * @param cfg_item Config item to set. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - */ -void bdk_config_set_int_no_param(int64_t value, bdk_config_t cfg_item) -{ - /* Make sure the correct access function was called */ - if (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_INT) - bdk_fatal("bdk_config_set_int_no_param() called for %s, not an int\n", - config_info[cfg_item].format); - - char name[64]; - char valstr[20]; - /* Create a name without the optional parameters */ - strncpy(name, config_info[cfg_item].format, sizeof(name) - 1); - name[sizeof(name) - 1] = 0; - char *ptr = strchr(name, '.'); - if (ptr) - *ptr = 0; - - if (!config_fdt) - { - bdk_error("bdk-config set %s before configuration loaded\n", name); - return; - } - if ((value < config_info[cfg_item].min_value) || (value > config_info[cfg_item].max_value)) - { - bdk_error("Set out of range for %s = \"0x%lx\", ignoring\n", name, value); - return; - } - - if (value < 10) - snprintf(valstr, sizeof(valstr), "%ld", value); - else - snprintf(valstr, sizeof(valstr), "0x%lx", value); - - int status = fdt_setprop_string(config_fdt, config_node, name, valstr); - if (status < 0) - bdk_fatal("Failed to set %s=%s in FDT\n", name, valstr); -} - -/** - * Set an integer configuration item. Note this only sets the item in memory, - * persistent storage is not updated. - * - * @param value Configuration item value - * @param cfg_item Config item to set. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - */ -void bdk_config_set_int(int64_t value, bdk_config_t cfg_item, ...) -{ - /* Make sure the correct access function was called */ - if (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_INT) - bdk_fatal("bdk_config_set_int() called for %s, not an int\n", - config_info[cfg_item].format); - - char name[64]; - char valstr[20]; - va_list args; - va_start(args, cfg_item); - vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); - va_end(args); - - if (!config_fdt) - { - bdk_error("bdk-config set %s before configuration loaded\n", name); - return; - } - if ((value < config_info[cfg_item].min_value) || (value > config_info[cfg_item].max_value)) - { - bdk_error("Set out of range for %s = \"0x%lx\", ignoring\n", name, value); - return; - } - - if (value < 10) - snprintf(valstr, sizeof(valstr), "%ld", value); - else - snprintf(valstr, sizeof(valstr), "0x%lx", value); - - int status = fdt_setprop_string(config_fdt, config_node, name, valstr); - if (status < 0) - bdk_fatal("Failed to set %s=%s in FDT\n", name, valstr); -} - -/** - * Set an integer configuration item. Note this only sets the item in memory, - * persistent storage is not updated. - * - * @param value Configuration item value - * @param cfg_item Config item to set. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - */ -void bdk_config_set_str(const char *value, bdk_config_t cfg_item, ...) -{ - /* Make sure the correct access function was called */ - if (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_STR) - bdk_fatal("bdk_config_set_str() called for %s, not a str\n", - config_info[cfg_item].format); - - char name[64]; - va_list args; - - va_start(args, cfg_item); - vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); - va_end(args); - - if (!config_fdt) - { - bdk_error("bdk-config set %s before configuration loaded\n", name); - return; - } - - int status; - if (value) - status = fdt_setprop_string(config_fdt, config_node, name, value); - else - status = fdt_delprop(config_fdt, config_node, name); - - if ((status < 0) && (status != -FDT_ERR_NOTFOUND)) - bdk_fatal("Failed to set %s=%s in FDT\n", name, value); -} - -/** - * Set a blob configuration item. Note this only sets the - * item in memory, persistent storage is not updated. The optional - * parameters for the setting are not supplied, meaning this function - * only changes the global default. - * - * @param size Size of the item in bytes. A size of zero removes the device tree field - * @param value Configuration item value - * @param cfg_item Config item to set. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - */ -void bdk_config_set_blob_no_param(int size, const void *value, bdk_config_t cfg_item) -{ - /* Make sure the correct access function was called */ - if ((config_info[cfg_item].ctype != BDK_CONFIG_TYPE_BINARY) && - (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_STR_LIST)) - bdk_fatal("bdk_config_set_blob() called for %s, not binary\n", - config_info[cfg_item].format); - - char name[64]; - /* Create a name without the optional parameters */ - strncpy(name, config_info[cfg_item].format, sizeof(name) - 1); - name[sizeof(name) - 1] = 0; - char *ptr = strchr(name, '.'); - if (ptr) - *ptr = 0; - - if (!config_fdt) - { - bdk_error("bdk-config set %s before configuration loaded\n", name); - return; - } - - int status; - if (size) - status = fdt_setprop(config_fdt, config_node, name, value, size); - else - status = fdt_delprop(config_fdt, config_node, name); - - if ((status < 0) && (status != -FDT_ERR_NOTFOUND)) - bdk_fatal("Failed to set %s in FDT\n", name); -} - -/** - * Set a blob configuration item. Note this only sets the - * item in memory, persistent storage is not updated. - * - * @param size Size of the item in bytes. A size of zero removes the device tree field - * @param value Configuration item value - * @param cfg_item Config item to set. If the item takes parameters (see bdk_config_t), then the - * parameters are listed following cfg_item. - */ -void bdk_config_set_blob(int size, const void *value, bdk_config_t cfg_item, ...) -{ - /* Make sure the correct access function was called */ - if ((config_info[cfg_item].ctype != BDK_CONFIG_TYPE_BINARY) && - (config_info[cfg_item].ctype != BDK_CONFIG_TYPE_STR_LIST)) - bdk_fatal("bdk_config_set_blob() called for %s, not binary\n", - config_info[cfg_item].format); - - char name[64]; - va_list args; - - va_start(args, cfg_item); - vsnprintf(name, sizeof(name)-1, config_info[cfg_item].format, args); - va_end(args); - - if (!config_fdt) - { - bdk_error("bdk-config set %s before configuration loaded\n", name); - return; - } - - int status; - if (size) - status = fdt_setprop(config_fdt, config_node, name, value, size); - else - status = fdt_delprop(config_fdt, config_node, name); - - if ((status < 0) && (status != -FDT_ERR_NOTFOUND)) - bdk_fatal("Failed to set %s in FDT\n", name); -} - -/** - * Multiple functions need to display the config item help string in a format - * suitable for inclusion in a device tree. This function displays the help - * message properly indented and such. - * - * @param cfg Config item to display help for - */ -static void display_help(bdk_config_t cfg) -{ - /* Print the help text as a comment before the entry */ - /* Indent with tabs like Linux requires */ - printf("\n"); - printf("\t/* "); - const char *ptr = bdk_config_get_help(cfg); - while (*ptr) - { - putchar(*ptr); - if (*ptr == '\n') - putchar('\t'); - ptr++; - } - printf(" */\n"); - /* Print the parameter and its default value a comment. This will be - a reference that is easy for the user to change */ - printf("\t//%s = ", config_info[cfg].format); - switch (config_info[cfg].ctype) - { - case BDK_CONFIG_TYPE_INT: - if (config_info[cfg].default_value < 10) - printf("\"%ld\"", config_info[cfg].default_value); - else - printf("\"0x%lx\"", config_info[cfg].default_value); - break; - case BDK_CONFIG_TYPE_STR: - case BDK_CONFIG_TYPE_STR_LIST: - if (config_info[cfg].default_value) - printf("\"%s\"", (const char *)config_info[cfg].default_value); - else - printf("\"\""); - break; - case BDK_CONFIG_TYPE_BINARY: - printf("[]"); - break; - } - printf(";\n"); -} - -/** - * Display the active configuration as a valid device tree - */ -void bdk_config_show(void) -{ - /* Output the standard DTS headers */ - printf("/dts-v1/;\n"); - printf("\n"); - printf("/ {\n"); - printf("cavium,bdk {\n"); - for (bdk_config_t cfg = 0; cfg < __BDK_CONFIG_END; cfg++) - { - /* Show the help message */ - display_help(cfg); - - /* Figure out how much of the config item is fixed versus - the optional parameters */ - const char *format = config_info[cfg].format; - const char *format_param = strchr(format, '.'); - int format_length = 0; - if (format_param) - format_length = format_param - format; - - /* Loop through all device tree entries displaying the ones that - match this format */ - int offset = fdt_first_property_offset(config_fdt, config_node); - while (offset >= 0) - { - /* Get the device tree item */ - const char *name = NULL; - int data_size = 0; - const char *data = fdt_getprop_by_offset(config_fdt, offset, &name, &data_size); - const char *data_end = data + data_size; - /* Find the first param */ - const char *name_param = strchr(name, '.'); - int name_length = 0; - if (name_param) - { - /* We want to compare up to the first param */ - name_length = name_param - name; - /* If the lengths are different not including the parameters, - then we force a full matchn which will always fail */ - if (name_length != format_length) - name_length = 0; - } - else /* No params, match base of format */ - name_length = format_length; - - /* Check if it matches the current config format */ - int match; - if (name_length) - { - /* Check the prefix */ - match = strncmp(name, format, name_length); - if (match == 0) - { - /* Prefix matched. We only really match if the next - character is the end of the string or a '.' */ - if ((name[name_length] != 0) && (name[name_length] != '.')) - match = 1; - } - } - else - match = strcmp(name, format); - /* Print matching entries */ - if (match == 0) - { - if (config_info[cfg].ctype == BDK_CONFIG_TYPE_BINARY) - { - printf("\t%s = [", name); - const char *ptr = data; - while (ptr < data_end) - { - printf(" %02x", (int)*ptr); - ptr++; - } - printf(" ]"); - } - else - { - printf("\t%s = \"%s\"", name, data); - data += strlen(data) + 1; - while (data < data_end) - { - printf(",\n\t\t\"%s\"", data); - data += strlen(data) + 1; - } - } - printf(";\n"); - } - offset = fdt_next_property_offset(config_fdt, offset); - } - } - /* Output the standard DTS footers */ - printf("}; /* cavium,bdk */\n"); - printf("}; /* / */\n"); -} - -/** - * Display a list of all possible config items with help text - */ -void bdk_config_help(void) -{ - /* Write out formatted as part of a device tree source (dts) file */ - printf("/dts-v1/;\n"); - printf("\n"); - printf("/ {\n"); - printf("cavium,bdk {\n"); - for (bdk_config_t cfg = 0; cfg < __BDK_CONFIG_END; cfg++) - display_help(cfg); - printf("}; /* cavium,bdk */\n"); - printf("}; /* / */\n"); -} - - -/** - * Save the current configuration to flash - * - * @return Zero on success, negative on failure - */ -int bdk_config_save(void) -{ - /* Pack the FDT so it uses less space */ - int status = fdt_pack(config_fdt); - if (status < 0) - { - bdk_error("FDT error %d: %s\n", status, fdt_strerror(status)); - return -1; - } - - /* Calculate a CRC32 of the FDT */ - int fdt_size = fdt_totalsize(config_fdt); - uint32_t crc32 = bdk_crc32(config_fdt, fdt_size, 0); - - /* Open the output file */ - FILE *outf = fopen("/fatfs/default.dtb", "wb"); - if (!outf) - { - bdk_error("Failed to open flash"); - return -1; - } - - /* Write the FDT */ - if (fwrite(config_fdt, fdt_size, 1, outf) != 1) - { - bdk_error("Failed to write FDT"); - fclose(outf); - return -1; - } - - /* Save the CRC32 in the same endianness as the FDT */ - crc32 = cpu_to_fdt32(crc32); - if (fwrite(&crc32, sizeof(crc32), 1, outf) != 1) - { - bdk_error("Failed to write FDT CRC32"); - fclose(outf); - return -1; - } - - fclose(outf); - return 0; -} - -/** - * Takes the current live device tree and exports it to a memory address suitable - * for passing to the next binary in register X1. - * - * @return Physical address of the device tree, or 0 on failure - */ -uint64_t __bdk_config_export_to_mem(void) -{ - void *end_ptr = sbrk(0); - bdk_node_t node = bdk_numa_master(); - int fdt_size = fdt_totalsize(config_fdt); - - /* Round size up to 4KB boundary, be sure to add 4 bytes for CRC32 */ - int fdt_space = (fdt_size + 4 + 0xfff) & -4096; - /* First try 4MB - FDT size as this keeps the FDT in the 4MB secure space - setup by ATF */ - void *fdt_ptr = bdk_phys_to_ptr(0x400000 - fdt_space); - if (!__bdk_is_dram_enabled(node)) - { - /* Address must be in L2 */ - int l2_size = bdk_l2c_get_cache_size_bytes(node); - void *l2_ptr = bdk_phys_to_ptr(l2_size - fdt_space); - if (l2_ptr < fdt_ptr) - fdt_ptr = l2_ptr; - if (fdt_ptr < end_ptr) - { - bdk_error("No room for FDT to pass to next binary\n"); - return 0; - } - } - else - { - /* We have DRAM, make sure we're past the end of this image */ - if (fdt_ptr < end_ptr) - fdt_ptr = end_ptr; - } - uint32_t crc32 = bdk_crc32(config_fdt, fdt_size, 0); - fdt_move(config_fdt, fdt_ptr, fdt_size); - /* CRC32 is stored in same endianness as FDT at the end */ - *(uint32_t *)((const char *)fdt_ptr + fdt_size) = cpu_to_fdt32(crc32); - BDK_TRACE(FDT_OS, "Exported device tree to memory %p, size 0x%x, CRC32 %08x\n", - fdt_ptr, fdt_size, crc32); - return bdk_ptr_to_phys(fdt_ptr); -} - -/** - * Return a pointer to the device tree used for configuration - * - * @return FDT or NULL on failure - */ -void* bdk_config_get_fdt(void) -{ - return config_fdt; -} - -/** - * Set the device tree used for configuration - * - * @param fdt Device tree to use. Memory is assumed to be from malloc() and bdk_config takes - * over ownership on success - * - * @return Zero on success, negative on failure - */ -int bdk_config_set_fdt(void *fdt) -{ - int offset = fdt_path_offset(fdt, "/cavium,bdk"); /* Find our node */ - if (offset < 0) - return -1; - free(config_fdt); - config_fdt = fdt; - config_node = offset; - return 0; -} - -/** - * Write all default values to a FDT. Missing config items get defaults in the - * BDK config, this function adds those defaults to the FDT. This way other code - * gets the default value without needing special code. - * - * @param fdt FDT structure to fill defaults into - * - * @return Zero on success, negative on failure - */ -int bdk_config_expand_defaults(void *fdt) -{ - const struct fdt_property *prop; - - /* The best defaults may have changed while this image was running if DRAM - is setup. Update the defaults before expanding them */ - config_set_defaults(); - - int fdt_node = fdt_path_offset(fdt, "/cavium,bdk"); /* Find our node */ - if (fdt_node < 0) - { - bdk_error("Failed to find top node, FDT error %d: %s\n", - fdt_node, fdt_strerror(fdt_node)); - return -1; - } - - /* Loop through all configuration items */ - for (bdk_config_t cfg = 0; cfg < __BDK_CONFIG_END; cfg++) - { - /* Figure out the base name without and dot parameters */ - const char *name = config_info[cfg].format; - const char *name_end = strchr(name, '.'); - int name_len; - if (name_end) - name_len = name_end - name; - else - name_len = strlen(name); - /* Try and find the base name in the FDT */ - prop = fdt_get_property_namelen(fdt, fdt_node, name, name_len, NULL); - /* If it wasn't found, then we need to add the default */ - if (prop == NULL) - { - /* Create a copy of the name for use in FDT calls */ - char temp_name[name_len + 1]; - memcpy(temp_name, name, name_len); - temp_name[name_len] = 0; - /* Call the correct FDT call based on the type */ - int status = 0; - switch (config_info[cfg].ctype) - { - case BDK_CONFIG_TYPE_INT: - { - char temp_value[20]; - if (config_info[cfg].default_value < 10) - snprintf(temp_value, sizeof(temp_value), "%ld", config_info[cfg].default_value); - else - snprintf(temp_value, sizeof(temp_value), "0x%lx", config_info[cfg].default_value); - /* Store the default int value */ - status = fdt_setprop_string(fdt, fdt_node, temp_name, temp_value); - break; - } - case BDK_CONFIG_TYPE_STR: - /* Store the default string value, if present */ - if (config_info[cfg].default_value) - { - status = fdt_setprop_string(fdt, fdt_node, temp_name, - (const char *)config_info[cfg].default_value); - } - break; - case BDK_CONFIG_TYPE_STR_LIST: - /* Do nothing, string list default to empty */ - break; - case BDK_CONFIG_TYPE_BINARY: - /* Do nothing, binary defaults to empty */ - break; - } - if (status < 0) - { - bdk_error("Failed to set default for %s, FDT error %d: %s\n", - temp_name, status, fdt_strerror(status)); - return -1; - } - } - } - return 0; -} - -/** - * Some of the default config values can vary based on runtime parameters. This - * function sets those default parameters. It must be run before anyone calls - * bdk_config_get_*(). - */ -static void config_set_defaults(void) -{ - bool isEmulation = bdk_is_platform(BDK_PLATFORM_EMULATOR); - /* This is Cavium's OUI with the local admin bit. We will use this as a - default as it won't collide with official addresses, but is sort of - part of the Cavium range. The lower three bytes will be updated with - the wafer info */ - uint64_t mac_address = 0x020fb7000000ull; - /* Set the lower MAC address bits based on the chip manufacturing - information. This should give reasonable MAC address defaults - for production parts */ - if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) - { - BDK_CSR_INIT(fus_dat0, bdk_numa_local(), BDK_MIO_FUS_DAT0); - mac_address |= fus_dat0.u & 0xffffff; - } - else - { - mac_address |= bdk_fuse_read_range(bdk_numa_local(), BDK_FUS_FUSE_NUM_E_MFG_INFOX(0), 24); - } - config_info[BDK_CONFIG_MAC_ADDRESS].default_value = mac_address; - - /* Set the number of packet buffers */ - int num_packet_buffers = 4096; - /* If DRAM is setup, allocate 8K buffers for 8 ports plus some slop */ - if (__bdk_is_dram_enabled(bdk_numa_master())) - num_packet_buffers = 8192 * 16 + 1024; - else if (isEmulation) { - if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) - num_packet_buffers = 4096 * 4; - } - config_info[BDK_CONFIG_NUM_PACKET_BUFFERS].default_value = num_packet_buffers; - config_info[BDK_CONFIG_PACKET_BUFFER_SIZE].default_value = 1024; - - /* Asim doesn't scale to 48 cores well. Limit to 4 */ - if (bdk_is_platform(BDK_PLATFORM_ASIM)) - config_info[BDK_CONFIG_COREMASK].default_value = 0xf; - /* CN88XX pass 1.x doesn't support EA */ - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) - config_info[BDK_CONFIG_PCIE_EA].default_value = 0; - /* Emulator only supports 4 cores */ - if (isEmulation) - config_info[BDK_CONFIG_COREMASK].default_value = 0xf; -} - -/** - * BDK configuration items are stored in a device tree so thay can be passed to - * other software later. This function creates the initial empty device tree - * used for BDK configuration items. The values will be populated as configuration - * files are read from flash. - */ -static void config_setup_fdt(void) -{ - const int FDT_SIZE = 0x10000; - config_fdt = calloc(1, FDT_SIZE); - if (!config_fdt) - bdk_fatal("Unable to allocate memory for config FDT\n"); - if (fdt_create_empty_tree(config_fdt, FDT_SIZE) < 0) - bdk_fatal("Unable to create FDT for config\n"); - config_node = fdt_add_subnode(config_fdt, 0, "cavium,bdk"); - if (config_node < 0) - bdk_fatal("Unable to create cavium,bdk node in FDT\n"); -} - -/** - * Parse a FDT and copy its properties to our configuration FDT - * - * @param fdt FDT to parse - */ -static int config_parse_fdt(const void *fdt, const char *base_path) -{ - /* Check the FDT header */ - int result = fdt_check_header(fdt); - if (result) - goto fail; - - /* Find our node */ - result = fdt_path_offset(fdt, base_path); - if (result < 0) - goto fail; - - /* Copy all parameters to our in memory FDT */ - int offset = fdt_first_property_offset(fdt, result); - while (offset >= 0) - { - const char *name = NULL; - int blob_size = 0; - const char *data = fdt_getprop_by_offset(fdt, offset, &name, &blob_size); - result = fdt_setprop(config_fdt, config_node, name, data, blob_size); - offset = fdt_next_property_offset(fdt, offset); - } - return 0; -fail: - bdk_error("FDT error %d: %s\n", result, fdt_strerror(result)); - return -1; -} - -/** - * Load a FDT from a file and pull in its configuration properties - * - * @param filename File to read from - * @param offset Offset into the file to read from - * - * @return Zero on success, negative on failure - */ -static int config_load_file(const char *filename, uint64_t offset) -{ - uint64_t ftd_size = 0; - bdk_signed_flags_t sign_flags = BDK_SIGNED_FLAG_NONE; - if (offset) - sign_flags = BDK_SIGNED_FLAG_ALLOW_UNSIGNED | BDK_SIGNED_FLAG_NOT_ENCRYPTED; - void *fdt = bdk_signed_load(filename, offset, BDK_SIGNED_DTS, sign_flags, &ftd_size); - if (!fdt) - return -1; - - /* Make sure the read succeeded */ - if (ftd_size < (int)sizeof(struct fdt_header)) - { - bdk_error("Invalid device tee %s\n", filename); - free(fdt); - return -1; - } - - if (fdt_check_header(fdt)) - { - bdk_error("Invalid FDT header read from %s\n", filename); - free(fdt); - return -1; - } - - /* Make sure we read enough data to contain the FDT */ - int correct_size = fdt_totalsize(fdt); - if ((int)ftd_size < correct_size) - { - bdk_error("Unable to read FDT from %s\n", filename); - free(fdt); - return -1; - } - - /* Check if a CRC32 was added on the end of the FDT */ - if ((int)ftd_size >= correct_size + 4) - { - uint32_t crc32 = bdk_crc32(fdt, correct_size, 0); - uint32_t correct_crc32 = *(uint32_t *)((const char *)fdt + correct_size); - /* CRC32 is stored in same endianness as FDT */ - correct_crc32 = fdt32_to_cpu(correct_crc32); - if (crc32 != correct_crc32) - { - bdk_error("FDT failed CRC32 verification (%s)\n", filename); - free(fdt); - return -1; - } - //printf("PASS: FDT CRC32 verification (%s)\n", filename); - } - - /* Parse the device tree, adding its configuration to ours */ - if (config_parse_fdt(fdt, "/cavium,bdk")) - { - free(fdt); - return -1; - } - - free(fdt); - return 0; -} - -/** - * Internal BDK function to initialize the config system. Must be called before - * any configuration functions are called - */ -void __bdk_config_init(void) -{ - bool done_trust_init = false; - /* Set default that can vary dynamically at runtime */ - config_set_defaults(); - - /* Regsiter X1 is expected to be a device tree when we boot. Check that - the physical address seems correct, then load the device tree */ - if ((__bdk_init_reg_x1 > 0) && /* Not zero */ - (__bdk_init_reg_x1 < 0x1000000) && /* In the lower 16MB */ - ((__bdk_init_reg_x1 & 0xfff) == 0)) /* Aligned on a 4KB boundary */ - { - const void *fdt = (const void *)__bdk_init_reg_x1; - /* Check the FDT header */ - int result = fdt_check_header(fdt); - if (result) - result = -1; /* Invalid tree */ - else - { - int fdt_size = fdt_totalsize(fdt); - uint32_t crc32 = bdk_crc32(fdt, fdt_size, 0); - uint32_t correct_crc32 = *(uint32_t *)((const char *)fdt + fdt_size); - /* CRC32 is stored in same endianness as FDT */ - correct_crc32 = fdt32_to_cpu(correct_crc32); - if (crc32 == correct_crc32) - { - //printf("Previous image FDT passed CRC32 verification(%p, size 0x%x, CRC32 %08x)\n", fdt, fdt_size, crc32); - result = fdt_path_offset(fdt, "/cavium,bdk"); /* Find our node */ - } - else - { - bdk_error("Previous image FDT failed CRC32 verification(%p, size 0x%x)\n", fdt, fdt_size); - result = -1; /* Invalid tree */ - } - } - /* If tree is valid so far, attempt to move it into our memory space */ - if (result > 0) - { - /* 4KB extra room for growth */ - const int fdt_size = fdt_totalsize(fdt) + 4096; - config_fdt = calloc(1, fdt_size); - if (config_fdt) - { - int result = fdt_move(fdt, config_fdt, fdt_size); - if (result == 0) - { - /* Find our node */ - config_node = fdt_path_offset(config_fdt, "/cavium,bdk"); - if (config_node > 0) - { - printf("Using configuration from previous image\n"); - goto done; - } - else - { - bdk_error("Unable to find BDK node after move\n"); - free(config_fdt); - config_node = 0; - config_fdt = NULL; - } - } - else - { - bdk_error("Unable to move passed device tree\n"); - free(config_fdt); - config_fdt = NULL; - } - } - else - bdk_error("Failed to allocate memory for passed device tree (%d bytes)\n", fdt_size); - } - } - - /* Create the global device tree used to store config items */ - config_setup_fdt(); - /* Setup trust level so reading device trees works */ - __bdk_trust_init(); - done_trust_init = true; - - if (bdk_is_platform(BDK_PLATFORM_ASIM)) - { - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) - bdk_config_set_str("ASIM-CN88XX", BDK_CONFIG_BOARD_MODEL); - else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) - bdk_config_set_str("ASIM-CN83XX", BDK_CONFIG_BOARD_MODEL); - else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) - bdk_config_set_str("ASIM-CN81XX", BDK_CONFIG_BOARD_MODEL); - else if (CAVIUM_IS_MODEL(CAVIUM_CN93XX)) - bdk_config_set_str("ASIM-CN93XX", BDK_CONFIG_BOARD_MODEL); - } - else if (bdk_is_platform(BDK_PLATFORM_EMULATOR)) - { - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) - bdk_config_set_str("EMUL-CN88XX", BDK_CONFIG_BOARD_MODEL); - else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) - bdk_config_set_str("EMUL-CN83XX", BDK_CONFIG_BOARD_MODEL); - else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) - bdk_config_set_str("EMUL-CN81XX", BDK_CONFIG_BOARD_MODEL); - else if (CAVIUM_IS_MODEL(CAVIUM_CN93XX)) - bdk_config_set_str("EMUL-CN93XX", BDK_CONFIG_BOARD_MODEL); - } - else if (config_load_file("/rom/boardcfg.dtb", 0) == 0) - { - printf("Board manufacturing information loaded from ROM-FS\n"); - } - /* Load manufacturing data from the top 64KB of flash */ - else if (config_load_file("/boot", BDK_CONFIG_MANUFACTURING_ADDRESS) != 0) - { - printf("\33[1m"); /* Bold */ - bdk_warn("\n"); - bdk_warn("********************************************************\n"); - bdk_warn("* Board manufacturing information not found. Program\n"); - bdk_warn("* the board manufacturing information in the Setup menu.\n"); - bdk_warn("********************************************************\n"); - bdk_warn("\n"); - printf("\33[0m"); /* Normal */ - goto done; - } - - const char *model = bdk_config_get_str(BDK_CONFIG_BOARD_MODEL); - const char *revision = bdk_config_get_str(BDK_CONFIG_BOARD_REVISION); - - /* Load BOARD-REVISION.cfg if it is on ROM-FS */ - if (model && revision) - { - char filename[64]; - snprintf(filename, sizeof(filename), "/rom/%s-%s.dtb", model, revision); - if (config_load_file(filename, 0) == 0) - goto done; - } - - /* Load BOARD.cfg if it is on ROM-FS */ - if (model) - { - char filename[64]; - snprintf(filename, sizeof(filename), "/rom/%s.dtb", model); - if (config_load_file(filename, 0) == 0) - goto done; - } - - /* Load default.dtb if it is there */ - if (config_load_file("/fatfs/default.dtb", 0) == 0) - goto done; - - /* Load BOARD-REVISION.cfg if it is there */ - if (model && revision) - { - char filename[64]; - snprintf(filename, sizeof(filename), "/fatfs/%s-%s.dtb", model, revision); - if (config_load_file(filename, 0) == 0) - goto done; - } - - /* Load BOARD.cfg if it is there */ - if (model) - { - char filename[64]; - snprintf(filename, sizeof(filename), "/fatfs/%s.dtb", model); - if (config_load_file(filename, 0) == 0) - goto done; - } - - /* No board specific configuration was found. Warn the user */ - printf("\33[1m"); /* Bold */ - bdk_warn("\n"); - bdk_warn("********************************************************\n"); - bdk_warn("* Board configuration file not found. Either the board\n"); - bdk_warn("* model is incorrect, or factory settings are not\n"); - bdk_warn("* available. DTB file not found for board \"%s\".\n", model); - bdk_warn("********************************************************\n"); - bdk_warn("\n"); - printf("\33[0m"); /* Normal */ - -done: - bdk_config_set_str(bdk_version_string(), BDK_CONFIG_VERSION); - /* Load the tracing level */ - bdk_trace_enables = bdk_config_get_int(BDK_CONFIG_TRACE); - if (BDK_TRACE_OVERRIDE) - bdk_trace_enables = BDK_TRACE_OVERRIDE; - if (!done_trust_init) - __bdk_trust_init(); -} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-ecam-io.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-ecam-io.c new file mode 100644 index 0000000000..8120820626 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-ecam-io.c @@ -0,0 +1,373 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-arch/bdk-csrs-ecam.h" +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-pccpf.h" +#include "libbdk-arch/bdk-csrs-pem.h" +#include "libbdk-hal/device/bdk-device.h" +#include "libbdk-hal/bdk-ecam.h" + +#if 1 /* Support CN88XX pass 1.0 */ +/******************************************************************* + ******************************************************************* + These functions are related to CN88XX pass 1.0 errata and do not + apply to any other chip + ******************************************************************* + *******************************************************************/ + +/** + * Errata (ECAM-22630) ECAM function accesses can fault + * For some errata workaround we need a check to tell if a ECAM access is to a + * valid intenral device. This function decodes a pcc_dev_con_e enumeration and + * checks if the supplied arguments match it. This should only + * ever be called on CN88XX pass 1.0. + * + * @param ecam ECAM to check + * @param bus ECAM bus number + * @param dev Device to check + * @param fn sub function of device + * @param dev_con Enumeration to match against + * + * @return Non zero if the device matches + */ +static int is_internal_cn88xxp1_0(const bdk_device_t *device, int dev_con) +{ + union bdk_pcc_dev_con_s d = { .u = dev_con }; + return (d.cn8.ecam == device->ecam) && (d.s.bus == device->bus) && (d.s.func == ((device->dev<<3)|device->func)); +} + +/** + * Errata (ECAM-22630) ECAM function accesses can fault + * This is a companion to the function above to determine if the ECAM device is + * any of the valid internal devices. This should only ever be + * called on CN88XX pass 1.0. + * + * @param ecam ECAM to check + * @param bus ECAM bus number + * @param dev Device to check + * @param fn sub function of device + * + * @return Non zero if the device matches + */ +static int is_any_internal_cn88xxp1_0(const bdk_device_t *device) +{ + /* Errata (ECAM-22630) ECAM function accesses can fault + CN88XXP1.0: The ECAM has a bug where accessing a non-existent + device causes an exception. This is a list of all valid devices + for CN88XX pass 1.0 */ + static const uint32_t INTERNAL_DEVICES_CN88XXP1_0[] = { + BDK_PCC_DEV_CON_E_BGXX(0), + BDK_PCC_DEV_CON_E_BGXX(1), + BDK_PCC_DEV_CON_E_DAP, + BDK_PCC_DEV_CON_E_DFA, + BDK_PCC_DEV_CON_E_FUSF, + BDK_PCC_DEV_CON_E_GIC_CN8, + BDK_PCC_DEV_CON_E_GPIO_CN8, + BDK_PCC_DEV_CON_E_GSERX(0), + BDK_PCC_DEV_CON_E_GSERX(1), + BDK_PCC_DEV_CON_E_GSERX(10), + BDK_PCC_DEV_CON_E_GSERX(11), + BDK_PCC_DEV_CON_E_GSERX(12), + BDK_PCC_DEV_CON_E_GSERX(13), + BDK_PCC_DEV_CON_E_GSERX(2), + BDK_PCC_DEV_CON_E_GSERX(3), + BDK_PCC_DEV_CON_E_GSERX(4), + BDK_PCC_DEV_CON_E_GSERX(5), + BDK_PCC_DEV_CON_E_GSERX(6), + BDK_PCC_DEV_CON_E_GSERX(7), + BDK_PCC_DEV_CON_E_GSERX(8), + BDK_PCC_DEV_CON_E_GSERX(9), + BDK_PCC_DEV_CON_E_GTI_CN8, + BDK_PCC_DEV_CON_E_IOBNX(0), + BDK_PCC_DEV_CON_E_IOBNX(1), + BDK_PCC_DEV_CON_E_KEY, + BDK_PCC_DEV_CON_E_L2C, + BDK_PCC_DEV_CON_E_L2C_CBCX(0), + BDK_PCC_DEV_CON_E_L2C_CBCX(1), + BDK_PCC_DEV_CON_E_L2C_CBCX(2), + BDK_PCC_DEV_CON_E_L2C_CBCX(3), + BDK_PCC_DEV_CON_E_L2C_MCIX(0), + BDK_PCC_DEV_CON_E_L2C_MCIX(1), + BDK_PCC_DEV_CON_E_L2C_MCIX(2), + BDK_PCC_DEV_CON_E_L2C_MCIX(3), + BDK_PCC_DEV_CON_E_L2C_TADX(0), + BDK_PCC_DEV_CON_E_L2C_TADX(1), + BDK_PCC_DEV_CON_E_L2C_TADX(2), + BDK_PCC_DEV_CON_E_L2C_TADX(3), + BDK_PCC_DEV_CON_E_L2C_TADX(4), + BDK_PCC_DEV_CON_E_L2C_TADX(5), + BDK_PCC_DEV_CON_E_L2C_TADX(6), + BDK_PCC_DEV_CON_E_L2C_TADX(7), + BDK_PCC_DEV_CON_E_LMCX(0), + BDK_PCC_DEV_CON_E_LMCX(1), + BDK_PCC_DEV_CON_E_LMCX(2), + BDK_PCC_DEV_CON_E_LMCX(3), + BDK_PCC_DEV_CON_E_MIO_BOOT, + BDK_PCC_DEV_CON_E_MIO_EMM, + BDK_PCC_DEV_CON_E_MIO_FUS, + BDK_PCC_DEV_CON_E_MIO_PTP, + BDK_PCC_DEV_CON_E_MIO_TWSX(0), + BDK_PCC_DEV_CON_E_MIO_TWSX(1), + BDK_PCC_DEV_CON_E_MIO_TWSX(2), + BDK_PCC_DEV_CON_E_MIO_TWSX(3), + BDK_PCC_DEV_CON_E_MIO_TWSX(4), + BDK_PCC_DEV_CON_E_MIO_TWSX(5), + BDK_PCC_DEV_CON_E_MPI, + BDK_PCC_DEV_CON_E_MRML, + BDK_PCC_DEV_CON_E_NCSI, + BDK_PCC_DEV_CON_E_NIC_CN88XX, + BDK_PCC_DEV_CON_E_OCLAX_CN8(0), + BDK_PCC_DEV_CON_E_OCLAX_CN8(1), + BDK_PCC_DEV_CON_E_OCLAX_CN8(2), + BDK_PCC_DEV_CON_E_OCLAX_CN8(3), + BDK_PCC_DEV_CON_E_OCLAX_CN8(4), + BDK_PCC_DEV_CON_E_OCX, + BDK_PCC_DEV_CON_E_PCCBR_DFA, + BDK_PCC_DEV_CON_E_PCCBR_MRML, + BDK_PCC_DEV_CON_E_PCCBR_NIC_CN88XX, + BDK_PCC_DEV_CON_E_PCCBR_RAD_CN88XX, + BDK_PCC_DEV_CON_E_PCCBR_ZIP_CN88XX, + BDK_PCC_DEV_CON_E_PCIERC0_CN88XX, + BDK_PCC_DEV_CON_E_PCIERC1_CN88XX, + BDK_PCC_DEV_CON_E_PCIERC2_CN88XX, + BDK_PCC_DEV_CON_E_PCIERC3_CN88XX, + BDK_PCC_DEV_CON_E_PCIERC4, + BDK_PCC_DEV_CON_E_PCIERC5, + BDK_PCC_DEV_CON_E_PEMX(0), + BDK_PCC_DEV_CON_E_PEMX(1), + BDK_PCC_DEV_CON_E_PEMX(2), + BDK_PCC_DEV_CON_E_PEMX(3), + BDK_PCC_DEV_CON_E_PEMX(4), + BDK_PCC_DEV_CON_E_PEMX(5), + BDK_PCC_DEV_CON_E_RAD_CN88XX, + BDK_PCC_DEV_CON_E_RNM_CN88XX, + BDK_PCC_DEV_CON_E_RST, + BDK_PCC_DEV_CON_E_SATA0_CN88XX, + BDK_PCC_DEV_CON_E_SATA1_CN88XX, + BDK_PCC_DEV_CON_E_SATA10, + BDK_PCC_DEV_CON_E_SATA11, + BDK_PCC_DEV_CON_E_SATA12, + BDK_PCC_DEV_CON_E_SATA13, + BDK_PCC_DEV_CON_E_SATA14, + BDK_PCC_DEV_CON_E_SATA15, + BDK_PCC_DEV_CON_E_SATA2, + BDK_PCC_DEV_CON_E_SATA3, + BDK_PCC_DEV_CON_E_SATA4, + BDK_PCC_DEV_CON_E_SATA5, + BDK_PCC_DEV_CON_E_SATA6, + BDK_PCC_DEV_CON_E_SATA7, + BDK_PCC_DEV_CON_E_SATA8, + BDK_PCC_DEV_CON_E_SATA9, + BDK_PCC_DEV_CON_E_SGP, + BDK_PCC_DEV_CON_E_SLI0_CN88XX, + BDK_PCC_DEV_CON_E_SLI1, + BDK_PCC_DEV_CON_E_SMI, + BDK_PCC_DEV_CON_E_SMMU0_CN8, + BDK_PCC_DEV_CON_E_SMMU1, + BDK_PCC_DEV_CON_E_SMMU2, + BDK_PCC_DEV_CON_E_SMMU3, + BDK_PCC_DEV_CON_E_TNS, + BDK_PCC_DEV_CON_E_UAAX_CN8(0), + BDK_PCC_DEV_CON_E_UAAX_CN8(1), + BDK_PCC_DEV_CON_E_USBHX(0), + BDK_PCC_DEV_CON_E_USBHX(1), + BDK_PCC_DEV_CON_E_VRMX(0), + BDK_PCC_DEV_CON_E_VRMX(1), + BDK_PCC_DEV_CON_E_ZIP_CN88XX, + 0, + }; + + int loc = 0; + while (INTERNAL_DEVICES_CN88XXP1_0[loc]) + { + if (is_internal_cn88xxp1_0(device, INTERNAL_DEVICES_CN88XXP1_0[loc])) + return 1; + loc++; + } + return 0; +} + +static int is_accessable_cn88xxp1_0(const bdk_device_t *device) +{ + /* Errata (ECAM-22630) ECAM function accesses can fault */ + /* Skip internal devices that don't exists */ + if (!is_any_internal_cn88xxp1_0(device)) + return 0; + + /* Errata (ECAM-23020) PCIERC transactions fault unless PEM is + out of reset. The PCIe ports don't work until the PEM is + turned on. Check for one of the PCIe ports */ + int pem = -1; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_PCIERC0_CN88XX)) + pem = 0; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_PCIERC1_CN88XX)) + pem = 1; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_PCIERC2_CN88XX)) + pem = 2; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_PCIERC3_CN88XX)) + pem = 3; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_PCIERC4)) + pem = 4; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_PCIERC5)) + pem = 5; + if (pem != -1) + { + BDK_CSR_INIT(pem_on, device->node, BDK_PEMX_ON(pem)); + if (!pem_on.s.pemon || !pem_on.s.pemoor) + return 0; + } + + { + /* SATA ports should be hidden if they aren't configured at the QLM */ + int qlm = -1; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA0_CN88XX) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA1_CN88XX) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA2) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA3)) + qlm = 2; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA4) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA5) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA6) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA7)) + qlm = 3; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA8) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA9) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA10) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA11)) + qlm = 6; + if (is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA12) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA13) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA14) || + is_internal_cn88xxp1_0(device, BDK_PCC_DEV_CON_E_SATA15)) + qlm = 7; + if (qlm != -1) + { + BDK_CSR_INIT(cfg, device->node, BDK_GSERX_CFG(qlm)); + if (!cfg.s.sata) + return 0; + } + } + return 1; +} + +#endif /* Support CN88XX pass 1.0 */ + +/** + * Build an ECAM config space request address for a device + * + * @param device Device being accessed + * @param reg Register to access + * + * @return 64bit IO address + */ +uint64_t __bdk_ecam_build_address(const bdk_device_t *device, int reg) +{ + /* CN88XX pass 1.0 had a plethora of errata related to ECAM access. This + checks to make sure we're allowed to access this location based on + the various errata */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_0) && !is_accessable_cn88xxp1_0(device)) + return 0; + + if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) + { + /* Build the address */ + union bdk_ecam_cfg_addr_s address; + address.u = BDK_ECAM_BAR_E_ECAMX_PF_BAR2(device->ecam); + address.s.node = device->node; + address.s.bus = device->bus; + address.s.func = device->dev << 3 | device->func; + address.s.addr = reg; + return address.u; + } + else + { + /* Build the address. The architects decided to make it different + from CN8XXX for no obvious reason */ + union bdk_ecam_cfg_addr_s address; + address.u = BDK_ECAM_BAR_E_ECAMX_PF_BAR2(0); + address.s.node = device->node; + address.s.dmn = device->ecam; + address.s.bus = device->bus; + address.s.func = device->dev << 3 | device->func; + address.s.addr = reg; + return address.u; + } +} + +/** + * Read from an ECAM + * + * @param device Device to read from + * @param reg Register to read + * + * @return Result of the read of -1 on failure + */ +uint32_t bdk_ecam_read32(const bdk_device_t *device, int reg) +{ + uint64_t address = __bdk_ecam_build_address(device, reg); + uint32_t result; + if (address) + result = bdk_le32_to_cpu(bdk_read64_uint32(address)); + else + result = 0xffffffff; + + /* Errata ECAM-22630: CN88XX pass 1.x, except pass 1.0, will return zero + for non-existent devices instead of ones. We look for this special case + for 32bit reads for reg=0 so we can scan device properly */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X) && (reg == 0) && (result == 0)) + result = 0xffffffff; + + return result; +} + +/** + * Write to an ECAM register + * + * @param device Device to write to + * @param reg Register to write + * @param value Value to write + */ +void bdk_ecam_write32(const bdk_device_t *device, int reg, uint32_t value) +{ + uint64_t address = __bdk_ecam_build_address(device, reg); + if (address) + bdk_write64_uint32(address, bdk_cpu_to_le32(value)); +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-ecam.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-ecam.c new file mode 100644 index 0000000000..f79eb587f4 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-ecam.c @@ -0,0 +1,216 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <string.h> +#include "libbdk-arch/bdk-csrs-ecam.h" +#include "libbdk-arch/bdk-csrs-pccbr.h" +#include "libbdk-arch/bdk-csrs-pccpf.h" +#include "libbdk-arch/bdk-csrs-rvu.h" +#include "libbdk-hal/device/bdk-device.h" +#include "libbdk-hal/bdk-ecam.h" + +/* This code is an optional part of the BDK. It is only linked in + if BDK_REQUIRE() needs it */ +BDK_REQUIRE_DEFINE(ECAM); + +/** + * Walk an ECAM finding all internal devices. Each internal + * device is then added to the list of device maintained by + * bdk-device. + * + * @param node Node to walk + * @param ecam Ecam to walk + * @param bus Zero on first call. Will be non-zero when sub busses are walked + */ +static void ecam_walk_internal_bus(bdk_node_t node, int ecam, int bus) +{ + /* Create a fake bdk-device to pass around until we create the + real device */ + bdk_device_t device; + memset(&device, 0, sizeof(device)); + device.node = node; + device.ecam = ecam; + device.bus = bus; + + /* Scan all possible device IDs on the bus */ + for (int dev = 0; dev < 32; dev++) + { + /* Update the current scan location */ + device.dev = dev; + device.func = 0; + + uint32_t device_id = bdk_ecam_read32(&device, BDK_PCCPF_XXX_ID); + + /* Only add devices that exist. Our internal devices can have function + zero missing. The all ones we get back matches the multi-function + check, but not a bridge. This means the later code works fine */ + if (device_id != (uint32_t)-1) + bdk_device_add(device.node, device.ecam, device.bus, device.dev, device.func); + + /* Check for Multi function and Bridge devices */ + BDK_CSR_DEFINE(clsize, BDK_PCCPF_XXX_CLSIZE); + clsize.u = bdk_ecam_read32(&device, BDK_PCCPF_XXX_CLSIZE); + int ismultifunction = (clsize.s.hdrtype & 0x80); + int isbridge = (clsize.s.hdrtype & 0x7f) == 1; + + if (ismultifunction) + { + /* Scan for other functions on multifunction devices */ + for (int func = 1; func < 8; func++) + { + /* Check if we're past all functions */ + device.func = func; + device_id = bdk_ecam_read32(&device, BDK_PCCPF_XXX_ID); + if (device_id != (uint32_t)-1) + bdk_device_add(device.node, device.ecam, device.bus, device.dev, device.func); + } + device.func = 0; + } + if (isbridge) + { + /* Internal bus numbers are hard coded. Read the bus ID */ + bdk_pccbr_xxx_bus_t ibus; + ibus.u = bdk_ecam_read32(&device, BDK_PCCBR_XXX_BUS); + /* Asim used to have a bug where bus number were zero, report errors + for those */ + if (ibus.s.sbnum == 0) + { + bdk_error("N%d:E%d:%d:%d.%d: Secondary bus number is zero\n", + device.node, device.ecam, device.bus, device.dev, device.func); + } + /* Real PCIe external device use high bus numbers, so skip them */ + else if (ibus.s.sbnum < 16) + { + ecam_walk_internal_bus(node, ecam, ibus.s.sbnum); + } + } + } +} + +/** + * Return the number of internal ECAMS on a node. + * + * @param node Node to query + * + * @return Number of ECAMs available + */ +int bdk_ecam_get_num(bdk_node_t node) +{ + /* CN88XX lacks the ECAM_CONST for finding the number of ECAMs */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + return 4; + else if (CAVIUM_IS_MODEL(CAVIUM_CN93XX)) + return 3; /* Map ECAMs to the first 3 domains */ + else + { + BDK_CSR_INIT(ecam_const, node, BDK_ECAMX_CONST(0)); + if (ecam_const.s.ecams == 0) + { + bdk_error("N%d.ECAM: Number of ecams incorrect in ECAMX_CONST\n", node); + return 1; + } + return ecam_const.s.ecams; + } +} + +/** + * Initialize RVU functions for use by the BDK. This doesn't setup the hardware + * behind RVU, juse allows register access to it. The BDK uses a static RVU + * configuration where everything is accessable from RVU PF0. + * + * @param node Node to initialize + * + * @return Zero on success, negative on failure + */ +static int __bdk_ecam_rvu_init(bdk_node_t node) +{ + const int rvu_pf = 0; + /* Enable PF access to all blocks */ + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_CPTX_CFG(rvu_pf, 0), + c.s.num_lfs = 1); // FIXME: How many LFs? + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_INT_CFG(rvu_pf), + c.s.msix_offset = 0); + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_MSIX_CFG(rvu_pf), + c.s.pf_msixt_offset = 0; + c.s.pf_msixt_sizem1 = 0; + c.s.vf_msixt_offset = 0; + c.s.vf_msixt_sizem1 = 0); + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_NIXX_CFG(rvu_pf, 0), + c.s.has_lf = 1); + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_NPA_CFG(rvu_pf), + c.s.has_lf = 1); + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_SSO_CFG(rvu_pf), + c.s.num_lfs = 1); // FIXME: How many LFs? + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_SSOW_CFG(rvu_pf), + c.s.num_lfs = 1); // FIXME: How many LFs? + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_TIM_CFG(rvu_pf), + c.s.num_lfs = 1); // FIXME: How many LFs? + /* Enable RVU with full access */ + BDK_CSR_MODIFY(c, node, BDK_RVU_PRIV_PFX_CFG(rvu_pf), + c.s.me_flr_ena = 1; + c.s.af_ena = 1; + c.s.ena = 1; + c.s.nvf = 0; + c.s.first_hwvf = 0); + return 0; +} + +/** + * Scan all ECAMs for devices and add them to bdk-device + * + * @param node Node to scan + * + * @return Zero on success, negative on failure + */ +int bdk_ecam_scan_all(bdk_node_t node) +{ + /* RVU must be setup before we scan the bus otherwise it doesn't + show up */ + if (CAVIUM_IS_MODEL(CAVIUM_CN9XXX)) + __bdk_ecam_rvu_init(node); + + int num_ecams = bdk_ecam_get_num(node); + for (int ecam = 0; ecam < num_ecams; ecam++) + ecam_walk_internal_bus(node, ecam, 0); + + bdk_device_init(); + + return 0; +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-gpio.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-gpio.c index 55f0dbf3f2..986f68fac3 100644 --- a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-gpio.c +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-gpio.c @@ -38,6 +38,7 @@ ***********************license end**************************************/ #include <bdk.h> #include "libbdk-arch/bdk-csrs-gpio.h" +#include "libbdk-hal/bdk-gpio.h" /* This code is an optional part of the BDK. It is only linked in if BDK_REQUIRE() needs it */ diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-l2c.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-l2c.c index b1e2a88ce1..6c163da2d4 100644 --- a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-l2c.c +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-l2c.c @@ -41,6 +41,8 @@ #include "libbdk-arch/bdk-csrs-l2c.h" #include "libbdk-arch/bdk-csrs-l2c_cbc.h" #include "libbdk-arch/bdk-csrs-mio_fus.h" +#include "libbdk-hal/bdk-l2c.h" +#include "libbdk-hal/bdk-utils.h" typedef struct { @@ -51,56 +53,6 @@ typedef struct static l2_node_state_t l2_node_state[BDK_NUMA_MAX_NODES]; -/** - * Perform one time initialization of L2 for improved - * performance. This can be called after L2 is in use. - * - * @return Zero on success, negative on failure. - */ -int bdk_l2c_initialize(bdk_node_t node) -{ - if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) - { - /* Tell L2 to give the IOB statically higher priority compared to the - cores. This avoids conditions where IO blocks might be starved under - very high L2 loads */ - BDK_CSR_MODIFY(c, node, BDK_L2C_CTL, - c.s.rsp_arb_mode = 1; - c.s.xmc_arb_mode = 0); - } - - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X) && !bdk_is_platform(BDK_PLATFORM_ASIM)) - { - /* Errata: (L2C-22279) RCAS/RSTC which hits S/S can use wrong compare data */ - BDK_CSR_MODIFY(c, node, BDK_L2C_CTL, - c.s.dissblkdty = 1); - /* Errata: (L2C-22249) Broadcast invals can cause starvation on the INV bus */ - for (int i = 0; i < 4; i++) - BDK_CSR_MODIFY(c, node, BDK_L2C_CBCX_SCRATCH(i), - c.s.invdly = 1); - } - - // FIXME: Disable partial writes on pass 2 until it is debugged - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS2_X) && !bdk_is_platform(BDK_PLATFORM_ASIM)) - { - BDK_CSR_MODIFY(c, node, BDK_L2C_CTL, - c.s.dissblkdty = 1); - } - - if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX) && bdk_is_platform(BDK_PLATFORM_EMULATOR)) - { - /* The emulator requires L2C_CTL[DISSBLKDTY] to be set */ - BDK_CSR_MODIFY(c, node, BDK_L2C_CTL, - c.s.dissblkdty = 1); - } - return 0; -} - -int bdk_l2c_get_core_way_partition(bdk_node_t node, int core) -{ - return (BDK_CSR_READ(node, BDK_L2C_WPAR_PPX(core)) & 0xffff); -} - int bdk_l2c_set_core_way_partition(bdk_node_t node, int core, uint32_t mask) { uint32_t valid_mask = (1 << bdk_l2c_get_num_assoc(node)) - 1; @@ -120,82 +72,11 @@ int bdk_l2c_set_hw_way_partition(bdk_node_t node, uint32_t mask) return 0; } - int bdk_l2c_get_hw_way_partition(bdk_node_t node) { return (BDK_CSR_READ(node, BDK_L2C_WPAR_IOBX(0)) & 0xffff); } - -int bdk_l2c_lock_mem_region(bdk_node_t node, uint64_t start, uint64_t len) -{ - /* Round start/end to cache line boundaries */ - len += start & BDK_CACHE_LINE_MASK; - start &= ~BDK_CACHE_LINE_MASK; - len = (len + BDK_CACHE_LINE_MASK) & ~BDK_CACHE_LINE_MASK; - void *ptr = (start) ? bdk_phys_to_ptr(start) : NULL; - - while (len) - { - BDK_CACHE_LCK_L2(ptr); - ptr += BDK_CACHE_LINE_SIZE; - len -= BDK_CACHE_LINE_SIZE; - } - l2_node_state[node].is_locked = true; - return 0; -} - -void bdk_l2c_flush(bdk_node_t node) -{ - /* The number of ways can be reduced with fuses, but the equations below - assume the max number of ways */ - const int MAX_WAYS = 16; - int num_sets = bdk_l2c_get_num_sets(node); - int num_ways = bdk_l2c_get_num_assoc(node); - - int is_rtg = 1; /* Clear remote tags */ - for (int l2_way = 0; l2_way < num_ways; l2_way++) - { - for (int l2_set = 0; l2_set < num_sets; l2_set++) - { - uint64_t encoded = 128 * (l2_set + num_sets * (l2_way + (is_rtg * MAX_WAYS))); - BDK_CACHE_WBI_L2_INDEXED(encoded); - } - } - - is_rtg = 0; /* Clear local tags */ - for (int l2_way = 0; l2_way < num_ways; l2_way++) - { - for (int l2_set = 0; l2_set < num_sets; l2_set++) - { - uint64_t encoded = 128 * (l2_set + num_sets * (l2_way + (is_rtg * MAX_WAYS))); - BDK_CACHE_WBI_L2_INDEXED(encoded); - } - } - l2_node_state[node].is_locked = false; -} - -int bdk_l2c_unlock_mem_region(bdk_node_t node, uint64_t start, uint64_t len) -{ - /* Round start/end to cache line boundaries */ - len += start & BDK_CACHE_LINE_MASK; - start &= ~BDK_CACHE_LINE_MASK; - len = (len + BDK_CACHE_LINE_MASK) & ~BDK_CACHE_LINE_MASK; - void *ptr = (start) ? bdk_phys_to_ptr(start) : NULL; - - while (len > 0) - { - /* Must use invalidate version to release lock */ - BDK_CACHE_WBI_L2(ptr); - ptr += BDK_CACHE_LINE_SIZE; - len -= BDK_CACHE_LINE_SIZE; - } - - l2_node_state[node].is_locked = false; - return 0; -} - - int bdk_l2c_get_cache_size_bytes(bdk_node_t node) { return bdk_l2c_get_num_sets(node) * bdk_l2c_get_num_assoc(node) * BDK_CACHE_LINE_SIZE; @@ -254,17 +135,22 @@ int bdk_l2c_get_num_assoc(bdk_node_t node) return l2_node_state[node].ways; } -/** - * Return true if the BDK has locked itself in L2 - * - * @return - */ -int bdk_l2c_is_locked(bdk_node_t node) +int bdk_l2c_unlock_mem_region(bdk_node_t node, uint64_t start, uint64_t len) { - /* Determining the lock state of L2 requires reading exact tags from L2 - which varies per chip. Rather than deal with that complexity, we just - keep a flag around saying if the L2 lock functions have been called. - This works for the BDK as its use of locking is very simple */ - return l2_node_state[node].is_locked; -} + /* Round start/end to cache line boundaries */ + len += start & BDK_CACHE_LINE_MASK; + start &= ~BDK_CACHE_LINE_MASK; + len = (len + BDK_CACHE_LINE_MASK) & ~BDK_CACHE_LINE_MASK; + void *ptr = (start) ? bdk_phys_to_ptr(start) : NULL; + while (len > 0) + { + /* Must use invalidate version to release lock */ + BDK_CACHE_WBI_L2(ptr); + ptr += BDK_CACHE_LINE_SIZE; + len -= BDK_CACHE_LINE_SIZE; + } + + l2_node_state[node].is_locked = false; + return 0; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-nic.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-nic.c new file mode 100644 index 0000000000..b6a9384e10 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-nic.c @@ -0,0 +1,1090 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <malloc.h> +#include "libbdk-arch/bdk-csrs-nic.h" + +#define MAX_MTU 9212 +#define CQ_ENTRIES_QSIZE 0 +#define CQ_ENTRIES (1024 << CQ_ENTRIES_QSIZE) +#define SQ_ENTRIES_QSIZE 0 +#define SQ_ENTRIES (1024 << SQ_ENTRIES_QSIZE) +#define RBDR_ENTRIES_QSIZE 0 +#define RBDR_ENTRIES (8192 << RBDR_ENTRIES_QSIZE) + +typedef struct +{ + /* VNIC related config */ + bdk_node_t node : 8; /* Node the NIC is on */ + bdk_nic_type_t ntype : 8; /* They type of device this NIC is connected to */ + uint8_t nic_vf; /* NIC VF index number (0 - MAX_VNIC-1) */ + uint8_t sq; /* Send Queue (SQ) inside NIC VF (0-7) */ + uint8_t cq; /* Complete Queue (CQ) inside NIC VF (0-7) */ + uint8_t rq; /* Receive Queue (RQ) inside NIC VF (0-7) */ + uint8_t rbdr; /* Receive Buffer Descriptor Ring (RBDR) inside NIC VF (0-1) */ + uint8_t bpid; /* Backpressure ID (0-127) */ + bdk_if_handle_t handle; /* bdk-if handle associated with this NIC */ + + /* Transmit */ + void * sq_base; /* Pointer to the beginning of the SQ in memory */ + int sq_loc; /* Location where the next send should go */ + int sq_available; /* Amount of space left in the queue (fuzzy) */ +} nic_t; + +typedef struct +{ + void *base; + int loc; +} nic_rbdr_state_t; + +typedef struct +{ + int num_nic_vf; + int next_free_nic_vf; + int next_free_cpi; + int next_free_rssi; + int next_free_bpid; + nic_t *nic_map[0]; /* Indexed by handle->nic_id */ +} nic_node_state_t; + +static nic_node_state_t *global_node_state[BDK_NUMA_MAX_NODES]; +static int global_buffer_size = 0; + +/** + * Setup a receive Completion Queue (CQ). CQ can be shared across multiple NICs + * to save space. This happens if the NIC has "shares_cq" set. + * + * @param nic NIC to setup + * + * @return Zero on success, negative on failure + */ +static int vnic_setup_cq(nic_t *nic) +{ + /* CN88XX pass 1.x had the drop level reset value too low */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_CQM_CFG, + c.s.drop_level = 128); + + /* All devices using the same NIC VF use the same CQ */ + if (nic->handle->index == 0) + { + BDK_TRACE(NIC, "%s: Setting up CQ(%d, %d)\n", nic->handle->name, nic->nic_vf, nic->cq); + /* Note that the completion queue requires 512 byte alignment */ + void *cq_memory = memalign(512, 512 * CQ_ENTRIES); + if (!cq_memory) + { + bdk_error("%s: Failed to allocate memory for completion queue\n", nic->handle->name); + return -1; + } + /* Configure the completion queue (CQ) */ + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_CQX_BASE(nic->nic_vf, nic->cq), + bdk_ptr_to_phys(cq_memory)); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_QSX_CQX_CFG(nic->nic_vf, nic->cq), + c.s.ena = 1; + c.s.caching = 1; + c.s.qsize = CQ_ENTRIES_QSIZE); + } + + /* Configure our vnic to send to the CQ */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_QSX_SQX_CFG(nic->nic_vf, nic->sq), + c.s.cq_qs = nic->nic_vf; + c.s.cq_idx = nic->cq); + return 0; +} + +/** + * Add buffers to a receive buffer descriptor ring (RBDR). Note that RBDRs are + * shared between NICs using the same CQ. + * + * @param nic NIC using the RBDR + * @param rbdr_free Number of buffers to add + */ +static void vnic_fill_receive_buffer(const nic_t *nic, int rbdr_free) +{ + int nic_vf = nic->nic_vf; + int rbdr = nic->rbdr; + + BDK_CSR_INIT(rbdr_base, nic->node, BDK_NIC_QSX_RBDRX_BASE(nic_vf, rbdr)); + BDK_CSR_INIT(rbdr_tail, nic->node, BDK_NIC_QSX_RBDRX_TAIL(nic_vf, rbdr)); + BDK_TRACE(NIC, "%s: In Filling RBDR(%d, %d) base 0x%lx\n", nic->handle->name, nic->nic_vf, nic->rbdr, rbdr_base.u); + + uint64_t *rbdr_ptr = bdk_phys_to_ptr(rbdr_base.u); + int loc = rbdr_tail.s.tail_ptr; + BDK_TRACE(NIC, "%s: In Filling RBDR(%d, %d) loc %d\n", nic->handle->name, nic->nic_vf, nic->rbdr, loc); + + int added = 0; + for (int i = 0; i < rbdr_free; i++) + { + bdk_if_packet_t packet; + if (bdk_if_alloc(&packet, global_buffer_size)) + { + bdk_error("%s: Failed to allocate buffer for RX ring (added %d)\n", nic->handle->name, added); + break; + } + rbdr_ptr[loc] = bdk_cpu_to_le64(packet.packet[0].s.address); + BDK_TRACE(NIC, "%s: In Filling RBDR(%d, %d) loc %d = 0x%lx\n", nic->handle->name, nic->nic_vf, nic->rbdr, loc, rbdr_ptr[loc]); + loc++; + loc &= RBDR_ENTRIES - 1; + added++; + } + BDK_WMB; + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_RBDRX_DOOR(nic_vf, rbdr), added); + BDK_TRACE(NIC, "%s: In Filling RBDR(%d, %d) added %d\n", nic->handle->name, nic->nic_vf, nic->rbdr, added); +} + +/** + * Setup a receive buffer descriptor ring (RBDR). Note that NIC share the RBDR if + * "share_cq" is set. + * + * @param nic NIC to setup RBDR for + * + * @return Zero on success, negative on failure + */ +static int vnic_setup_rbdr(nic_t *nic) +{ + bool do_fill; + + /* All devices using the same NIC VF use the same RBDRs. Don't fill them + for and ports except the first */ + if (nic->handle->index) + { + do_fill = false; + } + else + { + BDK_TRACE(NIC, "%s: Setting up RBDR(%d, %d)\n", nic->handle->name, nic->nic_vf, nic->rbdr); + void *rbdr_base = memalign(BDK_CACHE_LINE_SIZE, 8 * RBDR_ENTRIES); + if (!rbdr_base) + { + bdk_error("%s: Failed to allocate memory for RBDR\n", nic->handle->name); + return -1; + } + /* Configure the receive buffer ring (RBDR) */ + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_RBDRX_BASE(nic->nic_vf, nic->rbdr), + bdk_ptr_to_phys(rbdr_base)); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_QSX_RBDRX_CFG(nic->nic_vf, nic->rbdr), + c.s.ena = 1; + c.s.ldwb = BDK_USE_DWB; + c.s.qsize = RBDR_ENTRIES_QSIZE; + c.s.lines = global_buffer_size / BDK_CACHE_LINE_SIZE); + do_fill = true; + } + + BDK_TRACE(NIC, "%s: Setting up RQ(%d, %d)\n", nic->handle->name, nic->nic_vf, nic->rq); + /* Configure our vnic to use the RBDR */ + /* Connect this RQ to the RBDR. Both the first and next buffers come from + the same RBDR */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_QSX_RQX_CFG(nic->nic_vf, nic->rq), + c.s.caching = 1; /* Allocate to L2 */ + c.s.cq_qs = nic->nic_vf; + c.s.cq_idx = nic->cq; + c.s.rbdr_cont_qs = nic->nic_vf; + c.s.rbdr_cont_idx = nic->rbdr; + c.s.rbdr_strt_qs = nic->nic_vf; + c.s.rbdr_strt_idx = nic->rbdr); + /* NIC_PF_CQM_CFG is configure to drop everything if the CQ has 128 or + less entries available. Start backpressure when we have 256 or less */ + int cq_bp = 256; + int rbdr_bp = 256; + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_QSX_RQX_BP_CFG(nic->nic_vf, nic->rq), + c.s.rbdr_bp_ena = 1; + c.s.cq_bp_ena = 1; + c.s.rbdr_bp = rbdr_bp * 256 / RBDR_ENTRIES; /* Zero means no buffers, 256 means lots available */ + c.s.cq_bp = cq_bp * 256 / CQ_ENTRIES; /* Zero means full, 256 means idle */ + c.s.bpid = nic->bpid); + /* Errata (NIC-21269) Limited NIC receive scenario verification */ + /* RED drop set with pass=drop, so no statistical dropping */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_QSX_RQX_DROP_CFG(nic->nic_vf, nic->rq), + c.s.rbdr_red = 0; + c.s.cq_red = 0; + c.s.rbdr_pass = 0; /* Zero means no buffers, 256 means lots available */ + c.s.rbdr_drop = 0; + c.s.cq_pass = 0; /* Zero means full, 256 means idle */ + c.s.cq_drop = 0); + + if (do_fill) + { + BDK_TRACE(NIC, "%s: Filling RBDR(%d, %d)\n", nic->handle->name, nic->nic_vf, nic->rbdr); + /* We probably don't have enough space to completely fill the RBDR. Use + 1/8 of the buffers available */ + int fill_num = bdk_config_get_int(BDK_CONFIG_NUM_PACKET_BUFFERS) / 8; + if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) fill_num = fill_num/3; /* CN83XX has more nics */ + /* Note that RBDR must leave one spot empty */ + if (fill_num > RBDR_ENTRIES - 1) + fill_num = RBDR_ENTRIES - 1; + vnic_fill_receive_buffer(nic, fill_num); + } + + return 0; +} + +/** + * Setup traffic shapping for a NIC. This put the shappers in passthrough mode + * where no shapping is applied. + * + * @param nic NIC to configure shaping for + * + * @return Zero on success, negative on failure + */ +static int vnic_setup_tx_shaping(nic_t *nic) +{ + int tl1_index = -1; + int tl2_index = -1; + int tl3_index = -1; + int tl4_index = -1; + int nic_chan_e = -1; + + BDK_TRACE(NIC, "%s: Setting up shaping(%d, %d)\n", nic->handle->name, nic->nic_vf, nic->sq); + + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + /* TL1 feeds the DMA engines. One for each BGX */ + tl1_index = nic->handle->interface; + /* TL2 feeds TL1 based on the top/bottom half. Use an independent TL1 + entry for each BGX port */ + tl2_index = tl1_index * 32 + nic->handle->index; + /* Each block of 4 TL3 feed TL2 */ + tl3_index = tl2_index * 4; + /* Each block of 4 TL4 feed TL3 */ + tl4_index = tl3_index * 4; + nic_chan_e = BDK_NIC_CHAN_E_BGXX_PORTX_CHX(nic->handle->interface, nic->handle->index, 0/*channel*/); + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + { + switch (nic->ntype) + { + case BDK_NIC_TYPE_BGX: + tl1_index = BDK_NIC_LMAC_E_BGXX_LMACX(nic->handle->interface, nic->handle->index); + nic_chan_e = 0 ; /* Channel is lmac-relative */ + break; + case BDK_NIC_TYPE_LBK: + tl1_index = BDK_NIC_LMAC_E_LBKX_CN83XX((nic->handle->interface == 3) ? 1 : 0); + nic_chan_e = nic->handle->index; /* Channel is lmac-relative */ + break; + default: + bdk_error("%s: Unsupported NIC TYPE %d\n", nic->handle->name, nic->ntype); + return -1; + } + /* TL1 index by NIC_LMAC_E */ + /* Set in above switch statement */ + /* TL2 index is software defined, make it the same as TL1 for straight through */ + tl2_index = tl1_index; + /* Each block of 4 TL3 feed TL2. This assumes there are never more than 4 ports per interface */ + tl3_index = tl2_index * 4 + nic->handle->index; + /* TL4 index is the same as TL3, 1:1 hookup */ + tl4_index = tl3_index; + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + { + switch (nic->ntype) + { + case BDK_NIC_TYPE_BGX: + tl1_index = BDK_NIC_LMAC_E_BGXX_LMACX(nic->handle->interface, nic->handle->index); + nic_chan_e = BDK_NIC_CHAN_E_BGXX_LMACX_CHX(nic->handle->interface, nic->handle->index, 0/*channel*/); + break; + case BDK_NIC_TYPE_RGMII: + tl1_index = BDK_NIC_LMAC_E_RGXX_LMACX(nic->handle->interface, nic->handle->index); + nic_chan_e = 0; /* Channel is lmac-relative */ + break; + case BDK_NIC_TYPE_LBK: + tl1_index = BDK_NIC_LMAC_E_LBKX_CN81XX(nic->handle->interface); + nic_chan_e = nic->handle->index; /* Channel is lmac-relative */ + break; + default: + bdk_error("%s: Unsupported NIC TYPE %d\n", nic->handle->name, nic->ntype); + return -1; + } + /* TL1 index by NIC_LMAC_E */ + /* Set in above switch statement */ + /* TL2 index is software defined, make it the same as TL1 for straight through */ + tl2_index = tl1_index; + /* Each block of 4 TL3 feed TL2. This assumes there are never more than 4 ports per interface */ + tl3_index = tl2_index * 4 + nic->handle->index; + /* TL4 index is the same as TL3, 1:1 hookup */ + tl4_index = tl3_index; + } + else + { + bdk_error("%s: Unsupported chip (NIC shaping)\n", nic->handle->name); + return -1; + } + + /* Setup TL2 to TL1 mappings */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL2X_CFG(tl2_index), + c.s.rr_quantum = (MAX_MTU+4) / 4); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL2X_PRI(tl2_index), + c.s.rr_pri = 0); + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL2X_LMAC(tl2_index), + c.s.lmac = tl1_index); + } + + /* TL3 feeds Tl2 */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL3AX_CFG(tl3_index / 4), + c.s.tl3a = tl2_index); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL3X_CFG(tl3_index), + c.s.rr_quantum = (MAX_MTU+4) / 4); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL3X_CHAN(tl3_index), + c.s.chan = nic_chan_e); + + /* TL4 feeds TL3 */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL4AX_CFG(tl4_index / 4), + c.s.tl4a = tl3_index); + } + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_TL4X_CFG(tl4_index), + c.s.sq_qs = nic->nic_vf; + c.s.sq_idx = nic->sq; + c.s.rr_quantum = (MAX_MTU+4) / 4); + + /* SQ feeds TL4 */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_QSX_SQX_CFG2(nic->nic_vf, nic->sq), + c.s.tl4 = tl4_index); + + return 0; +} + +/** + * Free the buffers in a packet to the RBDR used by the port + * + * @param priv Determines which RBDR is used + * @param packet Packet to put in RBDR + */ +static void if_free_to_rbdr(bdk_if_packet_t *packet, nic_rbdr_state_t *vnic_rbdr_state) +{ + uint64_t *rbdr_ptr = vnic_rbdr_state->base; + int loc = vnic_rbdr_state->loc; + + for (int s = 0; s < packet->segments; s++) + { + /* Make sure we strip off any padding added by the hardware in the address */ + uint64_t address = packet->packet[s].s.address & -BDK_CACHE_LINE_SIZE; + rbdr_ptr[loc] = bdk_cpu_to_le64(address); + loc++; + loc &= RBDR_ENTRIES - 1; + } + vnic_rbdr_state->loc = loc; +} + +/** + * Process a CQ receive entry + * + * @param node Node containing the CQ + * @param vnic_rbdr_state + * Current RBDR state for the RBDR connected to the CQ + * @param cq_header CQ header to process + * @param use_cqe_rx2 + * True of the CQ will contain an extended CQE_RX2 header + * + * @return Returns the amount the RBDR doorbell needs to increment + */ +static int if_process_complete_rx(int node, nic_rbdr_state_t *vnic_rbdr_state, const union bdk_nic_cqe_rx_s *cq_header, const union bdk_nic_cqe_rx_s *cq_header_le, bool use_cqe_rx2) +{ + nic_node_state_t *node_state = global_node_state[node]; + int nic_id = cq_header->s.rq_qs * 8 + cq_header->s.rq_idx; + + bdk_if_packet_t packet; + packet.length = cq_header->s.len; + packet.segments = cq_header->s.rb_cnt; + packet.if_handle = node_state->nic_map[nic_id]->handle; + /* Combine the errlev and errop into a single 11 bit number. Errop + is 8 bits, so errlev will be in the top byte */ + packet.rx_error = cq_header->s.errlev; + packet.rx_error <<= 8; + packet.rx_error |= cq_header->s.errop; + + const uint16_t *rb_sizes = (void*)cq_header_le + 24; /* Offset of RBSZ0 */ + const uint64_t *rb_addresses = (uint64_t*)(cq_header_le+1); + /* Update offset if nic_cqe_rx2_s is used */ + if (use_cqe_rx2) + rb_addresses += sizeof(union bdk_nic_cqe_rx2_s) / 8; + int segment_length = 0; + + for (int s = 0; s < packet.segments; s++) + { + uint64_t addr = bdk_le64_to_cpu(rb_addresses[s]); + BDK_PREFETCH(bdk_phys_to_ptr(addr), 0); + packet.packet[s].u = addr; + packet.packet[s].s.size = bdk_le16_to_cpu(rb_sizes[s]); + BDK_TRACE(NIC, " Receive segment size %d address 0x%lx\n", packet.packet[s].s.size, addr); + segment_length += packet.packet[s].s.size; + } + + /* If we ran out of buffer the packet could be truncated */ + if (segment_length < packet.length) + packet.length = segment_length; + + if (bdk_likely(packet.if_handle)) + { + /* Do RX stats in software as it is fast and I don't really trust + the hardware. The hardware tends to count packets that are received + and dropped in some weird way. Hopefully the hardware counters + looking for drops can find these. It is important that they + aren't counted as good */ + packet.if_handle->stats.rx.packets++; + packet.if_handle->stats.rx.octets += packet.length; + if (packet.if_handle->flags & BDK_IF_FLAGS_HAS_FCS) + packet.if_handle->stats.rx.octets += 4; + if (packet.rx_error) + packet.if_handle->stats.rx.errors++; + bdk_if_dispatch_packet(&packet); + } + else + { + bdk_error("Unable to determine interface for NIC %d.%d\n", cq_header->s.rq_qs, cq_header->s.rq_idx); + } + + if_free_to_rbdr(&packet, vnic_rbdr_state); + return packet.segments; +} + +/** + * Process all entries in a completion queue (CQ). Note that a CQ is shared + * among many ports, so packets will be dispatch for other port handles. + * + * @param handle Interface handle connected to the CQ + * + * @return Number of packets received + */ +static void if_receive(int unused, void *hand) +{ + const nic_t *nic = hand; + + /* Sadly the hardware team decided to change the meaning of NIC_PF_RX_CFG + for chips after CN88XX. This stupid spec change was really hard to + find */ + bool use_cqe_rx2 = !CAVIUM_IS_MODEL(CAVIUM_CN88XX); + + /* Figure out which completion queue we're using */ + int nic_vf = nic->nic_vf; + int rbdr = nic->rbdr; + int cq = nic->cq; + + BDK_CSR_INIT(cq_base, nic->node, BDK_NIC_QSX_CQX_BASE(nic_vf, cq)); + const void *cq_ptr = bdk_phys_to_ptr(cq_base.u); + + /* Find the current CQ location */ + BDK_CSR_INIT(cq_head, nic->node, BDK_NIC_QSX_CQX_HEAD(nic_vf, cq)); + int loc = cq_head.s.head_ptr; + + /* Store the RBDR data locally to avoid contention */ + BDK_CSR_INIT(rbdr_base, nic->node, BDK_NIC_QSX_RBDRX_BASE(nic_vf, rbdr)); + BDK_CSR_INIT(rbdr_tail, nic->node, BDK_NIC_QSX_RBDRX_TAIL(nic_vf, rbdr)); + nic_rbdr_state_t vnic_rbdr_state; + vnic_rbdr_state.base = bdk_phys_to_ptr(rbdr_base.u); + vnic_rbdr_state.loc = rbdr_tail.s.tail_ptr; + + BDK_TRACE(NIC, "%s: Receive thread for CQ(%d, %d) started\n", nic->handle->name, nic->nic_vf, nic->cq); + + while (1) + { + /* Exit immediately if the CQ is empty */ + BDK_CSR_INIT(cq_status, nic->node, BDK_NIC_QSX_CQX_STATUS(nic_vf, cq)); + int pending_count = cq_status.s.qcount; + if (bdk_likely(!pending_count)) + { + bdk_wait_usec(1); + continue; + } + + /* Loop through all pending CQs */ + int rbdr_doorbell = 0; + int count = 0; + const union bdk_nic_cqe_rx_s *cq_next = cq_ptr + loc * 512; + BDK_TRACE(NIC, "%s: Receive thread CQ(%d, %d): %d pending\n", nic->handle->name, nic->nic_vf, nic->cq, pending_count); + while (count < pending_count) + { + const union bdk_nic_cqe_rx_s *cq_header = cq_next; + const union bdk_nic_cqe_rx_s *cq_header_le = cq_header; +#if __BYTE_ORDER == __BIG_ENDIAN + union bdk_nic_cqe_rx_s cq_be; + for (int i = 0; i < 6; i++) + cq_be.u[i] = bdk_le64_to_cpu(cq_header_le->u[i]); + cq_header = &cq_be; +#endif + BDK_TRACE(NIC, "%s: Receive HDR[%p] = 0x%lx 0x%lx 0x%lx 0x%lx\n", + nic->handle->name, cq_header_le, cq_header->u[0], cq_header->u[1], cq_header->u[2], cq_header->u[3]); + loc++; + loc &= CQ_ENTRIES - 1; + cq_next = cq_ptr + loc * 512; + BDK_PREFETCH(cq_next, 0); + if (bdk_likely(cq_header->s.cqe_type == BDK_NIC_CQE_TYPE_E_RX)) + rbdr_doorbell += if_process_complete_rx(nic->node, &vnic_rbdr_state, cq_header, cq_header_le, use_cqe_rx2); + else + bdk_error("Unsupported CQ header type %d\n", cq_header->s.cqe_type); + count++; + } + /* Ring the RBDR doorbell for all packets */ + BDK_WMB; + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_RBDRX_DOOR(nic_vf, rbdr), rbdr_doorbell); + /* Free all the CQs that we've processed */ + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_CQX_DOOR(nic_vf, cq), count); + /* Yield before going through more packets. The low core count chips + don't have enough cores to dedicate for TX and RX. This forces + sharing under load. If there are enough cores, the yield does + nothing */ + bdk_thread_yield(); + } +} + +/** + * Configure NIC for a specific port. This is called for each + * port on every interface that connects to NIC. + * + * @param handle Handle for port to config + * @param ntype Type of LMAC this NIC connects to + * @param lmac_credits + * Size of the LMAC buffer in bytes. Used to configure the number of credits to + * setup between the NIC and LMAC + * + * @return Zero on success, negative on failure + */ +int bdk_nic_port_init(bdk_if_handle_t handle, bdk_nic_type_t ntype, int lmac_credits) +{ + int nic_chan_idx_e; /* Flow channel for the CPI */ + bool has_rx_nic = (-1 == handle->pki_channel); /* true when nic rx channel exists - may be BGX or LBK-NIC*/ + bool has_tx_nic = (-1 == handle->pko_queue); /* true when nic tx channel exists - may be BGX or LBK-NIC*/ + int nic_intf_e = -1; /* Interface enumeration */ + int nic_intf_block_e; /* Interface Block ID Enumeration */ + int nic_lmac_e=-1; /* LMAC enumeration */ + + if (global_buffer_size == 0) + global_buffer_size = bdk_config_get_int(BDK_CONFIG_PACKET_BUFFER_SIZE); + + if (!has_rx_nic && !has_tx_nic) return 0; + + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + /* Flow here is a compressed NIC_CHAN_E enum value. Flow is bit[8] and + bit[6:0] from NIC_CHAN_E. This works out as: + bit 7: BGX interface number(0-1) + bit 6:4: BGX port number(0-3) + bit 3:0: BGX channel on a port (0-15) */ + nic_chan_idx_e = (handle->interface) ? 0x80 : 0x00; + nic_chan_idx_e += handle->index * 16; + nic_chan_idx_e += 0; /* channel */ + nic_intf_e = BDK_NIC_INTF_E_BGXX(handle->interface); + nic_intf_block_e = BDK_NIC_INTF_BLOCK_E_BGXX_BLOCK(handle->interface); + nic_lmac_e = BDK_NIC_LMAC_E_BGXX_LMACX(handle->interface, handle->index); + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + { + switch (ntype) + { + case BDK_NIC_TYPE_BGX: + nic_chan_idx_e = BDK_NIC_CHAN_IDX_E_BGXX_LMACX_CHX(handle->interface, handle->index, 0/*channel*/); + nic_intf_e = BDK_NIC_INTF_E_BGXX(handle->interface); + nic_intf_block_e = BDK_NIC_INTF_BLOCK_E_BGXX(handle->interface); + nic_lmac_e = BDK_NIC_LMAC_E_BGXX_LMACX(handle->interface, handle->index); + break; + case BDK_NIC_TYPE_LBK: + nic_chan_idx_e = BDK_NIC_CHAN_IDX_E_LBKX_CHX_CN83XX((handle->interface == 3) ? 1 : 0, handle->index); + // rx interface + if (3 == handle->interface) { + nic_intf_e = BDK_NIC_INTF_E_LBKX_CN83XX(1); + } else if (2 == handle->interface) { + nic_intf_e = BDK_NIC_INTF_E_LBKX_CN83XX(0); + } + nic_intf_block_e = BDK_NIC_INTF_BLOCK_E_LBKX(handle->interface); + // tx interface + if (3 == handle->interface) { + nic_lmac_e = BDK_NIC_LMAC_E_LBKX_CN83XX(1); + } else if (1 == handle->interface) { + nic_lmac_e = BDK_NIC_LMAC_E_LBKX_CN83XX(0); + } + break; + default: + bdk_error("%s: Unsupported NIC TYPE %d\n", handle->name, ntype); + return -1; + } + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + { + switch (ntype) + { + case BDK_NIC_TYPE_BGX: + nic_chan_idx_e = BDK_NIC_CHAN_IDX_E_BGXX_LMACX_CHX(handle->interface, handle->index, 0/*channel*/); + nic_intf_e = BDK_NIC_INTF_E_BGXX(handle->interface); + nic_intf_block_e = BDK_NIC_INTF_BLOCK_E_BGXX(handle->interface); + nic_lmac_e = BDK_NIC_LMAC_E_BGXX_LMACX(handle->interface, handle->index); + break; + case BDK_NIC_TYPE_RGMII: + nic_chan_idx_e = BDK_NIC_CHAN_IDX_E_RGXX_LMACX_CHX(handle->interface, handle->index, 0/*channel*/); + nic_intf_e = BDK_NIC_INTF_E_RGXX(handle->index); + nic_intf_block_e = BDK_NIC_INTF_BLOCK_E_BGXX(handle->interface + 2); + nic_lmac_e = BDK_NIC_LMAC_E_RGXX_LMACX(handle->interface, handle->index); + break; + case BDK_NIC_TYPE_LBK: + nic_chan_idx_e = BDK_NIC_CHAN_IDX_E_LBKX_CHX_CN81XX(handle->interface, handle->index); + nic_intf_e = BDK_NIC_INTF_E_LBKX_CN81XX(handle->interface); + nic_intf_block_e = BDK_NIC_INTF_BLOCK_E_LBKX(handle->interface); + nic_lmac_e = BDK_NIC_LMAC_E_LBKX_CN81XX(handle->interface); + break; + default: + bdk_error("%s: Unsupported NIC TYPE %d\n", handle->name, ntype); + return -1; + } + } + else + { + bdk_error("%s: Unsupported chip (NIC init)\n", handle->name); + return -1; + } + + /* Make sure the node global state has been allocated */ + if (global_node_state[handle->node] == NULL) + { + int num_nic_vf; + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + /* NIC_PF_CONST1 didn't exist on this chip */ + num_nic_vf = 128; + } + else + { + BDK_CSR_INIT(nic_pf_const1, handle->node, BDK_NIC_PF_CONST1); + num_nic_vf = nic_pf_const1.s.vnics; + } + global_node_state[handle->node] = calloc(1, sizeof(nic_node_state_t) + sizeof(handle) * num_nic_vf * 8); + if (global_node_state[handle->node] == NULL) + { + bdk_error("N%d.NIC: Failed to allocate node state\n", handle->node); + return -1; + } + global_node_state[handle->node]->num_nic_vf = num_nic_vf; + } + nic_node_state_t *node_state = global_node_state[handle->node]; + + /* See if we have a free VF */ + if (!handle->index && (node_state->next_free_nic_vf >= node_state->num_nic_vf)) + { + bdk_error("N%d.NIC: Ran out of NIC VFs\n", handle->node); + return -1; + } + + /* VNIC setup requirements + The code in this file makes the following assumptions: + 1) One RBDR for each CQ. No locking is done on RBDR + 2) A CQ can be shared across multiple ports, saving space as the + cost of performance. + 3) One SQ per physical port, no locking on TX + 4) One RQ per physical port, many RQ may share RBDR/CQ + + Current setup without DRAM: + 1) One NIC VF is used for an entire interface (BGX, LBK). The variable + nic_vf represents the NIC virtual function. + 2) SQs are allocated one per port. SQ index equals handle->index + 3) RQs are allocated one per port. RQ index equals handle->index + 4) One CQ is allcoated per entire interface, using index 0 + 5) One RBDR is used for the CQ, index 0 + + Current setup with DRAM: + FIXME: Same as without DRAM. There are not enough RBDR to have + independent CQs without locking. + */ + void *sq_memory = NULL; + if (has_tx_nic) { + sq_memory = memalign(BDK_CACHE_LINE_SIZE, 16 * SQ_ENTRIES); + if (!sq_memory) + { + bdk_error("%s: Unable to allocate queues\n", handle->name); + return -1; + } + } + nic_t *nic = calloc(1, sizeof(nic_t)); + if (!nic) + { + if (sq_memory) free(sq_memory); + bdk_error("%s: Unable to NIC state\n", handle->name); + return -1; + } + + /* Fill in the various NIC indexes */ + nic->node = handle->node; + nic->ntype = ntype; + if (handle->index) + nic->nic_vf = node_state->next_free_nic_vf - 1; /* reuse last one */ + else + nic->nic_vf = node_state->next_free_nic_vf++; /* New nic */ + nic->sq = handle->index; + nic->cq = 0; + nic->rq = handle->index; + nic->rbdr = 0; + nic->bpid = node_state->next_free_bpid++; + nic->handle = handle; + BDK_TRACE(NIC, "%s: Creating NIC(%d, sq=%d, cq=%d, rq=%d, rbdr=%d, bpid=%d)\n", + nic->handle->name, nic->nic_vf, nic->sq, nic->cq, nic->rq, nic->rbdr, nic->bpid); + + /* Connect this NIC to the handle */ + handle->nic_id = nic->nic_vf * 8 + nic->rq; + node_state->nic_map[handle->nic_id] = nic; + + /* Enable global BP state updates */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_BP_CFG, + c.s.bp_poll_ena = 1; + c.s.bp_poll_dly = 3); + + /* Enable interface level backpresure */ + if (-1 != nic_intf_e) { + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_INTFX_BP_CFG(nic_intf_e), + c.s.bp_ena = 1; + c.s.bp_type = ((nic->ntype == BDK_NIC_TYPE_BGX) || + (nic->ntype == BDK_NIC_TYPE_RGMII)) ? 0 : 1; /* 0=BGX, 1=LBK/TNS */ + c.s.bp_id = nic_intf_block_e); + } + if (has_tx_nic) { + /* Configure the submit queue (SQ) */ + nic->sq_base = sq_memory; + nic->sq_loc = 0; + nic->sq_available = SQ_ENTRIES; + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_SQX_BASE(nic->nic_vf, nic->sq), + bdk_ptr_to_phys(sq_memory)); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_QSX_SQX_CFG(nic->nic_vf, nic->sq), + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) + c.s.cq_limit = 1; + c.s.ena = 1; + c.s.ldwb = BDK_USE_DWB; + c.s.qsize = SQ_ENTRIES_QSIZE); + } + int cpi=0; + int rssi=0; + if (has_rx_nic) { + /* Configure the receive queue (RQ) */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_QSX_RQ_GEN_CFG(nic->nic_vf), + c.s.vlan_strip = 0; + c.s.len_l4 = 0; + c.s.len_l3 = 0; + c.s.csum_l4 = 0; + c.s.ip6_udp_opt = 0; + c.s.splt_hdr_ena = 0; + c.s.cq_hdr_copy = 0; + c.s.max_tcp_reass = 0; + c.s.cq_pkt_size = 0; + c.s.later_skip = 0; + c.s.first_skip = 0); + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_QSX_RQX_CFG(nic->nic_vf, nic->rq), + c.s.ena = 1; + c.s.tcp_ena = 0); + + cpi = node_state->next_free_cpi++; /* Allocate a new Channel Parse Index (CPI) */ + rssi = node_state->next_free_rssi++;/* Allocate a new Receive-Side Scaling Index (RSSI) */ + /* NIC_CHAN_E hard mapped to "flow". Flow chooses the CPI */ + + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_CHANX_RX_CFG(nic_chan_idx_e), + c.s.cpi_alg = BDK_NIC_CPI_ALG_E_NONE; + c.s.cpi_base = cpi); + /* Setup backpressure */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_CHANX_RX_BP_CFG(nic_chan_idx_e), + c.s.ena = 1; + c.s.bpid = nic->bpid); + } + if ( has_tx_nic) { + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_CHANX_TX_CFG(nic_chan_idx_e), + c.s.bp_ena = 1); + } + + if (has_rx_nic) { + /* CPI is the output of the above alogrithm, this is used to lookup the + VNIC for receive and RSSI */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_CPIX_CFG(cpi), + c.cn88xxp1.vnic = nic->nic_vf; /* TX and RX use the same VNIC */ + c.cn88xxp1.rss_size = 0; /* RSS hash is disabled */ + c.s.padd = 0; /* Used if we have multiple channels per port */ + c.cn88xxp1.rssi_base = rssi); /* Base RSSI */ + + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) + { + /* CN88XX pass 2 moved some fields to a different CSR */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_MPIX_CFG(cpi), + c.s.vnic = nic->nic_vf; /* TX and RX use the same VNIC */ + c.s.rss_size = 0; /* RSS hash is disabled */ + c.s.rssi_base = rssi); /* Base RSSI */ + } + + /* The RSSI is used to determine which Receive Queue (RQ) we use */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_RSSIX_RQ(rssi), + c.s.rq_qs = nic->nic_vf; + c.s.rq_idx = nic->rq); + /* Set the min and max packet size. PKND comes from BGX. It is always zero + for now */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_PKINDX_CFG(handle->pknd), + c.s.lenerr_en = 0; + c.s.minlen = 0; + c.s.maxlen = 65535); + } + + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + /* Bypass the TNS */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_INTFX_SEND_CFG(handle->interface), + c.s.tns_nonbypass = 0; + c.s.block = 0x8 + handle->interface); + } + + /* Errata (NIC-21858) If NIC_PF_QS()_CFG ENA is set after RRM enabled...RRM breaks */ + /* Do global vnic init */ + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_QSX_CFG(nic->nic_vf), + c.s.ena = 1; + c.s.vnic = nic->nic_vf); + + if (has_tx_nic && vnic_setup_tx_shaping(nic)) + return -1; + + /* Completion queue may be used by both tx and rx. + ** Define it even if only one of rx/tx is in use + */ + if (vnic_setup_cq(nic)) + return -1; + /* RBDR is defined regardless of rx_nic to avoid possible backpressure */ + if ( vnic_setup_rbdr(nic)) + return -1; + + /* Program LMAC credits */ + if ((has_tx_nic) && (-1 != nic_lmac_e)) { + int credit; + if ((BDK_NIC_TYPE_LBK == nic->ntype) && CAVIUM_IS_MODEL(CAVIUM_CN83XX) ) + credit = 512; /* HRM guidance */ + else + credit = (lmac_credits - MAX_MTU) / 16; + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_LMACX_CREDIT(nic_lmac_e), + c.s.cc_unit_cnt = credit; + c.s.cc_packet_cnt = 0x1ff; + c.s.cc_enable = 1); + + /* Pad packets to 60 bytes, 15 32bit words (before FCS) */ + if (nic->ntype != BDK_NIC_TYPE_LBK) + BDK_CSR_MODIFY(c, nic->node, BDK_NIC_PF_LMACX_CFG(nic_lmac_e), + c.s.min_pkt_size = 15); + } + /* Create a receive thread if this handle has its own CQ/RBDR */ + if (handle->index == 0) + { + /* FIXME + * At this time thread monitors both CQ and RBDR and uses it only for receive + * Setting up RBDR for tx only nics is wasteful. + * When nic_tx in bdk starts using CQ, thread needs to change + */ + if (has_rx_nic && bdk_thread_create(nic->node, 0, if_receive, 0, nic, 0)) + { + bdk_error("%s: Failed to allocate receive thread\n", handle->name); + return -1; + } + } + + return 0; +} + +/** + * Send a packet + * + * @param handle Handle of port to send on + * @param packet Packet to send + * + * @return Zero on success, negative on failure + */ +int bdk_nic_transmit(bdk_if_handle_t handle, const bdk_if_packet_t *packet) +{ + /* The SQ can't be filled completely as it reguires at least one free + entry so the head and pointer don't look like empty. SQ_SLOP is the + amount of SQ space we reserve to make sure of this */ + const int SQ_SLOP = 1; + const nic_node_state_t *node_state = global_node_state[handle->node]; + nic_t *nic = node_state->nic_map[handle->nic_id]; + BDK_TRACE(NIC, "%s: Transmit packet of %d bytes, %d segments\n", + nic->handle->name, packet->length, packet->segments); + + /* Update the SQ available if we're out of space. The NIC should have sent + packets, making more available. This allows us to only read the STATUS + CSR when really necessary, normally using the L1 cached value */ + if (nic->sq_available < packet->segments + 1 + SQ_SLOP) + { + BDK_CSR_INIT(sq_status, nic->node, BDK_NIC_QSX_SQX_STATUS(nic->nic_vf, nic->sq)); + nic->sq_available = SQ_ENTRIES - sq_status.s.qcount; + /* Re-Check for space. A packets is a header plus its segments */ + if (nic->sq_available < packet->segments + 1 + SQ_SLOP) + { + BDK_TRACE(NIC, "%s: Transmit fail, queue full\n", nic->handle->name); + return -1; + } + } + + /* Build the command */ + void *sq_ptr = nic->sq_base; + int loc = nic->sq_loc; + union bdk_nic_send_hdr_s send_hdr; + send_hdr.u[0] = 0; + send_hdr.u[1] = 0; + send_hdr.s.subdc = BDK_NIC_SEND_SUBDC_E_HDR; + send_hdr.s.subdcnt = packet->segments; + send_hdr.s.total = packet->length; + switch (packet->packet_type) + { + case BDK_IF_TYPE_UNKNOWN: + break; + case BDK_IF_TYPE_UDP4: + send_hdr.s.ckl3 = 1; /* L3 - IPv4 checksum enable */ + send_hdr.s.l3ptr = 14; /* L2 header is 14 bytes */ + send_hdr.s.ckl4 = BDK_NIC_SEND_CKL4_E_UDP; /* L4 - UDP checksum enable */ + send_hdr.s.l4ptr = 14 + 20; /* 14 bytes L2 + 20 bytes IPv4 */ + break; + case BDK_IF_TYPE_TCP4: + send_hdr.s.ckl3 = 1; /* L3 - IPv4 checksum enable */ + send_hdr.s.l3ptr = 14; /* L2 header is 14 bytes */ + send_hdr.s.ckl4 = BDK_NIC_SEND_CKL4_E_TCP; /* L4 - TCP checksum enable */ + send_hdr.s.l4ptr = 14 + 20; /* 14 bytes L2 + 20 bytes IPv4 */ + if (packet->mtu) + { + int headers = 14 + 20 + 20; + send_hdr.s.tso = 1; /* Use TCP offload */ + send_hdr.s.tso_sb = headers; /* 14 bytes L2 + 20 bytes IPv4, 20 bytes TCP */ + send_hdr.s.tso_mps = packet->mtu - headers; /* Max TCP data payload size */ + } + break; + } + volatile uint64_t *wptr = (uint64_t *)(sq_ptr + loc * 16); + wptr[0] = bdk_cpu_to_le64(send_hdr.u[0]); + wptr[1] = bdk_cpu_to_le64(send_hdr.u[1]); + BDK_TRACE(NIC, "%s: Transmit HDR[%p] = 0x%lx 0x%lx\n", + nic->handle->name, sq_ptr + loc * 16, send_hdr.u[0], send_hdr.u[1]); + loc++; + loc &= SQ_ENTRIES - 1; + for (int s = 0; s < packet->segments; s++) + { + union bdk_nic_send_gather_s gather; + gather.u[0] = 0; + gather.u[1] = 0; + gather.s.addr = packet->packet[s].s.address; + gather.s.subdc = BDK_NIC_SEND_SUBDC_E_GATHER; + gather.s.ld_type = (BDK_USE_DWB) ? BDK_NIC_SEND_LD_TYPE_E_LDWB : BDK_NIC_SEND_LD_TYPE_E_LDD; + gather.s.size = packet->packet[s].s.size; + wptr = (uint64_t *)(sq_ptr + loc * 16); + wptr[0] = bdk_cpu_to_le64(gather.u[0]); + wptr[1] = bdk_cpu_to_le64(gather.u[1]); + BDK_TRACE(NIC, "%s: Transmit Gather[%p] = 0x%lx 0x%lx\n", + nic->handle->name, sq_ptr + loc * 16, gather.u[0], gather.u[1]); + loc++; + loc &= SQ_ENTRIES - 1; + } + + BDK_WMB; + + /* Ring the doorbell */ + BDK_CSR_WRITE(nic->node, BDK_NIC_QSX_SQX_DOOR(nic->nic_vf, nic->sq), + packet->segments + 1); + BDK_TRACE(NIC, "%s: Transmit Doorbell %d\n", nic->handle->name, packet->segments + 1); + + /* Update our cached state */ + nic->sq_available -= packet->segments + 1; + nic->sq_loc = loc; + if (handle->iftype != BDK_IF_BGX) { + /* Update stats as we do them in software for non-BGX */ + handle->stats.tx.packets++; + handle->stats.tx.octets += packet->length; + if (handle->flags & BDK_IF_FLAGS_HAS_FCS) + handle->stats.tx.octets += 4; + } + return 0; +} + +/** + * Get the current TX queue depth. Note that this operation may be slow + * and adversly affect packet IO performance. + * + * @param handle Port to check + * + * @return Depth of the queue in packets + */ +int bdk_nic_get_queue_depth(bdk_if_handle_t handle) +{ + const nic_node_state_t *node_state = global_node_state[handle->node]; + const nic_t *nic = node_state->nic_map[handle->nic_id]; + BDK_CSR_INIT(sq_status, nic->node, BDK_NIC_QSX_SQX_STATUS(nic->nic_vf, nic->sq)); + return sq_status.s.qcount; +} + +/** + * Query NIC and fill in the transmit stats for the supplied + * interface handle. + * + * @param handle Port handle + */ +void bdk_nic_fill_tx_stats(bdk_if_handle_t handle) +{ + const int vnic = handle->nic_id >> 3; + + /* Transmit stats are done in software due to CN81XX not having enough NICs */ + + /* Note drops are shared across a BGX. People will be confused */ + BDK_CSR_INIT(drps, handle->node, BDK_NIC_VNICX_TX_STATX(vnic, BDK_NIC_STAT_VNIC_TX_E_TX_DROP)); + handle->stats.tx.dropped_packets = bdk_update_stat_with_overflow(drps.u, handle->stats.tx.dropped_packets, 48); + /* Dropped Octets are not available */ +} + +/** + * Query NIC and fill in the receive stats for the supplied + * interface handle. + * + * @param handle Port handle + */ +void bdk_nic_fill_rx_stats(bdk_if_handle_t handle) +{ + /* Account for RX FCS */ + const int bytes_off_rx = (handle->flags & BDK_IF_FLAGS_HAS_FCS) ? 4 : 0; + const int vnic = handle->nic_id >> 3; + + /* Note stats are shared across a BGX. People will be confused */ + + /* Read the RX statistics. These do not include the ethernet FCS */ + BDK_CSR_INIT(rx_red, handle->node, BDK_NIC_VNICX_RX_STATX(vnic, BDK_NIC_STAT_VNIC_RX_E_RX_RED)); + BDK_CSR_INIT(rx_red_octets, handle->node, BDK_NIC_VNICX_RX_STATX(vnic, BDK_NIC_STAT_VNIC_RX_E_RX_RED_OCTS)); + BDK_CSR_INIT(rx_ovr, handle->node, BDK_NIC_VNICX_RX_STATX(vnic, BDK_NIC_STAT_VNIC_RX_E_RX_ORUN)); + BDK_CSR_INIT(rx_ovr_octets, handle->node, BDK_NIC_VNICX_RX_STATX(vnic, BDK_NIC_STAT_VNIC_RX_E_RX_ORUN_OCTS)); + uint64_t drops = rx_red.u + rx_ovr.u; + uint64_t drop_octets = rx_red_octets.u + rx_ovr_octets.u; + + /* Drop and error counters */ + handle->stats.rx.dropped_octets -= handle->stats.rx.dropped_packets * bytes_off_rx; + handle->stats.rx.dropped_octets = bdk_update_stat_with_overflow(drop_octets, handle->stats.rx.dropped_octets, 48); + handle->stats.rx.dropped_packets = bdk_update_stat_with_overflow(drops, handle->stats.rx.dropped_packets, 48); + handle->stats.rx.dropped_octets += handle->stats.rx.dropped_packets * bytes_off_rx; + + /* Normal RX stats are done by software on receive */ +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-pcie-cn8xxx.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-pcie-cn8xxx.c new file mode 100644 index 0000000000..16034d27c3 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-pcie-cn8xxx.c @@ -0,0 +1,1263 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <string.h> +#include "libbdk-arch/bdk-csrs-dtx.h" +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-gic.h" +#include "libbdk-arch/bdk-csrs-pem.h" +#include "libbdk-arch/bdk-csrs-pcierc.h" +#include "libbdk-arch/bdk-csrs-sli.h" +#include "libbdk-arch/bdk-csrs-rst.h" +#include "libbdk-hal/bdk-pcie.h" +#include "libbdk-hal/bdk-config.h" +#include "libbdk-hal/bdk-utils.h" +#include "libbdk-hal/if/bdk-if.h" +#include "libbdk-hal/bdk-qlm.h" +#include "libbdk-hal/device/bdk-device.h" +#include "libbdk-hal/bdk-ecam.h" + +/** + * Return the number of possible PCIe ports on a node. The actual number + * of configured ports may be less and may also be disjoint. + * + * @param node Node to query + * + * @return Number of PCIe ports that are possible + */ +int bdk_pcie_get_num_ports(bdk_node_t node) +{ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + return 6; + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + return 4; + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + return 3; + else + return 0; +} + + +/** + * Given a PCIe port, determine which SLI controls its memory regions + * + * @param node Node for the PCIe port + * @param pcie_port The PCIe port + * @param sli The SLI index is written to this integer pointer + * @param sli_group The index of the PCIe port on the SLI is returned here. This is a sequencial + * number for each PCIe on an SLI. Use this to index SLI regions. + */ +static void __bdk_pcie_get_sli(bdk_node_t node, int pcie_port, int *sli, int *sli_group) +{ + /* This mapping should be determined by find the SLI number on the + same ECAM bus as the PCIERC bridge. That is fairly complex, so it is + hardcoded for now */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + /* Ports 0-2 goto SLI0, ports 3-5 goto SLI1 */ + *sli = (pcie_port >= 3) ? 1 : 0; + *sli_group = pcie_port - *sli * 3; + return; + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX) || CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + { + /* Only one SLI */ + *sli = 0; + *sli_group = pcie_port; + return; + } + else + bdk_fatal("Unable to determine SLI for PCIe port. Update __bdk_pcie_get_sli()\n"); +} + +/** + * Return the Core physical base address for PCIe MEM access. Memory is + * read/written as an offset from this address. + * + * @param node Node to use in a Numa setup + * @param pcie_port PCIe port the memory is on + * @param mem_type Type of memory + * + * @return 64bit physical address for read/write + */ +uint64_t bdk_pcie_get_base_address(bdk_node_t node, int pcie_port, bdk_pcie_mem_t mem_type) +{ + /* See __bdk_pcie_sli_initialize() for a description about how SLI regions work */ + int sli; + int sli_group; + __bdk_pcie_get_sli(node, pcie_port, &sli, &sli_group); + int region = (sli_group << 6) | (mem_type << 4); + union bdk_sli_s2m_op_s s2m_op; + s2m_op.u = 0; + s2m_op.s.io = 1; + s2m_op.s.node = node; + s2m_op.s.did_hi = 0x8 + sli; + s2m_op.s.region = region; + return s2m_op.u; +} + +/** + * Size of the Mem address region returned at address + * bdk_pcie_get_base_address() + * + * @param node Node to use in a Numa setup + * @param pcie_port PCIe port the IO is for + * @param mem_type Type of memory + * + * @return Size of the Mem window + */ +uint64_t bdk_pcie_get_base_size(bdk_node_t node, int pcie_port, bdk_pcie_mem_t mem_type) +{ + return 1ull << 36; +} + +/** + * @INTERNAL + * Initialize the RC config space CSRs + * + * @param pcie_port PCIe port to initialize + */ +static void __bdk_pcie_rc_initialize_config_space(bdk_node_t node, int pcie_port) +{ + int sli; + int sli_group; + __bdk_pcie_get_sli(node, pcie_port, &sli, &sli_group); + + /* The reset default for config retries is too short. Set it to 48ms, which + is what the Octeon SDK team is using. There is no documentation about + where they got the 48ms number */ + int cfg_retry = 48 * 1000000 / (bdk_clock_get_rate(node, BDK_CLOCK_SCLK) >> 16); + if (cfg_retry >= 0x10000) + cfg_retry = 0xfffff; + BDK_CSR_MODIFY(c, node, BDK_PEMX_CTL_STATUS(pcie_port), + c.cn83xx.cfg_rtry = cfg_retry); + + + /* Max Payload Size (PCIE*_CFG030[MPS]) */ + /* Max Read Request Size (PCIE*_CFG030[MRRS]) */ + /* Relaxed-order, no-snoop enables (PCIE*_CFG030[RO_EN,NS_EN] */ + /* Error Message Enables (PCIE*_CFG030[CE_EN,NFE_EN,FE_EN,UR_EN]) */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG030(pcie_port), + c.s.mps = 1; /* Support 256 byte MPS */ + c.s.mrrs = 0x5; /* Support 4KB MRRS */ + c.s.ro_en = 1; /* Enable relaxed order processing. This will allow devices to affect read response ordering */ + c.s.ns_en = 1; /* Enable no snoop processing. Not used */ + c.s.ce_en = 1; /* Correctable error reporting enable. */ + c.s.nfe_en = 1; /* Non-fatal error reporting enable. */ + c.s.fe_en = 1; /* Fatal error reporting enable. */ + c.s.ur_en = 1); /* Unsupported request reporting enable. */ + + /* Configure the PCIe slot number if specified */ + int slot_num = bdk_config_get_int(BDK_CONFIG_PCIE_PHYSICAL_SLOT, node, pcie_port); + if (slot_num != -1) + { + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG028(pcie_port), + c.s.si = 1); /* Slot Implemented*/ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG033(pcie_port), + c.s.ps_num = slot_num); + } + + /* Disable ECRC Generation as not all card support it. The OS can enable it + later if desired (PCIE*_CFG070[GE,CE]) */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG070(pcie_port), + c.s.ge = 0; /* ECRC generation disable. */ + c.s.ce = 0); /* ECRC check disable. */ + + /* Access Enables (PCIE*_CFG001[MSAE,ME]) */ + /* ME and MSAE should always be set. */ + /* Interrupt Disable (PCIE*_CFG001[I_DIS]) */ + /* System Error Message Enable (PCIE*_CFG001[SEE]) */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG001(pcie_port), + c.s.msae = 1; /* Memory space enable. */ + c.s.me = 1; /* Bus master enable. */ + c.s.i_dis = 1; /* INTx assertion disable. */ + c.s.see = 1); /* SERR# enable */ + + /* Advanced Error Recovery Message Enables */ + /* (PCIE*_CFG066,PCIE*_CFG067,PCIE*_CFG069) */ + BDK_CSR_WRITE(node, BDK_PCIERCX_CFG066(pcie_port), 0); + /* Use BDK_PCIERCX_CFG067 hardware default */ + BDK_CSR_WRITE(node, BDK_PCIERCX_CFG069(pcie_port), 0); + + + /* Active State Power Management (PCIE*_CFG032[ASLPC]) */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG032(pcie_port), + c.s.aslpc = 0); /* Active state Link PM control. */ + + /* Link Width Mode (PCIERCn_CFG452[LME]) - Set during bdk_pcie_rc_initialize_link() */ + /* Primary Bus Number (PCIERCn_CFG006[PBNUM]) */ + /* Use bus numbers as follows: + 0 - 31: Reserved for internal ECAM + 32 - 87: First PCIe on SLI + 88 - 143: Second PCIe on SLI + 144 - 199: Third PCIe on SLI + 200 - 255: Fourth PCIe on SLI + Start bus = 32 + pcie * 56 */ + const int BUSSES_PER_PCIE = 56; + int bus = 32 + sli_group * BUSSES_PER_PCIE; + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG006(pcie_port), + c.s.pbnum = 0; + c.s.sbnum = bus; + c.s.subbnum = bus + BUSSES_PER_PCIE - 1); + + /* Memory-mapped I/O BAR (PCIERCn_CFG008) */ + uint64_t mem_base = bdk_pcie_get_base_address(node, pcie_port, BDK_PCIE_MEM_NORMAL); + uint64_t mem_limit = mem_base + bdk_pcie_get_base_size(node, pcie_port, BDK_PCIE_MEM_NORMAL) - 1; + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG008(pcie_port), + c.s.mb_addr = mem_base >> 16; + c.s.ml_addr = mem_limit >> 16); + + /* Prefetchable BAR (PCIERCn_CFG009,PCIERCn_CFG010,PCIERCn_CFG011) */ + uint64_t prefetch_base = bdk_pcie_get_base_address(node, pcie_port, BDK_PCIE_MEM_PREFETCH); + uint64_t prefetch_limit = prefetch_base + bdk_pcie_get_base_size(node, pcie_port, BDK_PCIE_MEM_PREFETCH) - 1; + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG009(pcie_port), + c.s.lmem_base = prefetch_base >> 16; + c.s.lmem_limit = prefetch_limit >> 16); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG010(pcie_port), + c.s.umem_base = prefetch_base >> 32); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG011(pcie_port), + c.s.umem_limit = prefetch_limit >> 32); + + /* System Error Interrupt Enables (PCIERCn_CFG035[SECEE,SEFEE,SENFEE]) */ + /* PME Interrupt Enables (PCIERCn_CFG035[PMEIE]) */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG035(pcie_port), + c.s.secee = 1; /* System error on correctable error enable. */ + c.s.sefee = 1; /* System error on fatal error enable. */ + c.s.senfee = 1; /* System error on non-fatal error enable. */ + c.s.pmeie = 1); /* PME interrupt enable. */ + + /* Advanced Error Recovery Interrupt Enables */ + /* (PCIERCn_CFG075[CERE,NFERE,FERE]) */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG075(pcie_port), + c.s.cere = 1; /* Correctable error reporting enable. */ + c.s.nfere = 1; /* Non-fatal error reporting enable. */ + c.s.fere = 1); /* Fatal error reporting enable. */ + + /* Make sure the PEM agrees with GSERX about the speed its going to try */ + BDK_CSR_INIT(pem_cfg, node, BDK_PEMX_CFG(pcie_port)); + switch (pem_cfg.cn83xx.md) + { + case 0: /* Gen 1 */ + /* Set the target link speed */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG040(pcie_port), + c.s.tls = 1); + break; + case 1: /* Gen 2 */ + /* Set the target link speed */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG040(pcie_port), + c.s.tls = 2); + break; + case 2: /* Gen 3 */ + /* Set the target link speed */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG040(pcie_port), + c.s.tls = 3); + break; + default: + bdk_error("N%d.PCIe%d: Unexpected rate of %d\n", node, pcie_port, pem_cfg.cn83xx.md); + break; + } + + BDK_CSR_INIT(pemx_cfg, node, BDK_PEMX_CFG(pcie_port)); + BDK_CSR_INIT(cfg452, node, BDK_PCIERCX_CFG452(pcie_port)); + BDK_CSR_INIT(cfg031, node, BDK_PCIERCX_CFG031(pcie_port)); + int lme = cfg452.s.lme; + int mlw = cfg031.s.mlw; + + /* Link Width Mode (PCIERCn_CFG452[LME]) */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + lme = (pemx_cfg.cn88xx.lanes8) ? 0xf : 0x7; + mlw = (pemx_cfg.cn88xx.lanes8) ? 8 : 4; + } + /* CN83XX can support 8 lanes on QLM0+1 or QLM2+3. 4 lanes on DLM5+6 */ + if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + { + switch (pcie_port) + { + case 0: /* PEM0 on QLM0-1 */ + lme = (pemx_cfg.cn83xx.lanes8) ? 0xf : 0x7; + mlw = (pemx_cfg.cn83xx.lanes8) ? 8 : 4; + break; + case 1: /* PEM1 on QLM1 */ + lme = 0x7; + mlw = 4; + break; + case 2: /* PEM2 on QLM2-3 or DLM4 */ + { + BDK_CSR_INIT(pemx_qlm, node, BDK_PEMX_QLM(pcie_port)); + if (pemx_qlm.s.pem_bdlm) /* PEM2 is on DLM4 */ + { + lme = 0x3; + mlw = 2; + } + else /* PEM2 is on QLM2 */ + { + lme = (pemx_cfg.cn83xx.lanes8) ? 0xf : 0x7; + mlw = (pemx_cfg.cn83xx.lanes8) ? 8 : 4; + } + break; + } + case 3: /* PEM3 on QLM3 or DLM5-6 */ + { + BDK_CSR_INIT(pemx_qlm, node, BDK_PEMX_QLM(pcie_port)); + if (pemx_qlm.s.pem_bdlm) /* PEM3 is on DLM5-6 */ + { + lme = (pemx_cfg.cn83xx.lanes8) ? 0x7 : 0x3; + mlw = (pemx_cfg.cn83xx.lanes8) ? 4 : 2; + } + else /* PEM3 is on QLM3 */ + { + lme = 0x7; + mlw = 4; + } + break; + } + } + } + /* CN80XX only supports 1 lane on PEM0 */ + if (cavium_is_altpkg(CAVIUM_CN81XX) && (pcie_port == 0)) + { + lme = 1; + mlw = 1; + } + + /* Allow override of hardware max link width */ + int max_width = bdk_config_get_int(BDK_CONFIG_PCIE_WIDTH, node, pcie_port); + switch (max_width) + { + case 1: + lme = 1; + mlw = 1; + break; + case 2: + lme = 3; + mlw = 2; + break; + case 4: + lme = 7; + mlw = 4; + break; + case 8: + lme = 0xf; + mlw = 8; + break; + case 16: + lme = 0x1f; + mlw = 16; + break; + default: + /* No change */ + break; + } + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG452(pcie_port), + c.s.lme = lme); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG031(pcie_port), + c.s.mlw = mlw); + + /* Errata PEM-25990 - Disable ASLPMS */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG031(pcie_port), + c.s.aslpms = 0); + + /* Errata PEM-26189 - PEM EQ Preset Removal */ + /* CFG554.PRV default changed from 16'h7ff to 16'h593. Should be + safe to apply to CN88XX, CN81XX, and CN83XX */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG554(pcie_port), + c.s.prv = bdk_config_get_int(BDK_CONFIG_PCIE_PRESET_REQUEST_VECTOR, node, pcie_port)); + + /* Errata PEM-26189 - Disable the 2ms timer on all chips */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG554(pcie_port), + c.s.p23td = 1); + + /* Errata PEM-21178 - Change the CFG[089-092] LxUTP and LxDTP defaults. + Should be safe to apply to CN88XX, CN81XX, and CN83XX */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG089(pcie_port), + c.s.l0dtp = 0x7; + c.s.l0utp = 0x7; + c.cn83xx.l1dtp = 0x7; + c.s.l1utp = 0x7); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG090(pcie_port), + c.s.l2dtp = 0x7; + c.s.l2utp = 0x7; + c.s.l3dtp = 0x7; + c.s.l3utp = 0x7); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG091(pcie_port), + c.s.l4dtp = 0x7; + c.s.l4utp = 0x7; + c.s.l5dtp = 0x7; + c.s.l5utp = 0x7); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG092(pcie_port), + c.s.l6dtp = 0x7; + c.s.l6utp = 0x7; + c.s.l7dtp = 0x7; + c.s.l7utp = 0x7); + + /* (ECAM-27114) PCIERC has incorrect device code */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG002(pcie_port), + c.s.sc = 0x4; + c.s.bcc = 0x6); + + /* Errata PCIE-29440 - Atomic Egress ATOM_OP/ATOM_OP_EP not implemented + correctly */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG038(pcie_port), + c.s.atom_op =0x1; + c.s.atom_op_eb=0); + + /* Errata PCIE-29566 PEM Link Hangs after going into L1 */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG548(pcie_port), + c.s.grizdnc = 0x0); +} + +/** + * Get the PCIe LTSSM state for the given port + * + * @param node Node to query + * @param pcie_port PEM to query + * + * @return LTSSM state + */ +static int __bdk_pcie_rc_get_ltssm_state(bdk_node_t node, int pcie_port) +{ + /* LTSSM state is in debug select 0 */ + BDK_CSR_WRITE(node, BDK_DTX_PEMX_SELX(pcie_port, 0), 0); + BDK_CSR_WRITE(node, BDK_DTX_PEMX_ENAX(pcie_port, 0), 0xfffffffffull); + /* Read the value */ + uint64_t debug = BDK_CSR_READ(node, BDK_DTX_PEMX_DATX(pcie_port, 0)); + /* Disable the PEM from driving OCLA signals */ + BDK_CSR_WRITE(node, BDK_DTX_PEMX_ENAX(pcie_port, 0), 0); + if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + return bdk_extract(debug, 0, 6); /* DBGSEL = 0x0, bits[5:0] */ + else + return bdk_extract(debug, 3, 6); /* DBGSEL = 0x0, bits[8:3] */ +} + +/** + * Get the PCIe LTSSM state for the given port + * + * @param node Node to query + * @param pcie_port PEM to query + * + * @return LTSSM state + */ +static const char *ltssm_string(int ltssm) +{ + switch (ltssm) + { + case 0x00: return "DETECT_QUIET"; + case 0x01: return "DETECT_ACT"; + case 0x02: return "POLL_ACTIVE"; + case 0x03: return "POLL_COMPLIANCE"; + case 0x04: return "POLL_CONFIG"; + case 0x05: return "PRE_DETECT_QUIET"; + case 0x06: return "DETECT_WAIT"; + case 0x07: return "CFG_LINKWD_START"; + case 0x08: return "CFG_LINKWD_ACEPT"; + case 0x09: return "CFG_LANENUM_WAIT"; + case 0x0A: return "CFG_LANENUM_ACEPT"; + case 0x0B: return "CFG_COMPLETE"; + case 0x0C: return "CFG_IDLE"; + case 0x0D: return "RCVRY_LOCK"; + case 0x0E: return "RCVRY_SPEED"; + case 0x0F: return "RCVRY_RCVRCFG"; + case 0x10: return "RCVRY_IDLE"; + case 0x11: return "L0"; + case 0x12: return "L0S"; + case 0x13: return "L123_SEND_EIDLE"; + case 0x14: return "L1_IDLE"; + case 0x15: return "L2_IDLE"; + case 0x16: return "L2_WAKE"; + case 0x17: return "DISABLED_ENTRY"; + case 0x18: return "DISABLED_IDLE"; + case 0x19: return "DISABLED"; + case 0x1A: return "LPBK_ENTRY"; + case 0x1B: return "LPBK_ACTIVE"; + case 0x1C: return "LPBK_EXIT"; + case 0x1D: return "LPBK_EXIT_TIMEOUT"; + case 0x1E: return "HOT_RESET_ENTRY"; + case 0x1F: return "HOT_RESET"; + case 0x20: return "RCVRY_EQ0"; + case 0x21: return "RCVRY_EQ1"; + case 0x22: return "RCVRY_EQ2"; + case 0x23: return "RCVRY_EQ3"; + default: return "Unknown"; + } +} + +/** + * During PCIe link initialization we need to make config request to the attached + * device to verify its speed and width. These config access happen very early + * after the device is taken out of reset, so may fail for some amount of time. + * This function automatically retries these config accesses. The normal builtin + * hardware retry isn't enough for this very early access. + * + * @param node Note to read from + * @param pcie_port PCIe port to read from + * @param bus PCIe bus number + * @param dev PCIe device + * @param func PCIe function on the device + * @param reg Register to read + * + * @return Config register value, or all ones on failure + */ +static uint32_t cfg_read32_retry(bdk_node_t node, int pcie_port, int bus, int dev, int func, int reg) +{ + /* Read the PCI config register until we get a valid value. Some cards + require time after link up to return data. Wait at most 3 seconds */ + uint64_t timeout = bdk_clock_get_count(BDK_CLOCK_TIME) + bdk_clock_get_rate(bdk_numa_local(), BDK_CLOCK_TIME) * 3; + uint32_t val; + do + { + /* Read PCI capability pointer */ + val = bdk_pcie_config_read32(node, pcie_port, bus, dev, func, reg); + /* Check the read succeeded */ + if (val != 0xffffffff) + return val; + /* Failed, wait a little and try again */ + bdk_wait_usec(10000); + } while (bdk_clock_get_count(BDK_CLOCK_TIME) < timeout); + + BDK_TRACE(PCIE, "N%d.PCIe%d: Config read failed, can't communicate with device\n", + node, pcie_port); + return 0xffffffff; +} + +/** + * Initialize a host mode PCIe link. This function assumes the PEM has already + * been taken out of reset and configure. It brings up the link and checks that + * the negotiated speed and width is correct for the configured PEM and the + * device plugged into it. Note that the return code will signal a retry needed + * for some link failures. The caller is responsible for PEM reset and retry. + * + * @param node Node the PEM is on + * @param pcie_port PCIe port to initialize link on + * + * @return Zero on success + * Negative on failures where retries are not needed + * Positive if a retry is needed to fix a failure + */ +static int __bdk_pcie_rc_initialize_link(bdk_node_t node, int pcie_port) +{ + #define LTSSM_HISTORY_SIZE 64 /* Number of LTSSM transitions to record, must be a power of 2 */ + uint8_t ltssm_history[LTSSM_HISTORY_SIZE]; + int ltssm_history_loc; + bool do_retry_speed = false; + + BDK_TRACE(PCIE, "N%d.PCIe%d: Checking the PEM is out of reset\n", node, pcie_port); + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_PEMX_ON(pcie_port), pemoor, ==, 1, 100000)) + { + printf("N%d.PCIe%d: PEM in reset, skipping.\n", node, pcie_port); + return -1; + } + + /* Determine the maximum link speed and width */ + BDK_CSR_INIT(pciercx_cfg031, node, BDK_PCIERCX_CFG031(pcie_port)); + int max_gen = pciercx_cfg031.s.mls; /* Max speed of PEM from config (1-3) */ + int max_width = pciercx_cfg031.s.mlw; /* Max lane width of PEM (1-8) */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Link supports up to %d lanes, speed gen%d\n", + node, pcie_port, max_width, max_gen); + + /* Record starting LTSSM state for debug */ + memset(ltssm_history, -1, sizeof(ltssm_history)); + ltssm_history[0] = __bdk_pcie_rc_get_ltssm_state(node, pcie_port); + ltssm_history_loc = 0; + + /* Bring up the link */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Enabling the link\n", node, pcie_port); + BDK_CSR_MODIFY(c, node, BDK_PEMX_CTL_STATUS(pcie_port), c.cn83xx.lnk_enb = 1); + + if (bdk_config_get_int(BDK_CONFIG_PCIE_SKIP_LINK_TRAIN, node, pcie_port)) { + BDK_TRACE(PCIE, "N%d.PCIe%d: Skipping link configuration\n", node, pcie_port); + return 0; + } + +retry_speed: + /* Clear RC Correctable Error Status Register */ + BDK_CSR_WRITE(node, BDK_PCIERCX_CFG068(pcie_port), -1); + + /* Wait for the link to come up and link training to be complete */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Waiting for link\n", node, pcie_port); + + uint64_t clock_rate = bdk_clock_get_rate(bdk_numa_local(), BDK_CLOCK_TIME); + uint64_t hold_time = clock_rate / 5; /* 200ms */ + uint64_t bounce_allow_time = clock_rate / 100; /* 10ms */ + uint64_t timeout = bdk_clock_get_count(BDK_CLOCK_TIME) + clock_rate; /* Timeout = 1s */ + uint64_t good_time = 0; /* Records when the link first went good */ + BDK_CSR_DEFINE(pciercx_cfg032, BDK_PCIERCX_CFG032(pcie_port)); + bool link_up; + bool is_loop_done; + do + { + /* Read link state */ + pciercx_cfg032.u = BDK_CSR_READ(node, BDK_PCIERCX_CFG032(pcie_port)); + + /* Record LTSSM state for debug */ + int ltssm_state = __bdk_pcie_rc_get_ltssm_state(node, pcie_port); + if (ltssm_history[ltssm_history_loc] != ltssm_state) + { + ltssm_history_loc = (ltssm_history_loc + 1) & (LTSSM_HISTORY_SIZE - 1); + ltssm_history[ltssm_history_loc] = ltssm_state; + } + + /* Check if the link is up */ + uint64_t current_time = bdk_clock_get_count(BDK_CLOCK_TIME); + link_up = (pciercx_cfg032.s.dlla && !pciercx_cfg032.s.lt); + if (link_up) + { + /* Is this the first link up? */ + if (!good_time) + { + /* Mark the time when the link transitioned to good */ + good_time = current_time; + } + else + { + /* Check for a link error */ + BDK_CSR_INIT(cfg068, node, BDK_PCIERCX_CFG068(pcie_port)); + if (cfg068.s.res) + { + /* Ignore errors before we've been stable for bounce_allow_time */ + if (good_time + bounce_allow_time <= current_time) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link errors after link up\n", node, pcie_port); + return 1; /* Link error, signal a retry */ + } + else + { + /* Clear RC Correctable Error Status Register */ + BDK_CSR_WRITE(node, BDK_PCIERCX_CFG068(pcie_port), -1); + BDK_TRACE(PCIE, "N%d.PCIe%d: Ignored error during settling time\n", node, pcie_port); + } + } + } + } + else if (good_time) + { + if (good_time + bounce_allow_time <= current_time) + { + /* We allow bounces for bounce_allow_time after the link is good. + Once this time passes any bounce requires a retry */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Link bounce detected\n", node, pcie_port); + return 1; /* Link bounce, signal a retry */ + } + else + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Ignored bounce during settling time\n", node, pcie_port); + } + } + + /* Determine if we've hit the timeout */ + is_loop_done = (current_time >= timeout); + /* Determine if we've had a good link for the required hold time */ + is_loop_done |= link_up && (good_time + hold_time <= current_time); + } while (!is_loop_done); + + /* Trace the LTSSM state */ + BDK_TRACE(PCIE, "N%d.PCIe%d: LTSSM History\n", node, pcie_port); + for (int i = 0; i < LTSSM_HISTORY_SIZE; i++) + { + ltssm_history_loc = (ltssm_history_loc + 1) & (LTSSM_HISTORY_SIZE - 1); + if (ltssm_history[ltssm_history_loc] != 0xff) + BDK_TRACE(PCIE, "N%d.PCIe%d: %s\n", + node, pcie_port, ltssm_string(ltssm_history[ltssm_history_loc])); + } + + if (!link_up) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link down, Data link layer %s(DLLA=%d), Link training %s(LT=%d), LTSSM %s\n", + node, pcie_port, + pciercx_cfg032.s.dlla ? "active" : "down", pciercx_cfg032.s.dlla, + pciercx_cfg032.s.lt ? "active" : "complete", pciercx_cfg032.s.lt, + ltssm_string(__bdk_pcie_rc_get_ltssm_state(node, pcie_port))); + return 1; /* Link down, signal a retry */ + } + + /* Report the negotiated link speed and width */ + int neg_gen = pciercx_cfg032.s.ls; /* Current speed of PEM (1-3) */ + int neg_width = pciercx_cfg032.s.nlw; /* Current lane width of PEM (1-8) */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Link negotiated %d lanes, speed gen%d\n", + node, pcie_port, neg_width, neg_gen); + + /* Determine PCIe bus number the directly attached device uses */ + BDK_CSR_INIT(pciercx_cfg006, node, BDK_PCIERCX_CFG006(pcie_port)); + int bus = pciercx_cfg006.s.sbnum; + + int dev_gen = 1; /* Device max speed (1-3) */ + int dev_width = 1; /* Device max lane width (1-16) */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Reading device max speed and width\n", + node, pcie_port); + + /* Read PCI capability pointer */ + uint32_t cap = cfg_read32_retry(node, pcie_port, bus, 0, 0, 0x34); + + /* Check if we were able to read capabilities pointer */ + if (cap == 0xffffffff) + return 1; /* Signal retry needed */ + + /* Read device max speed and width */ + int cap_next = cap & 0xff; + while (cap_next) + { + cap = cfg_read32_retry(node, pcie_port, bus, 0, 0, cap_next); + if (cap == 0xffffffff) + return 1; /* Signal retry needed */ + + /* Is this a PCIe capability (0x10)? */ + if ((cap & 0xff) == 0x10) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Found PCIe capability at offset 0x%x\n", + node, pcie_port, cap_next); + /* Offset 0xc contains the max link info */ + cap = cfg_read32_retry(node, pcie_port, bus, 0, 0, cap_next + 0xc); + if (cap == 0xffffffff) + return 1; /* Signal retry needed */ + dev_gen = cap & 0xf; /* Max speed of PEM from config (1-3) */ + dev_width = (cap >> 4) & 0x3f; /* Max lane width of PEM (1-16) */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Device supports %d lanes, speed gen%d\n", + node, pcie_port, dev_width, dev_gen); + break; + } + /* Move to next capability */ + cap_next = (cap >> 8) & 0xff; + } + + /* Desired link speed and width is either limited by the device or our PEM + configuration. Choose the most restrictive limit */ + int desired_gen = (dev_gen < max_gen) ? dev_gen : max_gen; + int desired_width = (dev_width < max_width) ? dev_width : max_width; + + /* We need a change if we don't match the desired speed or width. Note that + we allow better than expected in case the device lied about its + capabilities */ + bool need_speed_change = (neg_gen < desired_gen); + bool need_lane_change = (neg_width < desired_width); + + if (need_lane_change) + { + /* We didn't get the maximum number of lanes */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Link width (%d) less that supported (%d)\n", + node, pcie_port, neg_width, desired_width); + return 2; /* Link wrong width, signal a retry */ + } + else if (need_speed_change) + { + if (do_retry_speed) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link speed (gen%d) less that supported (gen%d)\n", + node, pcie_port, neg_gen, desired_gen); + return 1; /* Link at width, but speed low. Request a retry */ + } + else + { + /* We didn't get the maximum speed. Request a speed change */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Link speed (gen%d) less that supported (gen%d), requesting a speed change\n", + node, pcie_port, neg_gen, desired_gen); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG515(pcie_port), + c.s.dsc = 1); + bdk_wait_usec(100000); + do_retry_speed = true; + goto retry_speed; + } + } + else + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link at best speed and width\n", node, pcie_port); + /* For gen3 links check if we are getting errors over the link */ + if (neg_gen == 3) + { + /* Read RC Correctable Error Status Register */ + BDK_CSR_INIT(cfg068, node, BDK_PCIERCX_CFG068(pcie_port)); + if (cfg068.s.res) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link reporting error status\n", node, pcie_port); + return 1; /* Getting receiver errors, request a retry */ + } + } + return 0; /* Link at correct speed and width */ + } +} + +/** + * Setup the SLI memory mapped regions to allow access to PCIe by the cores + * using addresses returned by bdk_pcie_get_base_address(). + * + * @param node Node to configure + * @param pcie_port PCIe port to configure + */ +static void __bdk_pcie_sli_initialize(bdk_node_t node, int pcie_port) +{ + int sli; + int sli_group; + __bdk_pcie_get_sli(node, pcie_port, &sli, &sli_group); + + /* Setup store merge timer */ + BDK_CSR_MODIFY(c, node, BDK_SLIX_S2M_CTL(sli), + c.s.max_word = 0; /* Allow 16 words to combine */ + c.s.timer = 50); /* Wait up to 50 cycles for more data */ + + /* There are 256 regions per SLI. We need four regions per PCIe port to + support config, IO, normal, and prefetchable regions. The 256 regions + are shared across PCIe, so we need three groups of these (one group + for each PCIe). The setup is: + SLI bit[7:6]: PCIe port, relative to SLI (max of 4) + SLI bit[5:4]: Region. See bdk_pcie_mem_t enumeration + SLI bit[3:0]: Address extension from 32 bits to 36 bits + */ + for (bdk_pcie_mem_t mem_region = BDK_PCIE_MEM_CONFIG; mem_region <= BDK_PCIE_MEM_IO; mem_region++) + { + /* Use top two bits for PCIe port, next two bits for memory region */ + int sli_region = sli_group << 6; + /* Use next two bits for mem region type */ + sli_region |= mem_region << 4; + /* Figure out the hardware setting for each region */ + int ctype = 3; + int nmerge = 1; + int ordering = 0; + switch (mem_region) + { + case BDK_PCIE_MEM_CONFIG: /* Config space */ + ctype = 1; /* Config space */ + nmerge = 1; /* No merging allowed */ + ordering = 0; /* NO "relaxed ordering" or "no snoop" */ + break; + case BDK_PCIE_MEM_NORMAL: /* Memory, not prefetchable */ + ctype = 0; /* Memory space */ + nmerge = 1; /* No merging allowed */ + ordering = 0; /* NO "relaxed ordering" or "no snoop" */ + break; + case BDK_PCIE_MEM_PREFETCH: /* Memory, prefetchable */ + ctype = 0; /* Memory space */ + nmerge = 0; /* Merging allowed */ + ordering = 1; /* Yes "relaxed ordering" and "no snoop" */ + break; + case BDK_PCIE_MEM_IO: /* IO */ + ctype = 2; /* I/O space */ + nmerge = 1; /* No merging allowed */ + ordering = 0; /* NO "relaxed ordering" or "no snoop" */ + break; + } + /* Use the lower order bits to work as an address extension, allowing + each PCIe port to map a total of 36 bits (32bit each region, 16 + regions) */ + int epf = sli_group; + if (CAVIUM_IS_MODEL(CAVIUM_CN83XX) || CAVIUM_IS_MODEL(CAVIUM_CN81XX)) { + BDK_CSR_INIT(lmac_const0,node,BDK_SLIX_LMAC_CONST0X(sli,pcie_port)); + epf = lmac_const0.s.epf; + } + for (int r = sli_region; r < sli_region + 16; r++) + { + uint64_t address = 0; + /* Address only applies to memory space */ + if (mem_region == BDK_PCIE_MEM_NORMAL) + { + /* Normal starts at bus address 0 */ + address = r - sli_region; + } else if (mem_region == BDK_PCIE_MEM_PREFETCH) + { + /* Normal starts at bus address 0x10.0000.0000 */ + address = r - sli_region + 16; + } + BDK_CSR_MODIFY(c, node, BDK_SLIX_S2M_REGX_ACC(sli, r), + c.s.ctype = ctype; + c.s.zero = 0; + c.cn83xx.epf = epf; /* Superimposed onto c.cn81xx.mac. EPF value works for both */ + c.s.nmerge = nmerge; + c.s.wtype = ordering; + c.s.rtype = ordering; + c.s.ba = address); + } + } + + /* Setup MAC control */ + BDK_CSR_MODIFY(c, node, BDK_SLIX_M2S_MACX_CTL(sli, sli_group), + c.s.waitl_com = 1; /* Improves ordering in Ali flash testing */ + c.s.ctlp_ro = 1; + c.s.ptlp_ro = 1; + c.s.wind_d = 1; + c.s.bar0_d = 1; + c.s.wait_com = (bdk_config_get_int(BDK_CONFIG_PCIE_ORDERING) == 1)); +} + + +/** + * Perform a complete PCIe RC reset. This is documented in the HRM as issuing a + * fundamental reset + * + * @param node Node to reset + * @param pcie_port PCIe port to reset + * + * @return Zero on success, negative on failure + */ +static int __bdk_pcie_rc_reset(bdk_node_t node, int pcie_port) +{ + /* Find which QLM/DLM is associated with this PCIe port */ + int qlm = bdk_qlm_get_qlm_num(node, BDK_IF_PCIE, pcie_port, 0); + if (qlm < 0) + return -1; + + /* Check if this PCIe port combines two QLM/DLM */ + BDK_CSR_INIT(pemx_cfg, node, BDK_PEMX_CFG(pcie_port)); + int is_dual = CAVIUM_IS_MODEL(CAVIUM_CN81XX) ? pemx_cfg.cn81xx.lanes4 : pemx_cfg.cn83xx.lanes8; + + BDK_TRACE(PCIE, "N%d.PCIe%d: Performing PCIe fundamental reset\n", node, pcie_port); + + /* Host software may want to issue a fundamental reset to the PCIe bus. + Software should perform the following steps: + 1. Write PEM(0..1)_ON[PEMON] = 0. */ + BDK_CSR_MODIFY(c, node, BDK_PEMX_ON(pcie_port), + c.s.pemon = 0); + /* 2. Write RST_SOFT_PRST(0..3)[SOFT_PRST] = 1. + - This reassertion of [SOFT_PRST] causes the chip to drive PERSTn_L + low (if RST_CTL(0..3)[RST_DRV] = 1). */ + BDK_CSR_MODIFY(c, node, BDK_RST_SOFT_PRSTX(pcie_port), + c.s.soft_prst = 1); + /* 3. Read RST_SOFT_PRST(0..3). This ensures the PCIe bus is now in reset. + - Note that PCIERCn_CFGn registers cannot be accessed when + RST_SOFT_PRST(0..3)[SOFT_PRST] = 1. */ + BDK_CSR_READ(node, BDK_RST_SOFT_PRSTX(pcie_port)); + /* 4. Write GSER(0..8)_PHY_CTL[PHY_RESET] = 1. + - This puts the PHY in reset. */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 1); + if (is_dual) + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm + 1), + c.s.phy_reset = 1); + /* Wait 10 us before proceeding to step 5. */ + bdk_wait_usec(10); + /* 5. Write GSERx_PHY_CTL[PHY_RESET] = 0 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 0); + if (is_dual) + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm + 1), + c.s.phy_reset = 0); + + /* Turn on PEM clocks */ + if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + BDK_CSR_MODIFY(c, node, BDK_PEMX_CLK_EN(pcie_port), + c.cn83xx.pceclk_gate = 0; + c.cn83xx.csclk_gate = 0); + + /* 6. Wait 2 ms or more before taking the PCIe port out of reset. */ + bdk_wait_usec(2000); + + /* To take PCIe port out of reset, perform the following steps: */ + /* 1. Write PEM(0..1)_ON[PEMON] = 1. */ + BDK_CSR_MODIFY(c, node, BDK_PEMX_ON(pcie_port), + c.s.pemon = 1); + /* 2. Write RST_SOFT_PRST(0..3)[SOFT_PRST] = 0. */ + /* 3. After RST_CTL(0..3)[RST_DONE], perform any configuration as the + PCIe MAC has been reset. Set the PEM(0..1)_CTL_STATUS[LNK_ENB] = 1. */ + /* These steps are executed when we bring the link up. See + bdk_pcie_rc_initialize() */ + return 0; +} + +/** + * Before PCIe link can be brought up a number of steps must be performed to + * reset the PEM, take the PEM out of reset, initialize the PEM, initialize + * RC config space, and initialize SLI. These steps must be performed every + * time the PEM is reset, which may be repeated if the PCIe link doesn't come + * up at the desired speed and width. + * + * @param node Node to initialize + * @param pcie_port PCIe port to initialize + * + * @return Zero on success, negative on failure + */ +static int __bdk_pcie_rc_pre_link_init(bdk_node_t node, int pcie_port) +{ + /* Make sure the PEM and GSER do a full reset before starting PCIe */ + if (__bdk_pcie_rc_reset(node, pcie_port)) + { + bdk_error("N%d.PCIe%d: Reset failed.\n", node, pcie_port); + return -1; + } + + /* Bring the PCIe out of reset */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Taking port out of reset\n", node, pcie_port); + BDK_CSR_WRITE(node, BDK_RST_SOFT_PRSTX(pcie_port), 0); + + /* Check and make sure PCIe came out of reset. If it doesn't the board + probably hasn't wired the clocks up and the interface should be + skipped */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Waiting for reset to complete\n", node, pcie_port); + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_RST_CTLX(pcie_port), rst_done, ==, 1, 10000)) + { + printf("N%d.PCIe%d: Stuck in reset, skipping.\n", node, pcie_port); + return -1; + } + + /* Check BIST status */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Checking BIST\n", node, pcie_port); + BDK_CSR_INIT(pemx_bist_status, node, BDK_PEMX_BIST_STATUS(pcie_port)); + if (pemx_bist_status.u) + bdk_warn("N%d.PCIe%d: BIST FAILED (0x%016llx)\n", node, pcie_port, pemx_bist_status.u); + + /* Initialize the config space CSRs */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Setting up internal config space\n", node, pcie_port); + __bdk_pcie_rc_initialize_config_space(node, pcie_port); + + /* Enable gen2 speed selection */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Enabling dynamic speed changes\n", node, pcie_port); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG515(pcie_port), + c.s.dsc = 1); + + /* Setup the SLI windows to allow access to this PCIe from the core */ + BDK_TRACE(PCIE, "N%d.PCIe%d: Initializing SLI\n", node, pcie_port); + __bdk_pcie_sli_initialize(node, pcie_port); + return 0; +} + +/** + * Initialize a PCIe port for use in host(RC) mode. It doesn't + * enumerate the bus. + * + * @param pcie_port PCIe port to initialize + * + * @return Zero on success + */ +int bdk_pcie_rc_initialize(bdk_node_t node, int pcie_port) +{ + const int MAX_RETRIES = 2; /* Total of 3 attempts: First + 2 retries */ + int retry_count = 0; + int result= -1,i; + bdk_pemx_bar1_indexx_t bar1_idx; + + /* Make sure we aren't trying to setup a target mode interface in host + mode. Sadly this bit is RAZ for CN88XX and CN81XX because the hardware + team removed it. So much for backward compatibility */ + BDK_CSR_INIT(pemx_cfg, node, BDK_PEMX_CFG(pcie_port)); + int host_mode = CAVIUM_IS_MODEL(CAVIUM_CN83XX) ? pemx_cfg.cn83xx.hostmd : 1; + if (!host_mode) + { + printf("N%d.PCIe%d: Port in endpoint mode.\n", node, pcie_port); + return -1; + } + + while (retry_count <= MAX_RETRIES) + { + if (retry_count) + BDK_TRACE(PCIE, "N%d.PCIe%d: Starting link retry %d\n", node, pcie_port, retry_count); + /* Perform init that must be done after PEM reset, but before link */ + if (__bdk_pcie_rc_pre_link_init(node, pcie_port)) + return -1; + + if (retry_count == MAX_RETRIES) + { + BDK_CSR_INIT(pciercx_cfg031, node, BDK_PCIERCX_CFG031(pcie_port)); + /* Drop speed to gen2 if link bouncing */ + /* Result =-1 PEM in reset */ + /* Result = 0: link speed and width ok no retry needed */ + /* Result = 1: Link errors or speed change needed */ + /* Result = 2: lane width error */ + if ((pciercx_cfg031.s.mls == 3) && (result != 2)) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Dropping speed to gen2\n", node, pcie_port); + pciercx_cfg031.s.mls = 2; + BDK_CSR_WRITE(node, BDK_PCIERCX_CFG031(pcie_port), pciercx_cfg031.u); + /* Set the target link speed */ + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG040(pcie_port), + c.s.tls = 2); + } + } + /* Bring the link up */ + result = __bdk_pcie_rc_initialize_link(node, pcie_port); + if (result == 0) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link does not need a retry\n", node, pcie_port); + break; + } + else if (result > 0) + { + if (retry_count >= MAX_RETRIES) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Link requested a retry, but hit the max retries\n", node, pcie_port); + /* If the link is down, report failure */ + BDK_CSR_INIT(pciercx_cfg032, node, BDK_PCIERCX_CFG032(pcie_port)); + bool link_up = (pciercx_cfg032.s.dlla && !pciercx_cfg032.s.lt); + if (!link_up) + result = -1; + } + else + BDK_TRACE(PCIE, "N%d.PCIe%d: Link requested a retry\n", node, pcie_port); + } + if (result < 0) + { + int ltssm_state = __bdk_pcie_rc_get_ltssm_state(node, pcie_port); + printf("N%d.PCIe%d: Link timeout, probably the slot is empty (LTSSM %s)\n", + node, pcie_port, ltssm_string(ltssm_state)); + return -1; + } + retry_count++; + } + + /* Errata PCIE-28816: Link retrain initiated at GEN1 can cause PCIE + link to hang. For Gen1 links we must disable equalization */ + BDK_CSR_INIT(pciercx_cfg032, node, BDK_PCIERCX_CFG032(pcie_port)); + if (pciercx_cfg032.s.ls == 1) + { + BDK_TRACE(PCIE, "N%d.PCIe%d: Disabling equalization for Gen1 link\n", node, pcie_port); + BDK_CSR_MODIFY(c, node, BDK_PCIERCX_CFG548(pcie_port), + c.s.ed = 1); + } + + BDK_TRACE(PCIE, "N%d.PCIe%d: Setting up internal BARs\n", node, pcie_port); + /* Disable BAR0 */ + BDK_CSR_WRITE(node, BDK_PEMX_P2N_BAR0_START(pcie_port), -1); + /* BAR1 Starting at address 0 */ + BDK_CSR_WRITE(node, BDK_PEMX_P2N_BAR1_START(pcie_port), 0); + /* Set BAR2 to cover all memory starting at address 0 */ + BDK_CSR_WRITE(node, BDK_PEMX_P2N_BAR2_START(pcie_port), 0); + /* Setup BAR attributes */ + BDK_CSR_MODIFY(c, node, BDK_PEMX_BAR_CTL(pcie_port), + c.cn83xx.bar1_siz = 1; /* 64MB BAR1 */ + c.s.bar2_enb = 1; /* BAR2 is enabled */ + c.s.bar2_cax = 0); /* Cache in L2 */ + + /* Allow devices that truncate the bus address to 32-bits to reach the GITS_TRANSLATER */ + bar1_idx.u = 0; + bar1_idx.s.addr_idx = bdk_numa_get_address(node, BDK_GITS_TRANSLATER) >> 22; + bar1_idx.s.addr_v = 1; + + BDK_CSR_WRITE(node, BDK_PEMX_BAR1_INDEXX(pcie_port, 0), bar1_idx.u); + + /* The rest of the windows map linearly to match the BAR2 translation. */ + for (i = 1; i < 16; i++) + { + bar1_idx.s.addr_idx = i; + BDK_CSR_WRITE(node, BDK_PEMX_BAR1_INDEXX(pcie_port, i), bar1_idx.u); + } + + /* Display the link status */ + printf("N%d.PCIe%d: Link active, %d lanes, speed gen%d\n", + node, pcie_port, pciercx_cfg032.s.nlw, pciercx_cfg032.s.ls); + + return 0; + +} + +/** + * Return PCIe state + * + * @param pcie_port PCIe port to query + * + * @return True if port is up and running + */ +int bdk_pcie_is_running(bdk_node_t node, int pcie_port) +{ + BDK_CSR_INIT(pemx_on, node, BDK_PEMX_ON(pcie_port)); + BDK_CSR_INIT(rst_soft_prstx, node, BDK_RST_SOFT_PRSTX(pcie_port)); + BDK_CSR_INIT(pciercx_cfg032, node, BDK_PCIERCX_CFG032(pcie_port)); + + if (!pemx_on.s.pemon || rst_soft_prstx.s.soft_prst) + return 0; + + return bdk_config_get_int(BDK_CONFIG_PCIE_SKIP_LINK_TRAIN, node, pcie_port) || + (pciercx_cfg032.s.dlla && !pciercx_cfg032.s.lt); +} + +/** + * Shutdown a PCIe port and put it in reset + * + * @param pcie_port PCIe port to shutdown + * + * @return Zero on success + */ +int bdk_pcie_rc_shutdown(bdk_node_t node, int pcie_port) +{ + /* Check that the controller is out of reset */ + BDK_CSR_INIT(rst_ctlx, node, BDK_RST_CTLX(pcie_port)); + if (!rst_ctlx.s.rst_done) + goto skip_idle_wait; + + /* Check if link is up */ + BDK_CSR_INIT(pciercx_cfg032, node, BDK_PCIERCX_CFG032(pcie_port)); + if ((pciercx_cfg032.s.dlla == 0) || (pciercx_cfg032.s.lt == 1)) + goto skip_idle_wait; +#if 0 // FIXME + /* Wait for all pending operations to complete */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_PEMX_CPL_LUT_VALID(pcie_port), tag, ==, 0, 2000)) + printf("N%d.PCIe%d: Shutdown timeout\n", node, pcie_port); +#endif +skip_idle_wait: + /* Bring down the link */ + BDK_CSR_MODIFY(c, node, BDK_PEMX_CTL_STATUS(pcie_port), c.cn83xx.lnk_enb = 0); + /* Force reset */ + __bdk_pcie_rc_reset(node, pcie_port); + return 0; +} + +/** + * @INTERNAL + * Build a PCIe config space request address for a device + * + * @param pcie_port PCIe port to access + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * + * @return 64bit IO address + */ +uint64_t pcie_build_config_addr(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg) +{ + int num_pems = bdk_pcie_get_num_ports(node); + if (pcie_port < num_pems) + { + /* Errata (SLI-22555) ECAM to off-chip PCI misroutes address. Use + the SLI regions instead of ECAMs for config space access */ + uint64_t address = bdk_pcie_get_base_address(node, pcie_port, BDK_PCIE_MEM_CONFIG); + /* Display the link status */ + address += (uint64_t)bus << 24; /* Bus is bits 31:24 */ + address += dev << 19; /* device+func is bits 23:16 */ + address += fn << 16; + address += reg; /* Offset is bits 11:0 */ + return address; + } + else if (pcie_port >= 100) + { + bdk_device_t device; + memset(&device, 0, sizeof(device)); + device.node = node; + device.ecam = pcie_port - 100; + device.bus = bus; + device.dev = dev; + device.func = fn; + return __bdk_ecam_build_address(&device, reg); + } + return 0; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-pcie.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-pcie.c new file mode 100644 index 0000000000..769550d6b2 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-pcie.c @@ -0,0 +1,221 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-hal/bdk-pcie.h" +#include "libbdk-hal/bdk-utils.h" +#include "libbdk-hal/if/bdk-if.h" + +/* This code is an optional part of the BDK. It is only linked in + if BDK_REQUIRE() needs it */ +BDK_REQUIRE_DEFINE(PCIE); + +/** + * Read 8bits from a Device's config space + * + * @param pcie_port PCIe port the device is on + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * + * @return Result of the read + */ +uint8_t bdk_pcie_config_read8(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg) +{ + uint64_t address = pcie_build_config_addr(node, pcie_port, bus, dev, fn, reg); + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Config Read8(bus=%d, dev=%d, fn=%d, reg=0x%x, internal=0x%llx)\n", + node, pcie_port, bus, dev, fn, reg, address); + uint8_t result; + if (address) + result = bdk_read64_uint8(address); + else + result = 0xff; + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Result=0x%02x\n", node, pcie_port, result); + return result; +} + + +/** + * Read 16bits from a Device's config space + * + * @param pcie_port PCIe port the device is on + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * + * @return Result of the read + */ +uint16_t bdk_pcie_config_read16(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg) +{ + uint64_t address = pcie_build_config_addr(node, pcie_port, bus, dev, fn, reg); + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Config Read16(bus=%d, dev=%d, fn=%d, reg=0x%x, internal=0x%llx)\n", + node, pcie_port, bus, dev, fn, reg, address); + uint16_t result; + if (address) + result = bdk_le16_to_cpu(bdk_read64_uint16(address)); + else + result = 0xffff; + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Result=0x%04x\n", node, pcie_port, result); + return result; +} + + +/** + * Read 32bits from a Device's config space + * + * @param pcie_port PCIe port the device is on + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * + * @return Result of the read + */ +uint32_t bdk_pcie_config_read32(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg) +{ + uint64_t address = pcie_build_config_addr(node, pcie_port, bus, dev, fn, reg); + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Config Read32(bus=%d, dev=%d, fn=%d, reg=0x%x, internal=0x%llx)\n", + node, pcie_port, bus, dev, fn, reg, address); + + uint32_t result; + if (address) + result = bdk_le32_to_cpu(bdk_read64_uint32(address)); + else + result = 0xffffffff; + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Result=0x%08x\n", node, pcie_port, result); + + /* Errata ECAM-22630: CN88XX pass 1.x, except pass 1.0, will return zero + for non-existent devices instead of ones. We look for this special case + for 32bit reads for reg=0 so we can scan device properly */ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X) && (reg == 0) && (result == 0)) + result = 0xffffffff; + + return result; +} + + +/** + * Write 8bits to a Device's config space + * + * @param pcie_port PCIe port the device is on + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * @param val Value to write + */ +void bdk_pcie_config_write8(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg, uint8_t val) +{ + uint64_t address = pcie_build_config_addr(node, pcie_port, bus, dev, fn, reg); + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Config Write8(bus=%d, dev=%d, fn=%d, reg=0x%x, val=0x%02x, internal=0x%llx)\n", + node, pcie_port, bus, dev, fn, reg, val, address); + if (address) + bdk_write64_uint8(address, val); +} + + +/** + * Write 16bits to a Device's config space + * + * @param pcie_port PCIe port the device is on + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * @param val Value to write + */ +void bdk_pcie_config_write16(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg, uint16_t val) +{ + uint64_t address = pcie_build_config_addr(node, pcie_port, bus, dev, fn, reg); + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Config Write16(bus=%d, dev=%d, fn=%d, reg=0x%x, val=0x%04x, internal=0x%llx)\n", + node, pcie_port, bus, dev, fn, reg, val, address); + if (address) + bdk_write64_uint16(address, bdk_cpu_to_le16(val)); +} + + +/** + * Write 32bits to a Device's config space + * + * @param pcie_port PCIe port the device is on + * @param bus Sub bus + * @param dev Device ID + * @param fn Device sub function + * @param reg Register to access + * @param val Value to write + */ +void bdk_pcie_config_write32(bdk_node_t node, int pcie_port, int bus, int dev, int fn, int reg, uint32_t val) +{ + uint64_t address = pcie_build_config_addr(node, pcie_port, bus, dev, fn, reg); + BDK_TRACE(PCIE_CONFIG, "N%d.PCIe%d: Config Write32(bus=%d, dev=%d, fn=%d, reg=0x%x, val=0x%08x, internal=0x%llx)\n", + node, pcie_port, bus, dev, fn, reg, val, address); + if (address) + bdk_write64_uint32(address, bdk_cpu_to_le32(val)); +} + +/** + * Read 64bits from PCIe using a memory transaction + * + * @param node Node to read from + * @param pcie_port PCIe port to read + * @param address PCIe address to read + * + * @return Result of the read + */ +uint64_t bdk_pcie_mem_read64(bdk_node_t node, int pcie_port, uint64_t address) +{ + uint64_t base_address = bdk_pcie_get_base_address(node, pcie_port, BDK_PCIE_MEM_NORMAL); + return bdk_read64_uint64(base_address + address); +} + +/** + * Write 64bits to PCIe memory + * + * @param node Node to write to + * @param pcie_port PCIe port to use + * @param address Address to write + * @param data Data to write + */ +void bdk_pcie_mem_write64(bdk_node_t node, int pcie_port, uint64_t address, uint64_t data) +{ + uint64_t base_address = bdk_pcie_get_base_address(node, pcie_port, BDK_PCIE_MEM_NORMAL); + bdk_write64_uint64(base_address + address, data); +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-qlm.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-qlm.c new file mode 100644 index 0000000000..f7d631fb5b --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-qlm.c @@ -0,0 +1,423 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <string.h> +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-gsern.h" +#include "libbdk-hal/if/bdk-if.h" +#include "libbdk-hal/bdk-qlm.h" +#include "libbdk-hal/qlm/bdk-qlm-common.h" + +/* This code is an optional part of the BDK. It is only linked in + if BDK_REQUIRE() needs it */ +BDK_REQUIRE_DEFINE(QLM); + +/** + * Convert a mode into a configuration variable string value + * + * @param mode Mode to convert + * + * @return configuration value string + */ +const char *bdk_qlm_mode_to_cfg_str(bdk_qlm_modes_t mode) +{ +#define MODE_CASE(m) case m: return #m+13 + switch (mode) + { + MODE_CASE(BDK_QLM_MODE_DISABLED); + MODE_CASE(BDK_QLM_MODE_PCIE_1X1); + MODE_CASE(BDK_QLM_MODE_PCIE_2X1); + MODE_CASE(BDK_QLM_MODE_PCIE_1X2); + MODE_CASE(BDK_QLM_MODE_PCIE_1X4); + MODE_CASE(BDK_QLM_MODE_PCIE_1X8); + MODE_CASE(BDK_QLM_MODE_PCIE_1X16); + + MODE_CASE(BDK_QLM_MODE_SATA_4X1); + MODE_CASE(BDK_QLM_MODE_SATA_2X1); + + MODE_CASE(BDK_QLM_MODE_ILK); + MODE_CASE(BDK_QLM_MODE_SGMII_4X1); + MODE_CASE(BDK_QLM_MODE_SGMII_2X1); + MODE_CASE(BDK_QLM_MODE_SGMII_1X1); + MODE_CASE(BDK_QLM_MODE_XAUI_1X4); + MODE_CASE(BDK_QLM_MODE_RXAUI_2X2); + MODE_CASE(BDK_QLM_MODE_RXAUI_1X2); + MODE_CASE(BDK_QLM_MODE_OCI); + MODE_CASE(BDK_QLM_MODE_XFI_4X1); + MODE_CASE(BDK_QLM_MODE_XFI_2X1); + MODE_CASE(BDK_QLM_MODE_XFI_1X1); + MODE_CASE(BDK_QLM_MODE_XLAUI_1X4); + MODE_CASE(BDK_QLM_MODE_10G_KR_4X1); + MODE_CASE(BDK_QLM_MODE_10G_KR_2X1); + MODE_CASE(BDK_QLM_MODE_10G_KR_1X1); + MODE_CASE(BDK_QLM_MODE_40G_KR4_1X4); + MODE_CASE(BDK_QLM_MODE_QSGMII_4X1); + MODE_CASE(BDK_QLM_MODE_25G_4X1); + MODE_CASE(BDK_QLM_MODE_25G_2X1); + MODE_CASE(BDK_QLM_MODE_50G_2X2); + MODE_CASE(BDK_QLM_MODE_50G_1X2); + MODE_CASE(BDK_QLM_MODE_100G_1X4); + MODE_CASE(BDK_QLM_MODE_25G_KR_4X1); + MODE_CASE(BDK_QLM_MODE_25G_KR_2X1); + MODE_CASE(BDK_QLM_MODE_50G_KR_2X2); + MODE_CASE(BDK_QLM_MODE_50G_KR_1X2); + MODE_CASE(BDK_QLM_MODE_100G_KR4_1X4); + MODE_CASE(BDK_QLM_MODE_USXGMII_4X1); + MODE_CASE(BDK_QLM_MODE_USXGMII_2X1); + + case BDK_QLM_MODE_LAST: break; /* fall through error */ + } + return "INVALID_QLM_MODE_VALUE"; +} + +/** + * Convert a configuration variable value string into a mode + * + * @param val Configuration variable value + * + * @return mode + */ +bdk_qlm_modes_t bdk_qlm_cfg_string_to_mode(const char *val) +{ + bdk_qlm_modes_t mode; + + for (mode = 0; mode < BDK_QLM_MODE_LAST; mode++) + { + if (0 == strcmp(val, bdk_qlm_mode_to_cfg_str(mode))) + { + return mode; + } + } + return -1; +} + +/** + * Convert a mode into a human understandable string + * + * @param mode Mode to convert + * + * @return Easy to read string + */ +const char *bdk_qlm_mode_tostring(bdk_qlm_modes_t mode) +{ + const char *result = "Unknown, update bdk_qlm_mode_tostring()"; + switch (mode) + { + case BDK_QLM_MODE_DISABLED: + result = "Disabled"; + break; + case BDK_QLM_MODE_PCIE_1X1: + result = "1 PCIe, 1 lane"; + break; + case BDK_QLM_MODE_PCIE_2X1: + result = "2 PCIe, 1 lane each"; + break; + case BDK_QLM_MODE_PCIE_1X2: + result = "1 PCIe, 2 lanes"; + break; + case BDK_QLM_MODE_PCIE_1X4: + result = "1 PCIe, 4 lanes"; + break; + case BDK_QLM_MODE_PCIE_1X8: + result = "1 PCIe, 8 lanes"; + break; + case BDK_QLM_MODE_PCIE_1X16: + result = "1 PCIe, 16 lanes"; + break; + + case BDK_QLM_MODE_SATA_4X1: + result = "4 SATA, one lane each"; + break; + case BDK_QLM_MODE_SATA_2X1: + result = "2 SATA, one lane each"; + break; + + case BDK_QLM_MODE_ILK: + result = "Interlaken"; + break; + case BDK_QLM_MODE_SGMII_4X1: + result = "4 SGMII, 1 lane each"; + break; + case BDK_QLM_MODE_SGMII_2X1: + result = "2 SGMII, 1 lane each"; + break; + case BDK_QLM_MODE_SGMII_1X1: + result = "1 SGMII, 1 lane"; + break; + case BDK_QLM_MODE_XAUI_1X4: + result = "1 XAUI, 4 lanes"; + break; + case BDK_QLM_MODE_RXAUI_2X2: + result = "2 RXAUI, 2 lanes each"; + break; + case BDK_QLM_MODE_RXAUI_1X2: + result = "1 RXAUI, 2 lanes each"; + break; + case BDK_QLM_MODE_OCI: + result = "Cavium Coherent Processor Interconnect"; + break; + case BDK_QLM_MODE_XFI_4X1: + result = "4 XFI, 1 lane each"; + break; + case BDK_QLM_MODE_XFI_2X1: + result = "2 XFI, 1 lane each"; + break; + case BDK_QLM_MODE_XFI_1X1: + result = "1 XFI, 1 lane"; + break; + case BDK_QLM_MODE_XLAUI_1X4: + result = "1 XLAUI, 4 lanes"; + break; + case BDK_QLM_MODE_10G_KR_4X1: + result = "4 10GBASE-KR, 1 lane each"; + break; + case BDK_QLM_MODE_10G_KR_2X1: + result = "2 10GBASE-KR, 1 lane each"; + break; + case BDK_QLM_MODE_10G_KR_1X1: + result = "1 10GBASE-KR, 1 lane"; + break; + case BDK_QLM_MODE_40G_KR4_1X4: + result = "1 40GBASE-KR4, 4 lanes"; + break; + case BDK_QLM_MODE_QSGMII_4X1: + result = "4 QSGMII, 1 lane"; + break; + case BDK_QLM_MODE_25G_4X1: + result = "4 25G, 1 lane each"; + break; + case BDK_QLM_MODE_25G_2X1: + result = "2 25G, 1 lane each"; + break; + case BDK_QLM_MODE_50G_2X2: + result = "2 50G, 2 lanes each"; + break; + case BDK_QLM_MODE_50G_1X2: + result = "1 50G, 2 lanes"; + break; + case BDK_QLM_MODE_100G_1X4: + result = "1 100G, 4 lanes"; + break; + case BDK_QLM_MODE_25G_KR_4X1: + result = "4 25G, 1 lane each"; + break; + case BDK_QLM_MODE_25G_KR_2X1: + result = "2 25G, 1 lane each"; + break; + case BDK_QLM_MODE_50G_KR_2X2: + result = "2 50G, 2 lanes each"; + break; + case BDK_QLM_MODE_50G_KR_1X2: + result = "1 50G, 2 lanes"; + break; + case BDK_QLM_MODE_100G_KR4_1X4: + result = "1 100G, 4 lanes"; + break; + case BDK_QLM_MODE_USXGMII_4X1: + result = "4 USXGMII, 1 lane each"; + break; + case BDK_QLM_MODE_USXGMII_2X1: + result = "2 USXGMII, 1 lane each"; + break; + + case BDK_QLM_MODE_LAST: + break; /* fallthrough error */ + } + return result; +} + +int bdk_qlm_measure_clock(bdk_node_t node, int qlm) +{ + int ref_clock = __bdk_qlm_measure_refclock(node, qlm); + BDK_TRACE(QLM, "N%d.QLM%d: Ref clock %d Hz\n", node, qlm, ref_clock); + + return ref_clock; +} + +/** + * Set the QLM's clock source. + * + * @param node Node to use in a Numa setup + * @param qlm QLM to configure + * @param clk Clock source for QLM + * + * @return Zero on success, negative on failure + */ +int bdk_qlm_set_clock(bdk_node_t node, int qlm, bdk_qlm_clock_t clk) +{ + if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) + { + int sel; + int com1; + switch (clk) + { + case BDK_QLM_CLK_COMMON_0: + sel = 1; + com1 = 0; + break; + case BDK_QLM_CLK_COMMON_1: + sel = 1; + com1 = 1; + break; + case BDK_QLM_CLK_EXTERNAL: + sel = 0; + com1 = 0; + break; + default: + bdk_warn("Unrecognized clock mode %d for QLM%d on node %d.\n", + clk, qlm, node); + return -1; + } + + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_SEL(qlm), + c.s.com_clk_sel = sel; + c.s.use_com1 = com1); + } + else + { + int cclksel; + switch (clk) + { + case BDK_QLM_CLK_COMMON_0: + cclksel = 0; + break; + case BDK_QLM_CLK_COMMON_1: + cclksel = 1; + break; + case BDK_QLM_CLK_COMMON_2: + cclksel = 2; + break; + case BDK_QLM_CLK_EXTERNAL: + cclksel = 3; + break; + default: + bdk_warn("Unrecognized clock mode %d for QLM%d on node %d.\n", + clk, qlm, node); + return -1; + } + BDK_CSR_MODIFY(c, node, BDK_GSERNX_COMMON_REFCLK_BCFG(qlm), + c.s.pwdn = (clk == BDK_QLM_CLK_EXTERNAL) ? 0 : 1; + c.s.cclksel = cclksel); + } + return 0; +} + +/** + * Display an eye diagram for the given QLM lane. The eye data can be in "eye", or + * captured during the call if "eye" is NULL. + * + * @param node Node to use in numa setup + * @param qlm QLM to use + * @param qlm_lane Which lane + * @param format Display format. 0 = raw, 1 = Color ASCII + * @param eye Eye data to display, or NULL if the data should be captured. + * + * @return Zero on success, negative on failure + */ +int bdk_qlm_eye_display(bdk_node_t node, int qlm, int qlm_lane, int format, const bdk_qlm_eye_t *eye) +{ + int result; + int need_free = 0; + if (eye == NULL) + { + bdk_qlm_eye_t *eye_data = malloc(sizeof(bdk_qlm_eye_t)); + if (eye_data == NULL) + { + bdk_error("Failed to allocate space for eye\n"); + return -1; + } + if (bdk_qlm_eye_capture(node, qlm, qlm_lane, eye_data)) + return -1; + eye = eye_data; + } + + /* Calculate the max eye width */ + int eye_area = 0; + int eye_width = 0; + for (int y = 0; y < eye->height; y++) + { + int width = 0; + for (int x = 0; x < eye->width; x++) + { + if (eye->data[y][x] == 0) + { + width++; + eye_area++; + } + } + if (width > eye_width) + eye_width = width; + } + + /* Calculate the max eye height */ + int eye_height = 0; + for (int x = 0; x < eye->width; x++) + { + int height = 0; + for (int y = 0; y < eye->height; y++) + { + if (eye->data[y][x] == 0) + { + height++; + eye_area++; + } + } + if (height > eye_height) + eye_height = height; + } + + printf("\nEye Diagram for Node %d, QLM %d, Lane %d\n", node, qlm, qlm_lane); + + if (format == 0) /* Raw */ + { + for (int y = 0; y < eye->height; y++) + { + for (int x = 0; x < eye->width; x++) + printf("%u\t", eye->data[y][x]); + printf("\n"); + } + result = 0; + } + else + result = -1; + + if (need_free) + free((void*)eye); + return result; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-sata.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-sata.c new file mode 100644 index 0000000000..82e2d3da36 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-sata.c @@ -0,0 +1,1117 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <malloc.h> +#include "libbdk-arch/bdk-csrs-sata.h" + +/* This code is an optional part of the BDK. It is only linked in + if BDK_REQUIRE() needs it */ +BDK_REQUIRE_DEFINE(SATA); + +/* Most all information used to create this code was gotten from this wiki + page: http://wiki.osdev.org/AHCI */ + +/** + * Following code defines different kinds of FIS specified in Serial + * ATA Revision 3.0. + */ +typedef enum +{ + FIS_TYPE_REG_H2D = 0x27, /**< Register FIS - host to device */ + FIS_TYPE_REG_D2H = 0x34, /**< Register FIS - device to host */ + FIS_TYPE_DMA_ACT = 0x39, /**< DMA activate FIS - device to host */ + FIS_TYPE_DMA_SETUP = 0x41, /**< DMA setup FIS - bidirectional */ + FIS_TYPE_DATA = 0x46, /**< Data FIS - bidirectional */ + FIS_TYPE_BIST = 0x58, /**< BIST activate FIS - bidirectional */ + FIS_TYPE_PIO_SETUP = 0x5F, /**< PIO setup FIS - device to host */ + FIS_TYPE_DEV_BITS = 0xA1, /**< Set device bits FIS - device to host */ +} fis_type_t; + +/** + * A host to device register FIS is used by the host to send + * command or control to a device. As illustrated in the + * following data structure, it contains the IDE registers such + * as command, LBA, device, feature, count and control. An ATA + * command is constructed in this structure and issued to the + * device. All reserved fields in an FIS should be cleared to + * zero. + */ +typedef struct +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_REG_H2D */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv0:3; /**< Reserved */ + uint8_t c:1; /**< 1: Command, 0: Control */ + uint8_t command; /**< Command register */ + uint8_t featurel; /**< Feature register, 7:0 */ + // DWORD 1 + uint8_t lba0; /**< LBA low register, 7:0 */ + uint8_t lba1; /**< LBA mid register, 15:8 */ + uint8_t lba2; /**< LBA high register, 23:16 */ + uint8_t device; /**< Device register */ + // DWORD 2 + uint8_t lba3; /**< LBA register, 31:24 */ + uint8_t lba4; /**< LBA register, 39:32 */ + uint8_t lba5; /**< LBA register, 47:40 */ + uint8_t featureh; /**< Feature register, 15:8 */ + // DWORD 3 + uint16_t count; /**< Count register */ + uint8_t icc; /**< Isochronous command completion */ + uint8_t control; /**< Control register */ + // DWORD 4 + uint8_t rsv1[4]; /**< Reserved */ +#else + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_REG_H2D */ + uint8_t c:1; /**< 1: Command, 0: Control */ + uint8_t rsv0:3; /**< Reserved */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t command; /**< Command register */ + uint8_t featurel; /**< Feature register, 7:0 */ + // DWORD 1 + uint8_t lba0; /**< LBA low register, 7:0 */ + uint8_t lba1; /**< LBA mid register, 15:8 */ + uint8_t lba2; /**< LBA high register, 23:16 */ + uint8_t device; /**< Device register */ + // DWORD 2 + uint8_t lba3; /**< LBA register, 31:24 */ + uint8_t lba4; /**< LBA register, 39:32 */ + uint8_t lba5; /**< LBA register, 47:40 */ + uint8_t featureh; /**< Feature register, 15:8 */ + // DWORD 3 + uint16_t count; /**< Count register */ + uint8_t icc; /**< Isochronous command completion */ + uint8_t control; /**< Control register */ + // DWORD 4 + uint8_t rsv1[4]; /**< Reserved */ +#endif +} fis_reg_h2d_t; + +/** + * A device to host register FIS is used by the device to notify + * the host that some ATA register has changed. It contains the + * updated task files such as status, error and other registers. + */ +typedef struct +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_REG_D2H */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv0:2; /**< Reserved */ + uint8_t i:1; /**< Interrupt bit */ + uint8_t rsv1:1; /**< Reserved */ + uint8_t status; /**< Status register */ + uint8_t error; /**< Error register */ + // DWORD 1 + uint8_t lba0; /**< LBA low register, 7:0 */ + uint8_t lba1; /**< LBA mid register, 15:8 */ + uint8_t lba2; /**< LBA high register, 23:16 */ + uint8_t device; /**< Device register */ + // DWORD 2 + uint8_t lba3; /**< LBA register, 31:24 */ + uint8_t lba4; /**< LBA register, 39:32 */ + uint8_t lba5; /**< LBA register, 47:40 */ + uint8_t rsv2; /**< Reserved */ + // DWORD 3 + uint8_t countl; /**< Count register, 7:0 */ + uint8_t counth; /**< Count register, 15:8 */ + uint8_t rsv3[2]; /**< Reserved */ + // DWORD 4 + uint8_t rsv4[4]; /**< Reserved */ +#else + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_REG_D2H */ + uint8_t rsv1:1; /**< Reserved */ + uint8_t i:1; /**< Interrupt bit */ + uint8_t rsv0:2; /**< Reserved */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t status; /**< Status register */ + uint8_t error; /**< Error register */ + // DWORD 1 + uint8_t lba0; /**< LBA low register, 7:0 */ + uint8_t lba1; /**< LBA mid register, 15:8 */ + uint8_t lba2; /**< LBA high register, 23:16 */ + uint8_t device; /**< Device register */ + // DWORD 2 + uint8_t lba3; /**< LBA register, 31:24 */ + uint8_t lba4; /**< LBA register, 39:32 */ + uint8_t lba5; /**< LBA register, 47:40 */ + uint8_t rsv2; /**< Reserved */ + // DWORD 3 + uint8_t countl; /**< Count register, 7:0 */ + uint8_t counth; /**< Count register, 15:8 */ + uint8_t rsv3[2]; /**< Reserved */ + // DWORD 4 + uint8_t rsv4[4]; /**< Reserved */ +#endif +} fis_reg_d2h_t; + +/** + * This FIS is used by the host or device to send data payload. + * The data size can be varied. + */ +typedef struct +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_DATA */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv0:4; /**< Reserved */ + uint8_t rsv1[2]; /**< Reserved */ + // DWORD 1 ~ N + uint32_t data[1]; /**< Payload */ +#else + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_DATA */ + uint8_t rsv0:4; /**< Reserved */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv1[2]; /**< Reserved */ + // DWORD 1 ~ N + uint32_t data[1]; /**< Payload */ +#endif +} fis_data_t; + +/** + * This FIS is used by the device to tell the host that it's + * about to send or ready to receive a PIO data payload. + */ +typedef struct +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_PIO_SETUP */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv0:1; /**< Reserved */ + uint8_t d:1; /**< Data transfer direction, 1 - device to host */ + uint8_t i:1; /**< Interrupt bit */ + uint8_t rsv1:1; + uint8_t status; /**< Status register */ + uint8_t error; /**< Error register */ + // DWORD 1 + uint8_t lba0; /**< LBA low register, 7:0 */ + uint8_t lba1; /**< LBA mid register, 15:8 */ + uint8_t lba2; /**< LBA high register, 23:16 */ + uint8_t device; /**< Device register */ + // DWORD 2 + uint8_t lba3; /**< LBA register, 31:24 */ + uint8_t lba4; /**< LBA register, 39:32 */ + uint8_t lba5; /**< LBA register, 47:40 */ + uint8_t rsv2; /**< Reserved */ + // DWORD 3 + uint8_t countl; /**< Count register, 7:0 */ + uint8_t counth; /**< Count register, 15:8 */ + uint8_t rsv3; /**< Reserved */ + uint8_t e_status; /**< New value of status register */ + // DWORD 4 + uint16_t tc; /**< Transfer count */ + uint8_t rsv4[2]; /**< Reserved */ +#else + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_PIO_SETUP */ + uint8_t rsv1:1; + uint8_t i:1; /**< Interrupt bit */ + uint8_t d:1; /**< Data transfer direction, 1 - device to host */ + uint8_t rsv0:1; /**< Reserved */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t status; /**< Status register */ + uint8_t error; /**< Error register */ + // DWORD 1 + uint8_t lba0; /**< LBA low register, 7:0 */ + uint8_t lba1; /**< LBA mid register, 15:8 */ + uint8_t lba2; /**< LBA high register, 23:16 */ + uint8_t device; /**< Device register */ + // DWORD 2 + uint8_t lba3; /**< LBA register, 31:24 */ + uint8_t lba4; /**< LBA register, 39:32 */ + uint8_t lba5; /**< LBA register, 47:40 */ + uint8_t rsv2; /**< Reserved */ + // DWORD 3 + uint8_t countl; /**< Count register, 7:0 */ + uint8_t counth; /**< Count register, 15:8 */ + uint8_t rsv3; /**< Reserved */ + uint8_t e_status; /**< New value of status register */ + // DWORD 4 + uint16_t tc; /**< Transfer count */ + uint8_t rsv4[2]; /**< Reserved */ +#endif +} fis_pio_setup_t; + +/** + * DMA Setup ? Device to Host + */ +typedef struct __attribute__ ((__packed__)) +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_DMA_SETUP */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv0:1; /**< Reserved */ + uint8_t d:1; /**< Data transfer direction, 1 - device to host */ + uint8_t i:1; /**< Interrupt bit */ + uint8_t a:1; /**< Auto-activate. Specifies if DMA Activate FIS is needed */ + uint8_t rsved[2]; /**< Reserved */ + //DWORD 1&2 + uint64_t DMAbufferID; /**< DMA Buffer Identifier. Used to Identify DMA buffer in host memory. SATA Spec says host specific and not in Spec. Trying AHCI spec might work. */ + //DWORD 3 + uint32_t rsvd; /**< More reserved */ + //DWORD 4 + uint32_t DMAbufOffset; /**< Byte offset into buffer. First 2 bits must be 0 */ + //DWORD 5 + uint32_t TransferCount; /**< Number of bytes to transfer. Bit 0 must be 0 */ + //DWORD 6 + uint32_t resvd; /**< Reserved */ +#else + // DWORD 0 + uint8_t fis_type; /**< FIS_TYPE_DMA_SETUP */ + uint8_t a:1; /**< Auto-activate. Specifies if DMA Activate FIS is needed */ + uint8_t i:1; /**< Interrupt bit */ + uint8_t d:1; /**< Data transfer direction, 1 - device to host */ + uint8_t rsv0:1; /**< Reserved */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsved[2]; /**< Reserved */ + //DWORD 1&2 + uint64_t DMAbufferID; /**< DMA Buffer Identifier. Used to Identify DMA buffer in host memory. SATA Spec says host specific and not in Spec. Trying AHCI spec might work. */ + //DWORD 3 + uint32_t rsvd; /**< More reserved */ + //DWORD 4 + uint32_t DMAbufOffset; /**< Byte offset into buffer. First 2 bits must be 0 */ + //DWORD 5 + uint32_t TransferCount; /**< Number of bytes to transfer. Bit 0 must be 0 */ + //DWORD 6 + uint32_t resvd; /**< Reserved */ +#endif +} fis_dma_setup_t; + +typedef struct __attribute__ ((__packed__)) +{ + uint8_t fis_type; /**< FIS_TYPE_BIST */ + uint8_t pmport:4; /**< Port multiplier */ + uint8_t rsv0:4; /**< Reserved */ + uint8_t v:1; /**< Vendor Specific */ + uint8_t r:1; /**< Reserved */ + uint8_t p:1; /**< Primitive bit */ + uint8_t f:1; /**< Far end analog loopback */ + uint8_t l:1; /**< Far end retimed loopback */ + uint8_t s:1; /**< Scrambling bypass */ + uint8_t a:1; /**< Align bypass */ + uint8_t t:1; /**< Far end transmit only */ + uint8_t rsv1; /**< Reserved */ + uint32_t data1; /**< Only valid when "t" is set */ + uint32_t data2; /**< Only valid when "t" is set */ +} fis_bist_t; + +/** + * Received FIS Structure - AHCI rev 1.3 page 35 + */ +typedef struct +{ + // 0x00 + fis_dma_setup_t dsfis; /**< DMA Setup FIS */ + uint8_t pad0[4]; /* Filler 0x1c - 0x1f */ + // 0x20 + fis_pio_setup_t psfis; /**< PIO Setup FIS */ + uint8_t pad1[12]; /* Filler 0x34 - 0x3f */ + // 0x40 + fis_reg_d2h_t rfis; /**< Device to Host (D2H) Register FIS */ + uint8_t pad2[4]; /* Filler 0x54 - 0x57 */ + // 0x58 + uint8_t sdbfis[8]; /**< Set Device Bit FIS */ + // 0x60 + uint8_t ufis[64]; /**< Unknown FIS (up to 64 bytes) */ + // 0xA0 + uint8_t rsv[0x100-0xA0]; /* Reserved */ +} hba_fis_t; + +/** + * Command header - AHCI rev 1.3 page 36 + */ +typedef struct +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + // DW0 + uint8_t cfl:5; /**< Command FIS length in DWORDS, 2 ~ 16 */ + uint8_t a:1; /**< ATAPI */ + uint8_t w:1; /**< Write, 1: H2D, 0: D2H */ + uint8_t p:1; /**< Prefetchable */ + uint8_t r:1; /**< Reset */ + uint8_t b:1; /**< BIST */ + uint8_t c:1; /**< Clear busy upon R_OK */ + uint8_t rsv0:1; /**< Reserved */ + uint8_t pmp:4; /**< Port multiplier port */ + uint16_t prdtl; /**< Physical region descriptor table length in entries */ + // DW1 + uint32_t prdbc; /**< Physical region descriptor byte count transferred */ + // DW2, 3 + uint64_t ctba; /**< Command table descriptor base address. Must be 128 byte aligned */ + // DW4 - 7 + uint32_t rsv1[4]; /**< Reserved */ +#else + // DW0 + uint8_t p:1; /**< Prefetchable */ + uint8_t w:1; /**< Write, 1: H2D, 0: D2H */ + uint8_t a:1; /**< ATAPI */ + uint8_t cfl:5; /**< Command FIS length in DWORDS, 2 ~ 16 */ + uint8_t pmp:4; /**< Port multiplier port */ + uint8_t c:1; /**< Clear busy upon R_OK */ + uint8_t b:1; /**< BIST */ + uint8_t r:1; /**< Reset */ + uint8_t rsv0:1; /**< Reserved */ + uint16_t prdtl; /**< Physical region descriptor table length in entries */ + // DW1 + uint32_t prdbc; /**< Physical region descriptor byte count transferred */ + // DW2, 3 + uint64_t ctba; /**< Command table descriptor base address */ + // DW4 - 7 + uint32_t rsv1[4]; /**< Reserved */ +#endif +} hba_cmd_header_t; + +/** + * Physical Region Descriptor Table Entry - AHCI rev 1.3 page 39 + */ +typedef struct +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint64_t dba; /**< Data base address. Must be 2 byte aligned */ + uint32_t rsv0; /**< Reserved */ + uint32_t dbc:22; /**< Byte count - 1, 4M max. Must be even number of bytes to transfer */ + uint32_t rsv1:9; /**< Reserved */ + uint32_t i:1; /**< Interrupt on completion */ +#else + uint64_t dba; /**< Data base address */ + uint32_t rsv0; /**< Reserved */ + uint32_t dbc; +#endif +} hba_prdt_entry_t; + +/** + * Command Table - AHCI rev 1.3 page 39 + */ +typedef struct +{ + uint8_t cfis[64]; /**< Command FIS */ + uint8_t acmd[16]; /**< ATAPI command, 12 or 16 bytes */ + uint8_t rsv[48]; /**< Reserved */ + hba_prdt_entry_t prdt_entry[1]; /**< Physical region descriptor table entries, 0 ~ 65535 */ +} hba_cmd_tbl_t; + +/** + * Return the number of SATA controllers on the chip + * + * @param node Node to query + * + * @return Number of controllers, could be zero. + */ +int bdk_sata_get_controllers(bdk_node_t node) +{ + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + return 16; /* 16 controllers on QLMs 2,3, 6-7 */ + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + return 6; /* 6 controllers on DLMs 4-6 */ + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + return 2; /* 2 controllers on DLM 2 */ + else if (CAVIUM_IS_MODEL(CAVIUM_CN93XX)) + return 4; /* 4 controllers on DLM 4-5 */ + else + return 0; +} + +static int __bdk_sata_is_initialized(bdk_node_t node, int controller) +{ + /* Make sure port is clocked before proceeding */ + BDK_CSR_INIT(uctl_ctl, node, BDK_SATAX_UCTL_CTL(controller)); + if (!uctl_ctl.s.a_clk_en || uctl_ctl.s.a_clkdiv_rst) + return 0; + + /* See if the controller is started */ + BDK_CSR_INIT(cmd, node, BDK_SATAX_UAHC_P0_CMD(controller)); + return cmd.s.st; +} + +/** + * Initialize a SATA controller and begin device detection + * + * @param node Node to initialize + * @param controller Which controller to initialize + * + * @return Zero on success, negative on failure + */ +int bdk_sata_initialize(bdk_node_t node, int controller) +{ + _Static_assert(sizeof(fis_reg_h2d_t) == 5 * 4, "Size of fis_reg_h2d_t wrong"); + _Static_assert(sizeof(fis_reg_d2h_t)== 5 * 4, "Size of fis_reg_d2h_t wrong"); + _Static_assert(sizeof(fis_data_t) == 2 * 4, "Size of fis_data_t wrong"); + _Static_assert(sizeof(fis_pio_setup_t) == 5 * 4, "Size of fis_pio_setup_t wrong"); + _Static_assert(sizeof(fis_dma_setup_t) == 7 * 4, "Size of fis_dma_setup_t wrong"); + _Static_assert(sizeof(fis_bist_t) == 3 * 4, "Size of fis_bist_t wrong"); + _Static_assert(sizeof(hba_fis_t) == 256, "Size of hba_fis_t wrong"); + _Static_assert(sizeof(hba_cmd_header_t) == 8 * 4, "Size of hba_cmd_header_t wrong"); + _Static_assert(sizeof(hba_prdt_entry_t) == 4 * 4, "Size of hba_prdt_entry_t wrong"); + _Static_assert(sizeof(hba_cmd_tbl_t)== 128 + sizeof(hba_prdt_entry_t), "Size of hba_cmd_tbl_t wrong"); + + /* Make sure port is clocked before proceeding */ + BDK_CSR_INIT(uctl_ctl, node, BDK_SATAX_UCTL_CTL(controller)); + if (!uctl_ctl.s.a_clk_en || uctl_ctl.s.a_clkdiv_rst) + { + bdk_error("N%d.SATA%d: Not in SATA mode\n", node, controller); + return -1; + } + + /* The following SATA setup is from the AHCI 1.3 spec, section + 10.1.1, Firmware Specific Initialization. */ + /* Early firmware setup was done in __bdk_qlm_set_sata(), we're not + starting the staggered spin-up process */ + + /* 1. Indicate that system software is AHCI aware by setting GHC.AE to '1'. */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_GHC(controller), + c.s.ae = 1); /* AHCI enable */ + + /* 2. Ensure that PxCMD.ST = '0', PxCMD.CR = '0', PxCMD.FRE = '0', + PxCMD.FR = '0', and PxSCTL.DET = '0'. */ + BDK_CSR_INIT(p0_cmd, node, BDK_SATAX_UAHC_P0_CMD(controller)); + if (p0_cmd.s.st) + bdk_error("N%d.SATA%d: PxCMD[ST] is illegally set during init\n", node, controller); + if (p0_cmd.s.cr) + bdk_error("N%d.SATA%d: PxCMD[CR] is illegally set during init\n", node, controller); + if (p0_cmd.s.fre) + bdk_error("N%d.SATA%d: PxCMD[FRE] is illegally set during init\n", node, controller); + if (p0_cmd.s.fr) + bdk_error("N%d.SATA%d: PxCMD[FR] is illegally set during init\n", node, controller); + BDK_CSR_INIT(p0_sctl, node, BDK_SATAX_UAHC_P0_SCTL(controller)); + if (p0_sctl.s.det) + bdk_error("N%d.SATA%d: PxSCTL[DET] is illegally set during init\n", node, controller); + + /* 3. Allocate memory for the command list and the FIS receive area. Set + PxCLB and PxCLBU to the physical address of the allocated command list. + Set PxFB and PxFBU to the physical address of the allocated FIS receive + area. Then set PxCMD.FRE to '1'. */ + /* Allocate area for commands */ + uint64_t clb_pa = BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_CLB(controller)); + if (clb_pa == 0) + { + void *clb = memalign(1024, sizeof(hba_cmd_header_t) * 32); + if (clb == NULL) + { + bdk_error("N%d.SATA%d: Failed to allocate command list\n", node, controller); + return -1; + } + memset(clb, 0, sizeof(hba_cmd_header_t) * 32); + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_CLB(controller), + bdk_ptr_to_phys(clb)); + } + /* Allocate area for FIS DMAs */ + uint64_t fb_pa = BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_FB(controller)); + if (fb_pa == 0) + { + hba_fis_t *fb = memalign(256, sizeof(hba_fis_t)); + if (fb == NULL) + { + bdk_error("N%d.SATA%d: Failed to allocate FIS\n", node, controller); + return -1; + } + memset(fb, 0, sizeof(hba_fis_t)); + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_FB(controller), + bdk_ptr_to_phys(fb)); + } + + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_CMD(controller), + c.s.fre = 1); /* FIS-receive enable */ + + /* 4. Initiate a spin up of the SATA drive attached to the port; i.e. set + PxCMD.SUD to '1'.*/ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_CMD(controller), + c.s.pod = 1; /* Power on the device, only has affect if SATAX_UAHC_P0_CMD[CPD]=1 */ + c.s.sud = 1); /* Spin-up device */ + + /* 5. Wait for a positive indication that a device is attached to the port + (the maximum amount of time to wait for presence indication is specified + in the Serial ATA Revision 2.6 specification). This is done by polling + PxSSTS.DET. If PxSSTS.DET returns a value of 1h or 3h when read, then + system software shall continue to the next step, otherwise if the + polling process times out system software moves to the next implemented + port and returns to step 1. */ + /* Waiting for device detection, up to 500ms. PxCMD[DET] must be 1 or 3 */ + uint64_t timeout = bdk_clock_get_count(BDK_CLOCK_TIME) + bdk_clock_get_rate(bdk_numa_local(), BDK_CLOCK_TIME) / 2; + BDK_CSR_INIT(p0_ssts, node, BDK_SATAX_UAHC_P0_SSTS(controller)); + while ((p0_ssts.s.det != 1) && (p0_ssts.s.det != 3) && + (bdk_clock_get_count(BDK_CLOCK_TIME) <= timeout)) + { + p0_ssts.u = BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_SSTS(controller)); + bdk_thread_yield(); + } + if ((p0_ssts.s.det != 1) && (p0_ssts.s.det != 3)) + { + bdk_error("N%d.SATA%d: PxSCTL[DET]=%d failed to detect a device\n", node, controller, p0_ssts.s.det); + goto fail; + } + + /* 6. Clear the PxSERR register, by writing '1s' to each implemented bit + location. */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_SERR(controller), -1); + + /* 7. Wait for indication that SATA drive is ready. This is determined via + an examination of PxTFD.STS. If PxTFD.STS.BSY, PxTFD.STS.DRQ, and + PxTFD.STS.ERR are all '0', prior to the maximum allowed time as + specified in the ATA/ATAPI-7 specification, the device is ready. */ + /* Wait for the device to be ready. BSY(7), DRQ(3), and ERR(0) must be clear */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_SATAX_UAHC_P0_TFD(controller), sts & 0x89, ==, 0, 5000000)) + { + BDK_CSR_INIT(p0_tfd, node, BDK_SATAX_UAHC_P0_TFD(controller)); + bdk_error("N%d.SATA%d: PxTFD[STS]=0x%x, Drive not ready\n", node, controller, p0_tfd.s.sts); + goto fail; + } + + /* Enable AHCI command queuing */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_CCC_CTL(controller), + c.s.tv = 0; + c.s.en = 1); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_CCC_PORTS(controller), + c.s.prt = 1); + + /* Enable the FIS and clear any pending errors */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_FBS(controller), + c.s.dec = 1; + c.s.en = 1); + + /* Disable all interrupts */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_IE(controller), 0); + + /* Clear all status bits */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_IS(controller), -1); + + /* Start the port controller */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_CMD(controller), + c.s.st = 1); /* Start the controller */ + return 0; + +fail: + bdk_sata_shutdown(node, controller); + return -1; +} + +/** + * Shutdown a SATA controller + * + * @param node Node to access + * @param controller Controller to shutdown + * + * @return Zero on success, negative on failure + */ +int bdk_sata_shutdown(bdk_node_t node, int controller) +{ + /* Remember the current speed limit and power management */ + BDK_CSR_INIT(p0_sctl, node, BDK_SATAX_UAHC_P0_SCTL(controller)); + /* Perform a HBA reset */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_GHC(controller), + c.s.hr = 1); + /* Wait for the reset to complete */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_SATAX_UAHC_GBL_GHC(controller), hr, ==, 0, 100000)) + { + bdk_error("N%d.SATA%d: Timeout waiting for HBA reset to complete\n", node, controller); + return -1; + } + /* Restore the speed limit and power management */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_SCTL(controller), + c.s.ipm = p0_sctl.s.ipm; + c.s.spd = p0_sctl.s.spd); + return 0; +} + +/** + * Return the number of SATA ports connected to this AHCI controller + * + * @param node Node to query + * @param controller SATA controller + * + * @return Number of ports. Zero if the controller doesn't connect to a QLM. + */ +int bdk_sata_get_ports(bdk_node_t node, int controller) +{ + BDK_CSR_INIT(ctl, node, BDK_SATAX_UAHC_GBL_CCC_CTL(controller)); + return (ctl.s.en) ? 1 : 0; +} + +/** + * Convert an IDE string into a C string with a NULL terminator + * + * @param buffer Buffer for new string. Must be one longer than length + * @param original IDE string of identify command + * @param length Length of the string in bytes + */ +static void get_ide_string(char *buffer, void *original, int length) +{ + /* Copy the IDE string 2 bytes at a time, swapping as we go */ + uint16_t *newp = (uint16_t *)buffer; + uint16_t *oldp = (uint16_t *)original; + for (int i = 0; i < length / 2; i++) + newp[i] = bdk_swap16(oldp[i]); + + /* Force a NULL terminator */ + buffer[length] = 0; + + /* Remove all trailing spaces */ + while (buffer[length-1] == ' ') + { + buffer[length - 1] = 0; + length--; + } +} + +static int issue_command(bdk_node_t node, int controller, int command, int is_write, uint64_t lba, void *buffer, int size) +{ + /* Pick a command slot to use */ + int slot = 0; + hba_cmd_header_t *cmd_header = bdk_phys_to_ptr(BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_CLB(controller))); + cmd_header += slot; + + /* Build a command table with the command to execute */ + hba_cmd_tbl_t cmd_table BDK_CACHE_LINE_ALIGNED; + memset(&cmd_table, 0, sizeof(hba_cmd_tbl_t)); + /* Where the data is */ + cmd_table.prdt_entry[0].dba = bdk_cpu_to_le64(bdk_ptr_to_phys(buffer)); + cmd_table.prdt_entry[0].dbc = bdk_cpu_to_le32(size - 1); + + /* The actual command */ + fis_reg_h2d_t *cmd_fis = (fis_reg_h2d_t *)cmd_table.cfis; + cmd_fis->fis_type = FIS_TYPE_REG_H2D; + cmd_fis->command = command; + cmd_fis->device = 1 << 6; /* LBA mode */ + cmd_fis->c = 1; /* Write command register */ + cmd_fis->lba0 = (lba >> 0) & 0xff; + cmd_fis->lba1 = (lba >> 8) & 0xff; + cmd_fis->lba2 = (lba >> 16) & 0xff; + cmd_fis->lba3 = (lba >> 24) & 0xff; + cmd_fis->lba4 = (lba >> 32) & 0xff; + cmd_fis->lba5 = (lba >> 40) & 0xff; + cmd_fis->count = bdk_cpu_to_le16(size / 512); + + /* Setup the command header */ + cmd_header->cfl = sizeof(fis_reg_h2d_t) / 4; + cmd_header->w = is_write; + cmd_header->prdtl = bdk_cpu_to_le16(1); + cmd_header->ctba = bdk_cpu_to_le64(bdk_ptr_to_phys(&cmd_table)); + + BDK_WMB; + + /* Check that the slot is idle */ + BDK_CSR_INIT(ci, node, BDK_SATAX_UAHC_P0_CI(controller)); + if (ci.u & (1<<slot)) + { + bdk_error("N%d.SATA%d: Command slot busy before submit\n", node, controller); + return -1; + } + + /* Clear all status bits */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_IS(controller), -1); + BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_IS(controller)); + + /* Issue command */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_CI(controller), 1 << slot); + + /* Wait for command accept */ + const int TIMEOUT = 5000000; /* 5 seconds */ + if (BDK_CSR_WAIT_FOR_FIELD(node,BDK_SATAX_UAHC_P0_CI(controller), ci & (1<<slot), ==, 0, TIMEOUT)) + { + bdk_error("N%d.SATA%d: Command timeout\n", node, controller); + bdk_sata_shutdown(node, controller); + return -1; + } + + /* Wait for completion */ + if (BDK_CSR_WAIT_FOR_FIELD(node,BDK_SATAX_UAHC_P0_IS(controller), dhrs | c.s.pss | c.s.dss, !=, 0, TIMEOUT)) + { + bdk_error("N%d.SATA%d: Command Response timeout\n", node, controller); + bdk_sata_shutdown(node, controller); + return -1; + } + + /* Read status */ + BDK_CSR_INIT(p_is, node, BDK_SATAX_UAHC_P0_IS(controller)); + if (p_is.s.tfes) + { + bdk_error("N%d.SATA%d: Task-file error\n", node, controller); + bdk_sata_shutdown(node, controller); + return -1; + } + return 0; +} + +/** + * Identify the SATA device connected to a controller + * + * @param node Node to query + * @param controller Controller to query + * @param port Which SATA port on the controller, zero based + * + * @return Size of the disk in bytes + */ +uint64_t bdk_sata_identify(bdk_node_t node, int controller, int port) +{ + if (!__bdk_sata_is_initialized(node, controller)) + { + if (bdk_sata_initialize(node, controller)) + return 0; + } + + const int TIMEOUT = 1000000; /* 1 seconds */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_SATAX_UAHC_P0_SSTS(controller), ipm, !=, 0, TIMEOUT)) + { + bdk_error("N%d.SATA%d: Device not present or communication not established\n", node, controller); + return 0; + } + + /* Read the Serial ATA Status */ + BDK_CSR_INIT(ssts, node, BDK_SATAX_UAHC_P0_SSTS(controller)); + + /* Check the link power state */ + switch (ssts.s.ipm) + { + case 0: /* Device not present or communication not established */ + BDK_TRACE(SATA, "N%d.SATA%d: Device not present or communication not established\n", node, controller); + return 0; + case 1: /* Interface in active state */ + BDK_TRACE(SATA, "N%d.SATA%d: Interface in active state\n", node, controller); + break; + case 2: /* Interface in Partial power management state */ + BDK_TRACE(SATA, "N%d.SATA%d: Interface in Partial power management state\n", node, controller); + return 0; + case 6: /* Interface in Slumber power management state */ + BDK_TRACE(SATA, "N%d.SATA%d: Interface in Slumber power management state\n", node, controller); + return 0; + case 8: /* Interface in DevSleep power management state */ + BDK_TRACE(SATA, "N%d.SATA%d: Interface in DevSleep power management state\n", node, controller); + return 0; + default: + BDK_TRACE(SATA, "N%d.SATA%d: Interface in unknown power state %d\n", node, controller, ssts.s.ipm); + return 0; + } + + /* Check the link speed */ + switch (ssts.s.spd) + { + case 0: /* Device not present or communication not established */ + BDK_TRACE(SATA, "N%d.SATA%d: Device not present or communication not established\n", node, controller); + return 0; + case 1: + case 2: + case 3: + BDK_TRACE(SATA, "N%d.SATA%d: Speed Gen%d\n", node, controller, ssts.s.spd); + break; + default: + BDK_TRACE(SATA, "N%d.SATA%d: Interface in unknown speed %d\n", node, controller, ssts.s.spd); + return 0; + } + + /* Check the device detection */ + switch (ssts.s.det) + { + case 0: /* No device detected and Phy communication not established */ + BDK_TRACE(SATA, "N%d.SATA%d: No device detected and Phy communication not established\n", node, controller); + return 0; + case 1: /* Device presence detected but Phy communication not established */ + BDK_TRACE(SATA, "N%d.SATA%d: Device presence detected but Phy communication not established\n", node, controller); + return 0; + case 3: /* Device presence detected and Phy communication established */ + BDK_TRACE(SATA, "N%d.SATA%d: Device presence detected and Phy communication established\n", node, controller); + break; + case 4: /* Phy in offline mode as a result of the interface being disabled or running in a BIST loopback mode */ + BDK_TRACE(SATA, "N%d.SATA%d: Phy in offline mode\n", node, controller); + return 0; + default: + BDK_TRACE(SATA, "N%d.SATA%d: Device presence in unknown state %d\n", node, controller, ssts.s.det); + return 0; + } + + /* Read the port signature to identify the device type */ + BDK_CSR_INIT(sig, node, BDK_SATAX_UAHC_P0_SIG(controller)); + switch (sig.s.sig) + { + case 0x00000101: /* SATA_SIG_ATA 0x00000101, SATA drive */ + BDK_TRACE(SATA, "N%d.SATA%d: SATA drive\n", node, controller); + break; + case 0xEB140101: /* SATA_SIG_ATAPI 0xEB140101, SATAPI drive */ + BDK_TRACE(SATA, "N%d.SATA%d: ATAPI drive, not supported by the BDK\n", node, controller); + return 0; + case 0xC33C0101: /* SATA_SIG_SEMB 0xC33C0101, Enclosure management bridge */ + BDK_TRACE(SATA, "N%d.SATA%d: Enclosure management bridge, not supported by the BDK\n", node, controller); + return 0; + case 0x96690101: /* SATA_SIG_PM 0x96690101, Port multiplier */ + BDK_TRACE(SATA, "N%d.SATA%d: Port multiplier, not supported by the BDK\n", node, controller); + return 0; + default: /* Just assume it is a drive */ + BDK_TRACE(SATA, "N%d.SATA%d: Unknown signature 0x%08x, assuming a SATA drive\n", node, controller, sig.u); + break; + } + + /* Send identify to the device */ + const int ATA_CMD_IDENTIFY = 0xec; + char buffer[512]; + if (issue_command(node, controller, ATA_CMD_IDENTIFY, 0, 0, buffer, sizeof(buffer))) + return 0; + + /* Extract the data out of the IDENTIFY response */ + uint16_t *ptr = (uint16_t *)buffer; + uint64_t sectors = bdk_le16_to_cpu(ptr[57]); + sectors += (uint32_t)bdk_le16_to_cpu(ptr[58]) << 16; + char serial[20 + 1]; + get_ide_string(serial, ptr + 10, 20); + char firmware[8 + 1]; + get_ide_string(firmware, ptr + 23, 8); + char model[40 + 1]; + get_ide_string(model, ptr + 27, 40); + + printf("N%d.SATA%d: Model=\"%s\", Firmware=\"%s\", Serial=\"%s\", Sectors=%lu, Link=Gen%d\n", + node, controller, model, firmware, serial, sectors, ssts.s.spd); + + /* Return size in bytes */ + return sectors * 512; +} + +/** + * Read data from a SATA device + * + * @param node Node the controller is on + * @param controller Which controller + * @param port Which port on the controller, zero based + * @param lba 48 bit Block address to read + * @param sectors Number of 512 bytes sectors to read + * @param buffer Buffer to receive the data. Must be at least 512 * sectors in size + * + * @return Zero on success, negative on failure + */ +int bdk_sata_read(bdk_node_t node, int controller, int port, uint64_t lba, int sectors, void *buffer) +{ + if (!__bdk_sata_is_initialized(node, controller)) + { + if (bdk_sata_initialize(node, controller)) + return -1; + } + + const int ATA_READ_DMA = 0xc8; + if (issue_command(node, controller, ATA_READ_DMA, 0, lba, buffer, sectors * 512)) + return -1; + return 0; +} + +/** + * Write data to a SATA device + * + * @param node Node the controller is on + * @param controller Which controller + * @param port Which port on the controller, zero based + * @param lba 48 bit Block address to write + * @param sectors Number of 512 bytes sectors to write + * @param buffer Data buffer to write. Must be at least 512 * sectors in size + * + * @return Zero on success, negative on failure + */ +int bdk_sata_write(bdk_node_t node, int controller, int port, uint64_t lba, int sectors, const void *buffer) +{ + if (!__bdk_sata_is_initialized(node, controller)) + { + if (bdk_sata_initialize(node, controller)) + return -1; + } + + const int ATA_WRITE_DMA = 0xca; + if (issue_command(node, controller, ATA_WRITE_DMA, 1, lba, (void*)buffer, sectors * 512)) + return -1; + return 0; +} + +/** + * Enter one of the SATA pattern generation / loop testing modes + * + * @param node Node to access + * @param controller SATA controller to access + * @param port Which port on the controller + * @param mode Test mode to enter + * + * @return Zero on success, negative on failure + */ +int bdk_sata_bist_fis(bdk_node_t node, int controller, int port, bdk_sata_bist_fis_t mode) +{ + if (!__bdk_sata_is_initialized(node, controller)) + { + if (bdk_sata_initialize(node, controller)) + return -1; + } + + /* Select the port we're doing BIST loopback on */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_TESTR(controller), + c.s.psel = port); + + /* Select the pattern */ + int pattern; + switch (mode) + { + case BDK_SATA_BIST_SW_TX_ONLY_SSOP: + case BDK_SATA_BIST_SW_TX_ONLY_HTDP: + case BDK_SATA_BIST_SW_TX_ONLY_LTDP: + case BDK_SATA_BIST_SW_TX_ONLY_LFSCP: + case BDK_SATA_BIST_SW_TX_ONLY_COMP: + case BDK_SATA_BIST_SW_TX_ONLY_LBP: + case BDK_SATA_BIST_SW_TX_ONLY_MFTP: + case BDK_SATA_BIST_SW_TX_ONLY_HFTP: + case BDK_SATA_BIST_SW_TX_ONLY_LFTP: + pattern = mode - BDK_SATA_BIST_SW_TX_ONLY_SSOP; + break; + default: + pattern = 1; /* HTDP */ + break; + } + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_BISTCR(controller), + c.s.pattern = pattern); + + /* + Note from the Synopsys SATA bist training video on pattern generation + without using BIST FIS. + + 1) Far-end Re-timed Loopback Responder Mode (Software Initiated) + + In this mode the host controller receives the pattern and transmits + it back out. The setup of the mode is done by software, so no BIST FIS + frames are needed. After software sets it up, any pattern generator + should be able to send a pattern and get it back. + + Setup: + 1) Write SATAX_UAHC_GBL_BISTCR.ferlib = 1 + 2) Connect pattern generator + 3) Pattern generator must send ALIGNs for PHY sync up + 4) Pattern should be looped back out + + 2) Far-end Transmit Only Responder Mode (Software Initiated) + + In this mode the host controller sends a transmit pattern and ignores + all input. This is useful for checking the TX eye diagram without an + external pattern generator. + + Setup: + 1) Write SATAX_UAHC_GBL_BISTCR.pattern to select the pattern. + 2) Write SATAX_UAHC_GBL_BISTCR.txo = 1. + 3) Host starts sending the requested BIST pattern. + + BIST FIS Modes: + 1) Far-end Analog Loopback (F=1) + Far end loops the received pattern back to transmit without retiming + the symbols. This is optional in the SATA 3.0 spec. + 2) Far-end Retimed Loopback (L=1) + Far end loops the received pattern back to transmit after retiming + the symbols. This is mandatory in the SATA 3.0 spec. + 3) Far-end Transmit Only (T=1, with other bits) + Far end transits a pattern and ignores its input. This is optional + in the SATA 3.0 spec. + */ + if (mode == BDK_SATA_BIST_SW_RETIMED) + { + /* No FIS, just enter local retimed loopback */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_BISTCR(controller), + c.s.ferlib = 1); + BDK_TRACE(SATA, "N%d.SATA%d: Started Retimed loopback\n", node, controller); + return 0; + } + else if ((mode >= BDK_SATA_BIST_SW_TX_ONLY_SSOP) && (mode <= BDK_SATA_BIST_SW_TX_ONLY_LFTP)) + { + /* No FIS, just enter local transit only */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_BISTCR(controller), + c.s.txo = 1); + BDK_TRACE(SATA, "N%d.SATA%d: Started tranmsit only\n", node, controller); + return 0; + } + + /* Issue a BIST FIS command */ + + /* Pick a command slot to use */ + int slot = 0; + hba_cmd_header_t *cmd_header = bdk_phys_to_ptr(BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_CLB(controller))); + cmd_header += slot; + + /* Build a command table with the command to execute */ + hba_cmd_tbl_t cmd_table BDK_CACHE_LINE_ALIGNED; + memset(&cmd_table, 0, sizeof(hba_cmd_tbl_t)); + + /* The actual BIST FIS command */ + fis_bist_t *bist_fis = (fis_bist_t *)cmd_table.cfis; + bist_fis->fis_type = FIS_TYPE_BIST; + switch (mode) + { + case BDK_SATA_BIST_FIS_RETIMED: /* Send FIS to tell device to enter Retimed loopback */ + bist_fis->l = 1; + break; + case BDK_SATA_BIST_FIS_ANALOG: /* Send FIS to tell device to enter Analog loopback */ + bist_fis->f = 1; + break; + case BDK_SATA_BIST_FIS_TX_ONLY: /* Send FIS to tell device to transit only */ + bist_fis->t = 1; + break; + default: + bdk_error("Invalid SATA BIST FIS mode %d\n", mode); + return -1; + } + + /* Setup the command header */ + memset(cmd_header, 0, sizeof(hba_cmd_header_t)); + cmd_header->cfl = sizeof(fis_bist_t) / 4; + cmd_header->b = 1; + cmd_header->ctba = bdk_ptr_to_phys(&cmd_table); + + BDK_WMB; + + /* Check that the slot is idle */ + BDK_CSR_INIT(ci, node, BDK_SATAX_UAHC_P0_CI(controller)); + if (ci.u & (1<<slot)) + { + bdk_error("N%d.SATA%d: Command slot busy before submit\n", node, controller); + return -1; + } + + /* Clear all status bits */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_IS(controller), -1); + BDK_CSR_READ(node, BDK_SATAX_UAHC_P0_IS(controller)); + + /* Issue command */ + BDK_CSR_WRITE(node, BDK_SATAX_UAHC_P0_CI(controller), 1 << slot); + BDK_TRACE(SATA, "N%d.SATA%d: Sent BIST FIS\n", node, controller); + + return 0; +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-twsi.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-twsi.c deleted file mode 100644 index 4fbb78a876..0000000000 --- a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-twsi.c +++ /dev/null @@ -1,318 +0,0 @@ -/***********************license start*********************************** -* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights -* reserved. -* -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are -* met: -* -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* -* * Redistributions in binary form must reproduce the above -* copyright notice, this list of conditions and the following -* disclaimer in the documentation and/or other materials provided -* with the distribution. -* -* * Neither the name of Cavium Inc. nor the names of -* its contributors may be used to endorse or promote products -* derived from this software without specific prior written -* permission. -* -* This Software, including technical data, may be subject to U.S. export -* control laws, including the U.S. Export Administration Act and its -* associated regulations, and may be subject to export or import -* regulations in other countries. -* -* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" -* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR -* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT -* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY -* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT -* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES -* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR -* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, -* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK -* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. -***********************license end**************************************/ -#include <bdk.h> -#include "libbdk-arch/bdk-csrs-mio_tws.h" - -#define RECOVERY_UDELAY 5 -#define RECOVERY_CLK_CNT 9 -#define ARBLOST_UDELAY 5000 /* 5ms */ - -/* This code is an optional part of the BDK. It is only linked in - if BDK_REQUIRE() needs it */ -BDK_REQUIRE_DEFINE(TWSI); - -/** - * Initialize the TWSI blocks. This just sets the clock rate. - * Many times stuff will work without calling this, but some - * TWSI devices will fail. This is normally called automatically - * in bdk-init-main.c. - * - * @return Zero on success, negative on failure - */ -int bdk_twsix_initialize(bdk_node_t node) -{ - const int TWSI_BUS_FREQ = 100000; /* 100 KHz */ - const int TWSI_THP = 24; /* TCLK half period (default 24) */ - const int io_clock_hz = bdk_clock_get_rate(node, BDK_CLOCK_SCLK); - int N_divider; - int M_divider; - - /* Set the TWSI clock to a conservative TWSI_BUS_FREQ. Compute the - clocks M divider based on the SCLK. - TWSI freq = (core freq) / (20 x (M+1) x (thp+1) x 2^N) - M = ((core freq) / (20 x (TWSI freq) x (thp+1) x 2^N)) - 1 */ - for (N_divider = 0; N_divider < 8; N_divider++) - { - M_divider = (io_clock_hz / (20 * TWSI_BUS_FREQ * (TWSI_THP + 1) * (1 << N_divider))) - 1; - if (M_divider < 16) - break; - } - - BDK_CSR_DEFINE(sw_twsi, BDK_MIO_TWSX_SW_TWSI(bus)); - sw_twsi.u = 0; - sw_twsi.s.v = 1; /* Clear valid bit */ - sw_twsi.s.op = 0x6; /* See EOP field */ - sw_twsi.s.r = 0; /* Select CLKCTL when R = 0 */ - sw_twsi.s.eop_ia = 3; /* R=0 selects CLKCTL, R=1 selects STAT */ - sw_twsi.s.data = ((M_divider & 0xf) << 3) | ((N_divider & 0x7) << 0); - - int num_busses = 2; - if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) - num_busses = 6; - - for (int bus = 0; bus < num_busses; bus++) - { - /* Only init non-slave ports */ - BDK_CSR_INIT(state, node, BDK_MIO_TWSX_SW_TWSI(bus)); - if (!state.s.slonly) - BDK_CSR_WRITE(node, BDK_MIO_TWSX_SW_TWSI(bus), sw_twsi.u); - } - return 0; -} - -/** - * Do a twsi bus recovery in the case when the last transaction - * on the bus has been left unfinished. - * - * @param twsi_id which TWSI bus to use - */ -static void bdk_twsix_recover_bus(bdk_node_t node, int twsi_id) -{ - /* read TWSX_INT */ - BDK_CSR_INIT(twsx_int, node, BDK_MIO_TWSX_INT(twsi_id)); - - for (int i = 0; i < RECOVERY_CLK_CNT * 2; i++) - { - if (!twsx_int.s.scl_ovr) - { - /* SCL shouldn't be low here */ - if (!twsx_int.s.scl) - { - bdk_error("N%d.TWSI%d: SCL is stuck low\n", node, twsi_id); - return; - } - - /* Break if SDA is high */ - if (twsx_int.s.sda) - break; - } - - twsx_int.s.scl_ovr = !twsx_int.s.scl_ovr; - BDK_CSR_WRITE(node, BDK_MIO_TWSX_INT(twsi_id), twsx_int.u); - bdk_wait_usec(RECOVERY_UDELAY); - } - - /* - * Generate STOP condition using the register overrides - * in order to move the higher level controller out of - * the bad state. This is a workaround for the TWSI hardware. - */ - twsx_int.s.scl_ovr = 1; - twsx_int.s.sda_ovr = 1; - BDK_CSR_WRITE(node, BDK_MIO_TWSX_INT(twsi_id), twsx_int.u); - bdk_wait_usec(RECOVERY_UDELAY); - twsx_int.s.scl_ovr = 0; - BDK_CSR_WRITE(node, BDK_MIO_TWSX_INT(twsi_id), twsx_int.u); - bdk_wait_usec(RECOVERY_UDELAY); - twsx_int.s.sda_ovr = 0; - BDK_CSR_WRITE(node, BDK_MIO_TWSX_INT(twsi_id), twsx_int.u); -} - -/** - * Do a twsi read from a 7 bit device address using an (optional) - * internal address. Up to 4 bytes can be read at a time. - * - * @param twsi_id which TWSI bus to use - * @param dev_addr Device address (7 bit) - * @param internal_addr - * Internal address. Can be 0, 1 or 2 bytes in width - * @param num_bytes Number of data bytes to read (1-4) - * @param ia_width_bytes - * Internal address size in bytes (0, 1, or 2) - * - * @return Read data, or -1 on failure - */ -int64_t bdk_twsix_read_ia(bdk_node_t node, int twsi_id, uint8_t dev_addr, uint16_t internal_addr, int num_bytes, int ia_width_bytes) -{ - bdk_mio_twsx_sw_twsi_t sw_twsi_val; - bdk_mio_twsx_sw_twsi_ext_t twsi_ext; - int retry_limit = 5; - - if (num_bytes < 1 || num_bytes > 4 || ia_width_bytes < 0 || ia_width_bytes > 2) - return -1; -retry: - twsi_ext.u = 0; - sw_twsi_val.u = 0; - sw_twsi_val.s.v = 1; - sw_twsi_val.s.r = 1; - sw_twsi_val.s.sovr = 1; - sw_twsi_val.s.size = num_bytes - 1; - sw_twsi_val.s.addr = dev_addr; - - if (ia_width_bytes > 0) - { - sw_twsi_val.s.op = 1; - sw_twsi_val.s.ia = (internal_addr >> 3) & 0x1f; - sw_twsi_val.s.eop_ia = internal_addr & 0x7; - if (ia_width_bytes == 2) - { - sw_twsi_val.s.eia = 1; - twsi_ext.s.ia = internal_addr >> 8; - BDK_CSR_WRITE(node, BDK_MIO_TWSX_SW_TWSI_EXT(twsi_id), twsi_ext.u); - } - } - - BDK_CSR_WRITE(node, BDK_MIO_TWSX_SW_TWSI(twsi_id), sw_twsi_val.u); - if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_MIO_TWSX_SW_TWSI(twsi_id), v, ==, 0, 10000)) - { - bdk_warn("N%d.TWSI%d: Timeout waiting for read to complete...start recovering process\n", - node, twsi_id); - /* perform bus recovery */ - bdk_twsix_recover_bus(node, twsi_id); - if (retry_limit-- > 0) - goto retry; - - bdk_error("N%d.TWSI%d: Timeout waiting for operation to complete\n", node, twsi_id); - return -1; - } - sw_twsi_val.u = BDK_CSR_READ(node, BDK_MIO_TWSX_SW_TWSI(twsi_id)); - if (!sw_twsi_val.s.r) - { - /* Check the reason for the failure. We may need to retry to handle multi-master - ** configurations. - ** Lost arbitration : 0x38, 0x68, 0xB0, 0x78 - ** Core busy as slave: 0x80, 0x88, 0xA0, 0xA8, 0xB8, 0xC0, 0xC8 - */ - if (sw_twsi_val.s.data == 0x38 - || sw_twsi_val.s.data == 0x68 - || sw_twsi_val.s.data == 0xB0 - || sw_twsi_val.s.data == 0x78 - || sw_twsi_val.s.data == 0x80 - || sw_twsi_val.s.data == 0x88 - || sw_twsi_val.s.data == 0xA0 - || sw_twsi_val.s.data == 0xA8 - || sw_twsi_val.s.data == 0xB8 - || sw_twsi_val.s.data == 0xC8) - { - /* - * One of the arbitration lost conditions is recognized. - * The TWSI hardware has switched to the slave mode and - * expects the STOP condition on the bus. - * Make a delay before next retry. - */ - bdk_wait_usec(ARBLOST_UDELAY); - if (retry_limit-- > 0) - goto retry; - } - /* For all other errors, return an error code */ - return -1; - } - - return (sw_twsi_val.s.data & (0xFFFFFFFF >> (32 - num_bytes*8))); -} - - -/** - * Write 1-8 bytes to a TWSI device using an internal address. - * - * @param twsi_id which TWSI interface to use - * @param dev_addr TWSI device address (7 bit only) - * @param internal_addr - * TWSI internal address (0, 8, or 16 bits) - * @param num_bytes Number of bytes to write (1-8) - * @param ia_width_bytes - * internal address width, in bytes (0, 1, 2) - * @param data Data to write. Data is written MSB first on the twsi bus, and - * only the lower num_bytes bytes of the argument are valid. (If - * a 2 byte write is done, only the low 2 bytes of the argument is - * used. - * - * @return Zero on success, -1 on error - */ -int bdk_twsix_write_ia(bdk_node_t node, int twsi_id, uint8_t dev_addr, uint16_t internal_addr, int num_bytes, int ia_width_bytes, uint64_t data) -{ - bdk_mio_twsx_sw_twsi_t sw_twsi_val; - bdk_mio_twsx_sw_twsi_ext_t twsi_ext; - int retry_limit = 5; - int to; - - if (num_bytes < 1 || num_bytes > 8 || ia_width_bytes < 0 || ia_width_bytes > 2) - return -1; - -retry: - twsi_ext.u = 0; - sw_twsi_val.u = 0; - sw_twsi_val.s.v = 1; - sw_twsi_val.s.sovr = 1; - sw_twsi_val.s.size = num_bytes - 1; - sw_twsi_val.s.addr = dev_addr; - sw_twsi_val.s.data = 0xFFFFFFFF & data; - - if (ia_width_bytes > 0) - { - sw_twsi_val.s.op = 1; - sw_twsi_val.s.ia = (internal_addr >> 3) & 0x1f; - sw_twsi_val.s.eop_ia = internal_addr & 0x7; - } - if (ia_width_bytes == 2) - { - sw_twsi_val.s.eia = 1; - twsi_ext.s.ia = internal_addr >> 8; - } - if (num_bytes > 4) - twsi_ext.s.data = data >> 32; - - BDK_CSR_WRITE(node, BDK_MIO_TWSX_SW_TWSI_EXT(twsi_id), twsi_ext.u); - BDK_CSR_WRITE(node, BDK_MIO_TWSX_SW_TWSI(twsi_id), sw_twsi_val.u); - if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_MIO_TWSX_SW_TWSI(twsi_id), v, ==, 0, 10000)) - { - bdk_warn("N%d.TWSI%d: Timeout waiting for write to complete...start recovering process\n", - node, twsi_id); - /* perform bus recovery */ - bdk_twsix_recover_bus(node, twsi_id); - if (retry_limit-- > 0) - goto retry; - - // After retry but still not success, report error and return - bdk_error("N%d.TWSI%d: Timeout waiting for operation to complete\n", node, twsi_id); - return -1; - } - - /* Poll until reads succeed, or polling times out */ - to = 100; - while (to-- > 0) - { - if (bdk_twsix_read_ia(node, twsi_id, dev_addr, 0, 1, 0) >= 0) - break; - } - if (to <= 0) - return -1; - - return 0; -} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/bdk-usb.c b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-usb.c new file mode 100644 index 0000000000..eb2a85fa0d --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/bdk-usb.c @@ -0,0 +1,683 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <libbdk-arch/bdk-csrs-gpio.h> +#include <libbdk-arch/bdk-csrs-usbdrd.h> +#include <libbdk-arch/bdk-csrs-usbh.h> +#include <libbdk-hal/bdk-usb.h> +#include <libbdk-hal/bdk-config.h> + +/* This code is an optional part of the BDK. It is only linked in + if BDK_REQUIRE() needs it */ +BDK_REQUIRE_DEFINE(USB); + +/** + * Write to DWC3 indirect debug control register + * + * @param node Node to write to + * @param usb_port USB port to write to + * @param val 32bit value to write + */ +static void write_cr_dbg_cfg(bdk_node_t node, int usb_port, uint64_t val) +{ + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + BDK_CSR_WRITE(node, BDK_USBDRDX_UCTL_PORTX_CR_DBG_CFG(usb_port, 0), val); + else + BDK_CSR_WRITE(node, BDK_USBHX_UCTL_PORTX_CR_DBG_CFG(usb_port, 0), val); +} + +/** + * Poll the DWC3 internal status until the ACK bit matches a desired value. Return + * the final status. + * + * @param node Node to query + * @param usb_port USB port to query + * @param desired_ack + * Desired ACK bit state + * + * @return Final status with ACK at correct state + */ +static bdk_usbdrdx_uctl_portx_cr_dbg_status_t get_cr_dbg_status(bdk_node_t node, int usb_port, int desired_ack) +{ + const int TIMEOUT = 1000000; /* 1 sec */ + bdk_usbdrdx_uctl_portx_cr_dbg_status_t status; + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + { + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_USBDRDX_UCTL_PORTX_CR_DBG_STATUS(usb_port, 0), ack, ==, desired_ack, TIMEOUT)) + { + BDK_TRACE(USB_XHCI, "N%d.USB%d: Timeout waiting for indirect ACK\n", node, usb_port); + status.u = -1; + } + else + status.u = BDK_CSR_READ(node, BDK_USBDRDX_UCTL_PORTX_CR_DBG_STATUS(usb_port, 0)); + } + else + { + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_USBHX_UCTL_PORTX_CR_DBG_STATUS(usb_port, 0), ack, ==, desired_ack, TIMEOUT)) + { + BDK_TRACE(USB_XHCI, "N%d.USB%d: Timeout waiting for indirect ACK\n", node, usb_port); + status.u = -1; + } + else + status.u = BDK_CSR_READ(node, BDK_USBHX_UCTL_PORTX_CR_DBG_STATUS(usb_port, 0)); + } + return status; +} + +/** + * Perform an indirect read of an internal register inside the DWC3 usb block + * + * @param node Node to read + * @param usb_port USB port to read + * @param addr Indirect register address + * + * @return Value of the indirect register + */ +static uint32_t dwc3_uphy_indirect_read(bdk_node_t node, int usb_port, uint32_t addr) +{ + bdk_usbdrdx_uctl_portx_cr_dbg_cfg_t dbg_cfg; + bdk_usbdrdx_uctl_portx_cr_dbg_status_t status; + + /* See the CSR description for USBHX_UCTL_PORTX_CR_DBG_CFG, which describes + the steps implemented by this function */ + + dbg_cfg.u = 0; + dbg_cfg.s.data_in = addr; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + dbg_cfg.s.cap_addr = 1; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + status = get_cr_dbg_status(node, usb_port, 1); + if (status.u == (uint64_t)-1) + return 0xffffffff; + + write_cr_dbg_cfg(node, usb_port, 0); + get_cr_dbg_status(node, usb_port, 0); + + dbg_cfg.u = 0; + dbg_cfg.s.read = 1; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + status = get_cr_dbg_status(node, usb_port, 1); + + write_cr_dbg_cfg(node, usb_port, 0); + get_cr_dbg_status(node, usb_port, 0); + + return status.s.data_out; +} + +/** + * Perform an indirect write of an internal register inside the DWC3 usb block + * + * @param node Node to write + * @param usb_port USB port to write + * @param addr Indirect register address + * @param value Value for write + */ +static void dwc3_uphy_indirect_write(bdk_node_t node, int usb_port, uint32_t addr, uint16_t value) +{ + bdk_usbdrdx_uctl_portx_cr_dbg_cfg_t dbg_cfg; + + /* See the CSR description for USBHX_UCTL_PORTX_CR_DBG_CFG, which describes + the steps implemented by this function */ + + dbg_cfg.u = 0; + dbg_cfg.s.data_in = addr; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + dbg_cfg.s.cap_addr = 1; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + get_cr_dbg_status(node, usb_port, 1); + + write_cr_dbg_cfg(node, usb_port, 0); + get_cr_dbg_status(node, usb_port, 0); + + dbg_cfg.u = 0; + dbg_cfg.s.data_in = value; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + dbg_cfg.s.cap_data = 1; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + get_cr_dbg_status(node, usb_port, 1); + + write_cr_dbg_cfg(node, usb_port, 0); + get_cr_dbg_status(node, usb_port, 0); + + dbg_cfg.u = 0; + dbg_cfg.s.write = 1; + write_cr_dbg_cfg(node, usb_port, dbg_cfg.u); + + get_cr_dbg_status(node, usb_port, 1); + + write_cr_dbg_cfg(node, usb_port, 0); + get_cr_dbg_status(node, usb_port, 0); +} + +/** + * Errata USB-29206 - The USB HS PLL in all 28nm devices has a + * design issue that may cause the VCO to lock up on + * initialization. The Synopsys VCO is designed with an even + * number of stages and no kick-start circuit, which makes us + * believe that there is no question a latched up + * (non-oscillating) state is possible. The workaround is to + * check the PLL lock bit, which is just based on a counter and + * will not set if the VCO is not oscillating, and if it's not + * set do a power down/power up cycle on the PLL, which tests + * have proven is much more likely to guarantee the VCO will + * start oscillating. Part of the problem appears to be that + * the normal init sequence holds the VCO in reset during the + * power up sequence, whereas the plain power up/down sequence + * does not, so the voltage changing may be helping the circuit + * to oscillate. + * + * @param node Node to check + * @param usb_port USB port to check + * + * @return Zero on success, negative on failure + */ +static int dwc3_uphy_check_pll(bdk_node_t node, int usb_port) +{ + /* Internal indirect register that reports if the phy PLL has lock. This will + be 1 if lock, 0 if no lock */ + const int DWC3_INT_IND_PLL_LOCK_REG = 0x200b; + /* Internal indirect UPHY register that controls the power to the UPHY PLL */ + const int DWC3_INT_IND_UPHY_PLL_PU = 0x2012; + /* Write enable bit for DWC3_INT_IND_PLL_POWER_CTL */ + const int DWC3_INT_IND_UPHY_PLL_PU_WE = 0x20; + /* Power enable bit for DWC3_INT_IND_PLL_POWER_CTL */ + const int DWC3_INT_IND_UPHY_PLL_PU_POWER_EN = 0x02; + + uint32_t pll_locked = dwc3_uphy_indirect_read(node, usb_port, DWC3_INT_IND_PLL_LOCK_REG); + int retry_count = 0; + while (!pll_locked) + { + if (retry_count >= 3) + { + bdk_error("N%d.USB%d: USB2 PLL failed to lock\n", node, usb_port); + return -1; + } + + retry_count++; + BDK_TRACE(USB_XHCI, "N%d.USB%d: USB2 PLL didn't lock, retry %d\n", node, usb_port, retry_count); + + /* Turn on write enable for PLL power control */ + uint32_t pwr_val = dwc3_uphy_indirect_read(node, usb_port, DWC3_INT_IND_UPHY_PLL_PU); + pwr_val |= DWC3_INT_IND_UPHY_PLL_PU_WE; + dwc3_uphy_indirect_write(node, usb_port, DWC3_INT_IND_UPHY_PLL_PU, pwr_val); + + /* Power down the PLL */ + pwr_val &= ~DWC3_INT_IND_UPHY_PLL_PU_POWER_EN; + dwc3_uphy_indirect_write(node, usb_port, DWC3_INT_IND_UPHY_PLL_PU, pwr_val); + bdk_wait_usec(1000); + + /* Power on the PLL */ + pwr_val |= DWC3_INT_IND_UPHY_PLL_PU_POWER_EN; + dwc3_uphy_indirect_write(node, usb_port, DWC3_INT_IND_UPHY_PLL_PU, pwr_val); + bdk_wait_usec(1000); + + /* Check for PLL Lock again */ + pll_locked = dwc3_uphy_indirect_read(node, usb_port, DWC3_INT_IND_PLL_LOCK_REG); + } + return 0; +} + +/** + * Initialize the clocks for USB such that it is ready for a generic XHCI driver + * + * @param node Node to init + * @param usb_port Port to intialize + * @param clock_type Type of clock connected to the usb port + * + * @return Zero on success, negative on failure + */ + +int bdk_usb_initialize(bdk_node_t node, int usb_port, bdk_usb_clock_t clock_type) +{ + int is_usbdrd = !CAVIUM_IS_MODEL(CAVIUM_CN88XX); + + /* Perform the following steps to initiate a cold reset. */ + + /* 1. Wait for all voltages to reach a stable state. Ensure the + reference clock is up and stable. + a. If 3.3V is up first, 0.85V must be soon after (within tens of ms). */ + + /* 2. Wait for IOI reset to deassert. */ + + /* 3. If Over Current indication and/or Port Power Control features + are desired, program the GPIO CSRs appropriately. + a. For Over Current Indication, select a GPIO for the input and + program GPIO_USBH_CTL[SEL]. + b. For Port Power Control, set one of + GPIO_BIT_CFG(0..19)[OUTPUT_SEL] = USBH_VBUS_CTRL. */ + + /* 4. Assert all resets: + a. UPHY reset: USBDRD(0..1)_UCTL_CTL[UPHY_RST] = 1 + b. UAHC reset: USBDRD(0..1)_UCTL_CTL[UAHC_RST] = 1 + c. UCTL reset: USBDRD(0..1)_UCTL_CTL[UCTL_RST] = 1 */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.uphy_rst = 1; + c.s.uahc_rst = 1; + c.s.uctl_rst = 1); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.uphy_rst = 1; + c.s.uahc_rst = 1; + c.s.uctl_rst = 1); + } + + /* 5. Configure the controller clock: + a. Reset the clock dividers: USBDRD(0..1)_UCTL_CTL[H_CLKDIV_RST] = 1. + b. Select the controller clock frequency + USBDRD(0..1)_UCTL_CTL[H_CLKDIV] = desired value. + USBDRD(0..1)_UCTL_CTL[H_CLKDIV_EN] = 1 to enable the controller + clock. + Read USBDRD(0..1)_UCTL_CTL to ensure the values take effect. + c. Deassert the controller clock divider reset: USB- + DRD(0..1)_UCTL_CTL[H_CLKDIV_RST] = 0. */ + uint64_t sclk_rate = bdk_clock_get_rate(node, BDK_CLOCK_SCLK); + uint64_t divider = (sclk_rate + 300000000-1) / 300000000; + /* + ** According to HRM Rules are: + ** - clock must be below 300MHz + ** USB3 full-rate requires 150 MHz or better + ** USB3 requires 125 MHz + ** USB2 full rate requires 90 MHz + ** USB2 requires 62.5 MHz + */ + if (divider <= 1) + divider = 0; + else if (divider <= 2) + divider = 1; + else if (divider <= 4) + divider = 2; + else if (divider <= 6) + divider = 3; + else if (divider <= 8) + divider = 4; + else if (divider <= 16) + divider = 5; + else if (divider <= 24) + divider = 6; + else + divider = 7; + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.h_clkdiv_rst = 1); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.h_clkdiv_sel = divider; + c.s.h_clk_en = 1); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.h_clkdiv_rst = 0); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.h_clkdiv_rst = 1); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.h_clkdiv_sel = divider; + c.s.h_clk_en = 1); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.h_clkdiv_rst = 0); + } + { + static bool printit[2] = {true,true}; + if (printit[usb_port]) { + uint64_t fr_div; + if (divider < 5) fr_div = divider * 2; + else fr_div = 8 * (divider - 3); + uint64_t freq = (typeof(freq)) (sclk_rate / fr_div); + const char *token; + if (freq < 62500000ULL) token = "???Low"; + else if (freq < 90000000ULL) token = "USB2"; + else if (freq < 125000000ULL) token = "USB2 Full"; + else if (freq < 150000000ULL) token = "USB3"; + else token = "USB3 Full"; + BDK_TRACE(USB_XHCI, "Freq %lld - %s\n", + (unsigned long long)freq, token); + printit[usb_port] = false; + } + } + + /* 6. Configure the strap signals in USBDRD(0..1)_UCTL_CTL. + a. Reference clock configuration (see Table 31.2): USB- + DRD(0..1)_UCTL_CTL[REF_CLK_FSEL, MPLL_MULTIPLIER, + REF_CLK_SEL, REF_CLK_DIV2]. + b. Configure and enable spread-spectrum for SuperSpeed: + USBDRD(0..1)_UCTL_CTL[SSC_RANGE, SSC_EN, SSC_REF_CLK_SEL]. + c. Enable USBDRD(0..1)_UCTL_CTL[REF_SSP_EN]. + d. Configure PHY ports: + USBDRD(0..1)_UCTL_CTL[USB*_PORT_PERM_ATTACH, USB*_PORT_DISABLE]. */ + if (is_usbdrd) + { + int ref_clk_src = 0; + int ref_clk_fsel = 0x27; + if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) { + if (BDK_USB_CLOCK_SS_PAD_HS_PAD != clock_type) { + bdk_error("Node %d usb_port %d: usb clock type %d is invalid\n", node, usb_port, clock_type); + return -1; + } + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) { + switch (clock_type) + { + default: + bdk_error("Node %d usb_port %d: usb clock type %d is invalid\n", node, usb_port, clock_type); + return -1; + case BDK_USB_CLOCK_SS_PAD_HS_PAD : ref_clk_src = 2; break; + case BDK_USB_CLOCK_SS_REF0_HS_REF0 : ref_clk_src = 0; break; /* Superspeed and high speed use DLM/QLM ref clock 0 */ + case BDK_USB_CLOCK_SS_REF1_HS_REF1 : ref_clk_src = 1; break; /* Superspeed and high speed use DLM/QLM ref clock 1 */ + case BDK_USB_CLOCK_SS_PAD_HS_PLL : ref_clk_src = 6; ref_clk_fsel = 0x7; break; /* Superspeed uses PAD clock, high speed uses PLL ref clock */ + case BDK_USB_CLOCK_SS_REF0_HS_PLL : ref_clk_src = 4; ref_clk_fsel = 0x7; break; /* Superspeed uses DLM/QLM ref clock 0, high speed uses PLL ref clock */ + case BDK_USB_CLOCK_SS_REF1_HS_PLL: ref_clk_src = 5; ref_clk_fsel =0x7; break; /* Superspeed uses DLM/QLM ref clock 1, high speed uses PLL ref clock */ + } + } + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.ref_clk_fsel = ref_clk_fsel; + c.s.mpll_multiplier = 0x19; + c.s.ref_clk_sel = ref_clk_src; + c.s.ref_clk_div2 = 0); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.ssc_en = 1; + c.s.ssc_ref_clk_sel = 0); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.ref_ssp_en = 1); + } + else + { + if (BDK_USB_CLOCK_SS_PAD_HS_PAD != clock_type) { + bdk_error("Node %d usb_port %d: usb clock type %d is invalid\n", node, usb_port, clock_type); + return -1; + } + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.ref_clk_fsel = 0x27; + c.s.mpll_multiplier = 0; + c.s.ref_clk_sel = 0; + c.s.ref_clk_div2 = 0); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.ssc_en = 1; + c.s.ssc_ref_clk_sel = 0); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.ref_ssp_en = 1); + } + /* Hardware default is for ports to be enabled and not perm attach. Don't + change it */ + + /* 7. The PHY resets in lowest-power mode. Power up the per-port PHY + logic by enabling the following: + a. USBDRD(0..1)_UCTL_CTL [HS_POWER_EN] if high-speed/full-speed/low- + speed functionality needed. + b. USBDRD(0..1)_UCTL_CTL [SS_POWER_EN] if SuperSpeed functionality + needed. */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.hs_power_en = 1; + c.s.ss_power_en = 1); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.hs_power_en = 1; + c.s.ss_power_en = 1); + } + + /* 8. Wait 10 controller-clock cycles from step 5. for controller clock + to start and async FIFO to properly reset. */ + bdk_wait_usec(1); + + /* 9. Deassert UCTL and UAHC resets: + a. USBDRD(0..1)_UCTL_CTL[UCTL_RST] = 0 + b. USBDRD(0..1)_UCTL_CTL[UAHC_RST] = 0 + c. [optional] For port-power control: + - Set one of GPIO_BIT_CFG(0..47)[PIN_SEL] = USB0_VBUS_CTRLor USB1_VBUS_CTRL. + - Set USBDRD(0..1)_UCTL_HOST_CFG[PPC_EN] = 1 and USBDRD(0..1)_UCTL_HOST_CFG[PPC_ACTIVE_HIGH_EN] = 1. + - Wait for the external power management chip to power the VBUS.ional port-power control. + ] + d. You will have to wait 10 controller-clock cycles before accessing + any controller-clock-only registers. */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.uctl_rst = 0); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.uctl_rst = 0); + } + bdk_wait_usec(1); + + int usb_gpio = bdk_config_get_int(BDK_CONFIG_USB_PWR_GPIO, node, usb_port); + int usb_polarity = bdk_config_get_int(BDK_CONFIG_USB_PWR_GPIO_POLARITY, node, usb_port); + if (-1 != usb_gpio) { + int gsrc = BDK_GPIO_PIN_SEL_E_USBX_VBUS_CTRL_CN88XX(usb_port); + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) { + gsrc = BDK_GPIO_PIN_SEL_E_USBX_VBUS_CTRL_CN88XX(usb_port); + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) { + gsrc = BDK_GPIO_PIN_SEL_E_USBX_VBUS_CTRL_CN81XX(usb_port); + } + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) { + gsrc = BDK_GPIO_PIN_SEL_E_USBX_VBUS_CTRL_CN83XX(usb_port);} + else { + bdk_error("USB_VBUS_CTRL GPIO: unknown chip model\n"); + } + + BDK_CSR_MODIFY(c,node,BDK_GPIO_BIT_CFGX(usb_gpio), + c.s.pin_sel = gsrc; + c.s.pin_xor = (usb_polarity) ? 0 : 1; + ); + + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_HOST_CFG(usb_port), + c.s.ppc_en = 1; + c.s.ppc_active_high_en = 1); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_HOST_CFG(usb_port), + c.s.ppc_en = 1; + c.s.ppc_active_high_en = 1); + } + bdk_wait_usec(100000); + } + + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.uahc_rst = 0); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.uahc_rst = 0); + } + + bdk_wait_usec(100000); + bdk_wait_usec(1); + + /* 10. Enable conditional coprocessor clock of UCTL by writing USB- + DRD(0..1)_UCTL_CTL[CSCLK_EN] = 1. */ + if (is_usbdrd) + { + if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) + { + /* CN9XXX make coprocessor clock automatic */ + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.cn83xx.csclk_en = 1); + } + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.csclk_en = 1); + } + + /* 11. Set USBDRD(0..1)_UCTL_CTL[DRD_MODE] to 1 for device mode, 0 for + host mode. */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.drd_mode = 0); + } + + /* 12. Soft reset the UPHY and UAHC logic via the UAHC controls: + a. USBDRD(0..1)_UAHC_GUSB2PHYCFG(0)[PHYSOFTRST] = 1 + b. USBDRD(0..1)_UAHC_GUSB3PIPECTL(0)[PHYSOFTRST] = 1 + c. USBDRD(0..1)_UAHC_GCTL[CORESOFTRESET] = 1 */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GUSB2PHYCFGX(usb_port, 0), + c.s.physoftrst = 1); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GUSB3PIPECTLX(usb_port, 0), + c.s.physoftrst = 1); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GCTL(usb_port), + c.s.coresoftreset = 1); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GUSB2PHYCFGX(usb_port, 0), + c.s.physoftrst = 1); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GUSB3PIPECTLX(usb_port, 0), + c.s.physoftrst = 1); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GCTL(usb_port), + c.s.coresoftreset = 1); + } + + /* 13. Program USBDRD(0..1)_UAHC_GCTL[PRTCAPDIR] to 0x2 for device mode + or 0x1 for host mode. */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GCTL(usb_port), + c.s.prtcapdir = 1); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GCTL(usb_port), + c.s.prtcapdir = 1); + } + + /* 14. Wait 10us after step 13. for the PHY to complete its reset. */ + bdk_wait_usec(10); + + /* 15. Deassert UPHY reset: USBDRD(0..1)_UCTL_CTL[UPHY_RST] = 0. */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UCTL_CTL(usb_port), + c.s.uphy_rst = 0); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UCTL_CTL(usb_port), + c.s.uphy_rst = 0); + } + + /* 16. Wait for at least 45us after step 15. for UPHY to output + stable PHYCLOCK. */ + bdk_wait_usec(45); + + /* Workround Errata USB-29206 */ + if (dwc3_uphy_check_pll(node, usb_port)) + return -1; + + /* 17. Initialize any other strap signals necessary and make sure they + propagate by reading back the last register written. + a. UCTL + USBDRD(0..1)_UCTL_PORT0_CFG_*[*_TUNE] + USBDRD(0..1)_UCTL_PORT0_CFG_*[PCS_*] + USBDRD(0..1)_UCTL_PORT0_CFG_*[LANE0_TX_TERM_OFFSET] + USBDRD(0..1)_UCTL_PORT0_CFG_*[TX_VBOOST_LVL] + USBDRD(0..1)_UCTL__PORT0_CFG_*[LOS_BIAS] + USBDRD(0..1)_UCTL_HOST_CFG + USBDRD(0..1)_UCTL_SHIM_CFG + b. UAHC: only the following UAHC registers are accessible during + CoreSoftReset. + USBDRD(0..1)_UAHC_GCTL + USBDRD(0..1)_UAHC_GUCTL + USBDRD(0..1)_UAHC_GSTS + USBDRD(0..1)_UAHC_GUID + USBDRD(0..1)_UAHC_GUSB2PHYCFG(0) + USBDRD(0..1)_UAHC_GUSB3PIPECTL(0) */ + + /* 18. Release soft reset the UPHY and UAHC logic via the UAHC controls: + a. USBDRD(0..1)_UAHC_GUSB2PHYCFG(0)[PHYSOFTRST] = 0 + b. USBDRD(0..1)_UAHC_GUSB3PIPECTL(0)[PHYSOFTRST] = 0 + c. USBDRD(0..1)_UAHC_GCTL[CORESOFTRESET] = 0 */ + if (is_usbdrd) + { + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GUSB2PHYCFGX(usb_port, 0), + c.s.physoftrst = 0); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GUSB3PIPECTLX(usb_port, 0), + c.s.physoftrst = 0); + BDK_CSR_MODIFY(c, node, BDK_USBDRDX_UAHC_GCTL(usb_port), + c.s.coresoftreset = 0); + } + else + { + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GUSB2PHYCFGX(usb_port, 0), + c.s.physoftrst = 0); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GUSB3PIPECTLX(usb_port, 0), + c.s.physoftrst = 0); + BDK_CSR_MODIFY(c, node, BDK_USBHX_UAHC_GCTL(usb_port), + c.s.coresoftreset = 0); + } + + /* 19. Configure the remaining UAHC_G* registers as needed, including + any that were not configured in step 17.-b. */ + + /* 20. Initialize the USB controller: + a. To initialize the UAHC as a USB host controller, the application + should perform the steps described in the xHCI specification + (UAHC_X* registers). The xHCI sequence starts with poll for a 0 in + USBDRD(0..1)_UAHC_USBSTS[CNR]. + b. To initialize the UAHC as a device, the application should TBD. The + device initiation sequence starts with a device soft reset by + setting USBDRD(0..1)_UAHC_DCTL[CSFTRST] = 1 and wait for a read + operation to return 0. */ + return 0; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/device/bdk-device.c b/src/vendorcode/cavium/bdk/libbdk-hal/device/bdk-device.c new file mode 100644 index 0000000000..a8e65f061a --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/device/bdk-device.c @@ -0,0 +1,721 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <string.h> +#include "libbdk-arch/bdk-csrs-ap.h" +#include "libbdk-arch/bdk-csrs-pccpf.h" +#include "libbdk-hal/bdk-ecam.h" +#include "libbdk-hal/device/bdk-device.h" +#include "libbdk-hal/bdk-config.h" +#include "libbdk-driver/bdk-driver.h" +#include "libbdk-hal/bdk-utils.h" + +static struct bdk_driver_s *driver_list = NULL; + +#define DEVICE_GROW 64 +static bdk_device_t *device_list = NULL; +static int device_list_count = 0; +static int device_list_max = 0; + +/** + * Called to register a new driver with the bdk-device system. Drivers are probed + * and initialized as device are found for them. If devices have already been + * added before the driver was registered, the driver will be probed and + * initialized before this function returns. + * + * @param driver Driver functions + * + * @return Zero on success, negative on failure + */ +int bdk_device_add_driver(struct bdk_driver_s *driver) +{ + driver->next = driver_list; + driver_list = driver; + BDK_TRACE(DEVICE, "Added driver for %08x\n", driver->id); + return 0; +} + +/** + * Lookup the correct driver for a device + * + * @param device Device to lookup + * + * @return Driver, or NULL on failure + */ +static const bdk_driver_t *lookup_driver(const bdk_device_t *device) +{ + const bdk_driver_t *drv = driver_list; + while (drv) + { + if (drv->id == device->id) + return drv; + drv = drv->next; + } + return NULL; +} + +/** + * Populate the fields of a new device from the ECAM + * + * @param device Device to populate + */ +static void populate_device(bdk_device_t *device) +{ + /* The default name may be replaced by the driver with something easier to read */ + snprintf(device->name, sizeof(device->name), "N%d.E%d:%d:%d.%d", + device->node, device->ecam, device->bus, device->dev, device->func); + + BDK_TRACE(DEVICE_SCAN, "%s: Populating device\n", device->name); + + /* Get the current chip ID and pass. We'll need this to fill in version + information for the device */ + bdk_ap_midr_el1_t midr_el1; + BDK_MRS(MIDR_EL1, midr_el1.u); + + /* PCCPF_XXX_VSEC_SCTL[RID] with the revision of the chip, + read from fuses */ + BDK_CSR_DEFINE(sctl, BDK_PCCPF_XXX_VSEC_SCTL); + sctl.u = bdk_ecam_read32(device, BDK_PCCPF_XXX_VSEC_SCTL); + sctl.s.rid = midr_el1.s.revision | (midr_el1.s.variant<<3); + sctl.s.node = device->node; /* Program node bits */ + sctl.s.ea = bdk_config_get_int(BDK_CONFIG_PCIE_EA); + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) + sctl.s.ea = 0; /* EA is not supported on CN88XX pass 1.x */ + else + sctl.s.ea = bdk_config_get_int(BDK_CONFIG_PCIE_EA); + bdk_ecam_write32(device, BDK_PCCPF_XXX_VSEC_SCTL, sctl.u); + + /* Read the Device ID */ + device->id = bdk_ecam_read32(device, BDK_PCCPF_XXX_ID); + + /* Read the Device Type so we know how to handle BARs */ + bdk_pccpf_xxx_clsize_t clsize; + clsize.u = bdk_ecam_read32(device, BDK_PCCPF_XXX_CLSIZE); + int isbridge = (clsize.s.hdrtype & 0x7f) == 1; + + BDK_TRACE(DEVICE_SCAN, "%s: Device ID: 0x%08x%s\n", device->name, device->id, + (isbridge) ? " (Bridge)" : ""); + + /* Loop through all the BARs */ + int max_bar = (isbridge) ? BDK_PCCPF_XXX_BAR0U : BDK_PCCPF_XXX_BAR4U; + int bar = BDK_PCCPF_XXX_BAR0L; + unsigned guess_instance = 0; + while (bar <= max_bar) + { + int bar_index = (bar - BDK_PCCPF_XXX_BAR0L) / 8; + /* Read the BAR address and config bits [3:0] */ + uint64_t address = bdk_ecam_read32(device, bar); + int ismem = !(address & 1); /* Bit 0: 0 = mem, 1 = io */ + int is64 = ismem && (address & 4); /* Bit 2: 0 = 32 bit, 1 = 64 bit if mem */ + /* Bit 3: 1 = Is prefetchable. We on't care for now */ + + /* All internal BARs should be 64 bit. Skip if BAR isn't as that means + it is using Enhanced Allocation (EA) */ + if (!is64) + { + BDK_TRACE(DEVICE_SCAN, "%s: BAR%d Disabled or EA bar skipped (0x%08llx)\n", device->name, bar_index, address); + bar += 8; + continue; + } + + /* Get the upper part of 64bit BARs */ + address |= (uint64_t)bdk_ecam_read32(device, bar + 4) << 32; + + /* Write the bits to determine the size */ + bdk_ecam_write32(device, bar, -1); + bdk_ecam_write32(device, bar + 4, -1); + uint64_t size_mask = (uint64_t)bdk_ecam_read32(device, bar + 4) << 32; + size_mask |= bdk_ecam_read32(device, bar); + /* Make sure the node bits are correct in the address */ + address = (address & ~(3UL << 44)) | ((uint64_t)device->node << 44); + /* Restore address value */ + bdk_ecam_write32(device, bar, address); + bdk_ecam_write32(device, bar + 4, address >> 32); + + /* Convert the size into a power of 2 bits */ + int size_bits = bdk_dpop(~size_mask | 0xf); + if (size_bits <= 4) + size_bits = 0; + + /* Store the BAR info */ + device->bar[bar_index].address = address & ~0xfull; + device->bar[bar_index].size2 = size_bits; + device->bar[bar_index].flags = address & 0xf; + BDK_TRACE(DEVICE_SCAN, "%s: BAR%d 0x%llx/%d flags=0x%x\n", + device->name, bar_index, device->bar[bar_index].address, + device->bar[bar_index].size2, device->bar[bar_index].flags); + /* Move to the next BAR */ + bar += 8; + } + + /* Walk the PCI capabilities looking for PCIe support and EA headers */ + BDK_TRACE(DEVICE_SCAN, "%s: Walking PCI capabilites\n", device->name); + int has_pcie = 0; + bdk_pccpf_xxx_cap_ptr_t cap_ptr; + cap_ptr.u = bdk_ecam_read32(device, BDK_PCCPF_XXX_CAP_PTR); + int cap_loc = cap_ptr.s.cp; + while (cap_loc) + { + uint32_t cap = bdk_ecam_read32(device, cap_loc); + int cap_id = cap & 0xff; + int cap_next = (cap >> 8) & 0xff; + + BDK_TRACE(DEVICE_SCAN, "%s: PCI Capability 0x%02x ID:0x%02x Next:0x%02x\n", + device->name, cap_loc, cap_id, cap_next); + + if (cap_id == 0x10) + { + BDK_TRACE(DEVICE_SCAN, "%s: PCIe\n", device->name); + has_pcie = 1; + } + else if (cap_id == 0x01) + { + BDK_TRACE(DEVICE_SCAN, "%s: PCI Power Management Interface\n", device->name); + /* Do nothing for now */ + } + else if (cap_id == 0x11) + { + bdk_pccpf_xxx_msix_cap_hdr_t msix_cap_hdr; + bdk_pccpf_xxx_msix_table_t msix_table; + bdk_pccpf_xxx_msix_pba_t msix_pba; + msix_cap_hdr.u = cap; + msix_table.u = bdk_ecam_read32(device, cap_loc + 4); + msix_pba.u = bdk_ecam_read32(device, cap_loc + 8); + BDK_TRACE(DEVICE_SCAN, "%s: MSI-X Entries:%d, Func Mask:%d, Enable:%d\n", + device->name, msix_cap_hdr.s.msixts + 1, msix_cap_hdr.s.funm, msix_cap_hdr.s.msixen); + BDK_TRACE(DEVICE_SCAN, "%s: Table BAR%d, Offset:0x%x\n", + device->name, msix_table.s.msixtbir, msix_table.s.msixtoffs * 8); + BDK_TRACE(DEVICE_SCAN, "%s: PBA BAR%d, Offset:0x%x\n", + device->name, msix_pba.s.msixpbir, msix_pba.s.msixpoffs * 8); + } + else if (cap_id == 0x05) + { + BDK_TRACE(DEVICE_SCAN, "%s: MSI\n", device->name); + /* Do nothing for now */ + } + else if (cap_id == 0x14) + { + bdk_pccpf_xxx_ea_cap_hdr_t ea_cap_hdr; + ea_cap_hdr.u = cap; + cap_loc += 4; + BDK_TRACE(DEVICE_SCAN, "%s: Enhanced Allocation, %d entries\n", + device->name, ea_cap_hdr.s.num_entries); + if (isbridge) + { + cap = bdk_ecam_read32(device, cap_loc); + cap_loc += 4; + int fixed_secondary_bus = cap & 0xff; + int fixed_subordinate_bus = cap & 0xff; + BDK_TRACE(DEVICE_SCAN, "%s: Fixed Secondary Bus:0x%02x Fixed Subordinate Bus:0x%02x\n", + device->name, fixed_secondary_bus, fixed_subordinate_bus); + } + for (int entry = 0; entry < ea_cap_hdr.s.num_entries; entry++) + { + union bdk_pcc_ea_entry_s ea_entry; + memset(&ea_entry, 0, sizeof(ea_entry)); + uint32_t *ptr = (uint32_t *)&ea_entry; + *ptr++ = bdk_ecam_read32(device, cap_loc); +#if __BYTE_ORDER == __BIG_ENDIAN + /* For big endian we actually need the previous data + shifted 32 bits */ + *ptr = ptr[-1]; +#endif + asm volatile ("" ::: "memory"); /* Needed by gcc 5.0 to detect aliases on ea_entry */ + int entry_size = ea_entry.s.entry_size; + for (int i = 0; i < entry_size; i++) + { + *ptr++ = bdk_ecam_read32(device, cap_loc + 4*i + 4); + } +#if __BYTE_ORDER == __BIG_ENDIAN + /* The upper and lower 32bits need to be swapped */ + ea_entry.u[0] = (ea_entry.u[0] >> 32) | (ea_entry.u[0] << 32); + ea_entry.u[1] = (ea_entry.u[1] >> 32) | (ea_entry.u[1] << 32); + ea_entry.u[2] = (ea_entry.u[2] >> 32) | (ea_entry.u[2] << 32); +#endif + asm volatile ("" ::: "memory"); /* Needed by gcc 5.0 to detect aliases on ea_entry */ + BDK_TRACE(DEVICE_SCAN, "%s: Enable:%d Writeable:%d Secondary Prop:0x%02x Primary Prop:0x%02x BEI:%d Size:%d\n", + device->name, ea_entry.s.enable, ea_entry.s.w, ea_entry.s.sec_prop, ea_entry.s.pri_prop, ea_entry.s.bei, ea_entry.s.entry_size); + if (ea_entry.s.entry_size > 0) + { + BDK_TRACE(DEVICE_SCAN, "%s: Base:0x%08x 64bit:%d\n", + device->name, ea_entry.s.basel << 2, ea_entry.s.base64); + } + if (ea_entry.s.entry_size > 1) + { + BDK_TRACE(DEVICE_SCAN, "%s: MaxOffset:0x%08x 64bit:%d\n", + device->name, (ea_entry.s.offsetl << 2) | 3, ea_entry.s.offset64); + } + if (ea_entry.s.entry_size > 2) + { + BDK_TRACE(DEVICE_SCAN, "%s: BaseUpper:0x%08x\n", + device->name, ea_entry.s.baseh); + } + if (ea_entry.s.entry_size > 3) + { + BDK_TRACE(DEVICE_SCAN, "%s: MaxOffsetUpper:0x%08x\n", + device->name, ea_entry.s.offseth); + } + if (ea_entry.s.enable) + { + uint64_t base = (uint64_t)ea_entry.s.baseh << 32; + base |= (uint64_t)ea_entry.s.basel << 2; + /* Make sure the node bits are correct in the address */ + base = (base & ~(3UL << 44)) | ((uint64_t)device->node << 44); + uint64_t offset = (uint64_t)ea_entry.s.offseth << 32; + offset |= ((uint64_t)ea_entry.s.offsetl << 2) | 3; + switch (ea_entry.s.bei) + { + case 0: /* BAR 0 */ + case 2: /* BAR 1 */ + case 4: /* BAR 2 */ + { + int bar_index = ea_entry.s.bei/2; + device->bar[bar_index].address = base; + device->bar[bar_index].size2 = bdk_dpop(offset); + device->bar[bar_index].flags = ea_entry.s.base64 << 2; + BDK_TRACE(DEVICE_SCAN, "%s: Updated BAR%d 0x%llx/%d flags=0x%x\n", + device->name, bar_index, device->bar[bar_index].address, + device->bar[bar_index].size2, device->bar[bar_index].flags); + if (0 == ea_entry.s.bei) { + /* PEMs eg PCIEEP and PCIERC do not have instance id + ** We can calculate it for PCIERC based on BAR0 allocation. + ** PCIEEP will be dropped by probe + */ + guess_instance = (device->bar[bar_index].address >> 24) & 7; + } + break; + } + case 9: /* SR-IOV BAR 0 */ + case 11: /* SR-IOV BAR 1 */ + case 13: /* SR-IOV BAR 2 */ + // FIXME + break; + } + } + cap_loc += ea_entry.s.entry_size * 4 + 4; + } + } + else + { + /* Unknown PCI capability */ + bdk_warn("%s: ECAM device unknown PCI capability 0x%x\n", device->name, cap_id); + } + cap_loc = cap_next; + } + + /* Walk the PCIe capabilities looking for instance header */ + if (has_pcie) + { + BDK_TRACE(DEVICE_SCAN, "%s: Walking PCIe capabilites\n", device->name); + cap_loc = 0x100; + while (cap_loc) + { + uint32_t cap = bdk_ecam_read32(device, cap_loc); + int cap_id = cap & 0xffff; + int cap_ver = (cap >> 16) & 0xf; + int cap_next = cap >> 20; + BDK_TRACE(DEVICE_SCAN, "%s: PCIe Capability 0x%03x ID:0x%04x Version:0x%x Next:0x%03x\n", + device->name, cap_loc, cap_id, cap_ver, cap_next); + if (cap_id == 0xe) + { + /* ARI. Do nothing for now */ + BDK_TRACE(DEVICE_SCAN, "%s: ARI\n", device->name); + } + else if (cap_id == 0xb) + { + /* Vendor specific*/ + int vsec_id = bdk_ecam_read32(device, cap_loc + 4); + int vsec_id_id = vsec_id & 0xffff; + int vsec_id_rev = (vsec_id >> 16) & 0xf; + int vsec_id_len = vsec_id >> 20; + BDK_TRACE(DEVICE_SCAN, "%s: Vendor ID: 0x%04x Rev: 0x%x Size 0x%03x\n", + device->name, vsec_id_id, vsec_id_rev, vsec_id_len); + switch (vsec_id_id) + { + case 0x0001: /* RAS Data Path */ + BDK_TRACE(DEVICE_SCAN, "%s: Vendor RAS Data Path\n", device->name); + break; + + case 0x0002: /* RAS DES */ + BDK_TRACE(DEVICE_SCAN, "%s: Vendor RAS DES\n", device->name); + break; + + case 0x00a0: /* Cavium common */ + case 0x00a1: /* Cavium CN88XX */ + case 0x00a2: /* Cavium CN81XX */ + case 0x00a3: /* Cavium CN83XX */ + if ((vsec_id_rev == 1) || (vsec_id_rev == 2)) + { + int vsec_ctl = bdk_ecam_read32(device, cap_loc + 8); + int vsec_ctl_inst_num = vsec_ctl & 0xff; + int vsec_ctl_subnum = (vsec_ctl >> 8) & 0xff; + BDK_TRACE(DEVICE_SCAN, "%s: Cavium Instance: 0x%02x Static Bus: 0x%02x\n", + device->name, vsec_ctl_inst_num, vsec_ctl_subnum); + int vsec_sctl = bdk_ecam_read32(device, cap_loc + 12); + int vsec_sctl_rid = (vsec_sctl >> 16) & 0xff; + if (vsec_id_rev == 2) + { + int vsec_sctl_pi = (vsec_sctl >> 24) & 0xff; /* Only in Rev 2 */ + BDK_TRACE(DEVICE_SCAN, "%s: Revision ID: 0x%02x Programming Interface: 0x%02x\n", + device->name, vsec_sctl_rid, vsec_sctl_pi); + } + else + { + BDK_TRACE(DEVICE_SCAN, "%s: Revision ID: 0x%02x\n", + device->name, vsec_sctl_rid); + } + /* Record the device instance */ + device->instance = vsec_ctl_inst_num; + } + else + { + bdk_warn("%s: ECAM device Unknown Cavium extension revision\n", device->name); + } + break; + + default: /* Unknown Vendor extension */ + bdk_warn("%s: ECAM device unknown vendor extension ID 0x%x\n", device->name, vsec_id_id); + break; + } + } + else if (cap_id == 0x10) + { + /* Single Root I/O Virtualization (SR-IOV) */ + BDK_TRACE(DEVICE_SCAN, "%s: SR-IOV\n", device->name); + /* Loop through all the SR-IOV BARs */ + bar = cap_loc + 0x24; + while (bar <= (cap_loc + 0x3c)) + { + int bar_index = (bar - 0x24 - cap_loc) / 8; + /* Read the BAR address and config bits [3:0] */ + uint64_t address = bdk_ecam_read32(device, bar); + int ismem = !(address & 1); /* Bit 0: 0 = mem, 1 = io */ + int is64 = ismem && (address & 4); /* Bit 2: 0 = 32 bit, 1 = 64 bit if mem */ + /* Bit 3: 1 = Is prefetchable. We don't care for now */ + + /* All internal BARs should be 64 bit. Skip if BAR isn't as that means + it is using Enhanced Allocation (EA) */ + if (!is64) + { + BDK_TRACE(DEVICE_SCAN, "%s: SR-IOV BAR%d Disabled or EA bar skipped (0x%08llx)\n", device->name, bar_index, address); + bar += 8; + continue; + } + + /* Get the upper part of 64bit BARs */ + address |= (uint64_t)bdk_ecam_read32(device, bar + 4) << 32; + + /* Write the bits to determine the size */ + bdk_ecam_write32(device, bar, -1); + bdk_ecam_write32(device, bar + 4, -1); + uint64_t size_mask = (uint64_t)bdk_ecam_read32(device, bar + 4) << 32; + size_mask |= bdk_ecam_read32(device, bar); + /* Make sure the node bits are correct in the address */ + address = (address & ~(3UL << 44)) | ((uint64_t)device->node << 44); + /* Restore address value */ + bdk_ecam_write32(device, bar, address); + bdk_ecam_write32(device, bar + 4, address >> 32); + + /* Convert the size into a power of 2 bits */ + int size_bits = bdk_dpop(size_mask | 0xf); + if (size_bits <= 4) + size_bits = 0; + + BDK_TRACE(DEVICE_SCAN, "%s: SR-IOV BAR%d 0x%llx/%d flags=0x%llx\n", + device->name, bar_index, address & ~0xfull, + size_bits, address & 0xf); + /* Move to the next BAR */ + bar += 8; + } + } + else if (cap_id == 0x01) + { + /* Advanced Error Reporting Capability */ + BDK_TRACE(DEVICE_SCAN, "%s: Advanced Error Reporting\n", device->name); + } + else if (cap_id == 0x19) + { + /* Secondary PCI Express Extended Capability */ + BDK_TRACE(DEVICE_SCAN, "%s: Secondary PCI Express Extended\n", device->name); + } + else if (cap_id == 0x15) + { + /* PCI Express Resizable BAR (RBAR) Capability */ + BDK_TRACE(DEVICE_SCAN, "%s: PCI Express Resizable BAR (RBAR)\n", device->name); + } + else if (cap_id == 0x0d) + { + /* Extended access control := ACS Extended Capability */ + BDK_TRACE(DEVICE_SCAN, "%s: ACS\n", device->name); + } + else + { + /* Unknown PCIe capability */ + bdk_warn("%s: ECAM device unknown PCIe capability 0x%x\n", device->name, cap_id); + } + cap_loc = cap_next; + } + } + else + { + bdk_error("%s: ECAM device didn't have a PCIe capability\n", device->name); + } + if (BDK_NO_DEVICE_INSTANCE == device->instance) { + device->instance = guess_instance; + } + BDK_TRACE(DEVICE_SCAN, "%s: Device populated\n", device->name); +} + +/** + * Called by the ECAM code whan a new device is detected in the system + * + * @param node Node the ECAM is on + * @param ecam ECAM the device is on + * @param bus Bus number for the device + * @param dev Device number + * @param func Function number + * + * @return Zero on success, negative on failure + */ +int bdk_device_add(bdk_node_t node, int ecam, int bus, int dev, int func) +{ + if (device_list_count == device_list_max) + { + int grow = device_list_max + DEVICE_GROW; + bdk_device_t *tmp = malloc(grow * sizeof(bdk_device_t)); + if (!tmp) + memcpy(tmp, device_list, device_list_max * sizeof(bdk_device_t)); + free(device_list); + if (tmp == NULL) + { + bdk_error("bdk-device: Failed to allocate space for device\n"); + return -1; + } + device_list = tmp; + device_list_max = grow; + } + + bdk_device_t *device = &device_list[device_list_count++]; + memset(device, 0, sizeof(*device)); + + device->state = BDK_DEVICE_STATE_NOT_PROBED; + device->node = node; + device->ecam = ecam; + device->bus = bus; + device->dev = dev; + device->func = func; + device->instance = BDK_NO_DEVICE_INSTANCE; + populate_device(device); + + const bdk_driver_t *drv = lookup_driver(device); + if (drv) + BDK_TRACE(DEVICE, "%s: Added device\n", device->name); + else + BDK_TRACE(DEVICE, "%s: Added device without driver (0x%08x)\n", device->name, device->id); + return 0; +} + +/** + * Rename a device. Called by driver to give devices friendly names + * + * @param device Device to rename + * @param format Printf style format string + */ +void bdk_device_rename(bdk_device_t *device, const char *format, ...) +{ + char tmp[sizeof(device->name)]; + va_list args; + va_start(args, format); + vsnprintf(tmp, sizeof(tmp), format, args); + va_end(args); + tmp[sizeof(tmp) - 1] = 0; + BDK_TRACE(DEVICE, "%s: Renamed to %s\n", device->name, tmp); + strcpy(device->name, tmp); +} + +/** + * Called by the ECAM code once all devices have been added + * + * @return Zero on success, negative on failure + */ +int bdk_device_init(void) +{ + /* Probe all devices first */ + for (int i = 0; i < device_list_count; i++) + { + bdk_device_t *dev = &device_list[i]; + const bdk_driver_t *drv = lookup_driver(dev); + if (drv == NULL) + continue; + if (dev->state == BDK_DEVICE_STATE_NOT_PROBED) + { + BDK_TRACE(DEVICE, "%s: Probing\n", dev->name); + if (drv->probe(dev)) + { + BDK_TRACE(DEVICE, "%s: Probe failed\n", dev->name); + dev->state = BDK_DEVICE_STATE_PROBE_FAIL; + } + else + { + BDK_TRACE(DEVICE, "%s: Probe complete\n", dev->name); + dev->state = BDK_DEVICE_STATE_PROBED; + } + } + } + + /* Do init() after all the probes. See comments in top of bdk-device.h */ + for (int i = 0; i < device_list_count; i++) + { + bdk_device_t *dev = &device_list[i]; + const bdk_driver_t *drv = lookup_driver(dev); + if (drv == NULL) + continue; + if (dev->state == BDK_DEVICE_STATE_PROBED) + { + BDK_TRACE(DEVICE, "%s: Initializing\n", dev->name); + if (drv->init(dev)) + { + BDK_TRACE(DEVICE, "%s: Init failed\n", dev->name); + dev->state = BDK_DEVICE_STATE_INIT_FAIL; + } + else + { + BDK_TRACE(DEVICE, "%s: Init complete\n", dev->name); + dev->state = BDK_DEVICE_STATE_READY; + } + } + } + return 0; +} + +/** + * Lookup a device by ECAM ID and internal instance number. This can be used by + * one device to find a handle to an associated device. For example, PKI would + * use this function to get a handle to the FPA. + * + * @param node Node to lookup for + * @param id ECAM ID + * @param instance Cavium internal instance number + * + * @return Device pointer, or NULL if the device isn't found + */ +const bdk_device_t *bdk_device_lookup(bdk_node_t node, uint32_t id, int instance) +{ + for (int i = 0; i < device_list_count; i++) + { + bdk_device_t *dev = &device_list[i]; + if ((dev->node == node) && (dev->id == id) && (dev->instance == instance)) + return dev; + } + BDK_TRACE(DEVICE, "No device found for node %d, ID %08x, instance %d\n", node, id, instance); + return NULL; +} + +/** + * Read from a device BAR + * + * @param device Device to read from + * @param bar Which BAR to read from (0-3) + * @param size Size of the read + * @param offset Offset into the BAR + * + * @return Value read + */ +uint64_t bdk_bar_read(const bdk_device_t *device, int bar, int size, uint64_t offset) +{ + uint64_t address = offset & bdk_build_mask(device->bar[bar/2].size2); + address += device->bar[bar/2].address; + if (offset+size > (1ULL << device->bar[bar/2].size2)) { + /* The CSR address passed in offset doesn't contain the node number. Copy it + from the BAR address */ + offset |= address & (0x3ull << 44); + if (address != offset) + bdk_fatal("BAR read address 0x%llx doesn't match CSR address 0x%llx\n", address, offset); + } + switch (size) + { + case 1: + return bdk_read64_uint8(address); + case 2: + return bdk_le16_to_cpu(bdk_read64_uint16(address)); + case 4: + return bdk_le32_to_cpu(bdk_read64_uint32(address)); + case 8: + return bdk_le64_to_cpu(bdk_read64_uint64(address)); + } + bdk_fatal("%s: Unexpected read size %d\n", device->name, size); +} + +/** + * Write to a device BAR + * + * @param device Device to write to + * @param bar Which BAR to read from (0-3) + * @param size Size of the write + * @param offset Offset into the BAR + * @param value Value to write + */ +void bdk_bar_write(const bdk_device_t *device, int bar, int size, uint64_t offset, uint64_t value) +{ + uint64_t address = offset & bdk_build_mask(device->bar[bar/2].size2); + address += device->bar[bar/2].address; + if (offset+size > (1ULL << device->bar[bar/2].size2)) { + /* The CSR address passed in offset doesn't contain the node number. Copy it + from the BAR address */ + offset |= address & (0x3ull << 44); + if (address != offset) + bdk_fatal("BAR write address 0x%llx doesn't match CSR address 0x%llx\n", address, offset); + } + switch (size) + { + case 1: + bdk_write64_uint8(address, value); + return; + case 2: + bdk_write64_uint16(address, bdk_cpu_to_le16(value)); + return; + case 4: + bdk_write64_uint32(address, bdk_cpu_to_le32(value)); + return; + case 8: + bdk_write64_uint64(address, bdk_cpu_to_le64(value)); + return; + } + bdk_fatal("%s: Unexpected write size %d\n", device->name, size); +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-marvell.c b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-marvell.c new file mode 100644 index 0000000000..e8f3a009dd --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-marvell.c @@ -0,0 +1,115 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2016 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <libbdk-hal/bdk-mdio.h> +#include <libbdk-hal/bdk-qlm.h> +#include <libbdk-hal/if/bdk-if.h> + +/** + * Setup marvell PHYs + * This function sets up one port in a marvell 88E1512 in SGMII mode + */ +static void setup_marvell_phy(bdk_node_t node, int mdio_bus, int mdio_addr) +{ + int phy_status = 0; + + BDK_TRACE(PHY, "%s In SGMII mode for Marvell PHY 88E1512\n", __FUNCTION__); + /* Switch to Page 18 */ + phy_status = bdk_mdio_write(node, mdio_bus, mdio_addr, 22, 18); + if (phy_status < 0) + return; + + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 22); + if (phy_status < 0) + return; + + /* Change the Phy System mode from RGMII(default hw reset mode) to SGMII */ + phy_status = bdk_mdio_write(node, mdio_bus, mdio_addr, 20, 1); + if (phy_status < 0) + return; + + /* Requires a Software reset */ + phy_status = bdk_mdio_write(node, mdio_bus, mdio_addr, 20, 0x8001); + if (phy_status < 0) + return; + + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 20); + if (phy_status < 0) + return; + + /* Change the Page back to 0 */ + phy_status = bdk_mdio_write(node, mdio_bus, mdio_addr, 22, 0); + if (phy_status < 0) + return; + + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 22); + if (phy_status < 0) + return; + + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 17); + if (phy_status < 0) + return; +} + +int bdk_if_phy_marvell_setup(bdk_node_t node, int qlm, int mdio_bus, int phy_addr) +{ + BDK_TRACE(PHY,"In %s\n",__FUNCTION__); + + /* Check if the PHY is marvell PHY we expect */ + int phy_status = bdk_mdio_read(node, mdio_bus, phy_addr, BDK_MDIO_PHY_REG_ID1); + if (phy_status != 0x0141) + return 0; + + /* Check that the GSER mode is SGMII */ + /* Switch the marvell PHY to the correct mode */ + bdk_qlm_modes_t qlm_mode = bdk_qlm_get_mode(node, qlm); + + BDK_TRACE(PHY,"%s: QLM:%d QLM_MODE:%d\n",__FUNCTION__, qlm, qlm_mode); + + if ((qlm_mode != BDK_QLM_MODE_SGMII_1X1) && + (qlm_mode != BDK_QLM_MODE_SGMII_2X1)) + return 0; + + BDK_TRACE(PHY,"%s: Detected Marvell Phy in SGMII mode\n", __FUNCTION__); + for (int port = 0; port < 2; port++) + { + setup_marvell_phy(node, mdio_bus, phy_addr + port); + } + return 0; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse-8514.c b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse-8514.c new file mode 100644 index 0000000000..4d6ef8d25d --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse-8514.c @@ -0,0 +1,224 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2016 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <libbdk-hal/if/bdk-if.h> +#include <libbdk-hal/bdk-mdio.h> +#include <libbdk-hal/bdk-qlm.h> + +#define VSC_PHY_STD_PAGE (0x0) +#define VSC_PHY_EXT1_PAGE (0x1) +#define VSC_PHY_EXT2_PAGE (0x2) +#define VSC_PHY_EXT3_PAGE (0x3) +#define VSC_PHY_EXT4_PAGE (0x4) +#define VSC_PHY_GPIO_PAGE (0x10) +#define VSC_PHY_TEST_PAGE (0x2A30) +#define VSC_PHY_TR_PAGE (0x52B5) + +const uint16_t init_script_rev_a[] = { +// Op, Page, Reg, Value, Mask +// 0, 1, 2, 3, 4 +// --, ------, ----, ------, ----- + 0, 0x0000, 0x1f, 0x0000, 0xffff, + 1, 0x0000, 0x16, 0x0001, 0x0001, + 0, 0x0001, 0x1f, 0x2A30, 0xffff, + 1, 0x2A30, 0x08, 0x8000, 0x8000, + 0, 0x2A30, 0x1f, 0x52B5, 0xffff, + 0, 0x52B5, 0x12, 0x0068, 0xffff, + 0, 0x52B5, 0x11, 0x8980, 0xffff, + 0, 0x52B5, 0x10, 0x8f90, 0xffff, + 0, 0x52B5, 0x12, 0x0000, 0xffff, + 0, 0x52B5, 0x11, 0x0003, 0xffff, + 0, 0x52B5, 0x10, 0x8796, 0xffff, + 0, 0x52B5, 0x12, 0x0050, 0xffff, + 0, 0x52B5, 0x11, 0x100f, 0xffff, + 0, 0x52B5, 0x10, 0x87fa, 0xffff, + 0, 0x52B5, 0x1f, 0x2A30, 0xffff, + 1, 0x2A30, 0x08, 0x0000, 0x8000, + 0, 0x2A30, 0x1f, 0x0000, 0xffff, + 1, 0x0000, 0x16, 0x0000, 0x0001, + 0xf, 0xffff, 0xff, 0xffff, 0xffff +}; + +static void wr_masked(bdk_node_t node, int mdio_bus, int phy_addr, int reg, int value, int mask) +{ + int nmask = ~mask; + int old = bdk_mdio_read(node, mdio_bus, phy_addr, reg); + int vmask = value & mask; + int newv = old & nmask; + newv = newv | vmask; + bdk_mdio_write(node, mdio_bus, phy_addr, reg, newv); +} +static void vitesse_init_script(bdk_node_t node, int mdio_bus, int phy_addr) +{ + const uint16_t *ptr; + uint16_t reg_addr; + uint16_t reg_val; + uint16_t mask; + + BDK_TRACE(PHY,"In %s\n",__FUNCTION__); + BDK_TRACE(PHY,"Loading init script for VSC8514\n"); + + ptr = init_script_rev_a; + while (*ptr != 0xf) + { + reg_addr = *(ptr+2); + reg_val = *(ptr+3); + mask = *(ptr+4); + ptr+=5; + if (mask != 0xffff) + { + wr_masked(node, mdio_bus, phy_addr, reg_addr,reg_val,mask); + } + else + { + bdk_mdio_write(node,mdio_bus,phy_addr,reg_addr,reg_val); + } + } + + BDK_TRACE(PHY,"loading init script is done\n"); + +} + +static void vitesse_program(bdk_node_t node, int mdio_bus, int phy_addr) +{ + return; +} + +/** + * Setup Vitesse PHYs + * This function sets up one port in a Vitesse VSC8514 + */ +static void setup_vitesse_phy(bdk_node_t node, int mdio_bus, int phy_addr) +{ + /*setting MAC if*/ + bdk_mdio_write(node, mdio_bus, phy_addr, 31, VSC_PHY_GPIO_PAGE); + wr_masked(node,mdio_bus,phy_addr, 19, 0x4000, 0xc000); + bdk_mdio_write(node, mdio_bus, phy_addr, 18, 0x80e0); + + /*Setting media if*/ + bdk_mdio_write(node, mdio_bus, phy_addr, 31, VSC_PHY_STD_PAGE); + // Reg23, 10:8 Select copper, CAT5 copper only + wr_masked(node,mdio_bus,phy_addr, 23, 0x0000, 0x0700); + + // Reg0:15, soft Reset + wr_masked(node,mdio_bus,phy_addr, 0, 0x8000, 0x8000); + int time_out = 100; + while (time_out && bdk_mdio_read(node,mdio_bus,phy_addr, 0) & 0x8000) + { + bdk_wait_usec(100000); + time_out--; + } + + if (time_out == 0) + { + BDK_TRACE(PHY,"setting PHY TIME OUT\n"); + return; + } + else + { + BDK_TRACE(PHY,"Setting a phy port is done\n"); + } + + bdk_mdio_write(node, mdio_bus, phy_addr, 31, VSC_PHY_EXT3_PAGE); + bdk_mdio_write(node, mdio_bus, phy_addr, 16, 0x80); + // Select main registers + bdk_mdio_write(node, mdio_bus, phy_addr, 31, VSC_PHY_STD_PAGE); + + /* + + if (LOOP_INTERNAL) + { + reg0 = bdk_mdio_read(node, mdio_bus, phy_addr, 0); + reg0 = bdk_insert(reg0, 1, 14, 1); + bdk_mdio_write(node, mdio_bus, phy_addr, 0, reg0); + } + + // Far end loopback (External side) + if (LOOP_EXTERNAL) + { + reg23 = bdk_mdio_read(node, mdio_bus, phy_addr, 23); + reg23 = bdk_insert(reg23, 1, 3, 1); + bdk_mdio_write(node, mdio_bus, phy_addr, 23, reg23); + } + + + // Dump registers + if (false) + { + printf("\nVitesse PHY register dump, PHY address %d, mode %s\n", + phy_addr, (qsgmii) ? "QSGMII" : "SGMII"); + int phy_addr = 4; + for (int reg_set = 0; reg_set <= 0x10; reg_set += 0x10) + { + printf("\nDump registers with reg[31]=0x%x\n", reg_set); + bdk_mdio_write(node, mdio_bus, phy_addr, 31, reg_set); + for (int reg=0; reg < 32; reg++) + printf("reg[%02d]=0x%x\n", reg, bdk_mdio_read(node, mdio_bus, phy_addr, reg)); + } + } + */ +} + +//static void vetesse_setup(bdk_node_t node, int qlm, int mdio_bus, int phy_addr) +int bdk_if_phy_vsc8514_setup(bdk_node_t node, int qlm, int mdio_bus, int phy_addr) +{ + /* Check if the PHY is Vetesse PHY we expect */ + int phy_status_1 = bdk_mdio_read(node, mdio_bus, phy_addr, BDK_MDIO_PHY_REG_ID1); + int phy_status_2 = bdk_mdio_read(node, mdio_bus, phy_addr, BDK_MDIO_PHY_REG_ID2); + if (phy_status_1 != 0x0007 || phy_status_2 != 0x0670) + { + bdk_error("The PHY on this board is NOT VSC8514.\n"); + return -1; + } + + /* Check that the GSER mode is SGMII or QSGMII */ + bdk_qlm_modes_t qlm_mode = bdk_qlm_get_mode(node, qlm); + if (qlm_mode != BDK_QLM_MODE_QSGMII_4X1) + return -1; + + vitesse_init_script(node, mdio_bus, phy_addr); + vitesse_program(node, mdio_bus, phy_addr); + + /* VSC8514 just support QSGMII */ + for (int port = 0; port < 4; port++) + setup_vitesse_phy(node, mdio_bus, phy_addr + port); + + return 1; + +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse-xfi.c b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse-xfi.c new file mode 100644 index 0000000000..b6f052ad47 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse-xfi.c @@ -0,0 +1,395 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <libbdk-hal/if/bdk-if.h> +#include <libbdk-hal/bdk-mdio.h> +#include <libbdk-hal/bdk-twsi.h> + +/* This code is an optional part of the BDK. It is only linked in + * if BDK_REQUIRE() needs it */ +//BDK_REQUIRE(TWSI); +BDK_REQUIRE_DEFINE(XFI); + +/* +Rate Select Settings +Mode State : 6/8 +Rate Select State : 0 +RSEL1 : 0 +RSEL0 : 0 +Ref Clock Gen(MHz) : 156.25 +Data Rate(Gbps) : 10.3125 +Description : 10 GbE + + +Data Rate Detection Configuration Registers + +Mode Pin Settings: +Mode State : 0 +MODE1 : 0 +MODE0 : 0 +Mode : Two-wire serial interface mode + +LOS Pin Strap Mode Settings +Mode State : 2/6/8 +State : 4 +LOS1 : Float +LOS0 : Float +LOS Amplitude(mVpp) : 20 +LOS Hysteresis(dB) : 2 + +Input Equalization Retimer Mode Settings +Mode State : 6/8 +EQ State : 0 +EQ1 : 0 +EQ0 : 0 +EQ(dB) : Auto +DFE : Auto +Comment : Full Auto + +Input Equalization Re-Driver Mode Settings +Mode State : +EQ State : 0 +EQ1 : 0 +EQ0 : 0 +EQ(dB) : Auto +DFE : APowered Down +Comment : Analog EQ Only + + + +Output De-Emphasis Retimer Mode Settings +Mode State : 6/8 +DE State : 3 +TX1 : Float +TX0 : 0 +PRE c(-1) mA : -1 +MAIN c( 0) mA : 15 +POST c(+1) mA : 4 +DC Amplitude(mV): 500 +De-Emphasis(dB) : -6.02 +Comment : + + +Output De-Emphasis Re-Driver Mode Settings +Mode State : 2 +DE State : 3 +TX1 : Float +TX0 : 0 +Frequency(Gbps) : 10.3125 +DC Amplitude(mV): 600 +De-Emphasis(dB) : 4 +Comment : 10GbE + + +*/ + +static int debug = 0; + +#define xfi_printf(fmt, args...) \ + do { \ + if(debug == 1){ \ + printf(fmt, ##args); \ + } \ + } while(0) + + +int bdk_xfi_vsc7224_dump(int twsi_id, int unit){ + bdk_node_t node=0; + uint8_t dev_addr=0x10 + unit; + uint16_t internal_addr=0x7F; + int num_bytes=2; + int ia_width_bytes=1; + uint64_t data=0; + int p, i; + uint64_t result[0x100] = {0}; + + uint64_t pagenum[9] = {0x00, 0x01, 0x02, 0x03, 0x20, 0x21, 0x30, 0x31, 0x40}; + + for(p=0; p < (sizeof(pagenum)/sizeof(pagenum[0])); p++){ + data = pagenum[p]; + bdk_twsix_write_ia(node, twsi_id, dev_addr, internal_addr, num_bytes, ia_width_bytes, data); + for(i=0x80; i<=0xFF; i++){ + result[i] = 0x00; + result[i] = bdk_twsix_read_ia(node, twsi_id, dev_addr, (uint16_t)i, num_bytes, ia_width_bytes); + } + for(i=0x80; i<=0xFF; i++){ + if(i==0x80){ + printf("\npage_%02X[0x100] = {\n", (uint8_t)pagenum[p]); + } + if(i % 8 == 0){ + printf("/* 0x%2X */", i); + } + printf(" 0x%04X,", (uint16_t)result[i]); + if(i==0xFF){ + printf("};"); + } + if((i+1) % 8 == 0){ + printf("\n"); + } + } + } + + return 0; +} + +/* XFI ReTimer/ReDriver Mode Settings */ + +/* +power down regs: +Page Reg Position Mask val RegFieldName +0x00 0x89 b07 0x0080 1 PD_INBUF +0x00 0x8A b10 0x0400 1 PD_DFECRU +0x00 0x8A b01 0x0002 1 PD_DFE +0x00 0x8A b00 0x0001 1 PD_DFEADAPT +0x00 0x97 b15 0x8000 1 ASYN_SYNN +0x00 0x97 b09 0x0200 1 PD_OD +0x00 0xA0 b11 0x0800 1 PD_LOS +0x00 0xA4 b15 0x8000 1 PD_CH +0x00 0xB5 b07 0x0080 1 PD_INBUF +0x00 0xB9 b15 0x8000 1 ASYN_SYNN +0x00 0xB9 b09 0x0200 1 PD_OD +0x00 0xBF b07 0x0080 1 PD_INBUF +0x00 0xF0 b15 0x8000 1 ASYN_SYNN +0x00 0xF0 b09 0x0200 1 PD_OD +0x00 0xF6 b07 0x0080 1 PD_INBUF +0x00 0xFA b15 0x8000 1 ASYN_SYNN +0x00 0xFA b09 0x0200 1 PD_OD +*/ +struct regmap{ + short int page; + unsigned char reg; + unsigned short int retimer; + unsigned short int redriver; +}; + +/* This table only applies to SFF8104 */ +struct regmap xfiregmap[64] = { +//CH 0 +{0x00, 0x84, 0x0800, 0x0000}, //EQTABLE_DCOFF0 (0n_84) +{0x00, 0x8A, 0x7000, 0x0400}, //DFECRU_CTRL (0n_8A) +{0x00, 0x8B, 0x4060, 0x0000}, //DFECRU_CFVF_CFAP (0n_8B) +{0x00, 0x90, 0xDE85, 0x0000}, //DFECRU_DFEAUTO (0n_90) +{0x00, 0x91, 0x2020, 0x0000}, //DFECRU_BTMX_BFMX (0n_91) +{0x00, 0x92, 0x0860, 0x0000}, //DFECRU_DXMX_TRMX (0n_92) +{0x00, 0x93, 0x6000, 0x0000}, //DFECRU_TRMN_ERRI (0n_93) +{0x00, 0x94, 0x0001, 0x0000}, //DFECRU_DFEMODE (0n_94) +{0x00, 0x95, 0x0008, 0x0000}, //DFECRU_RATESEL (0n_95) +{0x00, 0x97, 0x0000, 0x8080}, //OUTDRVCTRL (0n_97) +{0x00, 0x99, 0x001E, 0x0014}, //KR_MAINTAP (0n_99) +{0x00, 0x9A, 0x000B, 0x0000}, //KR_PRETAP (0n_9A) +{0x00, 0x9B, 0x0010, 0x0000}, //KR_POSTTAP (0n_9B) +{0x00, 0x9E, 0x03E8, 0x07D0}, //LOSASSRT (0n_9E) +{0x00, 0x9F, 0x04EA, 0x09D5}, //LOSDASSRT (0n_9F) +{0x00, 0xB2, 0x0888, 0x0000}, //NA + +//CH 1 +{0x01, 0x84, 0x0800, 0x0000}, +{0x01, 0x8A, 0x7000, 0x0400}, +{0x01, 0x8B, 0x4060, 0x0000}, +{0x01, 0x90, 0xDE85, 0x0000}, +{0x01, 0x91, 0x2020, 0x0000}, +{0x01, 0x92, 0x0860, 0x0000}, +{0x01, 0x93, 0x6000, 0x0000}, +{0x01, 0x94, 0x0001, 0x0000}, +{0x01, 0x95, 0x0008, 0x0000}, +{0x01, 0x97, 0x0000, 0x8080}, +{0x01, 0x99, 0x001E, 0x0014}, +{0x01, 0x9A, 0x000B, 0x0000}, +{0x01, 0x9B, 0x0010, 0x0000}, +{0x01, 0x9E, 0x03E8, 0x07D0}, +{0x01, 0x9F, 0x04EA, 0x09D5}, +{0x01, 0xB2, 0x0888, 0x0000}, + +//POWER_DOWN Channel 2 and 3 +{0x02, 0x8A, 0x0400, 0x0400}, +{0x02, 0xA4, 0x8000, 0x8000}, +{0x03, 0x8A, 0x0400, 0x0400}, +{0x03, 0xA4, 0x8000, 0x8000}, + +{0x30, 0x80, 0x3453, 0x0000}, //FSYNM_NVAL (3f_80) +{0x30, 0x81, 0x00F6, 0x0000}, //FSYNFVAL_MSB (3f_81) +{0x30, 0x82, 0x8800, 0x0000}, //FSYNFVAL_LSB (3f_82) +{0x30, 0x83, 0x000F, 0x0000}, //FSYNRVAL_MSB (3f_83) +{0x30, 0x84, 0xB5E0, 0x0000}, //FSYNRVAL_LSB (3f_84) +{0x30, 0x85, 0x0000, 0x0400}, //FSYNTST (3f_85) + +{0x40, 0x80, 0x4C00, 0x0000}, //ANMUXSEL (40_80) +{0x40, 0x81, 0x4000, 0x0000}, //DGMUXCTRL (40_81) +{0x40, 0x82, 0x7800, 0xC000}, //RCKINCTRL (40_82) +{0x40, 0x84, 0x0020, 0x0000}, //CHRCKSEL (40_84) + +{-1, 0, 0, 0}, +}; + +int bdk_vsc7224_modeset(int twsi_id, int unit, int xfi_mode){ + bdk_node_t node=0; + uint8_t dev_addr=0x10 + unit; + uint16_t internal_addr=0x7F; + uint16_t page=0; + int num_bytes=2; + int ia_width_bytes=1; + uint64_t data=0; + int val=0; + int ret = 0, r=0; + uint16_t reg = 0; + + if(xfi_mode==0){ + printf("XFI Mode Retimer\n"); + }else{ + printf("XFI Mode Redriver\n"); + } + + while(xfiregmap[r].page != -1){ + page = xfiregmap[r].page; + reg = xfiregmap[r].reg; + if(xfi_mode==0){ + data = xfiregmap[r].retimer; + }else{ + data = xfiregmap[r].redriver; + } + ret = bdk_twsix_write_ia(node, twsi_id, dev_addr, internal_addr, num_bytes, ia_width_bytes, (uint64_t)page); + if(ret !=0){ + printf("XFI init Error\n"); + break; + } + ret = bdk_twsix_write_ia(node, twsi_id, dev_addr, reg, num_bytes, ia_width_bytes, data); + if(ret !=0){ + printf("XFI init Error\n"); + break; + } + val = bdk_twsix_read_ia(node, twsi_id, dev_addr, reg, num_bytes, ia_width_bytes); + if(val == -1){ + printf("XFI Read Reg Failed @ page:reg :: %2X:%2X \n",page, reg); + break; + }else{ + xfi_printf(" Page: reg: data: val :: %2X:%2X:%04X:%04X\n", page, reg, (uint16_t)data, val); + } + r++; + } + + return ret; +} + + +int bdk_vsc7224_regmap_modeget(int twsi_id, int unit){ + bdk_node_t node=0; + uint8_t dev_addr=0x10 + unit; + uint16_t internal_addr=0x7F; + uint16_t page=0; + int num_bytes=2; + int ia_width_bytes=1; + //uint64_t data=0; + uint16_t reg = 0; + int ret = 0, r=0; + int data; + + printf("\n===========================================\n"); + printf("Page :Reg :Value :Retimer :Redriver\n"); + printf("===========================================\n"); + while(xfiregmap[r].page != -1){ + page = xfiregmap[r].page; + reg = xfiregmap[r].reg; + + ret = bdk_twsix_write_ia(node, twsi_id, dev_addr, internal_addr, num_bytes, ia_width_bytes, (uint64_t)page); + if(ret !=0){ + printf("XFI init Error\n"); + break; + } + data = bdk_twsix_read_ia(node, twsi_id, dev_addr, reg, num_bytes, ia_width_bytes); + if(data == -1){ + printf("XFI Read Reg Failed @ page:reg :: %2X:%2X \n",page, reg); + break; + } + printf(" %02X: %02X: %04X: %04X: %04X\n", page, reg, (uint16_t)data, xfiregmap[r].retimer, xfiregmap[r].redriver); + r++; + } + printf("=======================================\n"); + + return ret; +} + +int bdk_vsc7224_wp_regs(int twsi_id, int unit, int xfi_wp){ + bdk_node_t node=0; + uint8_t dev_addr=0x10 + unit; + uint16_t internal_addr=0x7E; + uint16_t data=0x0000; + int num_bytes=2; + int ia_width_bytes=1; + int ret =0; + + if(xfi_wp == 1){ + data = 0x0000; + }else{ + data = 0xFFFF; + } + + ret = bdk_twsix_write_ia(node, twsi_id, dev_addr, internal_addr, num_bytes, ia_width_bytes, (uint64_t)data); + if(ret !=0){ + printf("XFI VSC7224 Write Protect Error\n"); + } + + return ret; +} + +int bdk_vsc7224_set_reg(int twsi_id, int unit, int page, int reg, int val){ + bdk_node_t node=0; + uint8_t dev_addr=0x10 + unit; + uint16_t internal_addr = reg; + int num_bytes=2; + int ia_width_bytes=1; + int ret=0; + + xfi_printf(" Unit: Page: reg: val :: %02x:%2X:%2X:%04X\n", unit, page, reg, val & 0xFFFF); + ret = bdk_twsix_write_ia(node, twsi_id, dev_addr, 0x7F, num_bytes, ia_width_bytes, (uint64_t)(page & 0xFF)); + if (ret) { + printf("XFI VSC7224 TWSI Set Page Register Error\n"); + } + + ret = bdk_twsix_write_ia(node, twsi_id, dev_addr, internal_addr, num_bytes, ia_width_bytes, (uint64_t)(val & 0xFFFF)); + if (ret) { + printf("XFI VSC7224 TWSI Set Register Error\n"); + } + + return ret; +} + +int bdk_vsc7224_debug(int _debug){ + debug =_debug; + return 0; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse.c b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse.c new file mode 100644 index 0000000000..e1ef6b0f2d --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy-vetesse.c @@ -0,0 +1,372 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <libbdk-hal/if/bdk-if.h> +#include <libbdk-hal/bdk-mdio.h> +#include <libbdk-hal/bdk-qlm.h> + +static bool LOOP_INTERNAL = false; +static bool LOOP_EXTERNAL = false; + +static uint8_t patch_arr[] = { + 0x44, 0x83, 0x02, 0x42, 0x12, 0x02, 0x44, 0x93, 0x02, 0x44, + 0xca, 0x02, 0x44, 0x4d, 0x02, 0x43, 0xef, 0xed, 0xff, 0xe5, + 0xfc, 0x54, 0x38, 0x64, 0x20, 0x70, 0x08, 0x65, 0xff, 0x70, + 0x04, 0xed, 0x44, 0x80, 0xff, 0x22, 0x8f, 0x19, 0x7b, 0xbb, + 0x7d, 0x0e, 0x7f, 0x04, 0x12, 0x3d, 0xd7, 0xef, 0x4e, 0x60, + 0x03, 0x02, 0x41, 0xf9, 0xe4, 0xf5, 0x1a, 0x74, 0x01, 0x7e, + 0x00, 0xa8, 0x1a, 0x08, 0x80, 0x05, 0xc3, 0x33, 0xce, 0x33, + 0xce, 0xd8, 0xf9, 0xff, 0xef, 0x55, 0x19, 0x70, 0x03, 0x02, + 0x41, 0xed, 0x85, 0x1a, 0xfb, 0x7b, 0xbb, 0xe4, 0xfd, 0xff, + 0x12, 0x3d, 0xd7, 0xef, 0x4e, 0x60, 0x03, 0x02, 0x41, 0xed, + 0xe5, 0x1a, 0x54, 0x02, 0x75, 0x1d, 0x00, 0x25, 0xe0, 0x25, + 0xe0, 0xf5, 0x1c, 0xe4, 0x78, 0xc5, 0xf6, 0xd2, 0x02, 0x12, + 0x41, 0xfa, 0x7b, 0xff, 0x7d, 0x12, 0x7f, 0x07, 0x12, 0x3d, + 0xd7, 0xef, 0x4e, 0x60, 0x03, 0x02, 0x41, 0xe7, 0xc2, 0x02, + 0x74, 0xc7, 0x25, 0x1a, 0xf9, 0x74, 0xe7, 0x25, 0x1a, 0xf8, + 0xe6, 0x27, 0xf5, 0x1b, 0xe5, 0x1d, 0x24, 0x5b, 0x12, 0x44, + 0x2a, 0x12, 0x3e, 0xda, 0x7b, 0xfc, 0x7d, 0x11, 0x7f, 0x07, + 0x12, 0x3d, 0xd7, 0x78, 0xcc, 0xef, 0xf6, 0x78, 0xc1, 0xe6, + 0xfe, 0xef, 0xd3, 0x9e, 0x40, 0x06, 0x78, 0xcc, 0xe6, 0x78, + 0xc1, 0xf6, 0x12, 0x41, 0xfa, 0x7b, 0xec, 0x7d, 0x12, 0x7f, + 0x07, 0x12, 0x3d, 0xd7, 0x78, 0xcb, 0xef, 0xf6, 0xbf, 0x07, + 0x06, 0x78, 0xc3, 0x76, 0x1a, 0x80, 0x1f, 0x78, 0xc5, 0xe6, + 0xff, 0x60, 0x0f, 0xc3, 0xe5, 0x1b, 0x9f, 0xff, 0x78, 0xcb, + 0xe6, 0x85, 0x1b, 0xf0, 0xa4, 0x2f, 0x80, 0x07, 0x78, 0xcb, + 0xe6, 0x85, 0x1b, 0xf0, 0xa4, 0x78, 0xc3, 0xf6, 0xe4, 0x78, + 0xc2, 0xf6, 0x78, 0xc2, 0xe6, 0xff, 0xc3, 0x08, 0x96, 0x40, + 0x03, 0x02, 0x41, 0xd1, 0xef, 0x54, 0x03, 0x60, 0x33, 0x14, + 0x60, 0x46, 0x24, 0xfe, 0x60, 0x42, 0x04, 0x70, 0x4b, 0xef, + 0x24, 0x02, 0xff, 0xe4, 0x33, 0xfe, 0xef, 0x78, 0x02, 0xce, + 0xa2, 0xe7, 0x13, 0xce, 0x13, 0xd8, 0xf8, 0xff, 0xe5, 0x1d, + 0x24, 0x5c, 0xcd, 0xe5, 0x1c, 0x34, 0xf0, 0xcd, 0x2f, 0xff, + 0xed, 0x3e, 0xfe, 0x12, 0x44, 0x6a, 0x7d, 0x11, 0x80, 0x0b, + 0x78, 0xc2, 0xe6, 0x70, 0x04, 0x7d, 0x11, 0x80, 0x02, 0x7d, + 0x12, 0x7f, 0x07, 0x12, 0x3e, 0x9a, 0x8e, 0x1e, 0x8f, 0x1f, + 0x80, 0x03, 0xe5, 0x1e, 0xff, 0x78, 0xc5, 0xe6, 0x06, 0x24, + 0xcd, 0xf8, 0xa6, 0x07, 0x78, 0xc2, 0x06, 0xe6, 0xb4, 0x1a, + 0x0a, 0xe5, 0x1d, 0x24, 0x5c, 0x12, 0x44, 0x2a, 0x12, 0x3e, + 0xda, 0x78, 0xc5, 0xe6, 0x65, 0x1b, 0x70, 0x82, 0x75, 0xdb, + 0x20, 0x75, 0xdb, 0x28, 0x12, 0x44, 0x42, 0x12, 0x44, 0x42, + 0xe5, 0x1a, 0x12, 0x44, 0x35, 0xe5, 0x1a, 0xc3, 0x13, 0x12, + 0x44, 0x35, 0x78, 0xc5, 0x16, 0xe6, 0x24, 0xcd, 0xf8, 0xe6, + 0xff, 0x7e, 0x08, 0x1e, 0xef, 0xa8, 0x06, 0x08, 0x80, 0x02, + 0xc3, 0x13, 0xd8, 0xfc, 0xfd, 0xc4, 0x33, 0x54, 0xe0, 0xf5, + 0xdb, 0xef, 0xa8, 0x06, 0x08, 0x80, 0x02, 0xc3, 0x13, 0xd8, + 0xfc, 0xfd, 0xc4, 0x33, 0x54, 0xe0, 0x44, 0x08, 0xf5, 0xdb, + 0xee, 0x70, 0xd8, 0x78, 0xc5, 0xe6, 0x70, 0xc8, 0x75, 0xdb, + 0x10, 0x02, 0x40, 0xfd, 0x78, 0xc2, 0xe6, 0xc3, 0x94, 0x17, + 0x50, 0x0e, 0xe5, 0x1d, 0x24, 0x62, 0x12, 0x42, 0x08, 0xe5, + 0x1d, 0x24, 0x5c, 0x12, 0x42, 0x08, 0x20, 0x02, 0x03, 0x02, + 0x40, 0x76, 0x05, 0x1a, 0xe5, 0x1a, 0xc3, 0x94, 0x04, 0x50, + 0x03, 0x02, 0x40, 0x3a, 0x22, 0xe5, 0x1d, 0x24, 0x5c, 0xff, + 0xe5, 0x1c, 0x34, 0xf0, 0xfe, 0x12, 0x44, 0x6a, 0x22, 0xff, + 0xe5, 0x1c, 0x34, 0xf0, 0xfe, 0x12, 0x44, 0x6a, 0x22, 0xd2, + 0x00, 0x75, 0xfb, 0x03, 0xab, 0x7e, 0xaa, 0x7d, 0x7d, 0x19, + 0x7f, 0x03, 0x12, 0x3e, 0xda, 0xe5, 0x7e, 0x54, 0x0f, 0x24, + 0xf3, 0x60, 0x03, 0x02, 0x42, 0xb9, 0x12, 0x44, 0xa3, 0x12, + 0x44, 0xaa, 0xd8, 0xfb, 0xff, 0x20, 0xe2, 0x2a, 0x13, 0x92, + 0x04, 0xef, 0xa2, 0xe1, 0x92, 0x03, 0x30, 0x04, 0x1f, 0xe4, + 0xf5, 0x10, 0xe5, 0x10, 0x24, 0x17, 0xfd, 0x7b, 0x54, 0x7f, + 0x04, 0x12, 0x3d, 0xd7, 0x74, 0x25, 0x25, 0x10, 0xf8, 0xa6, + 0x07, 0x05, 0x10, 0xe5, 0x10, 0xc3, 0x94, 0x02, 0x40, 0xe4, + 0x12, 0x44, 0xa3, 0x12, 0x44, 0xaa, 0xd8, 0xfb, 0x54, 0x05, + 0x64, 0x04, 0x70, 0x27, 0x78, 0xc4, 0xe6, 0x78, 0xc6, 0xf6, + 0xe5, 0x7d, 0xff, 0x33, 0x95, 0xe0, 0xef, 0x54, 0x0f, 0x78, + 0xc4, 0xf6, 0x12, 0x42, 0xcf, 0x20, 0x04, 0x0c, 0x12, 0x44, + 0xa3, 0x12, 0x44, 0xaa, 0xd8, 0xfb, 0x13, 0x92, 0x05, 0x22, + 0xc2, 0x05, 0x22, 0x12, 0x44, 0xa3, 0x12, 0x44, 0xaa, 0xd8, + 0xfb, 0x54, 0x05, 0x64, 0x05, 0x70, 0x1e, 0x78, 0xc4, 0x7d, + 0xb8, 0x12, 0x42, 0xc5, 0x78, 0xc1, 0x7d, 0x74, 0x12, 0x42, + 0xc5, 0xe4, 0x78, 0xc1, 0xf6, 0x22, 0x7b, 0x01, 0x7a, 0x00, + 0x7d, 0xee, 0x7f, 0x92, 0x12, 0x38, 0xbd, 0x22, 0xe6, 0xfb, + 0x7a, 0x00, 0x7f, 0x92, 0x12, 0x38, 0xbd, 0x22, 0x78, 0xc1, + 0xe6, 0xfb, 0x7a, 0x00, 0x7d, 0x74, 0x7f, 0x92, 0x12, 0x38, + 0xbd, 0xe4, 0x78, 0xc1, 0xf6, 0xf5, 0x11, 0x74, 0x01, 0x7e, + 0x00, 0xa8, 0x11, 0x08, 0x80, 0x05, 0xc3, 0x33, 0xce, 0x33, + 0xce, 0xd8, 0xf9, 0xff, 0x78, 0xc4, 0xe6, 0xfd, 0xef, 0x5d, + 0x60, 0x44, 0x85, 0x11, 0xfb, 0xe5, 0x11, 0x54, 0x02, 0x25, + 0xe0, 0x25, 0xe0, 0xfe, 0xe4, 0x24, 0x5b, 0xfb, 0xee, 0x12, + 0x44, 0x2d, 0x12, 0x3e, 0xda, 0x7b, 0x40, 0x7d, 0x11, 0x7f, + 0x07, 0x12, 0x3d, 0xd7, 0x74, 0xc7, 0x25, 0x11, 0xf8, 0xa6, + 0x07, 0x7b, 0x11, 0x7d, 0x12, 0x7f, 0x07, 0x12, 0x3d, 0xd7, + 0xef, 0x4e, 0x60, 0x09, 0x74, 0xe7, 0x25, 0x11, 0xf8, 0x76, + 0x04, 0x80, 0x07, 0x74, 0xe7, 0x25, 0x11, 0xf8, 0x76, 0x0a, + 0x05, 0x11, 0xe5, 0x11, 0xc3, 0x94, 0x04, 0x40, 0x9a, 0x78, + 0xc6, 0xe6, 0x70, 0x15, 0x78, 0xc4, 0xe6, 0x60, 0x10, 0x75, + 0xd9, 0x38, 0x75, 0xdb, 0x10, 0x7d, 0xfe, 0x12, 0x43, 0x7d, + 0x7d, 0x76, 0x12, 0x43, 0x7d, 0x79, 0xc6, 0xe7, 0x78, 0xc4, + 0x66, 0xff, 0x60, 0x03, 0x12, 0x40, 0x25, 0x78, 0xc4, 0xe6, + 0x70, 0x09, 0xfb, 0xfa, 0x7d, 0xfe, 0x7f, 0x8e, 0x12, 0x38, + 0xbd, 0x22, 0x7b, 0x01, 0x7a, 0x00, 0x7f, 0x8e, 0x12, 0x38, + 0xbd, 0x22, 0xe4, 0xf5, 0x19, 0x74, 0x25, 0x25, 0x19, 0xf8, + 0xe6, 0x64, 0x03, 0x60, 0x51, 0xe5, 0x19, 0x24, 0x17, 0xfd, + 0x7b, 0xeb, 0x7f, 0x04, 0x12, 0x3d, 0xd7, 0x8f, 0xfb, 0x7b, + 0x22, 0x7d, 0x18, 0x7f, 0x06, 0x12, 0x3d, 0xd7, 0xef, 0x64, + 0x01, 0x4e, 0x60, 0x1c, 0x7d, 0x1c, 0xe4, 0xff, 0x12, 0x3e, + 0x9a, 0xef, 0x54, 0x1b, 0x64, 0x0a, 0x70, 0x15, 0x7b, 0xcc, + 0x7d, 0x10, 0xff, 0x12, 0x3d, 0xd7, 0xef, 0x64, 0x01, 0x4e, + 0x70, 0x07, 0x12, 0x44, 0xb1, 0x7b, 0x03, 0x80, 0x0a, 0x12, + 0x44, 0xb1, 0x74, 0x25, 0x25, 0x19, 0xf8, 0xe6, 0xfb, 0x7a, + 0x00, 0x7d, 0x54, 0x12, 0x38, 0xbd, 0x05, 0x19, 0xe5, 0x19, + 0xc3, 0x94, 0x02, 0x40, 0x9c, 0x22, 0xe5, 0x7e, 0x30, 0xe5, + 0x35, 0x30, 0xe4, 0x0b, 0x7b, 0x02, 0x7d, 0x33, 0x7f, 0x35, + 0x12, 0x36, 0x29, 0x80, 0x10, 0x7b, 0x01, 0x7d, 0x33, 0x7f, + 0x35, 0x12, 0x36, 0x29, 0x90, 0x47, 0xd2, 0xe0, 0x44, 0x04, + 0xf0, 0x90, 0x47, 0xd2, 0xe0, 0x54, 0xf7, 0xf0, 0x90, 0x47, + 0xd1, 0xe0, 0x44, 0x10, 0xf0, 0x7b, 0x05, 0x7d, 0x84, 0x7f, + 0x86, 0x12, 0x36, 0x29, 0x22, 0xfb, 0xe5, 0x1c, 0x34, 0xf0, + 0xfa, 0x7d, 0x10, 0x7f, 0x07, 0x22, 0x54, 0x01, 0xc4, 0x33, + 0x54, 0xe0, 0xf5, 0xdb, 0x44, 0x08, 0xf5, 0xdb, 0x22, 0xf5, + 0xdb, 0x75, 0xdb, 0x08, 0xf5, 0xdb, 0x75, 0xdb, 0x08, 0x22, + 0xe5, 0x7e, 0x54, 0x0f, 0x64, 0x01, 0x70, 0x0d, 0xe5, 0x7e, + 0x30, 0xe4, 0x08, 0x90, 0x47, 0xd0, 0xe0, 0x44, 0x02, 0xf0, + 0x22, 0x90, 0x47, 0xd0, 0xe0, 0x54, 0xfd, 0xf0, 0x22, 0xab, + 0x07, 0xaa, 0x06, 0x7d, 0x10, 0x7f, 0x07, 0x12, 0x3e, 0xda, + 0x7b, 0xff, 0x7d, 0x10, 0x7f, 0x07, 0x12, 0x3d, 0xd7, 0xef, + 0x4e, 0x60, 0xf3, 0x22, 0x12, 0x44, 0xc5, 0x12, 0x44, 0xbb, + 0x90, 0x47, 0xfa, 0xe0, 0x54, 0xf8, 0x44, 0x02, 0xf0, 0x22, + 0x30, 0x04, 0x03, 0x12, 0x43, 0x87, 0x78, 0xc4, 0xe6, 0xff, + 0x60, 0x03, 0x12, 0x40, 0x25, 0x22, 0xe5, 0x7e, 0xae, 0x7d, + 0x78, 0x04, 0x22, 0xce, 0xa2, 0xe7, 0x13, 0xce, 0x13, 0x22, + 0xe5, 0x19, 0x24, 0x17, 0x54, 0x1f, 0x44, 0x80, 0xff, 0x22, + 0xe4, 0x78, 0xc4, 0xf6, 0xc2, 0x05, 0x78, 0xc1, 0xf6, 0x22, + 0xc2, 0x04, 0xc2, 0x03, 0x22, 0x22 +}; + +/** + * Setup Vitesse PHYs + * This function sets up one port in a Vitesse VSC8574 for + * either SGMII or QSGMII + */ +static void setup_vitesse_phy(bdk_node_t node, int mdio_bus, int phy_addr, bool qsgmii) +{ + // Select "G" registers + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x10); + // Reg 19G, bit 15:14 + // 0 = SGMII + // 1 = QSGMII + int reg19 = bdk_mdio_read(node, mdio_bus, phy_addr, 19); + int reg18; + if (qsgmii) + { + // QSGMII + reg19 = (reg19 & ~(3 << 14)) | (1 << 14); + reg18 = 0x80e0; + } + else + { + // SGMII + reg19 = (reg19 & ~(3 << 14)) | (0 << 14); + reg18 = 0x80f0; + } + bdk_mdio_write(node, mdio_bus, phy_addr, 19, reg19); + // Write 18G, change all 4 ports + bdk_mdio_write(node, mdio_bus, phy_addr, 18, reg18); + // Select main registers + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0); + // Reg23, 10:8 Select copper + int reg23 = bdk_mdio_read(node, mdio_bus, phy_addr, 23); + reg23 = (reg23 & ~(7 << 8)) | (0 << 8); + bdk_mdio_write(node, mdio_bus, phy_addr, 23, reg23); + // Reg0, Reset + int reg0 = bdk_mdio_read(node, mdio_bus, phy_addr, 0); + reg0 |= (1 << 15); + bdk_mdio_write(node, mdio_bus, phy_addr, 0, reg0); + // Reg 16E3, bit 7 auto negotiation + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 3); + bdk_mdio_write(node, mdio_bus, phy_addr, 16, 0x80); + // Select main registers + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0); + // Near end loopback (Thunder side) + if (LOOP_INTERNAL) + { + reg0 = bdk_mdio_read(node, mdio_bus, phy_addr, 0); + reg0 |= (1 << 14); + bdk_mdio_write(node, mdio_bus, phy_addr, 0, reg0); + } + + // Far end loopback (External side) + if (LOOP_EXTERNAL) + { + reg23 = bdk_mdio_read(node, mdio_bus, phy_addr, 23); + reg23 |= (1 << 3); + bdk_mdio_write(node, mdio_bus, phy_addr, 23, reg23); + } +} + +static void wr_masked(bdk_node_t node, int mdio_bus, int phy_addr, int reg, int value, int mask) +{ + int nmask = ~mask; + int old = bdk_mdio_read(node, mdio_bus, phy_addr, reg); + int vmask = value & mask; + int newv = old & nmask; + newv = newv | vmask; + bdk_mdio_write(node, mdio_bus, phy_addr, reg, newv); +} + +static void vitesse_program(bdk_node_t node, int mdio_bus, int phy_addr) +{ + printf("Programming Vitesse PHY at address %d\n", phy_addr); + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0010); + bdk_mdio_write(node, mdio_bus, phy_addr, 18, 0x800f); + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0010); + + int reg18g = bdk_mdio_read(node, mdio_bus, phy_addr, 18); + int timeout = 10; + while ((reg18g & (1<<15)) && (timeout > 0)) + { + bdk_wait_usec(100000); + reg18g = bdk_mdio_read(node, mdio_bus, phy_addr, 18); + timeout = timeout - 1; + } + if (timeout == 0) + bdk_error("Vetesse: Timeout waiting for complete\n"); + + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0000); + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0010); + wr_masked(node, mdio_bus, phy_addr, 12, 0x0000, 0x0800); + bdk_mdio_write(node, mdio_bus, phy_addr, 9, 0x005b); + bdk_mdio_write(node, mdio_bus, phy_addr, 10, 0x005b); + wr_masked(node, mdio_bus, phy_addr, 12, 0x0800, 0x0800); + bdk_mdio_write(node, mdio_bus, phy_addr, 18, 0x800f); + wr_masked(node, mdio_bus, phy_addr, 0, 0x0000, 0x8000); + bdk_mdio_write(node, mdio_bus, phy_addr, 18, 0x0000); + wr_masked(node, mdio_bus, phy_addr, 12, 0x0000, 0x0800); + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x10); + bdk_mdio_write(node, mdio_bus, phy_addr, 0, 0x7009); + bdk_mdio_write(node, mdio_bus, phy_addr, 12, 0x5002); + bdk_mdio_write(node, mdio_bus, phy_addr, 11, 0x0000); + + for (unsigned int i=0; i<sizeof(patch_arr); i++) + { + int d = 0x5000 | patch_arr[i]; + bdk_mdio_write(node, mdio_bus, phy_addr, 12, d); + } + bdk_mdio_write(node, mdio_bus, phy_addr, 12, 0x0000); + + bdk_mdio_write(node, mdio_bus, phy_addr, 3, 0x3eb7); + bdk_mdio_write(node, mdio_bus, phy_addr, 4, 0x4012); + bdk_mdio_write(node, mdio_bus, phy_addr, 12, 0x0100); + bdk_mdio_write(node, mdio_bus, phy_addr, 0, 0x4018); + bdk_mdio_write(node, mdio_bus, phy_addr, 0, 0xc018); + + // below verifies CRC is correct in 8051 RAM. CRC is 16-bit. + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0001); + bdk_mdio_write(node, mdio_bus, phy_addr, 25, 0x4000); + bdk_mdio_write(node, mdio_bus, phy_addr, 26, sizeof(patch_arr) + 1); + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0010); + bdk_mdio_write(node, mdio_bus, phy_addr, 18, 0x8008); + + reg18g = bdk_mdio_read(node, mdio_bus, phy_addr, 18); + timeout = 10; + while ((reg18g & (1<<15)) && (timeout > 0)) + { + bdk_wait_usec(100000); + reg18g = bdk_mdio_read(node, mdio_bus, phy_addr, 18); + timeout = timeout - 1; + } + if (timeout == 0) + bdk_error("Vetesse: Timeout waiting for complete\n"); + + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0001); + + int crc_calculated = bdk_mdio_read(node, mdio_bus, phy_addr, 25); + if (crc_calculated != 0xB7C2) + printf("8051 crc_calculated = 0x%x, expected_crc = 0x%x\n", crc_calculated, 0xB7C2); + + bdk_mdio_write(node, mdio_bus, phy_addr, 31, 0x0000); +} + +//static void vetesse_setup(bdk_node_t node, int qlm, int mdio_bus, int phy_addr) +int bdk_if_phy_vetesse_setup(bdk_node_t node, int qlm, int mdio_bus, int phy_addr) +{ + /* Check if the PHY is Vetesse PHY we expect */ + int phy_status = bdk_mdio_read(node, mdio_bus, phy_addr, BDK_MDIO_PHY_REG_ID1); + if (phy_status != 0x0007) + return 0; + + /* Check that the GSER mode is SGMII or QSGMII */ + bdk_qlm_modes_t qlm_mode = bdk_qlm_get_mode(node, qlm); + if ((qlm_mode != BDK_QLM_MODE_SGMII_1X1) && + (qlm_mode != BDK_QLM_MODE_SGMII_2X1) && + (qlm_mode != BDK_QLM_MODE_SGMII_4X1) && + (qlm_mode != BDK_QLM_MODE_QSGMII_4X1)) + return 0; + + /* Program the Vetesse PHY */ + vitesse_program(node, mdio_bus, phy_addr); + + /* Switch the Vitesse PHY to the correct mode */ + bool is_qsgmii = (qlm_mode == BDK_QLM_MODE_QSGMII_4X1); + if (is_qsgmii) + { + for (int port = 0; port < 4; port++) + setup_vitesse_phy(node, mdio_bus, phy_addr + port, true); + } + else + setup_vitesse_phy(node, mdio_bus, phy_addr, false); + return 0; +} + +#if 0 +int bdk_if_phy_vetesse_setup(bdk_node_t node) +{ + for (int bgx = 0; bgx < 4; bgx++) + { + int port = 0; + int phy_addr = bdk_config_get_int(BDK_CONFIG_PHY_ADDRESS, node, bgx, port); + if (phy_addr != -1) + { + int node = (phy_addr >> 24) & 0xff; + int mdio_bus = (phy_addr >> 8) & 0xff; + int mdio_addr = phy_addr & 0xff; + if (node == 0xff) + node = bdk_numa_local(); + if ((phy_addr & BDK_IF_PHY_TYPE_MASK) == BDK_IF_PHY_MDIO) + { + int qlm = bdk_qlm_get(node, BDK_IF_BGX, bgx, port); + if (qlm != -1) + vetesse_setup(node, qlm, mdio_bus, mdio_addr); + } + } + } + return 0; +} +#endif diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy.c b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy.c new file mode 100644 index 0000000000..5b02eff0cd --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/if/bdk-if-phy.c @@ -0,0 +1,445 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include <libbdk-hal/if/bdk-if.h> +#include <libbdk-hal/bdk-config.h> +#include <libbdk-hal/bdk-mdio.h> +#include <libbdk-hal/bdk-qlm.h> +#include <libbdk-hal/bdk-twsi.h> + +/** + * Called when the PHY is connected through TWSI + * + * @param dev_node Node the ethernet device is on + * @param phy_addr Encoded address, see bdk-if.h for format + * + * @return Link status + */ +static bdk_if_link_t __bdk_if_phy_get_twsi(bdk_node_t dev_node, int phy_addr) +{ + /* For TWSI: + Bits[31:24]: Node ID, 0xff for device node + Bits[23:16]: TWSI internal address width in bytes (0-2) + Bits[15:12]: 2=TWSI + Bits[11:8]: TWSI bus number + Bits[7:0]: TWSI address */ + int node = (phy_addr >> 24) & 0xff; + int twsi_ia_width = (phy_addr >> 16) & 0xff; + int twsi_bus = (phy_addr >> 8) & 0xf; + int twsi_addr = phy_addr & 0xff; + if (node == 0xff) + node = dev_node; + + bdk_if_link_t result; + result.u64 = 0; + + /* This is from the Avago SFP 1G Module data sheet + Register 17 (Extended Status 1) */ + int64_t phy_status = bdk_twsix_read_ia(node, twsi_bus, twsi_addr, 17, 2, twsi_ia_width); + if (phy_status != -1) + { + int speed = (phy_status >> 14)& 3; + int duplex = (phy_status >> 13)& 1; + int resolved = (phy_status >> 11)& 1; + int link = (phy_status >> 10)& 1; + if (resolved) + { + result.s.up = link; + result.s.full_duplex = duplex; + switch (speed) + { + case 0: /* 10 Mbps */ + result.s.speed = 10; + break; + case 1: /* 100 Mbps */ + result.s.speed = 100; + break; + case 2: /* 1 Gbps */ + result.s.speed = 1000; + break; + case 3: /* Illegal */ + result.u64 = 0; + break; + } + } + } + + return result; +} + +/** + * Read the status of a PHY + * + * @param dev_node Node the ethernet device is on + * @param phy_addr Encoded PHY address, see bdk-if.h for format + * + * @return Link status + */ +bdk_if_link_t __bdk_if_phy_get(bdk_node_t dev_node, int phy_addr) +{ + int node = (phy_addr >> 24) & 0xff; + int mdio_bus = (phy_addr >> 8) & 0xff; + int mdio_addr = phy_addr & 0xff; + if (node == 0xff) + node = dev_node; + int phy_status; + bdk_if_link_t result; + result.u64 = 0; + + /* PHY address of -1 menas there is no PHY and we should have never + gotten here */ + if (phy_addr == -1) + return result; + + /* A PHY address with the special value 0x1000 represents a PHY we can't + connect to through MDIO which is assumed to be at 1Gbps */ + if (phy_addr == BDK_IF_PHY_FIXED_1GB) + { + result.s.up = 1; + result.s.full_duplex = 1; + result.s.speed = 1000; + return result; + } + + /* A PHY address with the special value 0x1001 represents a PHY we can't + connect to through MDIO which is assumed to be at 100Mbps */ + if (phy_addr == BDK_IF_PHY_FIXED_100MB) + { + result.s.up = 1; + result.s.full_duplex = 1; + result.s.speed = 100; + return result; + } + + /* Check for a PHY connected through TWSI */ + if ((phy_addr & BDK_IF_PHY_TYPE_MASK) == BDK_IF_PHY_TWSI) + return __bdk_if_phy_get_twsi(dev_node, phy_addr); + + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, BDK_MDIO_PHY_REG_ID1); + if ((phy_status <= 0) || (phy_status == 0xffff)) + return result; + + switch (phy_status) + { + case 0x0141: /* Marvell */ + { + + /* This code assumes we are using a Marvell Gigabit PHY. All the + speed information can be read from register 17 in one go. Somebody + using a different PHY will need to handle it above in the board + specific area */ + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 17); + if (phy_status < 0) + return result; + + /* If the resolve bit 11 isn't set, see if autoneg is turned off + (bit 12, reg 0). The resolve bit doesn't get set properly when + autoneg is off, so force it */ + if ((phy_status & (1<<11)) == 0) + { + bdk_mdio_phy_reg_control_t control; + int phy_c = bdk_mdio_read(node, mdio_bus, mdio_addr, BDK_MDIO_PHY_REG_CONTROL); + if (phy_c < 0) + return result; + control.u16 = phy_c; + if (control.s.autoneg_enable == 0) + phy_status |= 1<<11; + } + + /* Only return a link if the PHY has finished auto negotiation + and set the resolved bit (bit 11) */ + if (phy_status & (1<<11)) + { + result.s.up = 1; + result.s.full_duplex = ((phy_status>>13)&1); + switch ((phy_status>>14)&3) + { + case 0: /* 10 Mbps */ + result.s.speed = 10; + break; + case 1: /* 100 Mbps */ + result.s.speed = 100; + break; + case 2: /* 1 Gbps */ + result.s.speed = 1000; + break; + case 3: /* Illegal */ + result.u64 = 0; + break; + } + } + break; + } + case 0x0022: /* Kendin */ + { + /* Register 1Fh - PHY Control */ + /* Micrel KSZ9031RNX, EBB8104 RGMII transceiver */ + /* Reports as "Kendin" in BDK_MDIO_PHY_REG_ID1 */ + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 0x1F); + if (phy_status & (1 << 6)) // Speed Status - 1000Base-T + { + result.s.up = 1; + result.s.speed = 1000; + } + else if (phy_status & (1 << 5)) // Speed Status - 100Base-TX + { + result.s.up = 1; + result.s.speed = 100; + } + else if (phy_status & (1 << 4)) // Speed Status - 10Base-T + { + result.s.up = 1; + result.s.speed = 10; + } + if (phy_status & (1 << 3)) // Duplex Status + { + result.s.full_duplex = 1; + } + break; + } + case 0x0007: /* Vitesse */ + { + /* Auxiliary Control and Status, Address 28 (0x1C) */ + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 0x1c); + result.s.full_duplex = (phy_status>>5)&1; + switch ((phy_status>>3) & 3) + { + case 0: + result.s.speed = 10; + result.s.up = 1; + break; + case 1: + result.s.speed = 100; + result.s.up = 1; + break; + default: + result.s.speed = 1000; + break; + } + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 0x01); + result.s.up = (phy_status>>2)&1; + break; + } + default: /* Treat like Broadcom */ + { + /* Below we are going to read SMI/MDIO register 0x19 which works + on Broadcom parts */ + phy_status = bdk_mdio_read(node, mdio_bus, mdio_addr, 0x19); + if (phy_status < 0) + return result; + + switch ((phy_status>>8) & 0x7) + { + case 0: + result.u64 = 0; + break; + case 1: + result.s.up = 1; + result.s.full_duplex = 0; + result.s.speed = 10; + break; + case 2: + result.s.up = 1; + result.s.full_duplex = 1; + result.s.speed = 10; + break; + case 3: + result.s.up = 1; + result.s.full_duplex = 0; + result.s.speed = 100; + break; + case 4: + result.s.up = 1; + result.s.full_duplex = 1; + result.s.speed = 100; + break; + case 5: + result.s.up = 1; + result.s.full_duplex = 1; + result.s.speed = 100; + break; + case 6: + result.s.up = 1; + result.s.full_duplex = 0; + result.s.speed = 1000; + break; + case 7: + result.s.up = 1; + result.s.full_duplex = 1; + result.s.speed = 1000; + break; + } + break; + } + } + + /* If link is down, return all fields as zero. */ + if (!result.s.up) + result.u64 = 0; + + return result; +} + +/** + * PHY XS initialization, primarily for RXAUI + * + * @param dev_node Node the ethernet device is on + * @param phy_addr Encoded PHY address, see bdk-if.h for format + * + * @return none + */ +void __bdk_if_phy_xs_init(bdk_node_t dev_node, int phy_addr) +{ + /* This code only supports PHYs connected through MDIO */ + if ((phy_addr & BDK_IF_PHY_TYPE_MASK) != BDK_IF_PHY_MDIO) + return; + + int node = (phy_addr >> 24) & 0xff; + int mdio_bus = (phy_addr >> 8) & 0xff; + int mdio_addr = phy_addr & 0xff; + if (node == 0xff) + node = dev_node; + + /* Read the PMA/PMD Device Identifier (1.2, 1.3) + OUI is spread across both registers */ + int dev_addr = 1; + int reg_addr = 2; + int phy_id1 = bdk_mdio_45_read(node, mdio_bus, mdio_addr, dev_addr, reg_addr); + if (phy_id1 == -1) + return; + reg_addr = 3; + int phy_id2 = bdk_mdio_45_read(node, mdio_bus, mdio_addr, dev_addr, reg_addr); + if (phy_id2 == -1) + return; + int model_number = (phy_id2 >> 4) & 0x3F; + int oui = phy_id1; + oui <<= 6; + oui |= (phy_id2 >> 10) & 0x3F; + switch (oui) + { + case 0x5016: /* Marvell */ + if (model_number == 9) /* 88X3140/3120 */ + { + BDK_TRACE(BGX, "N%d.MDIO%d.%d: Performing PHY reset on Marvell RXAUI PHY\n", + node, mdio_bus, mdio_addr); + dev_addr = 4; + reg_addr = 0; + /* Write bit 15, Software Reset, in PHY XS Control 1 (4.0). On CN78xx, + sometimes the PHY/BGX gets stuck in local fault mode, link never comes up, + and this appears to clear it up. Haven't seen this on CN81xx or T88, + but the reset seems like cheap insurance. */ + if (bdk_mdio_45_write(node, mdio_bus, mdio_addr, dev_addr, reg_addr, (1 << 15))) + { + bdk_error("PHY XS: MDIO write to (%d.%d) failed\n", dev_addr, reg_addr); + return; + } + + int reset_pending = 1; + while (reset_pending) + { + reset_pending = bdk_mdio_45_read(node, mdio_bus, mdio_addr, dev_addr, reg_addr); + reset_pending &= (1 << 15); + } + + /* Adjust the RXAUI TX Level for Marvell PHY, per Brendan Metzner + write 5 to register 4.49155 */ + reg_addr = 49155; + if (bdk_mdio_45_write(node, mdio_bus, mdio_addr, dev_addr, reg_addr, 5)) + { + bdk_error("PHY XS: MDIO write to (%d.%d) failed\n", dev_addr, reg_addr); + return; + } + } + break; + + default: /* Unknown PHY, or no PHY present */ + break; + } +} + +int bdk_if_phy_setup(bdk_node_t dev_node) +{ + /* 81xx has only 2 BGX (BGX0-BGX1); BGX2 is RGMII */ + for (int bgx = 0; bgx < 2; bgx++) + { + int port = 0; + int phy_addr = bdk_config_get_int(BDK_CONFIG_PHY_ADDRESS, dev_node, bgx, port); + if (phy_addr != -1) + { + int node = (phy_addr >> 24) & 0xff; + int mdio_bus = (phy_addr >> 8) & 0xff; + int mdio_addr = phy_addr & 0xff; + if (node == 0xff) + node = bdk_numa_local(); + if ((phy_addr & BDK_IF_PHY_TYPE_MASK) == BDK_IF_PHY_MDIO) + { + int qlm = bdk_qlm_get_qlm_num(node, BDK_IF_BGX, bgx, port); + if (qlm == -1) + continue; + + BDK_TRACE(PHY, "N%d.BGX%d.%d: Configuring ...\n", node, bgx, port); + + /* Check PHY id */ + int phy_status_1 = bdk_mdio_read(node, mdio_bus, phy_addr, BDK_MDIO_PHY_REG_ID1); + int phy_status_2 = bdk_mdio_read(node, mdio_bus, phy_addr, BDK_MDIO_PHY_REG_ID2); + + /* Vitesse */ + if (phy_status_1 == 0x0007) + { + if (phy_status_2 == 0x0670) + { + bdk_if_phy_vsc8514_setup(node, qlm, mdio_bus, mdio_addr); + } + else + { + bdk_if_phy_vetesse_setup(node, qlm, mdio_bus, mdio_addr); + } + } + + /* Marvell */ + else if (phy_status_1 == 0x0141) + bdk_if_phy_marvell_setup(node, qlm, mdio_bus, mdio_addr); + else + BDK_TRACE(PHY, "N%d.BGX%d.%d: Unknown PHY %x\n", node, bgx, port, phy_status_1); + } + } + } + return 0; +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-cn81xx.c b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-cn81xx.c new file mode 100644 index 0000000000..303b276a8b --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-cn81xx.c @@ -0,0 +1,1003 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-hal/bdk-qlm.h" +#include "libbdk-hal/qlm/bdk-qlm-common.h" +#include "libbdk-arch/bdk-csrs-bgx.h" +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-pem.h" +#include "libbdk-arch/bdk-csrs-sata.h" +#include "libbdk-arch/bdk-csrs-rst.h" +#include "libbdk-hal/bdk-config.h" +#include "libbdk-hal/qlm/bdk-qlm-errata-cn8xxx.h" +#include "libbdk-hal/bdk-gpio.h" + +/** + * Return the number of QLMs supported for the chip + * + * @return Number of QLMs + */ +int bdk_qlm_get_num(bdk_node_t node) +{ + return 4; /* 4 DLM */ +} + +/** + * Return the number of lanes in a QLM. QLMs normally contain + * 4 lanes, except for chips which only have half of a QLM. + * + * @param qlm QLM to get lanes number for + * + * @return Number of lanes on the QLM + */ +int bdk_qlm_get_lanes(bdk_node_t node, int qlm) +{ + + if ((qlm < 2) && cavium_is_altpkg(CAVIUM_CN81XX)) + return 1; /* DLM0 and DLM1 are a single lane on CN80XX */ + else + return 2; /* DLMs */ +} + +/** + * Lookup the hardware QLM number for a given interface type and index. This + * function will fail with a fatal error if called on invalid interfaces for + * a chip. It returns the QLM number for an interface without checking to + * see if the QLM is in the correct mode. + * + * @param iftype Interface type + * @param interface Interface index number + * + * @return QLM number. Dies on a fatal error on failure. + */ +int bdk_qlm_get_qlm_num(bdk_node_t node, bdk_if_t iftype, int interface, int index) +{ + switch (iftype) + { + case BDK_IF_BGX: + { + int qlm; + switch (interface) + { + case 0: + { + /* This BGX spans two DLMs. The index must be used to + figure out which DLM we are using */ + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(0)); + if (gserx_cfg.s.bgx) + { + if (gserx_cfg.s.bgx_quad) /* 4 lanes together */ + qlm = 0; + else if (gserx_cfg.s.bgx_dual) /* 2 lanes together */ + qlm = (index >= 1) ? 1 : 0; + else /* All lanes independent */ + { + bdk_qlm_modes_t mode = bdk_qlm_get_mode(node, 0); + if (mode == BDK_QLM_MODE_QSGMII_4X1) + qlm = 0; + else if (mode <= BDK_QLM_MODE_PCIE_1X8) + qlm = 1; + else if (cavium_is_altpkg(CAVIUM_CN81XX)) + { + bdk_qlm_modes_t mode1 = bdk_qlm_get_mode(node, 1); + if ((mode1 != BDK_QLM_MODE_QSGMII_4X1) && (index >= 2)) + return -1; + qlm = (index >= 1) ? 1 : 0; + } + else + qlm = (index >= 2) ? 1 : 0; + } + } + else + qlm = 1; + break; + } + case 1: + { + /* This BGX spans two DLMs. The index must be used to + figure out which DLM we are using */ + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(2)); + if (gserx_cfg.s.bgx) + { + if (gserx_cfg.s.bgx_quad) /* 4 lanes together */ + qlm = 2; + else if (gserx_cfg.s.bgx_dual) /* 2 lanes together */ + qlm = (index >= 1) ? 3 : 2; + else /* All lanes independent */ + { + bdk_qlm_modes_t mode = bdk_qlm_get_mode(node, 2); + if (mode == BDK_QLM_MODE_QSGMII_4X1) + qlm = 2; + else if (mode <= BDK_QLM_MODE_PCIE_1X8) + qlm = 1; + else + qlm = (index >= 2) ? 3 : 2; + } + } + else + qlm = 3; + break; + } + default: + return -1; + } + /* Make sure the QLM is powered up and out of reset */ + BDK_CSR_INIT(phy_ctl, node, BDK_GSERX_PHY_CTL(qlm)); + if (phy_ctl.s.phy_pd || phy_ctl.s.phy_reset) + return -1; + /* Make sure the QLM is in BGX mode */ + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(qlm)); + if (gserx_cfg.s.bgx) + return qlm; + else + return -1; + } + case BDK_IF_PCIE: /* PCIe */ + { + switch (interface) + { + case 0: /* PEM0 */ + { + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(0)); + if (gserx_cfg.s.pcie) + return 0; /* PEM0 is on DLM0 */ + else + return -1; /* PEM0 is disabled */ + } + case 1: /* PEM1 */ + { + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(2)); + if (gserx_cfg.s.pcie) + return 2; /* PEM1 is on DLM2 */ + else + return -1; /* PEM1 is disabled */ + } + case 2: /* PEM2 */ + { + BDK_CSR_INIT(pem1_cfg, node, BDK_PEMX_CFG(1)); + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(3)); + if (!pem1_cfg.cn81xx.lanes4 && gserx_cfg.s.pcie) + return 3; /* PEM2 is on DLM3 */ + else + return -1; /* PEM2 is disabled */ + } + default: /* Max of 3 PEMs, 0-2 */ + return -1; + } + } + default: /* Not supported by CN81XX */ + return -1; + } +} + +/** + * Get the mode of a QLM as a human readable string + * + * @param qlm QLM to examine + * + * @return String mode + */ +bdk_qlm_modes_t bdk_qlm_get_mode(bdk_node_t node, int qlm) +{ + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(qlm)); + if (gserx_cfg.s.pcie) + { + switch (qlm) + { + case 0: /* PEM0 */ + { + BDK_CSR_INIT(pemx_cfg, node, BDK_PEMX_CFG(0)); + if (cavium_is_altpkg(CAVIUM_CN81XX)) + return BDK_QLM_MODE_PCIE_1X1; /* PEM0 x1 */ + else if (pemx_cfg.cn81xx.lanes4) + return BDK_QLM_MODE_PCIE_1X4; /* PEM0 x4 */ + else + return BDK_QLM_MODE_PCIE_1X2; /* PEM0 x2 */ + } + case 1: /* PEM0 second two lanes */ + return BDK_QLM_MODE_PCIE_1X4; /* PEM0 x4 */ + case 2: /* Either PEM1 x4 or PEM1 x2 */ + { + BDK_CSR_INIT(pemx_cfg, node, BDK_PEMX_CFG(1)); + if (pemx_cfg.cn81xx.lanes4) + return BDK_QLM_MODE_PCIE_1X4; /* PEM1 x4 */ + else + return BDK_QLM_MODE_PCIE_1X2; /* PEM1 x2 */ + } + case 3: /* Either PEM1 x4 or PEM2 x2 */ + { + /* Can be last 2 lanes of PEM1 */ + BDK_CSR_INIT(pem1_cfg, node, BDK_PEMX_CFG(1)); + if (pem1_cfg.cn81xx.lanes4) + return BDK_QLM_MODE_PCIE_1X4; /* PEM1 x4 */ + /* Can be 2 lanes of PEM2 */ + return BDK_QLM_MODE_PCIE_1X2; /* PEM2 x2 */ + } + default: + return BDK_QLM_MODE_DISABLED; + } + } + else if (gserx_cfg.s.bgx) + { + int bgx; + int bgx_index; + switch (qlm) + { + case 0: + { + bgx = 0; + bgx_index = 0; + break; + } + case 1: + bgx = 0; + bgx_index = 2; + break; + case 2: + { + bgx = 1; + bgx_index = 0; + break; + } + case 3: + bgx = 1; + bgx_index = 2; + break; + default: + return BDK_QLM_MODE_DISABLED; + } + BDK_CSR_INIT(cmrx_config, node, BDK_BGXX_CMRX_CONFIG(bgx, bgx_index)); + bool is_kr = __bdk_qlm_is_lane_kr(node, qlm, 0); + switch (cmrx_config.s.lmac_type) + { + case BDK_BGX_LMAC_TYPES_E_SGMII: + if (cavium_is_altpkg(CAVIUM_CN81XX) && (qlm < 2)) + return BDK_QLM_MODE_SGMII_1X1; + else + return BDK_QLM_MODE_SGMII_2X1; + case BDK_BGX_LMAC_TYPES_E_XAUI: return BDK_QLM_MODE_XAUI_1X4; /* Doesn't differntiate between XAUI and DXAUI */ + case BDK_BGX_LMAC_TYPES_E_RXAUI: return BDK_QLM_MODE_RXAUI_1X2; + case BDK_BGX_LMAC_TYPES_E_TENG_R: + if (is_kr) + return (cavium_is_altpkg(CAVIUM_CN81XX) && (qlm < 2)) ? BDK_QLM_MODE_10G_KR_1X1 : BDK_QLM_MODE_10G_KR_2X1; + else + return (cavium_is_altpkg(CAVIUM_CN81XX) && (qlm < 2)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_XFI_2X1; + case BDK_BGX_LMAC_TYPES_E_FORTYG_R: + if (is_kr) + return BDK_QLM_MODE_40G_KR4_1X4; + else + return BDK_QLM_MODE_XLAUI_1X4; + case BDK_BGX_LMAC_TYPES_E_QSGMII: return BDK_QLM_MODE_QSGMII_4X1; + default: return BDK_QLM_MODE_DISABLED; + } + } + else if (gserx_cfg.s.sata) + return BDK_QLM_MODE_SATA_2X1; + else + return BDK_QLM_MODE_DISABLED; +} + +static int qlm_set_sata(bdk_node_t node, int qlm, bdk_qlm_modes_t mode, int baud_mhz, bdk_qlm_mode_flags_t flags) +{ + /* SATA has a fixed mapping for ports on CN81XX */ + int sata_port; + switch (qlm) + { + case 3: /* SATA 0-1 = DLM3 lanes 0-1 */ + sata_port = 0; + break; + default: + bdk_error("Attempted to configure SATA on QLM that doesn't support it\n"); + return -1; + } + return __bdk_qlm_set_sata_cn8xxx(node, qlm, baud_mhz, sata_port, sata_port + 1); +} + +/** + * For chips that don't use pin strapping, this function programs + * the QLM to the specified mode + * + * @param node Node to use in a Numa setup + * @param qlm QLM to configure + * @param mode Desired mode + * @param baud_mhz Desired speed + * @param flags Flags to specify mode specific options + * + * @return Zero on success, negative on failure + */ +int bdk_qlm_set_mode(bdk_node_t node, int qlm, bdk_qlm_modes_t mode, int baud_mhz, bdk_qlm_mode_flags_t flags) +{ + int lane_mode = 0xf; + int lmac_type = -1; + int is_pcie = 0; + int is_sata = 0; + int is_ilk = 0; + int is_bgx = 0; + int bgx_block; + int bgx_index; + + switch (qlm) + { + case 0: + bgx_block = 0; + bgx_index = 0; + break; + case 1: + bgx_block = 0; + bgx_index = 2; + break; + case 2: + bgx_block = 1; + bgx_index = 0; + break; + case 3: + bgx_block = 1; + bgx_index = 2; + break; + default: + bgx_block = -1; + bgx_index = -1; + break; + } + + int measured_ref = bdk_qlm_measure_clock(node, qlm); + int ref_clk = (mode == BDK_QLM_MODE_DISABLED) ? 0 : __bdk_qlm_round_refclock(node, qlm, measured_ref); + int kr_mode = 0; + + switch (mode) + { + case BDK_QLM_MODE_PCIE_1X1: + case BDK_QLM_MODE_PCIE_1X2: + case BDK_QLM_MODE_PCIE_1X4: + { + /* Note: PCIe ignores baud_mhz. Use the GEN 1/2/3 flags + to control speed */ + is_pcie = 1; + if (ref_clk == REF_100MHZ) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_SEL(qlm), + c.s.pcie_refclk125 = 0); + if (baud_mhz == 2500) + lane_mode = BDK_GSER_LMODE_E_R_25G_REFCLK100; + else if (baud_mhz == 5000) + lane_mode = BDK_GSER_LMODE_E_R_5G_REFCLK100; + else + lane_mode = BDK_GSER_LMODE_E_R_8G_REFCLK100; + } + else if (ref_clk == REF_125MHZ) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_SEL(qlm), + c.s.pcie_refclk125 = 1); + if (baud_mhz == 2500) + lane_mode = BDK_GSER_LMODE_E_R_25G_REFCLK125; + else if (baud_mhz == 5000) + lane_mode = BDK_GSER_LMODE_E_R_5G_REFCLK125; + else + lane_mode = BDK_GSER_LMODE_E_R_8G_REFCLK125; + } + else + { + bdk_error("Invalid reference clock for PCIe on QLM%d\n", qlm); + return -1; + } + int cfg_md; + if (baud_mhz == 2500) + cfg_md = 0; /* Gen1 Speed */ + else if (baud_mhz == 5000) + cfg_md = 1; /* Gen2 Speed */ + else + cfg_md = 2; /* Gen3 Speed */ + switch (qlm) + { + case 0: /* Either PEM0 x4 or PEM0 x2 or PEM0 x1 */ + BDK_CSR_MODIFY(c, node, BDK_RST_SOFT_PRSTX(0), + c.s.soft_prst = !(flags & BDK_QLM_MODE_FLAG_ENDPOINT)); + __bdk_qlm_setup_pem_reset(node, 0, flags & BDK_QLM_MODE_FLAG_ENDPOINT); + BDK_CSR_MODIFY(c, node, BDK_PEMX_CFG(0), + c.cn81xx.lanes4 = (mode == BDK_QLM_MODE_PCIE_1X4); + //c.cn81xx.hostmd = !(flags & BDK_QLM_MODE_FLAG_ENDPOINT); + c.cn81xx.md = cfg_md); + break; + case 1: /* Second two lanes for PEM0 x4 */ + /* PEMX_CFG already setup */ + break; + case 2: /* Either PEM1 x4 or PEM1 x2 */ + BDK_CSR_MODIFY(c, node, BDK_RST_SOFT_PRSTX(1), + c.s.soft_prst = !(flags & BDK_QLM_MODE_FLAG_ENDPOINT)); + __bdk_qlm_setup_pem_reset(node, 1, flags & BDK_QLM_MODE_FLAG_ENDPOINT); + BDK_CSR_MODIFY(c, node, BDK_PEMX_CFG(1), + c.cn81xx.lanes4 = (mode == BDK_QLM_MODE_PCIE_1X4); + //c.cn81xx.hostmd = !(flags & BDK_QLM_MODE_FLAG_ENDPOINT); + c.cn81xx.md = cfg_md); + break; + case 3: /* Either PEM1 x4 or PEM2 x2 */ + if (mode == BDK_QLM_MODE_PCIE_1X4) + { + /* Last 2 lanes of PEM1 */ + /* PEMX_CFG already setup */ + } + else + { + /* Two lanes for PEM2 */ + BDK_CSR_MODIFY(c, node, BDK_RST_SOFT_PRSTX(2), + c.s.soft_prst = !(flags & BDK_QLM_MODE_FLAG_ENDPOINT)); + __bdk_qlm_setup_pem_reset(node, 2, flags & BDK_QLM_MODE_FLAG_ENDPOINT); + BDK_CSR_MODIFY(c, node, BDK_PEMX_CFG(2), + c.cn81xx.lanes4 = 0; + //c.cn81xx.hostmd = !(flags & BDK_QLM_MODE_FLAG_ENDPOINT); + c.cn81xx.md = cfg_md); + } + break; + default: + return -1; + } + break; + } + case BDK_QLM_MODE_SGMII_4X1: + case BDK_QLM_MODE_SGMII_2X1: + case BDK_QLM_MODE_SGMII_1X1: + /* Disable port BGX ports 2-3 on CN80XX */ + if ((qlm < 2) && cavium_is_altpkg(CAVIUM_CN81XX)) + { + BDK_CSR_WRITE(node, BDK_BGXX_CMRX_RX_DMAC_CTL(0, 2), 0); + BDK_CSR_WRITE(node, BDK_BGXX_CMRX_RX_DMAC_CTL(0, 3), 0); + } + lmac_type = BDK_BGX_LMAC_TYPES_E_SGMII; /* SGMII */ + is_bgx = 1; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("SGMII", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + break; + case BDK_QLM_MODE_XAUI_1X4: + lmac_type = BDK_BGX_LMAC_TYPES_E_XAUI; /* XAUI */ + is_bgx = 5; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("XAUI", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + break; + case BDK_QLM_MODE_RXAUI_2X2: + case BDK_QLM_MODE_RXAUI_1X2: + lmac_type = BDK_BGX_LMAC_TYPES_E_RXAUI; /* RXAUI */ + is_bgx = 3; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("RXAUI", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + break; + case BDK_QLM_MODE_XFI_4X1: + case BDK_QLM_MODE_XFI_2X1: + case BDK_QLM_MODE_XFI_1X1: + /* Disable port BGX ports 2-3 on CN80XX */ + if ((qlm < 2) && cavium_is_altpkg(CAVIUM_CN81XX)) + { + BDK_CSR_WRITE(node, BDK_BGXX_CMRX_RX_DMAC_CTL(0, 2), 0); + BDK_CSR_WRITE(node, BDK_BGXX_CMRX_RX_DMAC_CTL(0, 3), 0); + } + lmac_type = BDK_BGX_LMAC_TYPES_E_TENG_R; /* 10G_R */ + is_bgx = 1; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("XFI", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + break; + case BDK_QLM_MODE_XLAUI_1X4: + lmac_type = BDK_BGX_LMAC_TYPES_E_FORTYG_R; /* 40G_R */ + is_bgx = 5; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("XLAUI", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + break; + case BDK_QLM_MODE_10G_KR_4X1: + case BDK_QLM_MODE_10G_KR_2X1: + case BDK_QLM_MODE_10G_KR_1X1: + /* Disable port BGX ports 2-3 on CN80XX */ + if ((qlm < 2) && cavium_is_altpkg(CAVIUM_CN81XX)) + { + BDK_CSR_WRITE(node, BDK_BGXX_CMRX_RX_DMAC_CTL(0, 2), 0); + BDK_CSR_WRITE(node, BDK_BGXX_CMRX_RX_DMAC_CTL(0, 3), 0); + } + lmac_type = BDK_BGX_LMAC_TYPES_E_TENG_R; /* 10G_R */ + is_bgx = 1; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("10G-KR", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + kr_mode = 1; + break; + case BDK_QLM_MODE_40G_KR4_1X4: + lmac_type = BDK_BGX_LMAC_TYPES_E_FORTYG_R; /* 40G_R */ + is_bgx = 5; + lane_mode = __bdk_qlm_get_lane_mode_for_speed_and_ref_clk("40G-KR", qlm, ref_clk, baud_mhz); + if (lane_mode == -1) + return -1; + kr_mode = 1; + break; + case BDK_QLM_MODE_QSGMII_4X1: + lmac_type = BDK_BGX_LMAC_TYPES_E_QSGMII; /* QSGMII */ + is_bgx = 1; + lane_mode = BDK_GSER_LMODE_E_R_5G_REFCLK15625_QSGMII; + break; + case BDK_QLM_MODE_SATA_2X1: + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANE_MODE(qlm), c.s.lmode = BDK_GSER_LMODE_E_R_8G_REFCLK100); + /* SATA initialization is different than BGX. Call its init function + and skip the rest of this routine */ + return qlm_set_sata(node, qlm, mode, baud_mhz, flags); + case BDK_QLM_MODE_DISABLED: + /* Set gser for the interface mode */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_CFG(qlm), + c.u = 0); + /* Put the PHY in reset */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 1); + return 0; + default: + bdk_error("Unsupported QLM mode %d\n", mode); + return -1; + } + + BDK_TRACE(QLM, "N%u.QLM%u: Power up...\n", node, qlm); + + /* Power up phy, but keep it in reset */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_pd = 0; + c.s.phy_reset = 1); + + /* Set gser for the interface mode */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_CFG(qlm), + c.s.sata = is_sata; + c.s.ila = is_ilk; + c.s.bgx = is_bgx & 1; + c.s.bgx_quad = (is_bgx >> 2) & 1; + c.s.bgx_dual = (is_bgx >> 1) & 1; + c.s.pcie = is_pcie); + + /* Lane mode */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANE_MODE(qlm), + c.s.lmode = lane_mode); + + /* LMAC type. We only program one port as the full setup is done in BGX */ + if (lmac_type != -1) + { + BDK_CSR_MODIFY(c, node, BDK_BGXX_CMRX_CONFIG(bgx_block, bgx_index), + c.s.enable = 0; + c.s.lmac_type = lmac_type); + } + + BDK_TRACE(QLM, "N%u.QLM%u: Deassert reset...\n", node, qlm); + + /* Bring phy out of reset */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 0); + + /* Wait 1us until the management interface is ready to accept + read/write commands.*/ + bdk_wait_usec(1); + + /* Configure the gser pll */ + __bdk_qlm_init_mode_table(node, qlm, ref_clk); + + /* Remember which lanes are using KR over BGX */ + if (is_bgx) + { + int num_lanes = bdk_qlm_get_lanes(node, qlm); + for (int lane = 0; lane < num_lanes; lane++) + __bdk_qlm_set_lane_kr(node, qlm, lane, kr_mode); + } + + /* Wait for reset to complete and the PLL to lock */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_PLL_STAT(qlm), pll_lock, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_PLL_STAT[pll_lock]\n", qlm); + return -1; + } + + /* PCIe mode doesn't become ready until the PEM block attempts to bring + the interface up. Skip this check for PCIe */ + if (!is_pcie && BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_QLM_STAT(qlm), rst_rdy, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_QLM_STAT[rst_rdy]\n", qlm); + return -1; + } + + /* cdrlock will be checked in the BGX */ + + /* Errata (GSER-27140) SERDES temperature drift sensitivity in receiver */ + int channel_loss = bdk_config_get_int(BDK_CONFIG_QLM_CHANNEL_LOSS, node, qlm); + __bdk_qlm_errata_gser_27140(node, qlm, baud_mhz, channel_loss); + + /* Apply any custom tuning */ + __bdk_qlm_tune(node, qlm, mode, baud_mhz); + + /* Some modes require 4 lanes, which spans DLMs. For these modes, we need + to setup the second DLM at the same time we setup the first. The second + DLM also must use the same reference clock as the first */ + bool paired_dlm = ((qlm & 1) == 0) && /* We're on the first (even) DLM */ + ((mode == BDK_QLM_MODE_PCIE_1X4) || /* We're using a 4 lane mode */ + (mode == BDK_QLM_MODE_XAUI_1X4) || + (mode == BDK_QLM_MODE_XLAUI_1X4) || + (mode == BDK_QLM_MODE_40G_KR4_1X4)); + if (paired_dlm) + { + /* Use the same reference clock for the second QLM */ + BDK_CSR_WRITE(node, BDK_GSERX_REFCLK_SEL(qlm + 1), + BDK_CSR_READ(node, BDK_GSERX_REFCLK_SEL(qlm))); + return bdk_qlm_set_mode(node, qlm + 1, mode, baud_mhz, flags); + } + + return 0; +} + +/** + * Get the speed (Gbaud) of the QLM in Mhz. + * + * @param qlm QLM to examine + * + * @return Speed in Mhz + */ +int bdk_qlm_get_gbaud_mhz(bdk_node_t node, int qlm) +{ + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(qlm)); + if (gserx_cfg.u == 0) + return 0; + if (gserx_cfg.s.pcie) + { + /* QLMs in PCIe mode ignore LMODE and get their speed from + the PEM block that controls them */ + int pem; + switch (qlm) + { + case 0: /* PEM0 */ + case 1: /* PEM0 */ + pem = 0; + break; + case 2: /* PEM1 */ + pem = 1; + break; + case 3: /* PEM1 or PEM2 */ + { + BDK_CSR_INIT(pemx_cfg, node, BDK_PEMX_CFG(1)); + if (pemx_cfg.cn81xx.lanes4) + pem = 1; + else + pem = 2; + break; + } + default: + bdk_fatal("QLM%d: In PCIe mode, which shouldn't happen\n", qlm); + } + return __bdk_qlm_get_gbaud_mhz_pem(node, pem); + } + else if (gserx_cfg.s.sata) + { + int sata; + switch (qlm) + { + case 3: + sata = 0; + break; + default: + return 0; + } + BDK_CSR_INIT(sata_uctl_ctl, node, BDK_SATAX_UCTL_CTL(sata)); + if (!sata_uctl_ctl.s.a_clk_en) + return 0; + BDK_CSR_INIT(sctl, node, BDK_SATAX_UAHC_P0_SCTL(sata)); + switch (sctl.s.spd) + { + case 1: return 1500; + case 2: return 3000; + case 3: return 6000; + default: return 6000; /* No limit, assume 6G */ + } + } + else + return __bdk_qlm_get_gbaud_mhz_lmode(node, qlm); +} + +/** + * Initialize the QLM layer + */ +void bdk_qlm_init(bdk_node_t node) +{ + /* Setup how each PEM drives the PERST lines */ + for (int pem = 0; pem < 3; pem++) + { + BDK_CSR_INIT(rst_ctlx, node, BDK_RST_CTLX(pem)); + __bdk_qlm_setup_pem_reset(node, pem, !rst_ctlx.s.host_mode); + } +} + +static void __bdk_qlm_sff81xx_set_reference(bdk_node_t node, int qlm, int ref_clk) +{ + int use_clock; + + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX) || CAVIUM_IS_MODEL(CAVIUM_CN83XX) || CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + { + // Common clock 0 is 156MHz + // Common clock 1 is 100MHz + switch (qlm) + { + case 0: + use_clock = 1; /* DLMC_REF_CLK1 of 100MHz */ + break; + case 1: + if (ref_clk == REF_100MHZ) + use_clock = 1; /* DLMC_REF_CLK1 of 100MHz */ + else + use_clock = 2; /* DLM1_REF_CLK of 156MHz */ + break; + case 2: + case 3: + default: + if (ref_clk == REF_100MHZ) + use_clock = 1; /* DLMC_REF_CLK1 of 100MHz */ + else + use_clock = 2; /* DLM1_REF_CLK of 156MHz */ + break; + } + + BDK_TRACE(QLM, "Setting N%d.QLM%d to use ref clock %d\n", node, qlm, use_clock); + } + else + { + bdk_error("Update %s for qlm auto config of this chip\n",__FUNCTION__); + return; + } + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_SEL(qlm), + c.s.com_clk_sel = (use_clock != 2); + c.s.use_com1 = (use_clock == 1)); +} + +int bdk_qlm_auto_config(bdk_node_t node) +{ + return -1; +} + +/** + * For Cavium SFF board, query the DIP switches in GPIO o determine the QLM setup. + * Configure the GPIOs to read the DLM settings + * SW1.1 -> DLM0_SEL -> GPIO_26 + * SW1.2 -> DLM1_SEL -> GPIO_25 + * SW1.3 -> DLM2_SEL -> GPIO_31 + * SW1.4 -> DLM3_SEL -> GPIO_4 + *V1.x boards SW3.8 -> QSGMII/XFI SEL ->GPIO_9 + *V2.x boards SW3.7 -> QSGMII/XFI SEL ->GPIO_36 +*/ +int bdk_qlm_dip_auto_config(bdk_node_t node) +{ + bdk_qlm_modes_t dlm_mode[4]; + int dlm_speed = 0; + int use_ref = 0; + bdk_qlm_mode_flags_t dlm_flags = 0; + + unsigned int dlm_config, dlm3, dlm2, dlm1, dlm0; + uint64_t gpio = 0; + + /* Configure the GPIOs to read the DLM settings */ + /* SW1.1 -> DLM0_SEL -> GPIO_26 */ + /* SW1.2 -> DLM1_SEL -> GPIO_25 */ + /* SW1.3 -> DLM2_SEL -> GPIO_31 */ + /* SW1.4 -> DLM3_SEL -> GPIO_4 */ + //V1.x boards /* SW3.8 -> QSGMII/XFI SEL ->GPIO_9 */ + //V2.x boards /* SW3.7 -> QSGMII/XFI SEL ->GPIO_36 */ + /* Configure the GPIOs are input */ + bdk_gpio_initialize(node, 26, 0, 0); + bdk_gpio_initialize(node, 25, 0, 0); + bdk_gpio_initialize(node, 31, 0, 0); + bdk_gpio_initialize(node, 4, 0, 0); + bdk_gpio_initialize(node, 36, 0, 0); + + + /* Read the GPIOs */ + gpio = bdk_gpio_read(node, 0); + + dlm3 = !!(gpio & (1ULL<<4)); + dlm2 = !!(gpio & (1ULL<<31)); + dlm1 = !!(gpio & (1ULL<<25)); + dlm0 = !!(gpio & (1ULL<<26)); + + + dlm_config = (dlm0<<3)| (dlm1<<2) | (dlm2<<1) | (dlm3); + + BDK_TRACE(QLM, "DLM CONFIG:%d gpio36: %d\n", dlm_config, !!(gpio & (1ULL<<36))); + + switch(dlm_config) + { + case 0: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = BDK_QLM_MODE_DISABLED; + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 1: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = BDK_QLM_MODE_DISABLED; + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 2: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = BDK_QLM_MODE_DISABLED; + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 3: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = BDK_QLM_MODE_DISABLED; + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 4: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 5: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 6: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 7: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 8: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[1] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 9: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[1] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 10: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[1] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 11: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[1] = BDK_QLM_MODE_PCIE_1X4; + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 12: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 13: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + case 14: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_PCIE_1X2; + break; + case 15: + dlm_mode[0] = BDK_QLM_MODE_PCIE_1X2; + dlm_mode[1] = (!!(gpio & (1ULL<<36)) ? BDK_QLM_MODE_XFI_1X1 : BDK_QLM_MODE_QSGMII_4X1); + dlm_mode[2] = BDK_QLM_MODE_XFI_2X1; + dlm_mode[3] = BDK_QLM_MODE_SATA_2X1; + break; + default: + return -1; + } + + for(int dlm = 0; dlm < 4; dlm++) + { + const char *dlm_mode_str = bdk_qlm_mode_tostring(dlm_mode[dlm]); + switch(dlm_mode[dlm]) + { + case BDK_QLM_MODE_DISABLED: + break; + case BDK_QLM_MODE_XFI_2X1: + case BDK_QLM_MODE_XFI_1X1: + use_ref = REF_156MHZ; + dlm_speed = 10312; + break; + case BDK_QLM_MODE_SATA_2X1: + dlm_speed = 6000; + use_ref = REF_100MHZ; + break; + case BDK_QLM_MODE_PCIE_1X2: + case BDK_QLM_MODE_PCIE_1X4: + dlm_speed = 8000; + use_ref =REF_100MHZ; + break; + case BDK_QLM_MODE_QSGMII_4X1: + use_ref = REF_100MHZ; + dlm_speed = 5000; + break; + default: + bdk_error("Unsupported N%d.QLM%d mode: %s(%d)", + node, dlm, + dlm_mode_str ? dlm_mode_str : "???", + dlm_mode[dlm]); + return -1; + } + if ((1 == dlm) && (dlm_mode[dlm] != BDK_QLM_MODE_QSGMII_4X1) && (dlm_mode[dlm] != BDK_QLM_MODE_DISABLED)) + { + /* This code is specific to sff8104 board + ** QSGMII phy is wired to dlm1-gser lane 2 + ** AQR-107 phy is wired to dlm1-gser lane 3 + ** bdk always uses bgx0.port0 on that board + */ + // If dlm1 is in XFI mode, change PHY address to mdio of aquantia phy + unsigned mdio_bus = 1; + unsigned mdio_addr = 0; + int phy_cfg = 0xff<<24 | ((mdio_bus& 0xf)<<8) | (mdio_addr & 0xff); + bdk_config_set_int((uint32_t) phy_cfg,BDK_CONFIG_PHY_ADDRESS, node, 0, 0); + /* Indicate serdes lane 3 , aquantia phy active */ + int aq_phy = (0x3<<8) | 1; + bdk_config_set_int(aq_phy, BDK_CONFIG_AQUANTIA_PHY,node,0,0); + BDK_TRACE(QLM,"Disabling phys 0.1,0.2,0.3\n"); + for (int i = 1; i<4; i++) { + bdk_config_set_int(-1,BDK_CONFIG_PHY_ADDRESS, node, 0, i); + bdk_config_set_int(0,BDK_CONFIG_BGX_ENABLE,node,0,i); + } + } + + BDK_TRACE(QLM, "Setting N%d.QLM%d mode %s(%d), speed %d, flags 0x%x\n", + node, dlm, dlm_mode_str, dlm_mode[dlm], dlm_speed, dlm_flags); + + /* Set the reference clock for this QLM */ + __bdk_qlm_sff81xx_set_reference(node, dlm, use_ref); + + if (bdk_qlm_set_mode(node, dlm, dlm_mode[dlm], dlm_speed, dlm_flags)) + return -1; + } + return 0; +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-common-sata.c b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-common-sata.c new file mode 100644 index 0000000000..9e31ad1dce --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-common-sata.c @@ -0,0 +1,625 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-hal/if/bdk-if.h" +#include "libbdk-hal/bdk-qlm.h" +#include "libbdk-hal/qlm/bdk-qlm-common.h" +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-sata.h" + +/** + * Initialize a DLM/QLM for use with SATA controllers + * + * @param node Node to intialize + * @param qlm Which DLM/QLM to init + * @param baud_mhz QLM speed in Gbaud + * @param sata_first First SATA controller connected to this DLM/QLM + * @param sata_last Last SATA controller connected to this DLM/QLM (inclusive) + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_set_sata_cn8xxx(bdk_node_t node, int qlm, int baud_mhz, int sata_first, int sata_last) +{ + const int NUM_LANES = sata_last - sata_first + 1; + const int MAX_A_CLK = 333000000; /* Max of 333Mhz */ + + /* 26.4.1 Cold Reset */ + /* 1. Ensure that the SerDes reference clock is up and stable. */ + /* Already done */ + + /* 2. Optionally program the GPIO CSRs for SATA features. + a. For cold-presence detect, select a GPIO for the input and program GPI- + O_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E::SATA(0..15)_CP_DET. + b. For mechanical-presence detect, select a GPIO for the input and program + GPIO_BIT_CFG(0..50)[PIN_SEL] = GPI- + O_PIN_SEL_E::SATA(0..15)_MP_SWITCH. + c. For BIST board-test loopback, select a GPIO for the input and program GPI- + O_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E:::SATA_LAB_LB. + d. For LED activity, select a GPIO for the output and program GPI- + O_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E:::SATA(0..15)_ACT_LED. + e. For cold-presence power-on-device, select a GPIO for the output and program + GPIO_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E:::SATA(0..15)_CP_- + POD. */ + /* Skipping */ + + /* 3. Optionally program the SGPIO unit. */ + /* Skipping */ + + /* 4. Assert all resets: + a. UAHC reset: SATA(0..15)_UCTL_CTL[SATA_UAHC_RST] = 1 + b. UCTL reset: SATA(0..15)_UCTL_CTL[SATA_UCTL_RST] = 1 */ + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.sata_uahc_rst = 1; + c.s.sata_uctl_rst = 1); + } + + /* 5. Configure the ACLK: + a. Reset the clock dividers: SATA(0..15)_UCTL_CTL[A_CLKDIV_RST] = 1. + b. Select the ACLK frequency (refer to maximum values in Table 26 1). + i. SATA(0..15)_UCTL_CTL[A_CLKDIV_SEL] = desired value, + ii. SATA(0..15)_UCTL_CTL[A_CLK_EN] = 1 to enable the ACLK. + c. Deassert the ACLK clock divider reset: + SATA(0..15)_UCTL_CTL[A_CLKDIV_RST] = 0. */ + int divisor = (bdk_clock_get_rate(node, BDK_CLOCK_SCLK) + MAX_A_CLK - 1) / MAX_A_CLK; + int a_clkdiv; + /* This screwy if logic is from the description of + SATAX_UCTL_CTL[a_clkdiv_sel] in the CSR */ + if (divisor <= 4) + { + a_clkdiv = divisor - 1; + /* Divisor matches calculated value */ + } + else if (divisor <= 6) + { + a_clkdiv = 4; + divisor = 6; + } + else if (divisor <= 8) + { + a_clkdiv = 5; + divisor = 8; + } + else if (divisor <= 16) + { + a_clkdiv = 6; + divisor = 16; + } + else if (divisor <= 24) + { + a_clkdiv = 7; + divisor = 24; + } + else + { + bdk_error("Unable to determine SATA clock divisor\n"); + return -1; + } + /* Calculate the final clock rate */ + int a_clk = bdk_clock_get_rate(node, BDK_CLOCK_SCLK) / divisor; + + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.a_clkdiv_rst = 1); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.a_clk_byp_sel = 0; + c.s.a_clkdiv_sel = a_clkdiv; + c.s.a_clk_en = 1); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.a_clkdiv_rst = 0); + } + bdk_wait_usec(1); + + /* 8. Configure PHY for SATA. Refer to Section 21.1.2. */ + /* Done below, section 24.1.2.3 */ + + /* 9. TBD: Poll QLM2_MPLL_STATUS for MPLL lock */ + /* Not needed */ + + /* 10. Initialize UAHC as described in the AHCI specification + (UAHC_* registers). */ + /* Done when a SATA driver is initialized */ + + /* 24.1.2.3 SATA Configuration + Software must perform the following steps to configure the GSER_WEST + for a SATA application. Note that the GSERW steps are on a QLM basis. */ + + /* 1. Configure the SATA controller (refer to Chapter 26). */ + /* This is the code above */ + + /* 2. Configure the QLM Reference clock. + Set GSER(0..13)_REFCLK_SEL[COM_CLK_SEL] = 1 to source the reference + clock from the external clock multiplexer. + Configure GSER(0..13)_REFCLK_SEL[USE_COM1]: + 0 = use QLMC_REF_CLK0_P/N + 1 = use QLMC_REF_CLK1_P/N */ + /* Already done */ + + /* Make sure the PHY is in reset before we reconfig */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 1); + bdk_wait_usec(1); + + /* 3. Configure the QLM for SATA mode: set GSER(0..13)_CFG[SATA] = 1. */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_CFG(qlm), + c.u = 0; + c.s.sata = 1); + + /* 9. Clear the appropriate lane resets: + GSER(0..13)_SATA_LANE_RST[Ln_RST] = 0, where n is the lane number 0-3. */ + BDK_CSR_WRITE(node, BDK_GSERX_SATA_LANE_RST(qlm), 0); + BDK_CSR_READ(node, BDK_GSERX_SATA_LANE_RST(qlm)); + + /* We'll check for the SATA_PCS Ready in step 8a below */ + /* Short 1 usec wait */ + bdk_wait_usec(1); + + /* 4. Take the PHY out of reset: write GSER(0..13)_PHY_CTL[PHY_RESET] = 0. */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 0); + + /* 4a. Poll for PHY RST_RDY indicating the PHY has initialized before + trying to access internal registers to reconfigure for SATA */ + /* 8. Wait for GSER(0..13)_QLM_STAT[RST_RDY] = 1, indicating that the PHY + has been reconfigured and PLLs are locked. */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_QLM_STAT(qlm), rst_rdy, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_QLM_STAT[rst_rdy]\n", qlm); + return -1; + } + + /* Workaround for errata GSER-30310: SATA HDD Not Ready due to + PHY SDLL/LDLL lockup at 3GHz */ + for (int slice = 0; slice < NUM_LANES / 2; slice++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_SLICEX_PCIE1_MODE(qlm, slice), + c.s.rx_pi_bwsel = 1; + c.s.rx_ldll_bwsel = 1; + c.s.rx_sdll_bwsel = 1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_SLICEX_PCIE2_MODE(qlm, slice), + c.s.rx_pi_bwsel = 1; + c.s.rx_ldll_bwsel = 1; + c.s.rx_sdll_bwsel = 1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_SLICEX_PCIE3_MODE(qlm, slice), + c.s.rx_pi_bwsel = 1; + c.s.rx_ldll_bwsel = 1; + c.s.rx_sdll_bwsel = 1); + } + + /* 5. Change the P2 termination + GSERn_RX_PWR_CTRL_P2[P2_RX_SUBBLK_PD<0>] = 0 (termination) */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_RX_PWR_CTRL_P2(qlm), + c.s.p2_rx_subblk_pd &= 0x1e); + + /* 6. Modify the electrical IDLE detect on delay: set + GSER(0..13)_LANE(0..3)_MISC_CFG_0[EIE_DET_STL_ON_TIME] = 0x4 */ + for (int lane = 0; lane < NUM_LANES; lane++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_MISC_CFG_0(qlm, lane), + c.s.eie_det_stl_on_time = 4); + } + + /* 7. Modify the PLL and lane-protocol-mode registers to configure the + PHY for SATA */ + /* Errata (GSER-26724) SATA never indicates GSER QLM_STAT[RST_RDY] + We program PLL_PX_MODE_0 last due to this errata */ + for (int p=0; p<3; p++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_PLL_PX_MODE_1(qlm, p), + c.s.pll_16p5en = 0x0; + c.s.pll_cpadj = 0x2; + c.s.pll_pcie3en = 0; + c.s.pll_opr = 0x0; + c.s.pll_div = 0x1e); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANE_PX_MODE_0(qlm, p), + c.s.ctle = 0x0; + c.s.pcie = 0; + c.s.tx_ldiv = 0x0; + c.s.rx_ldiv = 2 - p; + c.s.srate = 0; + c.s.tx_mode = 3; + c.s.rx_mode = 3); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANE_PX_MODE_1(qlm, p), + c.s.vma_fine_cfg_sel = 0; + c.s.vma_mm = 1; + c.s.cdr_fgain = 0xa; + c.s.ph_acc_adj = 0x15); + } + for (int p=0; p<3; p++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_PLL_PX_MODE_0(qlm, p), + c.s.pll_icp = 0x1; + c.s.pll_rloop = 0x3; + c.s.pll_pcs_div = 0x5); + } + + for (int s = 0; s < NUM_LANES / 2; s++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_SLICEX_RX_SDLL_CTRL(qlm, s), + c.s.pcs_sds_oob_clk_ctrl = 2; + c.s.pcs_sds_rx_sdll_tune = 0; + c.s.pcs_sds_rx_sdll_swsel = 0); + } + + for (int lane = 0; lane < NUM_LANES; lane++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_MISC_CFG_0(qlm, lane), + c.s.use_pma_polarity = 0; + c.s.cfg_pcs_loopback = 0; + c.s.pcs_tx_mode_ovrrd_en = 0; + c.s.pcs_rx_mode_ovrrd_en = 0; + c.s.cfg_eie_det_cnt = 0; + c.s.eie_det_stl_on_time = 4; + c.s.eie_det_stl_off_time = 0; + c.s.tx_bit_order = 1; + c.s.rx_bit_order = 1); + } + + /* 8. Wait for GSER(0..13)_QLM_STAT[RST_RDY] = 1, indicating that the PHY + has been reconfigured and PLLs are locked. */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_QLM_STAT(qlm), rst_rdy, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_QLM_STAT[rst_rdy]\n", qlm); + return -1; + } + /* 8a. Check that the SATA_PCS is "Ready" here, should be but check it */ + /* Poll GSERX_SATA_STATUS for PX_RDY = 1 */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_SATA_STATUS(qlm), p0_rdy, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_SATA_STATUS[p0_rdy]\n", qlm); + return -1; + } + + /* Add 1ms delay for everything to stabilize*/ + bdk_wait_usec(1000); + + /* Apply any custom tuning */ + __bdk_qlm_tune(node, qlm, BDK_QLM_MODE_SATA_4X1, baud_mhz); + bdk_wait_usec(1000); + + + /* 6. Deassert UCTL and UAHC resets: + a. SATA(0..15)_UCTL_CTL[SATA_UAHC_RST] = 0 + b. SATA(0..15)_UCTL_CTL[SATA_UCTL_RST] = 0 + c. Wait 10 ACLK cycles before accessing any ACLK-only registers. */ + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.sata_uahc_rst = 0; + c.s.sata_uctl_rst = 0); + } + bdk_wait_usec(1); + + /* 7. Enable conditional SCLK of UCTL by writing + SATA(0..15)_UCTL_CTL[CSCLK_EN] = 1. */ + for (int p = sata_first; p <= sata_last; p++) + { + if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) + { + /* CN9XXX make coprocessor clock automatic */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.cn8.csclk_en = 1); + } + } + + /* Check BIST on the SATA controller. Start BIST in parallel on the + controllers */ + + for (int p = sata_first; p <= sata_last; p++) + { + /* Make sure BIST is configured properly before we start it. We + want full BIST, not just CLEAR */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.clear_bist = 0; + c.s.start_bist = 0); + /* Start BIST */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.start_bist = 1); + } + bdk_wait_usec(1000); + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_INIT(bist, node, BDK_SATAX_UCTL_BIST_STATUS(p)); + if (bist.u) + bdk_error("N%d.SATA%d: Controller failed BIST (0x%llx)\n", node, p, bist.u); + else + BDK_TRACE(SATA, "N%d.SATA%d: Passed BIST\n", node, p); + } + /* Clear start_bist so it is ready for the next run */ + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.start_bist = 0); + } + + int spd; + if (baud_mhz < 3000) + spd = 1; + else if (baud_mhz < 6000) + spd = 2; + else + spd = 3; + + for (int p = sata_first; p <= sata_last; p++) + { + /* From the synopsis data book, SATAX_UAHC_GBL_TIMER1MS is the + AMBA clock in MHz * 1000, which is a_clk(Hz) / 1000 */ + BDK_TRACE(QLM, "QLM%d: SATA%d set to %d Hz\n", qlm, p, a_clk); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_TIMER1MS(p), + c.s.timv = a_clk / 1000); + /* Set speed */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_SCTL(p), + c.s.ipm = 3; /* Disable parial and slumber power management */ + c.s.spd = spd); + /* The following SATA setup is from the AHCI 1.3 spec, section + 10.1.1, Firmware Specific Initialization. */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_CAP(p), + c.s.sss = 1; /* Support staggered spin-up */ + c.s.smps = 1); /* Support mechanical presence switch */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_PI(p), + c.s.pi = 1); /* One port per controller */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_CMD(p), + c.s.hpcp = 1; /* Hot-plug-capable support */ + c.s.mpsp = 1; /* Mechanical presence switch attached to port */ + c.s.cpd = 1); /* Cold-presence detection */ + } + return 0; +} + +/** + * Initialize a DLM/QLM for use with SATA controllers + * + * @param node Node to intialize + * @param qlm Which DLM/QLM to init + * @param baud_mhz QLM speed in Gbaud + * @param sata_first First SATA controller connected to this DLM/QLM + * @param sata_last Last SATA controller connected to this DLM/QLM (inclusive) + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_set_sata_cn9xxx(bdk_node_t node, int qlm, int baud_mhz, int sata_first, int sata_last) +{ + //const int NUM_LANES = sata_last - sata_first + 1; + const int MAX_A_CLK = 333000000; /* Max of 333Mhz */ + + /* 26.4.1 Cold Reset */ + /* 1. Ensure that the SerDes reference clock is up and stable. */ + /* Already done */ + + /* 2. Optionally program the GPIO CSRs for SATA features. + a. For cold-presence detect, select a GPIO for the input and program GPI- + O_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E::SATA(0..15)_CP_DET. + b. For mechanical-presence detect, select a GPIO for the input and program + GPIO_BIT_CFG(0..50)[PIN_SEL] = GPI- + O_PIN_SEL_E::SATA(0..15)_MP_SWITCH. + c. For BIST board-test loopback, select a GPIO for the input and program GPI- + O_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E:::SATA_LAB_LB. + d. For LED activity, select a GPIO for the output and program GPI- + O_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E:::SATA(0..15)_ACT_LED. + e. For cold-presence power-on-device, select a GPIO for the output and program + GPIO_BIT_CFG(0..50)[PIN_SEL] = GPIO_PIN_SEL_E:::SATA(0..15)_CP_- + POD. */ + /* Skipping */ + + /* 3. Optionally program the SGPIO unit. */ + /* Skipping */ + + /* 4. Assert all resets: + a. UAHC reset: SATA(0..15)_UCTL_CTL[SATA_UAHC_RST] = 1 + b. UCTL reset: SATA(0..15)_UCTL_CTL[SATA_UCTL_RST] = 1 */ + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.sata_uahc_rst = 1; + c.s.sata_uctl_rst = 1); + } + + /* 5. Configure the ACLK: + a. Reset the clock dividers: SATA(0..15)_UCTL_CTL[A_CLKDIV_RST] = 1. + b. Select the ACLK frequency (refer to maximum values in Table 26 1). + i. SATA(0..15)_UCTL_CTL[A_CLKDIV_SEL] = desired value, + ii. SATA(0..15)_UCTL_CTL[A_CLK_EN] = 1 to enable the ACLK. + c. Deassert the ACLK clock divider reset: + SATA(0..15)_UCTL_CTL[A_CLKDIV_RST] = 0. */ + int divisor = (bdk_clock_get_rate(node, BDK_CLOCK_SCLK) + MAX_A_CLK - 1) / MAX_A_CLK; + int a_clkdiv; + /* This screwy if logic is from the description of + SATAX_UCTL_CTL[a_clkdiv_sel] in the CSR */ + if (divisor <= 4) + { + a_clkdiv = divisor - 1; + /* Divisor matches calculated value */ + } + else if (divisor <= 6) + { + a_clkdiv = 4; + divisor = 6; + } + else if (divisor <= 8) + { + a_clkdiv = 5; + divisor = 8; + } + else if (divisor <= 16) + { + a_clkdiv = 6; + divisor = 16; + } + else if (divisor <= 24) + { + a_clkdiv = 7; + divisor = 24; + } + else + { + bdk_error("Unable to determine SATA clock divisor\n"); + return -1; + } + /* Calculate the final clock rate */ + int a_clk = bdk_clock_get_rate(node, BDK_CLOCK_SCLK) / divisor; + + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.a_clkdiv_rst = 1); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.a_clk_byp_sel = 0; + c.s.a_clkdiv_sel = a_clkdiv; + c.s.a_clk_en = 1); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.a_clkdiv_rst = 0); + } + bdk_wait_usec(1); + + /* 8. Configure PHY for SATA. Refer to Section 21.1.2. */ + /* Done below, section 24.1.2.3 */ + + /* 9. TBD: Poll QLM2_MPLL_STATUS for MPLL lock */ + /* Not needed */ + + /* 10. Initialize UAHC as described in the AHCI specification + (UAHC_* registers). */ + /* Done when a SATA driver is initialized */ + + /* 24.1.2.3 SATA Configuration + Software must perform the following steps to configure the GSER_WEST + for a SATA application. Note that the GSERW steps are on a QLM basis. */ + + /* 1. Configure the SATA controller (refer to Chapter 26). */ + /* This is the code above */ + + /* 2. Configure the QLM Reference clock. + Set GSER(0..13)_REFCLK_SEL[COM_CLK_SEL] = 1 to source the reference + clock from the external clock multiplexer. + Configure GSER(0..13)_REFCLK_SEL[USE_COM1]: + 0 = use QLMC_REF_CLK0_P/N + 1 = use QLMC_REF_CLK1_P/N */ + /* Already done */ + + // FIXME: GSERN setup + + /* 6. Deassert UCTL and UAHC resets: + a. SATA(0..15)_UCTL_CTL[SATA_UAHC_RST] = 0 + b. SATA(0..15)_UCTL_CTL[SATA_UCTL_RST] = 0 + c. Wait 10 ACLK cycles before accessing any ACLK-only registers. */ + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.sata_uahc_rst = 0; + c.s.sata_uctl_rst = 0); + } + bdk_wait_usec(1); + + /* 7. Enable conditional SCLK of UCTL by writing + SATA(0..15)_UCTL_CTL[CSCLK_EN] = 1. */ + for (int p = sata_first; p <= sata_last; p++) + { + if (CAVIUM_IS_MODEL(CAVIUM_CN8XXX)) + { + /* CN9XXX make coprocessor clock automatic */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.cn8.csclk_en = 1); + } + } + + /* Check BIST on the SATA controller. Start BIST in parallel on the + controllers */ + for (int p = sata_first; p <= sata_last; p++) + { + /* Make sure BIST is configured properly before we start it. We + want full BIST, not just CLEAR */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.clear_bist = 0; + c.s.start_bist = 0); + /* Start BIST */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.start_bist = 1); + } + bdk_wait_usec(1000); + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_INIT(bist, node, BDK_SATAX_UCTL_BIST_STATUS(p)); + if (bist.u) + bdk_error("N%d.SATA%d: Controller failed BIST (0x%llx)\n", node, p, bist.u); + else + BDK_TRACE(SATA, "N%d.SATA%d: Passed BIST\n", node, p); + } + /* Clear start_bist so it is ready for the next run */ + for (int p = sata_first; p <= sata_last; p++) + { + BDK_CSR_MODIFY(c, node, BDK_SATAX_UCTL_CTL(p), + c.s.start_bist = 0); + } + + int spd; + if (baud_mhz < 3000) + spd = 1; + else if (baud_mhz < 6000) + spd = 2; + else + spd = 3; + + for (int p = sata_first; p <= sata_last; p++) + { + /* From the synopsis data book, SATAX_UAHC_GBL_TIMER1MS is the + AMBA clock in MHz * 1000, which is a_clk(Hz) / 1000 */ + BDK_TRACE(QLM, "QLM%d: SATA%d set to %d Hz\n", qlm, p, a_clk); + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_TIMER1MS(p), + c.s.timv = a_clk / 1000); + /* Set speed */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_SCTL(p), + c.s.ipm = 3; /* Disable parial and slumber power management */ + c.s.spd = spd); + /* The following SATA setup is from the AHCI 1.3 spec, section + 10.1.1, Firmware Specific Initialization. */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_CAP(p), + c.s.sss = 1; /* Support staggered spin-up */ + c.s.smps = 1); /* Support mechanical presence switch */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_GBL_PI(p), + c.s.pi = 1); /* One port per controller */ + BDK_CSR_MODIFY(c, node, BDK_SATAX_UAHC_P0_CMD(p), + c.s.hpcp = 1; /* Hot-plug-capable support */ + c.s.mpsp = 1; /* Mechanical presence switch attached to port */ + c.s.cpd = 1); /* Cold-presence detection */ + } + return 0; +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-common.c b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-common.c new file mode 100644 index 0000000000..2b3390228a --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-common.c @@ -0,0 +1,1636 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-hal/if/bdk-if.h" +#include "libbdk-hal/bdk-qlm.h" +#include "libbdk-hal/qlm/bdk-qlm-common.h" +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-pem.h" +#include "libbdk-hal/bdk-config.h" +#include "libbdk-hal/bdk-utils.h" +#include "libbdk-hal/bdk-twsi.h" + +/* Indexed by QLM number and lane */ +static uint64_t prbs_errors[14][4]; + +/** + * Figure out which lane mode to use for a given reference clock and GBaud + * + * @param mode_name String name for error messages + * @param qlm QlM being configured + * @param ref_clk Reference clock in hertz + * @param baud_mhz Baud rate in Mhz + * + * @return Lane mode or -1 on failure + */ +int __bdk_qlm_get_lane_mode_for_speed_and_ref_clk(const char *mode_name, int qlm, int ref_clk, int baud_mhz) +{ + if (baud_mhz <= 1250) + { + if ((ref_clk == REF_156MHZ) || (ref_clk == REF_100MHZ)) + return BDK_GSER_LMODE_E_R_125G_REFCLK15625_SGMII; + else + { + bdk_error("Invalid reference clock for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; + } + } + else if (baud_mhz <= 2500) + { + if (ref_clk == REF_100MHZ) + return BDK_GSER_LMODE_E_R_25G_REFCLK100; + else if (ref_clk == REF_125MHZ) + return BDK_GSER_LMODE_E_R_25G_REFCLK125; + else + { + bdk_error("Invalid reference clock for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; + } + } + else if (baud_mhz <= 3125) + { + if (ref_clk == REF_156MHZ) + return BDK_GSER_LMODE_E_R_3125G_REFCLK15625_XAUI; + else + { + bdk_error("Invalid reference clock for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; + } + } + else if (baud_mhz <= 5000) + { + if (ref_clk == REF_100MHZ) + return BDK_GSER_LMODE_E_R_5G_REFCLK100; + else if (ref_clk == REF_125MHZ) + return BDK_GSER_LMODE_E_R_5G_REFCLK125; + else + return BDK_GSER_LMODE_E_R_5G_REFCLK15625_QSGMII; + } + else if (baud_mhz <= 6250) + { + if (ref_clk == REF_156MHZ) + return BDK_GSER_LMODE_E_R_625G_REFCLK15625_RXAUI; + else + { + bdk_error("Invalid reference clock for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; + } + } + else if (baud_mhz <= 8000) + { + if (ref_clk == REF_100MHZ) + return BDK_GSER_LMODE_E_R_8G_REFCLK100; + else if (ref_clk == REF_125MHZ) + return BDK_GSER_LMODE_E_R_8G_REFCLK125; + else + { + bdk_error("Invalid reference clock for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; + } + } + else /* Baud 10312.5 */ + { + if (ref_clk == REF_156MHZ) + return BDK_GSER_LMODE_E_R_103125G_REFCLK15625_KR; + else + { + bdk_error("Invalid reference clock for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; + } + } + bdk_error("Invalid speed for %s on QLM%d with speed %d, ref %d Mhz\n", mode_name, qlm, baud_mhz, ref_clk / 1000000); + return -1; +} + +/** + * Setup the PEM to either driver or receive reset from PRST based on RC or EP + * + * @param node Node to use in a Numa setup + * @param pem Which PEM to setuo + * @param is_endpoint + * Non zero if PEM is a EP + */ +void __bdk_qlm_setup_pem_reset(bdk_node_t node, int pem, int is_endpoint) +{ + /* Make sure is_endpoint is either 0 or 1 */ + is_endpoint = (is_endpoint != 0); + BDK_CSR_MODIFY(c, node, BDK_RST_CTLX(pem), + c.s.prst_link = 0; /* Link down doesn't automatically assert PERST */ + c.s.rst_link = is_endpoint; /* Link down automatically assert soft reset for EP */ + c.s.rst_drv = !is_endpoint; /* PERST is output for RC, input for EP */ + c.s.rst_rcv = is_endpoint; /* Only read PERST in EP mode */ + c.s.rst_chip = 0); /* PERST doesn't pull CHIP_RESET */ + + if (is_endpoint) + { + /* If we're configuring an endpoint manually the PEM will not + be turned on by default by the hardware. Turn it on now */ + BDK_CSR_INIT(pemx_on, node, BDK_PEMX_ON(pem)); + if (!pemx_on.s.pemon) + { + BDK_CSR_MODIFY(c, node, BDK_PEMX_CLK_EN(pem), + c.cn83xx.pceclk_gate = 0; + c.cn83xx.csclk_gate = 0); + BDK_CSR_MODIFY(c, node, BDK_PEMX_ON(pem), + c.s.pemon = 1); + } + } +} + +/** + * Measure the reference clock of a QLM + * + * @param qlm QLM to measure + * + * @return Clock rate in Hz + */ +int __bdk_qlm_measure_refclock(bdk_node_t node, int qlm) +{ + /* Clear the counter */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_EVT_CTRL(qlm), + c.s.enb = 0; + c.s.clr = 1); + bdk_wait_usec(1); /* Give counter a chance to clear */ + if (BDK_CSR_READ(node, BDK_GSERX_REFCLK_EVT_CNTR(qlm))) + bdk_error("GSER%d: Ref clock counter not zero\n", qlm); + /* Start counting */ + uint64_t start = bdk_clock_get_count(BDK_CLOCK_TIME); + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_EVT_CTRL(qlm), + c.s.enb = 1; + c.s.clr = 0); + /* Wait for a short time to get a number of counts */ + bdk_wait_usec(20000); /* 20ms */ + /* Stop counting */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_EVT_CTRL(qlm), + c.s.enb = 0); + uint64_t stop = bdk_clock_get_count(BDK_CLOCK_TIME); + bdk_wait_usec(1); /* Give counter a chance to stabalize */ + + /* Calculate the rate */ + uint64_t count = BDK_CSR_READ(node, BDK_GSERX_REFCLK_EVT_CNTR(qlm)); + count *= bdk_clock_get_rate(bdk_numa_local(), BDK_CLOCK_TIME); + count /= stop - start; + return count; +} + +/** + * Put a QLM into hardware reset + * + * @param node Node to use in a numa setup + * @param qlm QLM to use + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_reset(bdk_node_t node, int qlm) +{ + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 1); + return 0; +} + +/** + * Enable PRBS on a QLM + * + * @param node Node to use in a numa setup + * @param qlm QLM to use + * @param prbs PRBS mode (31, etc) + * @param dir Directions to enable. This is so you can enable TX and later + * enable RX after TX has run for a time + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_enable_prbs(bdk_node_t node, int qlm, int prbs, bdk_qlm_direction_t dir) +{ + const int NUM_LANES = bdk_qlm_get_lanes(node, qlm); + int mode; + switch (prbs) + { + case 31: + mode = 1; + break; + case 23: + mode = 2; /* Or 3? */ + break; + case 16: + mode = 4; + break; + case 15: + mode = 5; + break; + case 11: + mode = 6; + break; + case 7: + mode = 7; + break; + default: + mode = prbs & 0xff; + for (int lane = 0; lane < NUM_LANES; lane++) + BDK_CSR_WRITE(node, BDK_GSERX_LANEX_LBERT_PAT_CFG(qlm, lane), prbs >> 8); + BDK_TRACE(QLM, "Using mode 0x%x with custom pattern 0x%x\n", mode, prbs >> 8); + break; + } + + /* For some reason PRBS doesn't work if GSER is configured for PCIe. + Disconnect PCIe when we start PRBS */ + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(qlm)); + if (gserx_cfg.s.pcie) + { + gserx_cfg.s.pcie = 0; + BDK_CSR_WRITE(node, BDK_GSERX_CFG(qlm), gserx_cfg.u); + bdk_warn("N%d.QLM%d: Disabling PCIe for PRBS/pattern generation\n", node, qlm); + } + /* For some reason PRBS doesn't work if GSER is configured for SATA. + Disconnect SATA when we start PRBS */ + if (gserx_cfg.s.sata) + { + gserx_cfg.s.sata = 0; + BDK_CSR_WRITE(node, BDK_GSERX_CFG(qlm), gserx_cfg.u); + bdk_warn("N%d.QLM%d: Disabling SATA for PRBS/pattern generation\n", node, qlm); + bdk_warn("N%d.QLM%d: SATA PRBS/patterns always run at 6G\n", node, qlm); + } + + BDK_CSR_MODIFY(c, node, BDK_GSERX_PHY_CTL(qlm), + c.s.phy_reset = 0); + + if (dir & BDK_QLM_DIRECTION_TX) + { + /* Disable first in case already running */ + for (int lane = 0; lane < NUM_LANES; lane++) + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pg_en = 0); + for (int lane = 0; lane < NUM_LANES; lane++) + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pg_en = 1; /* Enable generator */ + c.s.lbert_pg_width = 3; /* 20 bit */ + c.s.lbert_pg_mode = mode); + } + + if (dir & BDK_QLM_DIRECTION_RX) + { + /* Clear the error counter and Disable the matcher */ + for (int lane = 0; lane < NUM_LANES; lane++) + { + prbs_errors[qlm][lane] = 0; + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pm_en = 0); + } + for (int lane = 0; lane < NUM_LANES; lane++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pm_en = 1; /* Enable matcher */ + c.s.lbert_pm_width = 3; /* 20 bit */ + c.s.lbert_pm_mode = mode); + } + /* Tell the matcher to start sync */ + for (int retry=0; retry < 4; retry++) + { + for (int lane = 0; lane < NUM_LANES; lane++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pm_sync_start = 1); + } + /* Wait 10ms */ + bdk_wait_usec(10000); + } + } + return 0; +} + +/** + * Disable PRBS on a QLM + * + * @param node Node to use in a numa setup + * @param qlm QLM to use + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_disable_prbs(bdk_node_t node, int qlm) +{ + const int NUM_LANES = bdk_qlm_get_lanes(node, qlm); + BDK_CSR_INIT(phy_ctl, node, BDK_GSERX_PHY_CTL(qlm)); + if (phy_ctl.s.phy_reset) + return -1; + + for (int lane = 0; lane < NUM_LANES; lane++) + { + prbs_errors[qlm][lane] = 0; + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pg_en = 0; + c.s.lbert_pm_en = 0); + } + return 0; +} + +/** + * Return the number of PRBS errors since PRBS started running + * + * @param node Node to use in numa setup + * @param qlm QLM to use + * @param lane Which lane + * @param clear Clear counter after return the current value + * + * @return Number of errors + */ +uint64_t __bdk_qlm_get_prbs_errors(bdk_node_t node, int qlm, int lane, int clear) +{ + /* Restart synchronization */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pm_sync_start = 1); + /* This CSR is self clearing per the CSR description, but it doesn't + seem to do that. Instead it clears when we trigger sync again */ + BDK_CSR_INIT(rx, node, BDK_GSERX_LANEX_LBERT_ECNT(qlm, lane)); + uint64_t errors = rx.s.lbert_err_cnt; + if (rx.s.lbert_err_ovbit14) + errors <<= 7; + prbs_errors[qlm][lane] += errors; + uint64_t result = prbs_errors[qlm][lane]; + if (clear) + prbs_errors[qlm][lane] = 0; + return result; +} + +/** + * Inject an error into PRBS + * + * @param node Node to use in numa setup + * @param qlm QLM to use + * @param lane Which lane + */ +void __bdk_qlm_inject_prbs_error(bdk_node_t node, int qlm, int lane) +{ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_LBERT_CFG(qlm, lane), + c.s.lbert_pg_err_insert = 1); +} + +/** + * Enable shallow loopback on a QLM + * + * @param node Node to use in a numa setup + * @param qlm QLM to use + * @param loop Type of loopback. Not all QLMs support all modes + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_enable_loop(bdk_node_t node, int qlm, bdk_qlm_loop_t loop) +{ + bdk_error("Chip doesn't support shallow QLM loopback\n"); + return -1; +} + +/** + * Initialize the QLM mode table + * + * @param node Node to initialize + * @param qlm Which QLM + * @param ref_clk Reference clock of the QLM in Hz + */ +void __bdk_qlm_init_mode_table(bdk_node_t node, int qlm, int ref_clk) +{ + /* The QLM PLLs are controlled by an array of parameters indexed + by the QLM mode for each QLM. We need to fill in these tables. + Also each lane has some mode parameters, again in a array index + by the lane_mode */ + for (int lane_mode = 0; lane_mode < 12; lane_mode++) + { + /* The values used below are all from + http://mawiki.caveonetworks.com/wiki/78xx/GSER_WEST */ + BDK_CSR_INIT(pll_mode_0 , node, BDK_GSERX_PLL_PX_MODE_0(qlm, lane_mode)); + BDK_CSR_INIT(pll_mode_1 , node, BDK_GSERX_PLL_PX_MODE_1(qlm, lane_mode)); + BDK_CSR_INIT(lane_mode_0, node, BDK_GSERX_LANE_PX_MODE_0(qlm, lane_mode)); + BDK_CSR_INIT(lane_mode_1, node, BDK_GSERX_LANE_PX_MODE_1(qlm, lane_mode)); + switch (lane_mode) + { + case BDK_GSER_LMODE_E_R_25G_REFCLK100: + case BDK_GSER_LMODE_E_R_5G_REFCLK100: + case BDK_GSER_LMODE_E_R_8G_REFCLK100: + /* These modes are used for PCIe where the defaults are + correct. Skip programming these */ + continue; + case BDK_GSER_LMODE_E_R_125G_REFCLK15625_KX: + pll_mode_0.s.pll_icp = 0x1; + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0x28; + + pll_mode_1.s.pll_16p5en = 0x1; + pll_mode_1.s.pll_cpadj = 0x3; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + pll_mode_1.s.pll_div = 0x10; + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x2; + lane_mode_0.s.rx_ldiv = 0x2; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x1; + lane_mode_1.s.cdr_fgain = 0xc; + lane_mode_1.s.ph_acc_adj = 0x1e; + break; + case BDK_GSER_LMODE_E_R_3125G_REFCLK15625_XAUI: + pll_mode_0.s.pll_icp = 0x1; + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0x14; + + pll_mode_1.s.pll_16p5en = 0x1; + pll_mode_1.s.pll_cpadj = 0x2; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + pll_mode_1.s.pll_div = 0x14; + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x1; + lane_mode_0.s.rx_ldiv = 0x1; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x1; + lane_mode_1.s.cdr_fgain = 0xc; + lane_mode_1.s.ph_acc_adj = 0x1e; + break; + case BDK_GSER_LMODE_E_R_103125G_REFCLK15625_KR: + pll_mode_0.s.pll_icp = 0x1; + pll_mode_0.s.pll_rloop = 0x5; + pll_mode_0.s.pll_pcs_div = 0xa; + + pll_mode_1.s.pll_16p5en = 0x1; + pll_mode_1.s.pll_cpadj = 0x2; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x1; + pll_mode_1.s.pll_div = 0x21; + + lane_mode_0.s.ctle = 0x3; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x0; + lane_mode_0.s.rx_ldiv = 0x0; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x1; + lane_mode_1.s.vma_mm = 0x0; + lane_mode_1.s.cdr_fgain = 0xa; + lane_mode_1.s.ph_acc_adj = 0xf; + break; + case BDK_GSER_LMODE_E_R_125G_REFCLK15625_SGMII: + pll_mode_0.s.pll_icp = 0x1; + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0x28; + + pll_mode_1.s.pll_16p5en = 0x1; + pll_mode_1.s.pll_cpadj = 0x3; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + pll_mode_1.s.pll_div = 0x10; + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x2; + lane_mode_0.s.rx_ldiv = 0x2; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x1; + lane_mode_1.s.cdr_fgain = 0xc; + lane_mode_1.s.ph_acc_adj = 0x1e; + if(ref_clk == REF_100MHZ) + { + pll_mode_0.s.pll_pcs_div = 0x28; + pll_mode_1.s.pll_div = 0x19; + pll_mode_1.s.pll_cpadj = 0x2; + } + break; + case BDK_GSER_LMODE_E_R_5G_REFCLK15625_QSGMII: + pll_mode_0.s.pll_icp = 0x1; /* Per Scott McIlhenny 5/17/2016 (t81) */ + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0xa; + + pll_mode_1.s.pll_16p5en = 0x0; + pll_mode_1.s.pll_cpadj = 0x2; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + /* QSGMII is a special case. We use the same table entry for + 100Mhz and 125Mhz clocks as the normal 156Mhz */ + switch (ref_clk) + { + case REF_100MHZ: + pll_mode_1.s.pll_div = 0x19; + break; + case REF_125MHZ: + pll_mode_1.s.pll_div = 0x14; + break; + default: /* REF_156MHZ */ + pll_mode_1.s.pll_div = 0x10; + break; + } + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x0; + lane_mode_0.s.rx_ldiv = 0x0; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x1; /* Per Scott McIlhenny 5/17/2016 (t81) */ + lane_mode_1.s.cdr_fgain = 0xc; + lane_mode_1.s.ph_acc_adj = 0x1e; + break; + case BDK_GSER_LMODE_E_R_625G_REFCLK15625_RXAUI: + pll_mode_0.s.pll_icp = 0x1; + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0xa; + + pll_mode_1.s.pll_16p5en = 0x0; + pll_mode_1.s.pll_cpadj = 0x2; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + pll_mode_1.s.pll_div = 0x14; + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x0; + lane_mode_0.s.rx_ldiv = 0x0; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x0; + lane_mode_1.s.cdr_fgain = 0xa; + lane_mode_1.s.ph_acc_adj = 0x14; + break; + case BDK_GSER_LMODE_E_R_25G_REFCLK125: + pll_mode_0.s.pll_icp = 0x3; + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0x5; + + pll_mode_1.s.pll_16p5en = 0x0; + pll_mode_1.s.pll_cpadj = 0x1; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + pll_mode_1.s.pll_div = 0x14; + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x1; + lane_mode_0.s.tx_ldiv = 0x1; + lane_mode_0.s.rx_ldiv = 0x1; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x1; + lane_mode_1.s.cdr_fgain = 0xa; + lane_mode_1.s.ph_acc_adj = 0x14; + break; + case BDK_GSER_LMODE_E_R_5G_REFCLK125: + pll_mode_0.s.pll_icp = 0x3; + pll_mode_0.s.pll_rloop = 0x3; + pll_mode_0.s.pll_pcs_div = 0xa; + + pll_mode_1.s.pll_16p5en = 0x0; + pll_mode_1.s.pll_cpadj = 0x1; + pll_mode_1.s.pll_pcie3en = 0x0; + pll_mode_1.s.pll_opr = 0x0; + pll_mode_1.s.pll_div = 0x14; + + lane_mode_0.s.ctle = 0x0; + lane_mode_0.s.pcie = 0x1; + lane_mode_0.s.tx_ldiv = 0x0; + lane_mode_0.s.rx_ldiv = 0x0; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x3; + lane_mode_0.s.rx_mode = 0x3; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x0; + lane_mode_1.s.cdr_fgain = 0xa; + lane_mode_1.s.ph_acc_adj = 0x14; + break; + case BDK_GSER_LMODE_E_R_8G_REFCLK125: + pll_mode_0.s.pll_icp = 0x2; + pll_mode_0.s.pll_rloop = 0x5; + pll_mode_0.s.pll_pcs_div = 0xa; + + pll_mode_1.s.pll_16p5en = 0x0; + pll_mode_1.s.pll_cpadj = 0x1; + pll_mode_1.s.pll_pcie3en = 0x1; + pll_mode_1.s.pll_opr = 0x1; + pll_mode_1.s.pll_div = 0x20; + + lane_mode_0.s.ctle = 0x3; + lane_mode_0.s.pcie = 0x0; + lane_mode_0.s.tx_ldiv = 0x0; + lane_mode_0.s.rx_ldiv = 0x0; + lane_mode_0.s.srate = 0x0; + lane_mode_0.s.tx_mode = 0x2; + lane_mode_0.s.rx_mode = 0x2; + + lane_mode_1.s.vma_fine_cfg_sel = 0x0; + lane_mode_1.s.vma_mm = 0x0; + lane_mode_1.s.cdr_fgain = 0xb; + lane_mode_1.s.ph_acc_adj = 0x23; + break; + } + BDK_CSR_WRITE(node, BDK_GSERX_PLL_PX_MODE_0(qlm, lane_mode), pll_mode_0.u); + BDK_CSR_WRITE(node, BDK_GSERX_PLL_PX_MODE_1(qlm, lane_mode), pll_mode_1.u); + BDK_CSR_WRITE(node, BDK_GSERX_LANE_PX_MODE_0(qlm, lane_mode), lane_mode_0.u); + BDK_CSR_WRITE(node, BDK_GSERX_LANE_PX_MODE_1(qlm, lane_mode), lane_mode_1.u); + } +} + +/** + * Given a valid PEM number, return its speed in Gbaud + * + * @param node Node to use in numa setup + * @param pem PEM to get speed of + * + * @return Speed in Gbaud. Zero if disabled + */ +int __bdk_qlm_get_gbaud_mhz_pem(bdk_node_t node, int pem) +{ + BDK_CSR_INIT(pem_cfg, node, BDK_PEMX_CFG(pem)); + switch (pem_cfg.cn83xx.md) + { + case 0: /* Gen 1 */ + return 2500; + case 1: /* Gen 2 */ + return 5000; + case 2: /* Gen 3 */ + return 8000; + default: + return 0; + } +} + +/** + * Get the speed of a QLM using its LMODE. This can't be used on PCIe QLMs. + * + * @param node Node to use in numa setup + * @param qlm Which QLM + * + * @return QLM speed on Gbaud + */ +int __bdk_qlm_get_gbaud_mhz_lmode(bdk_node_t node, int qlm) +{ + /* QLM is not in PCIe, assume LMODE is good enough for determining + the speed */ + BDK_CSR_INIT(lane_mode, node, BDK_GSERX_LANE_MODE(qlm)); + switch (lane_mode.s.lmode) + { + case BDK_GSER_LMODE_E_R_25G_REFCLK100: + return 2500; + case BDK_GSER_LMODE_E_R_5G_REFCLK100: + return 5000; + case BDK_GSER_LMODE_E_R_8G_REFCLK100: + return 8000; + case BDK_GSER_LMODE_E_R_125G_REFCLK15625_KX: + return 1250; + case BDK_GSER_LMODE_E_R_3125G_REFCLK15625_XAUI: + return 3125; + case BDK_GSER_LMODE_E_R_103125G_REFCLK15625_KR: + return 10312; + case BDK_GSER_LMODE_E_R_125G_REFCLK15625_SGMII: + return 1250; + case BDK_GSER_LMODE_E_R_5G_REFCLK15625_QSGMII: + return 5000; + case BDK_GSER_LMODE_E_R_625G_REFCLK15625_RXAUI: + return 6250; + case BDK_GSER_LMODE_E_R_25G_REFCLK125: + return 2500; + case BDK_GSER_LMODE_E_R_5G_REFCLK125: + return 5000; + case BDK_GSER_LMODE_E_R_8G_REFCLK125: + return 8000; + default: + return 0; + } +} + +/** + * Converts a measured reference clock to a likely ideal value. Rounds + * clock speed to the nearest REF_*Mhz define. + * + * @param node Node to use in numa setup + * @param qlm Which QLM + * @param measured_hz + * Measured value + * + * @return Value exactly matching a define + */ +int __bdk_qlm_round_refclock(bdk_node_t node, int qlm, int measured_hz) +{ + int ref_clk; + if ((measured_hz > REF_100MHZ - REF_100MHZ / 10) && (measured_hz < REF_100MHZ + REF_100MHZ / 10)) + { + ref_clk = REF_100MHZ; + } + else if ((measured_hz > REF_125MHZ - REF_125MHZ / 10) && (measured_hz < REF_125MHZ + REF_125MHZ / 10)) + { + ref_clk = REF_125MHZ; + } + else if ((measured_hz > REF_156MHZ - REF_156MHZ / 10) && (measured_hz < REF_156MHZ + REF_156MHZ / 10)) + { + ref_clk = REF_156MHZ; + } + else if (measured_hz < 1000000) + { + ref_clk = 0; /* Used for disabled QLMs */ + } + else + { + ref_clk = measured_hz; + bdk_error("N%d.QLM%d: Unexpected reference clock speed of %d Mhz\n", node, qlm, measured_hz / 1000000); + } + return ref_clk; +} + +/** + * TWSI reads from the MCU randomly timeout. Retry a few times on + * failure to try and recover + * + * @param node Node to use in a Numa setup. Can be an exact ID or a special + * value. + * @param twsi_id which TWSI bus to use + * @param dev_addr Device address (7 bit) + * @param internal_addr + * Internal address. Can be 0, 1 or 2 bytes in width + * @param num_bytes Number of data bytes to read (1-4) + * @param ia_width_bytes + * Internal address size in bytes (0, 1, or 2) + * + * @return Read data, or -1 on failure + */ +static int64_t mcu_read(bdk_node_t node, int twsi_id, uint8_t dev_addr, uint16_t internal_addr, int num_bytes, int ia_width_bytes) +{ + int read_tries = 0; + int64_t result; + do + { + result = bdk_twsix_read_ia(node, twsi_id, dev_addr, internal_addr, num_bytes, ia_width_bytes); + read_tries++; + if (result < 0) + { + BDK_TRACE(QLM, "Timeout %d reading from MCU\n", read_tries); + bdk_wait_usec(100000); + } + } while ((result < 0) && (read_tries < 3)); + return result; +} + +static void __bdk_qlm_set_reference(bdk_node_t node, int qlm, int ref_clk) +{ + int use_clock; + if (CAVIUM_IS_MODEL(CAVIUM_CN88XX) || CAVIUM_IS_MODEL(CAVIUM_CN83XX) || CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + { + switch (ref_clk) + { + case REF_100MHZ: + use_clock = 0; /* Common clock 0 */ + BDK_TRACE(QLM, "Setting N%d.QLM%d to use common clock 0\n", node, qlm); + break; + case REF_156MHZ: + use_clock = 1; /* Common clock 1 */ + BDK_TRACE(QLM, "Setting N%d.QLM%d to use common clock 1\n", node, qlm); + break; + default: + use_clock = 2; /* External clock */ + BDK_TRACE(QLM, "Setting N%d.QLM%d to use external clock\n", node, qlm); + break; + } + } + else + { + bdk_error("Update __bdk_qlm_set_reference() for qlm auto config of this chip\n"); + return; + } + BDK_CSR_MODIFY(c, node, BDK_GSERX_REFCLK_SEL(qlm), + c.s.com_clk_sel = (use_clock != 2); + c.s.use_com1 = (use_clock == 1)); +} + +/** + * For Cavium EVB and EBB board, query the MCU to determine the QLM setup. Applying + * any configuration found. + * + * @param node Node to configure + * + * @return Zero on success, negative on failure + */ +int bdk_qlm_mcu_auto_config(bdk_node_t node) +{ + const int MCU_TWSI_BUS = 0; + const int MCU_TWSI_ADDRESS = 0x60; + int64_t data; + + /* Check the two magic number bytes the MCU should return */ + data = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x00, 1, 1); + if (data != 0xa5) + { + printf("QLM Config: MCU not found, skipping auto configuration\n"); + return -1; + } + data = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x01, 1, 1); + if (data != 0x5a) + { + bdk_error("QLM Config: MCU magic number incorrect\n"); + return -1; + } + + /* Read the MCU version */ + int mcu_major = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x02, 1, 1); + int mcu_minor = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x03, 1, 1); + BDK_TRACE(QLM, "MCU version %d.%d\n", mcu_major, mcu_minor); + if ((mcu_major < 2) || ((mcu_major == 2) && (mcu_minor < 30))) + { + bdk_error("QLM Config: Unexpected MCU version %d.%d\n", mcu_major, mcu_minor); + return -1; + } + + /* Find out how many lanes the MCU thinks are available */ + int lanes = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x16, 1, 1); + BDK_TRACE(QLM, "MCU says board has %d lanes\n", lanes); + int correct_lanes = 0; + if (cavium_is_altpkg(CAVIUM_CN88XX)) + correct_lanes = 22; + else if (CAVIUM_IS_MODEL(CAVIUM_CN88XX)) + correct_lanes = 32; + else if (CAVIUM_IS_MODEL(CAVIUM_CN83XX)) + correct_lanes = 22; + else if (CAVIUM_IS_MODEL(CAVIUM_CN81XX)) + correct_lanes = 8; + if (lanes != correct_lanes) + { + bdk_error("QLM Config: Unexpected number of lanes (%d) from MCU\n", lanes); + return -1; + } + + int lane = 0; + int qlm = 0; + while (lane < lanes) + { + int write_status; + int width; + int mode; + int speed; + int refclk; + /* TWSI reads from the MCU randomly timeout. Retry a few times on + failure to try and recover */ + int read_tries = 0; + do + { + read_tries++; + if (read_tries > 3) + { + bdk_error("QLM Config: Timeouts reading from MCU\n"); + return -1; + } + /* Space request out 20ms */ + bdk_wait_usec(20000); + /* Select the lane we are interested in */ + write_status = bdk_twsix_write_ia(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x16, 1, 1, lane); + /* Space request out 20ms */ + bdk_wait_usec(20000); + /* Get the mode */ + width = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x17, 1, 1); + mode = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x18, 2, 1); + speed = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x19, 2, 1); + refclk = mcu_read(node, MCU_TWSI_BUS, MCU_TWSI_ADDRESS, 0x1a, 1, 1); + } while ((write_status < 0) || (width < 0) || (mode < 0) || (speed < 0) || (refclk < 0)); + + BDK_TRACE(QLM, "MCU lane %d, width %d, mode 0x%x, speed 0x%x, ref 0x%x\n", + lane, width, mode, speed, refclk); + if ((width != 0) && (width != 1) && (width != 2) && (width != 4) && (width != 8)) + { + bdk_error("QLM Config: Unexpected interface width (%d) from MCU\n", width); + return -1; + } + /* MCU reports a width of 0 for unconfigured QLMs. It reports a width + of 1 for some combinations on CN80XX, and two on others. Convert + either 0 or 1 to the actual width, or 2 for CN80XX. Yuck */ + if ((width == 0) || (width == 1)) + { + if (cavium_is_altpkg(CAVIUM_CN81XX) && (qlm < 2)) + width = 2; + else + width = bdk_qlm_get_lanes(node, qlm); + } + bdk_qlm_modes_t qlm_mode; + int qlm_speed = (speed >> 8) * 1000 + (speed & 0xff) * 1000 / 256; + int use_ref = 0; + bdk_qlm_mode_flags_t qlm_flags = 0; + if (mode < 0x4000) + { + switch (mode) + { + case 0x0000: /* No Configuration */ + qlm_mode = BDK_QLM_MODE_DISABLED; + break; + case 0x0101: /* PCIe Host */ + qlm_mode = (width == 8) ? BDK_QLM_MODE_PCIE_1X8 : + (width == 4) ? BDK_QLM_MODE_PCIE_1X4 : + BDK_QLM_MODE_PCIE_1X2; + use_ref = REF_100MHZ; + break; + case 0x0102: /* PCIe Endpoint */ + qlm_mode = (width == 8) ? BDK_QLM_MODE_PCIE_1X8 : + (width == 4) ? BDK_QLM_MODE_PCIE_1X4 : + BDK_QLM_MODE_PCIE_1X2; + qlm_flags = BDK_QLM_MODE_FLAG_ENDPOINT; + use_ref = 0; /* Use the external reference for EP mode */ + break; + case 0x1000: /* SGMII */ + qlm_mode = (width == 4) ? BDK_QLM_MODE_SGMII_4X1 : + (width == 2) ? BDK_QLM_MODE_SGMII_2X1 : + BDK_QLM_MODE_SGMII_1X1; + use_ref = REF_156MHZ; + /* CN80XX parts on EBBs use phy port 2 for SGMII, while QSGMII + uses the correct port. Fix this for DLM1 and DLM3 */ + if (cavium_is_altpkg(CAVIUM_CN81XX)) + { + int bgx = (qlm == 3) ? 1 : 0; + uint64_t phy = bdk_config_get_int(BDK_CONFIG_PHY_ADDRESS, 0, bgx, 2); + bdk_config_set_int(phy, BDK_CONFIG_PHY_ADDRESS, 0, bgx, 1); + } + break; + case 0x1100: /* QSGMII */ + qlm_mode = BDK_QLM_MODE_QSGMII_4X1; + use_ref = REF_100MHZ; + break; + case 0x2000: /* XAUI */ + qlm_mode = BDK_QLM_MODE_XAUI_1X4; + use_ref = REF_156MHZ; + break; + case 0x2100: /* RXAUI */ + qlm_mode = (width == 2) ? BDK_QLM_MODE_RXAUI_1X2 : BDK_QLM_MODE_RXAUI_2X2; + use_ref = REF_156MHZ; + break; + case 0x2200: /* DXAUI */ + qlm_mode = BDK_QLM_MODE_XAUI_1X4; + use_ref = REF_156MHZ; + break; + case 0x3001: /* Interlaken */ + qlm_mode = BDK_QLM_MODE_ILK; + use_ref = REF_156MHZ; + break; + default: + bdk_error("QLM Config: Unexpected interface mode (0x%x) from MCU\n", mode); + qlm_mode = BDK_QLM_MODE_DISABLED; + break; + } + } + else + { + switch (mode) + { + case 0x4000: /* SATA */ + qlm_mode = (width == 2) ? BDK_QLM_MODE_SATA_2X1 : BDK_QLM_MODE_SATA_4X1; + use_ref = REF_100MHZ; + break; + case 0x5001: /* XFI */ + qlm_mode = (width == 4) ? BDK_QLM_MODE_XFI_4X1 : + (width == 2) ? BDK_QLM_MODE_XFI_2X1 : + BDK_QLM_MODE_XFI_1X1; + use_ref = REF_156MHZ; + break; + case 0x5002: /* 10G-KR */ + qlm_mode = (width == 4) ? BDK_QLM_MODE_10G_KR_4X1 : + (width == 2) ? BDK_QLM_MODE_10G_KR_2X1 : + BDK_QLM_MODE_10G_KR_1X1; + use_ref = REF_156MHZ; + break; + case 0x6001: /* XLAUI */ + qlm_mode = BDK_QLM_MODE_XLAUI_1X4; + use_ref = REF_156MHZ; + break; + case 0x6002: /* 40G-KR4 */ + qlm_mode = BDK_QLM_MODE_40G_KR4_1X4; + use_ref = REF_156MHZ; + break; + default: + bdk_error("QLM Config: Unexpected interface mode (0x%x) from MCU\n", mode); + qlm_mode = BDK_QLM_MODE_DISABLED; + break; + } + } + lane += width; + do + { + int internal_qlm = qlm; + /* Alternate package parts have different QLM numbers for internal + versus external. The MCU uses the external numbers */ + if (cavium_is_altpkg(CAVIUM_CN88XX)) + { + switch (qlm) + { + case 0: /* QLM0 -> QLM4 */ + internal_qlm = 4; + break; + case 1: /* QLM1 -> QLM5 */ + internal_qlm = 5; + break; + case 2: /* QLM2 -> QLM0 */ + internal_qlm = 0; + break; + case 3: /* QLM3 -> QLM1 */ + internal_qlm = 1; + break; + case 4: /* DLM4 -> QLM2 */ + internal_qlm = 2; + break; + case 5: /* DLM5 -> QLM6 */ + internal_qlm = 6; + break; + case 6: /* DLM6 -> QLM7 */ + internal_qlm = 7; + break; + default: + bdk_error("Invalid external QLM%d from MCU\n", qlm); + return -1; + } + } + if (qlm_flags & BDK_QLM_MODE_FLAG_ENDPOINT) + { + BDK_TRACE(QLM, "Skipping N%d.QLM%d mode %s(%d), speed %d, flags 0x%x (EP should already be setup)\n", + node, internal_qlm, bdk_qlm_mode_tostring(qlm_mode), qlm_mode, qlm_speed, qlm_flags); + } + else + { + BDK_TRACE(QLM, "Setting N%d.QLM%d mode %s(%d), speed %d, flags 0x%x\n", + node, internal_qlm, bdk_qlm_mode_tostring(qlm_mode), qlm_mode, qlm_speed, qlm_flags); + /* Set the reference clock for this QLM */ + __bdk_qlm_set_reference(node, internal_qlm, use_ref); + if (bdk_qlm_set_mode(node, internal_qlm, qlm_mode, qlm_speed, qlm_flags)) + return -1; + } + int num_lanes = bdk_qlm_get_lanes(node, internal_qlm); + /* CN86XX looks like two lanes each for DLM4-7 */ + if (cavium_is_altpkg(CAVIUM_CN88XX) && (qlm >= 4)) + num_lanes = 2; + if (qlm_mode == BDK_QLM_MODE_PCIE_1X8) + { + /* PCIe x8 is a special case as the QLM config function + actually configures both QLMs in one go */ + qlm++; + width -= 8; + } + else if ((qlm_mode == BDK_QLM_MODE_PCIE_1X4) && (width > num_lanes)) + { + /* PCIe x4 is a special case as the QLM config function + actually configures both QLMs in one go */ + qlm++; + width -= 4; + } + else if (width >= num_lanes) + { + if (num_lanes == 1) + width -= 2; /* Special case for CN80XX */ + else + width -= num_lanes; + } + else + width = 0; + qlm++; + } while (width > 0); + } + return 0; +} + +/** + * Display the current settings of a QLM lane + * + * @param node Node the QLM is on + * @param qlm QLM to display + * @param qlm_lane Lane to use + * @param show_tx Display TX parameters + * @param show_rx Display RX parameters + */ +void bdk_qlm_display_settings(bdk_node_t node, int qlm, int qlm_lane, bool show_tx, bool show_rx) +{ + const char *dir_label[] = {"Hold", "Inc", "Dec", "Hold"}; + + uint64_t rx_aeq_out_0 = BDK_CSR_READ(node, BDK_GSERX_LANEX_RX_AEQ_OUT_0(qlm, qlm_lane)); + uint64_t rx_aeq_out_1 = BDK_CSR_READ(node, BDK_GSERX_LANEX_RX_AEQ_OUT_1(qlm, qlm_lane)); + uint64_t rx_aeq_out_2 = BDK_CSR_READ(node, BDK_GSERX_LANEX_RX_AEQ_OUT_2(qlm, qlm_lane)); + uint64_t rx_vma_status_0 = BDK_CSR_READ(node, BDK_GSERX_LANEX_RX_VMA_STATUS_0(qlm, qlm_lane)); + uint64_t rx_vma_status_1 = BDK_CSR_READ(node, BDK_GSERX_LANEX_RX_VMA_STATUS_1(qlm, qlm_lane)); + uint64_t sds_pin_mon_1 = BDK_CSR_READ(node, BDK_GSERX_LANEX_SDS_PIN_MON_1(qlm, qlm_lane)); + uint64_t sds_pin_mon_2 = BDK_CSR_READ(node, BDK_GSERX_LANEX_SDS_PIN_MON_2(qlm, qlm_lane)); + uint64_t br_rxx_eer = BDK_CSR_READ(node, BDK_GSERX_BR_RXX_EER(qlm, qlm_lane)); + + printf("N%d.QLM%d Lane %d:\n", node, qlm, qlm_lane); + if (show_rx) + { + printf(" DFE Tap 1: %llu, Tap 2: %lld, Tap 3: %lld, Tap 4: %lld, Tap 5: %lld\n", + bdk_extract(rx_aeq_out_1, 0, 5), + bdk_extract_smag(rx_aeq_out_1, 5, 9), + bdk_extract_smag(rx_aeq_out_1, 10, 14), + bdk_extract_smag(rx_aeq_out_0, 0, 4), + bdk_extract_smag(rx_aeq_out_0, 5, 9)); + printf(" Pre-CTLE Gain: %llu, Post-CTLE Gain: %llu, CTLE Peak: %llu, CTLE Pole: %llu\n", + bdk_extract(rx_aeq_out_2, 4, 4), + bdk_extract(rx_aeq_out_2, 0, 4), + bdk_extract(rx_vma_status_0, 2, 4), + bdk_extract(rx_vma_status_0, 0, 2)); + printf(" RX Equalization Tx Directions Hints TXPRE: %s, TXMAIN: %s, TXPOST: %s, Figure of Merit: %llu\n", + dir_label[bdk_extract(br_rxx_eer, 0, 2)], + dir_label[bdk_extract(br_rxx_eer, 2, 2)], + dir_label[bdk_extract(br_rxx_eer, 4, 2)], + bdk_extract(br_rxx_eer, 6, 8)); + } + if (show_tx) + { + printf(" TX Swing: %llu, Pre-emphasis Pre-cursor: %llu, Post-cursor: %llu\n", + bdk_extract(sds_pin_mon_1, 1, 5), + bdk_extract(sds_pin_mon_2, 0, 4), + bdk_extract(sds_pin_mon_2, 4, 5)); + printf(" TX Boost Enable: %llu, TX Turbo Mode: %llu\n", + bdk_extract(sds_pin_mon_2, 10, 1), + bdk_extract(sds_pin_mon_2, 9, 1)); + } + printf(" Training-done: %llu\n", + bdk_extract(rx_vma_status_1, 7, 1)); +} + +/** + * Perform RX equalization on a QLM + * + * @param node Node the QLM is on + * @param qlm QLM to perform RX equalization on + * @param qlm_lane Lane to use, or -1 for all lanes + * + * @return Zero on success, negative if any lane failed RX equalization + */ +int __bdk_qlm_rx_equalization(bdk_node_t node, int qlm, int qlm_lane) +{ + /* Don't touch QLMs is reset or powered down */ + BDK_CSR_INIT(phy_ctl, node, BDK_GSERX_PHY_CTL(qlm)); + if (phy_ctl.s.phy_pd || phy_ctl.s.phy_reset) + return -1; + /* Don't run on PCIe links */ + if (bdk_qlm_get_mode(node, qlm) <= BDK_QLM_MODE_PCIE_1X8) + return -1; + + int fail = 0; /* Bitmask of lanes that failed CDR Lock or Eltrical Idle check */ + int pending = 0; /* Bitmask of lanes that we're waiting for */ + int MAX_LANES = bdk_qlm_get_lanes(node, qlm); + + BDK_TRACE(QLM, "N%d.QLM%d: Starting RX equalization on lane %d\n", node, qlm, qlm_lane); + for (int lane = 0; lane < MAX_LANES; lane++) + { + /* Skip lanes we don't care about */ + if ((qlm_lane != -1) && (qlm_lane != lane)) + continue; + /* Check that the lane has completed CDR lock */ + BDK_CSR_INIT(eie_detsts, node, BDK_GSERX_RX_EIE_DETSTS(qlm)); + if (((1 << lane) & eie_detsts.s.cdrlock) == 0) + { + /* Mark bad so we skip this lane below */ + fail |= 1 << lane; + continue; + } + /* Enable software control */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_BR_RXX_CTL(qlm, lane), + c.s.rxt_swm = 1); + /* Clear the completion flag and initiate a new request */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_BR_RXX_EER(qlm, lane), + c.s.rxt_esv = 0; + c.s.rxt_eer = 1); + /* Remember that we have to wait for this lane */ + pending |= 1 << lane; + } + + /* Timing a few of these over XFI on CN73XX, each takes 21-23ms. XLAUI + was about the same time. DXAUI and RXAUI both took 2-3ms. Put the + timeout at 250ms, which is roughly 10x my measurements. */ + uint64_t timeout = bdk_clock_get_count(BDK_CLOCK_TIME) + bdk_clock_get_rate(node, BDK_CLOCK_TIME) / 4; + while (pending) + { + for (int lane = 0; lane < MAX_LANES; lane++) + { + int lane_mask = 1 << lane; + /* Only check lanes that are pending */ + if (!(pending & lane_mask)) + continue; + /* Read the registers for checking Electrical Idle / CDR lock and + the status of the RX equalization */ + BDK_CSR_INIT(eie_detsts, node, BDK_GSERX_RX_EIE_DETSTS(qlm)); + BDK_CSR_INIT(gserx_br_rxx_eer, node, BDK_GSERX_BR_RXX_EER(qlm, lane)); + /* Mark failure if lane entered Electrical Idle or lost CDR Lock. The + bit for the lane will have cleared in either EIESTS or CDRLOCK */ + if (!(eie_detsts.s.eiests & eie_detsts.s.cdrlock & lane_mask)) + { + fail |= lane_mask; + pending &= ~lane_mask; + } + else if (gserx_br_rxx_eer.s.rxt_esv) + { + /* Clear pending if RX equalization finished */ + pending &= ~lane_mask; + } + } + /* Break out of the loop on timeout */ + if (bdk_clock_get_count(BDK_CLOCK_TIME) > timeout) + break; + } + + /* Cleanup and report status */ + for (int lane = 0; lane < MAX_LANES; lane++) + { + /* Skip lanes we don't care about */ + if ((qlm_lane != -1) && (qlm_lane != lane)) + continue; + int lane_mask = 1 << lane; + /* Get the final RX equalization status */ + BDK_CSR_INIT(gserx_br_rxx_eer, node, BDK_GSERX_BR_RXX_EER(qlm, lane)); + /* Disable software control */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_BR_RXX_CTL(qlm, lane), + c.s.rxt_swm = 0); + /* Report status */ + if (fail & lane_mask) + { + BDK_TRACE(QLM, "N%d.QLM%d: Lane %d RX equalization lost CDR Lock or entered Electrical Idle\n", node, qlm, lane); + } + else if ((pending & lane_mask) || !gserx_br_rxx_eer.s.rxt_esv) + { + BDK_TRACE(QLM, "N%d.QLM%d: Lane %d RX equalization timeout\n", node, qlm, lane); + fail |= 1 << lane; + } + else + { + bdk_qlm_display_settings(node, qlm, lane, false, true); + } + } + + return (fail) ? -1 : 0; +} + +/** + * Configure the TX tuning parameters for a QLM lane. The tuning parameters can + * be specified as -1 to maintain their current value + * + * @param node Node to configure + * @param qlm QLM to configure + * @param lane Lane to configure + * @param tx_swing Transmit swing (coef 0) Range 0-31 + * @param tx_pre Pre cursor emphasis (Coef -1). Range 0-15 + * @param tx_post Post cursor emphasis (Coef +1). Range 0-31 + * @param tx_gain Transmit gain. Range 0-7 + * @param tx_vboost Transmit voltage boost. Range 0-1 + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_tune_lane_tx(bdk_node_t node, int qlm, int lane, int tx_swing, int tx_pre, int tx_post, int tx_gain, int tx_vboost) +{ + /* Check tuning constraints */ + if ((tx_swing < -1) || (tx_swing > 25)) + { + bdk_error("N%d.QLM%d: Lane %d: Invalid TX_SWING(%d)\n", node, qlm, lane, tx_swing); + return -1; + } + if ((tx_pre < -1) || (tx_pre > 10)) + { + bdk_error("N%d.QLM%d: Lane %d: Invalid TX_PRE(%d)\n", node, qlm, lane, tx_pre); + return -1; + } + if ((tx_post < -1) || (tx_post > 15)) + { + bdk_error("N%d.QLM%d: Lane %d: Invalid TX_POST(%d)\n", node, qlm, lane, tx_post); + return -1; + } + if ((tx_pre >= 0) && (tx_post >= 0) && (tx_swing >= 0) && (tx_pre + tx_post - tx_swing > 2)) + { + bdk_error("N%d.QLM%d: Lane %d: TX_PRE(%d) + TX_POST(%d) - TX_SWING(%d) must be less than or equal to 2\n", node, qlm, lane, tx_pre, tx_post, tx_swing); + return -1; + } + if ((tx_pre >= 0) && (tx_post >= 0) && (tx_swing >= 0) && (tx_pre + tx_post + tx_swing > 35)) + { + bdk_error("N%d.QLM%d: Lane %d: TX_PRE(%d) + TX_POST(%d) + TX_SWING(%d) must be less than or equal to 35\n", node, qlm, lane, tx_pre, tx_post, tx_swing); + return -1; + } + + if ((tx_gain < -1) || (tx_gain > 7)) + { + bdk_error("N%d.QLM%d: Lane %d: Invalid TX_GAIN(%d). TX_GAIN must be between 0 and 7\n", node, qlm, lane, tx_gain); + return -1; + } + + if ((tx_vboost < -1) || (tx_vboost > 1)) + { + bdk_error("N%d.QLM%d: Lane %d: Invalid TX_VBOOST(%d). TX_VBOOST must be 0 or 1.\n", node, qlm, lane, tx_vboost); + return -1; + } + + if ((tx_pre != -1) && (tx_post == -1)) + { + BDK_CSR_INIT(emphasis, node, BDK_GSERX_LANEX_TX_PRE_EMPHASIS(qlm, lane)); + tx_post = emphasis.s.cfg_tx_premptap >> 4; + } + + if ((tx_post != -1) && (tx_pre == -1)) + { + BDK_CSR_INIT(emphasis, node, BDK_GSERX_LANEX_TX_PRE_EMPHASIS(qlm, lane)); + tx_pre = emphasis.s.cfg_tx_premptap & 0xf; + } + + BDK_TRACE(QLM, "N%d.QLM%d: Lane %d: TX_SWING=%d, TX_PRE=%d, TX_POST=%d, TX_GAIN=%d, TX_VBOOST=%d\n", + node, qlm, lane, tx_swing, tx_pre, tx_post, tx_gain, tx_vboost); + + /* Manual Tx Swing and Tx Equalization Programming Steps */ + + /* 1) Enable Tx swing and Tx emphasis overrides */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_TX_CFG_1(qlm, lane), + c.s.tx_swing_ovrrd_en = (tx_swing != -1); + c.s.tx_premptap_ovrrd_val = (tx_pre != -1) && (tx_post != -1); + c.s.tx_vboost_en_ovrrd_en = (tx_vboost != -1)); /* Vboost override */ + /* 2) Program the Tx swing and Tx emphasis Pre-cursor and Post-cursor values */ + if (tx_swing != -1) + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_TX_CFG_0(qlm, lane), + c.s.cfg_tx_swing = tx_swing); + if ((tx_pre != -1) && (tx_post != -1)) + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_TX_PRE_EMPHASIS(qlm, lane), + c.s.cfg_tx_premptap = (tx_post << 4) | tx_pre); + /* Apply TX gain settings */ + if (tx_gain != -1) + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_TX_CFG_3(qlm, lane), + c.s.pcs_sds_tx_gain = tx_gain); + /* Apply TX vboost settings */ + if (tx_vboost != -1) + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_TX_CFG_3(qlm, lane), + c.s.cfg_tx_vboost_en = tx_vboost); + /* 3) Program override for the Tx coefficient request */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_0(qlm, lane), + if (((tx_pre != -1) && (tx_post != -1)) || (tx_swing != -1)) + c.s.cfg_tx_coeff_req_ovrrd_val = 1; + if (tx_vboost != -1) + c.s.cfg_tx_vboost_en_ovrrd_val = 1; + ); + /* 4) Enable the Tx coefficient request override enable */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + if (((tx_pre != -1) && (tx_post != -1)) || (tx_swing != -1)) + c.s.cfg_tx_coeff_req_ovrrd_en = 1; + if (tx_vboost != -1) + c.s.cfg_tx_vboost_en_ovrrd_en = 1 + ); + /* 5) Issue a Control Interface Configuration Override request to start + the Tx equalizer Optimization cycle which applies the new Tx swing + and equalization settings */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.ctlifc_ovrrd_req = 1); + + /* 6) Prepare for a subsequent Tx swing and Tx equalization adjustment: + a) Disable the Tx coefficient request override enable */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.cfg_tx_coeff_req_ovrrd_en = 0); + /* b) Issue a Control Interface Configuration Override request */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.ctlifc_ovrrd_req = 1); + /* The new Tx swing and Pre-cursor and Post-cursor settings will now take + effect. */ + return 0; +} + +/** + * Some QLM speeds need to override the default tuning parameters + * + * @param node Node to use in a Numa setup + * @param qlm QLM to configure + * @param mode Desired mode + * @param baud_mhz Desired speed + */ +void __bdk_qlm_tune(bdk_node_t node, int qlm, bdk_qlm_modes_t mode, int baud_mhz) +{ + /* Note: This function is not called for CCPI. For CCPI tuning, see + bdk-init-nz-node.c */ + /* Tuning parameters override the KR training. Don't apply them for KR links */ + switch (mode) + { + case BDK_QLM_MODE_10G_KR_1X1: + case BDK_QLM_MODE_10G_KR_2X1: + case BDK_QLM_MODE_10G_KR_4X1: + case BDK_QLM_MODE_40G_KR4_1X4: + return; + case BDK_QLM_MODE_PCIE_1X1: + case BDK_QLM_MODE_PCIE_2X1: + case BDK_QLM_MODE_PCIE_1X2: + case BDK_QLM_MODE_PCIE_1X4: + case BDK_QLM_MODE_PCIE_1X8: + /* Don't tune PCIe Gen3 as it has its own builtin, similar to KR */ + if (baud_mhz > 5000) + return; + break; + default: + break; + } + + /* We're apply tuning for all lanes on this QLM */ + int num_lanes = bdk_qlm_get_lanes(node, qlm); + for (int lane = 0; lane < num_lanes; lane++) + { + /* TX Swing: First read any board specific setting from the environment */ + int swing = bdk_config_get_int(BDK_CONFIG_QLM_TUNING_TX_SWING, node, qlm, lane); + /* If no setting, use hard coded generic defaults */ + if (swing == -1) + { + if (baud_mhz == 6250) + { + /* Email from Brendan Metzner about RXAUI around 2/7/2016 */ + swing = 0x12; + } + else if (baud_mhz == 10312) + { + /* From lab measurements of EBB8800 at 10.3125G */ + swing = 0xd; + } + } + + /* TX Premptap: First read any board specific setting from the environment */ + int premptap = bdk_config_get_int(BDK_CONFIG_QLM_TUNING_TX_PREMPTAP, node, qlm, lane); + /* If no setting, use hard coded generic defaults */ + if (premptap == -1) + { + if (baud_mhz == 6250) + { + /* From lab measurements of EBB8800 at 6.25G */ + premptap = 0xa0; + } + else if (baud_mhz == 10312) + { + /* From lab measurements of EBB8800 at 10.3125G */ + premptap = 0xd0; + } + } + + int tx_pre = (premptap == -1) ? -1 : premptap & 0xf; + int tx_post = (premptap == -1) ? -1 : premptap >> 4; + int gain = bdk_config_get_int(BDK_CONFIG_QLM_TUNING_TX_GAIN, node, qlm, lane); + int vboost = bdk_config_get_int(BDK_CONFIG_QLM_TUNING_TX_VBOOST, node, qlm, lane); + + __bdk_qlm_tune_lane_tx(node, qlm, lane, swing, tx_pre, tx_post, gain, vboost); + + /* Email from Brendan Metzner about RXAUI around 2/7/2016 suggested the + following setting for RXAUI at 6.25G with both PHY or cable. I'm + applying it to all lanes running at 6.25G */ + if (baud_mhz == 6250) + { + /* This is changing the Q/QB error sampler 0 threshold from 0xD + to 0xF */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_CFG_4(qlm, lane), + c.s.cfg_rx_errdet_ctrl = 0xcf6f); + } + } +} + +/** + * Disables DFE for the specified QLM lane(s). + * This function should only be called for low-loss channels. + * + * @param node Node to configure + * @param qlm QLM to configure + * @param lane Lane to configure, or -1 for all lanes + */ +void __bdk_qlm_dfe_disable(int node, int qlm, int lane) +{ + int num_lanes = bdk_qlm_get_lanes(node, qlm); + int l; + + for (l = 0; l < num_lanes; l++) { + if ((lane != -1) && (lane != l)) + continue; + /* 1. Write GSERX_LANEx_RX_LOOP_CTRL = 0x0270 (var "loop_ctrl" with bits 8 & 1 cleared). + * bit<1> dfe_en_byp = 1'b0 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_LOOP_CTRL(qlm, l), + c.s.cfg_rx_lctrl = c.s.cfg_rx_lctrl & 0x3fd); + + /* 2. Write GSERX_LANEx_RX_VALBBD_CTRL_1 = 0x0000 (var "ctrl1" with all bits cleared) + * bits<14:11> CFG_RX_DFE_C3_MVAL = 4'b0000 + * bit<10> CFG_RX_DFE_C3_MSGN = 1'b0 + * bits<9:6> CFG_RX_DFE_C2_MVAL = 4'b0000 + * bit<5> CFG_RX_DFE_C2_MSGN = 1'b0 + * bits<4:1> CFG_RX_DFE_C1_MVAL = 5'b0000 + * bits<0> CFG_RX_DFE_C1_MSGN = 1'b0 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_VALBBD_CTRL_1(qlm, l), + c.s.dfe_c3_mval = 0; + c.s.dfe_c3_msgn = 0; + c.s.dfe_c2_mval = 0; + c.s.dfe_c2_msgn = 0; + c.s.dfe_c1_mval = 0; + c.s.dfe_c1_msgn = 0); + + /* 3. Write GSERX_LANEx_RX_VALBBD_CTRL_0 = 0x2400 (var "ctrl0" with following bits set/cleared) + * bits<11:10> CFG_RX_DFE_GAIN = 0x1 + * bits<9:6> CFG_RX_DFE_C5_MVAL = 4'b0000 + * bit<5> CFG_RX_DFE_C5_MSGN = 1'b0 + * bits<4:1> CFG_RX_DFE_C4_MVAL = 4'b0000 + * bit<0> CFG_RX_DFE_C4_MSGN = 1'b0 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_VALBBD_CTRL_0(qlm, l), + c.s.dfe_gain = 0x1; + c.s.dfe_c5_mval = 0; + c.s.dfe_c5_msgn = 0; + c.s.dfe_c4_mval = 0; + c.s.dfe_c4_msgn = 0); + + /* 4. Write GSER(0..13)_LANE(0..3)_RX_VALBBD_CTRL_2 = 0x003F //enable DFE tap overrides + * bit<5> dfe_ovrd_en = 1 + * bit<4> dfe_c5_ovrd_val = 1 + * bit<3> dfe_c4_ovrd_val = 1 + * bit<2> dfe_c3_ovrd_val = 1 + * bit<1> dfe_c2_ovrd_val = 1 + * bit<0> dfe_c1_ovrd_val = 1 + */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_VALBBD_CTRL_2(qlm, l), + c.s.dfe_ovrd_en = 0x1; + c.s.dfe_c5_ovrd_val = 0x1; + c.s.dfe_c4_ovrd_val = 0x1; + c.s.dfe_c3_ovrd_val = 0x1; + c.s.dfe_c2_ovrd_val = 0x1; + c.s.dfe_c1_ovrd_val = 0x1); + + } +} + +/** + * Check if a specific lane is using KR training. This is used by low level GSER + * code to remember which QLMs and lanes need to support KR training for BGX. The + * hardware doesn't have a bit set aside to record this, so we repurpose the + * register GSERX_SCRATCH. + * + * @param node Node to check + * @param qlm QLM to check + * @param lane Lane to check + * + * @return True if this lane uses KR with BGX, false otherwise + */ +bool __bdk_qlm_is_lane_kr(bdk_node_t node, int qlm, int lane) +{ + uint64_t mask = BDK_CSR_READ(node, BDK_GSERX_SCRATCH(qlm)); + return 1 & (mask >> lane); +} + +/** + * Set if a specific lane is using KR training. This is used by low level GSER + * code to remember which QLMs and lanes need to support KR training for BGX. The + * hardware doesn't have a bit set aside to record this, so we repurpose the + * register GSERX_SCRATCH. + * + * @param node Node to set + * @param qlm QLM to set + * @param lane Lane to set + * @param is_kr KR (true) or XFI/XLAUI (false) + */ +void __bdk_qlm_set_lane_kr(bdk_node_t node, int qlm, int lane, bool is_kr) +{ + uint64_t mask = BDK_CSR_READ(node, BDK_GSERX_SCRATCH(qlm)); + if (is_kr) + mask |= 1 << lane; + else + mask &= ~(1 << lane); + BDK_CSR_WRITE(node, BDK_GSERX_SCRATCH(qlm), mask); +} diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-errata-cn8xxx.c b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-errata-cn8xxx.c new file mode 100644 index 0000000000..a7602de758 --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-errata-cn8xxx.c @@ -0,0 +1,398 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-arch/bdk-csrs-rst.h" +#include "libbdk-hal/qlm/bdk-qlm-errata-cn8xxx.h" + +/** + * Delay for the specified microseconds. When this code runs on secondary nodes + * before full init, the normal bdk-clock functions do not work. This function + * serves as a replacement that runs everywhere. + * + * @param usec Microseconds to wait + */ +static void wait_usec(bdk_node_t node, uint64_t usec) +{ + const uint64_t REF_CLOCK = 50000000; /* This is currently defined to be 50Mhz */ + uint64_t refclock = BDK_CSR_READ(node, BDK_RST_REF_CNTR); + uint64_t timeout = refclock + REF_CLOCK * usec / 1000000; + while (refclock < timeout) + { + refclock = BDK_CSR_READ(node, BDK_RST_REF_CNTR); + } +} + +/** + * Errata GSER-25992 - RX EQ Default Settings Update<p> + * For all GSER and all lanes when not PCIe EP: + * set GSER()_LANE()_RX_CFG_4[CFG_RX_ERRDET_CTRL<13:8>] = 13 (decimal) + * set GSER()_LANE()_RX_CTLE_CTRL[PCS_SDS_RX_CTLE_BIAS_CTRL] = 3 + * Applied when SERDES are configured for 8G and 10G.<p> + * Applies to: + * CN88XX pass 1.x + * Fixed in hardware: + * CN88XX pass 2.x + * CN81XX + * CN83XX + * + * @param node Node to apply errata fix for + * @param qlm QLM to apply errata fix to + * @param baud_mhz QLM speed in Mhz + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_errata_gser_25992(bdk_node_t node, int qlm, int baud_mhz) +{ + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) + return 0; + if (baud_mhz < 8000) + return 0; + + int num_lanes = 4; /* Only applies to CN88XX, where always 4 lanes */ + for (int lane = 0; lane < num_lanes; lane++) + { + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_CTLE_CTRL(qlm, lane), + c.s.pcs_sds_rx_ctle_bias_ctrl = 3); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_CFG_4(qlm, lane), + c.s.cfg_rx_errdet_ctrl = 0xcd6f); + } + return 0; +} + +/** + * (GSER-26150) 10G PHY PLL Temperature Failure + * + * 10 Gb temperature excursions can cause lock failure. Change + * the calibration point of the VCO at start up to shift some + * available range of the VCO from -deltaT direction to the + * +deltaT ramp direction allowing a greater range of VCO + * temperatures before experiencing the failure. + * + * Applies to: + * CN88XX pass 1.x + * Fix in hardware: + * CN88XX pass 2.x + * CN81XX + * CN83XX + * + * Only applies to QLMs running 8G and 10G + * + * @param node Node to apply errata to + * @param qlm QLM to apply errata fix to + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_errata_gser_26150(bdk_node_t node, int qlm, int baud_mhz) +{ + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) + return 0; + if (baud_mhz < 8000) + return 0; + + int num_lanes = 4; /* Only applies to CN88XX, where always 4 lanes */ + + BDK_CSR_INIT(gserx_cfg, node, BDK_GSERX_CFG(qlm)); + if (gserx_cfg.s.pcie) + { + /* Update PLL parameters */ + /* Step 1: Set GSER()_GLBL_PLL_CFG_3[PLL_VCTRL_SEL_LCVCO_VAL] = 0x2, and + GSER()_GLBL_PLL_CFG_3[PCS_SDS_PLL_VCO_AMP] = 0 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_GLBL_PLL_CFG_3(qlm), + c.s.pll_vctrl_sel_lcvco_val = 0x2; + c.s.pcs_sds_pll_vco_amp = 0); + /* Step 2: Set GSER()_GLBL_MISC_CONFIG_1[PCS_SDS_TRIM_CHP_REG] = 0x2. */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_GLBL_MISC_CONFIG_1(qlm), + c.s.pcs_sds_trim_chp_reg = 0x2); + return 0; + } + + /* Applying this errata twice causes problems */ + BDK_CSR_INIT(pll_cfg_3, node, BDK_GSERX_GLBL_PLL_CFG_3(qlm)); + if (pll_cfg_3.s.pll_vctrl_sel_lcvco_val == 0x2) + return 0; + + /* Put PHY in P2 Power-down state Need to Power down all lanes in a + QLM/DLM to force PHY to P2 state */ + for (int i=0; i<num_lanes; i++) + { + /* Step 1: Set GSER()_LANE(lane_n)_PCS_CTLIFC_0[CFG_TX_PSTATE_REQ_OVERRD_VAL] = 0x3 + Select P2 power state for Tx lane */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_0(qlm, i), + c.s.cfg_tx_pstate_req_ovrrd_val = 0x3); + /* Step 2: Set GSER()_LANE(lane_n)_PCS_CTLIFC_1[CFG_RX_PSTATE_REQ_OVERRD_VAL] = 0x3 + Select P2 power state for Rx lane */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_1(qlm, i), + c.s.cfg_rx_pstate_req_ovrrd_val = 0x3); + /* Step 3: Set GSER()_LANE(lane_n)_PCS_CTLIFC_2[CFG_TX_PSTATE_REQ_OVRRD_EN] = 1 + Enable Tx power state override and Set + GSER()_LANE(lane_n)_PCS_CTLIFC_2[CFG_RX_PSTATE_REQ_OVRRD_EN] = 1 + Enable Rx power state override */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, i), + c.s.cfg_tx_pstate_req_ovrrd_en = 0x1; + c.s.cfg_rx_pstate_req_ovrrd_en = 0X1); + /* Step 4: Set GSER()_LANE(lane_n)_PCS_CTLIFC_2[CTLIFC_OVRRD_REQ] = 1 + Start the CTLIFC override state machine */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, i), + c.s.ctlifc_ovrrd_req = 0x1); + } + + /* Update PLL parameters */ + /* Step 5: Set GSER()_GLBL_PLL_CFG_3[PLL_VCTRL_SEL_LCVCO_VAL] = 0x2, and + GSER()_GLBL_PLL_CFG_3[PCS_SDS_PLL_VCO_AMP] = 0 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_GLBL_PLL_CFG_3(qlm), + c.s.pll_vctrl_sel_lcvco_val = 0x2; + c.s.pcs_sds_pll_vco_amp = 0); + /* Step 6: Set GSER()_GLBL_MISC_CONFIG_1[PCS_SDS_TRIM_CHP_REG] = 0x2. */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_GLBL_MISC_CONFIG_1(qlm), + c.s.pcs_sds_trim_chp_reg = 0x2); + /* Wake up PHY and transition to P0 Power-up state to bring-up the lanes, + need to wake up all PHY lanes */ + for (int i=0; i<num_lanes; i++) + { + /* Step 7: Set GSER()_LANE(lane_n)_PCS_CTLIFC_0[CFG_TX_PSTATE_REQ_OVERRD_VAL] = 0x0 + Select P0 power state for Tx lane */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_0(qlm, i), + c.s.cfg_tx_pstate_req_ovrrd_val = 0x0); + /* Step 8: Set GSER()_LANE(lane_n)_PCS_CTLIFC_1[CFG_RX_PSTATE_REQ_OVERRD_VAL] = 0x0 + Select P0 power state for Rx lane */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_1(qlm, i), + c.s.cfg_rx_pstate_req_ovrrd_val = 0x0); + /* Step 9: Set GSER()_LANE(lane_n)_PCS_CTLIFC_2[CFG_TX_PSTATE_REQ_OVRRD_EN] = 1 + Enable Tx power state override and Set + GSER()_LANE(lane_n)_PCS_CTLIFC_2[CFG_RX_PSTATE_REQ_OVRRD_EN] = 1 + Enable Rx power state override */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, i), + c.s.cfg_tx_pstate_req_ovrrd_en = 0x1; + c.s.cfg_rx_pstate_req_ovrrd_en = 0X1); + /* Step 10: Set GSER()_LANE(lane_n)_PCS_CTLIFC_2[CTLIFC_OVRRD_REQ] = 1 + Start the CTLIFC override state machine */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, i), + c.s.ctlifc_ovrrd_req = 0x1); + } + + /* Step 11: Wait 10 msec */ + wait_usec(node, 10000); + + /* Release Lane Tx/Rx Power state override enables. */ + for (int i=0; i<num_lanes; i++) + { + /* Step 12: Set GSER()_LANE(lane_n)_PCS_CTLIFC_2[CFG_TX_PSTATE_REQ_OVRRD_EN] = 0 + Disable Tx power state override and Set + GSER()_LANE(lane_n)_PCS_CTLIFC_2[CFG_RX_PSTATE_REQ_OVRRD_EN] = 0 + Disable Rx power state override */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, i), + c.s.cfg_tx_pstate_req_ovrrd_en = 0x0; + c.s.cfg_rx_pstate_req_ovrrd_en = 0X0); + } + /* Step 13: Poll GSER()_PLL_STAT.[PLL_LOCK] = 1 + Poll and check that PLL is locked */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_PLL_STAT(qlm), pll_lock, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_PLL_STAT[pll_lock]\n", qlm); + return -1; + } + + /* Step 14: Poll GSER()_QLM_STAT.[RST_RDY] = 1 + Poll and check that QLM/DLM is Ready */ + if (BDK_CSR_WAIT_FOR_FIELD(node, BDK_GSERX_QLM_STAT(qlm), rst_rdy, ==, 1, 10000)) + { + bdk_error("QLM%d: Timeout waiting for GSERX_QLM_STAT[rst_rdy]\n", qlm); + return -1; + } + return 0; +} + +/** + * Errata (GSER-26636) 10G-KR/40G-KR - Inverted Tx Coefficient Direction Change + * Applied to all 10G standards (required for KR) but also applied to other + * standards in case software training is used. + * Applies to: + * CN88XX pass 1.x + * Fixed in hardware: + * CN88XX pass 2.x + * CN81XX + * CN83XX + * + * @param node Node to apply errata fix for + * @param qlm QLM to apply errata fix to + * @param baud_mhz QLM speed in Mhz + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_errata_gser_26636(bdk_node_t node, int qlm, int baud_mhz) +{ + if (!CAVIUM_IS_MODEL(CAVIUM_CN88XX_PASS1_X)) + return 0; + + BDK_CSR_MODIFY(c, node, BDK_GSERX_RX_TXDIR_CTRL_1(qlm), + c.s.rx_precorr_chg_dir = 1; + c.s.rx_tap1_chg_dir = 1); + return 0; +} + +/** + * (GSER-27140) SERDES has temperature drift sensitivity in the RX EQ<p> + * SERDES temperature drift sensitivity in receiver. Issues have + * been found with the Bit Error Rate (BER) reliability of + * 10GBASE-KR links over the commercial temperature range (0 to 100C), + * especially when subjected to rapid thermal ramp stress testing. + * (See HRM for corresponding case temperature requirements for each speed grade.)<p> + * Applies to: + * CN88XX pass 1.x + * CN88XX pass 2.x + * CN83XX pass 1.x + * CN81XX pass 1.x + * Fixed in hardware: + * TBD<p> + * Only applies to QLMs running 10G + * + * @param node Note to apply errata fix to + * @param qlm QLM to apply errata fix to + * @param baud_mhz QLM baud rate in Mhz + * @param channel_loss + * Insertion loss at Nyquist rate (e.g. 5.125Ghz for XFI/XLAUI) in dB + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_errata_gser_27140(bdk_node_t node, int qlm, int baud_mhz, int channel_loss) +{ + if (baud_mhz != 10312) + return 0; + + /* A channel loss of -1 means the loss is unknown. A short channel is + considered to have loss between 0 and 10 dB */ + bool short_channel = (channel_loss >= 0) && (channel_loss <= 10); + + /* I. For each GSER QLM: */ + /* Workaround GSER-27140: */ + /* (1) GSER-26150 = Applied by the caller */ + /* (2) Write GSER()_LANE_VMA_FINE_CTRL_0[RX_SDLL_IQ_MAX_FINE] = 0xE */ + /* (3) Write GSER()_LANE_VMA_FINE_CTRL_0[RX_SDLL_IQ_MIN_FINE] = 0x8 */ + /* (4) Write GSER()_LANE_VMA_FINE_CTRL_0[RX_SDLL_IQ_STEP_FINE] = 0x2 */ + /* (5) Write GSER()_LANE_VMA_FINE_CTRL_0[VMA_WINDOW_WAIT_FINE] = 0x5 */ + /* (6) Write GSER()_LANE_VMA_FINE_CTRL_0[LMS_WAIT_TIME_FINE] = 0x5 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANE_VMA_FINE_CTRL_0(qlm), + c.s.rx_sdll_iq_max_fine = 0xE; + c.s.rx_sdll_iq_min_fine = 0x8; + c.s.rx_sdll_iq_step_fine = 0x2; + c.s.vma_window_wait_fine = 0x5; + c.s.lms_wait_time_fine = 0x5); + /* (7) Write GSER()_LANE_VMA_FINE_CTRL_2[RX_PRECTLE_GAIN_MAX_FINE] = 0xB */ + /* (8) Write GSER()_LANE_VMA_FINE_CTRL_2[RX_PRECTLE_GAIN_MIN_FINE] = 0x6(long) or 0x0(short) */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANE_VMA_FINE_CTRL_2(qlm), + c.s.rx_prectle_gain_max_fine = 0xB; + c.s.rx_prectle_gain_min_fine = short_channel ? 0x0 : 0x6); + /* (9) Write GSER()_RX_TXDIR_CTRL_0[RX_BOOST_LO_THRES] = 0x4 */ + /* (10) Write GSER()_RX_TXDIR_CTRL_0[RX_BOOST_HI_THRES] = 0xB */ + /* (11) Write GSER()_RX_TXDIR_CTRL_0[RX_BOOST_HI_VAL] = 0xF */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_RX_TXDIR_CTRL_0(qlm), + c.s.rx_boost_lo_thrs = 0x4; + c.s.rx_boost_hi_thrs = 0xB; + c.s.rx_boost_hi_val = 0xF); + /* (12) Write GSER()_RX_TXDIR_CTRL_1[RX_TAP1_LO_THRS] = 0x8 */ + /* (13) Write GSER()_RX_TXDIR_CTRL_1[RX_TAP1_HI_THRS] = 0x17 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_RX_TXDIR_CTRL_1(qlm), + c.s.rx_tap1_lo_thrs = 0x8; + c.s.rx_tap1_hi_thrs = 0x17); + + /* (14) Write GSER()_EQ_WAIT_TIME[RXEQ_WAIT_CNT] = 0x6 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_EQ_WAIT_TIME(qlm), + c.s.rxeq_wait_cnt = 0x6); + /* (15) Write GSER()_RX_TXDIR_CTRL_2[RX_PRECORR_HI_THRS] = 0xC0 */ + /* (16) Write GSER()_RX_TXDIR_CTRL_2[RX_PRECORR_LO_THRS] = 0x40 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_RX_TXDIR_CTRL_2(qlm), + c.s.rx_precorr_hi_thrs = 0xc0; + c.s.rx_precorr_lo_thrs = 0x40); + + /* We can't call the normal bdk-qlm function as it uses pointers that + don't work when running in secondary nodes before CCPI is up */ + int num_lanes = 4; + if (CAVIUM_IS_MODEL(CAVIUM_CN81XX) || (CAVIUM_IS_MODEL(CAVIUM_CN83XX) && (qlm >= 4))) + num_lanes = 2; + + /* II. For each GSER QLM SerDes lane: */ + /* Establish typical values, which are already reset values in pass 2: */ + for (int lane = 0; lane < num_lanes; lane++) + { + /* (17) For each GSER lane in the 10GBASE-KR link: */ + /* (a) Write GSER()_LANE()_RX_VALBBD_CTRL_0[AGC_GAIN] = 0x3 */ + /* (b) Write GSER()_LANE()_RX_VALBBD_CTRL_0[DFE_GAIN] = 0x2 */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_VALBBD_CTRL_0(qlm, lane), + c.s.agc_gain = 0x3; + c.s.dfe_gain = 0x2); + } + + /* III. The GSER QLM SerDes Lanes are now ready. */ + return 0; +} + +/** + * Errata GSER-27882 -GSER 10GBASE-KR Transmit Equalizer + * Training may not update PHY Tx Taps. This function is not static + * so we can share it with BGX KR + * Applies to: + * CN88XX pass 1.x, 2.0, 2.1 + * Fixed in hardware: + * CN88XX pass 2.2 and higher + * CN81XX + * CN83XX + * + * @param node Node to apply errata fix for + * @param qlm QLM to apply errata fix to + * @param lane + * + * @return Zero on success, negative on failure + */ +int __bdk_qlm_errata_gser_27882(bdk_node_t node, int qlm, int lane) +{ + /* Toggle Toggle Tx Coeff Req override to force an update */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_0(qlm, lane), + c.s.cfg_tx_coeff_req_ovrrd_val = 1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.cfg_tx_coeff_req_ovrrd_en = 1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.ctlifc_ovrrd_req = 1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.cfg_tx_coeff_req_ovrrd_en = 0); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PCS_CTLIFC_2(qlm, lane), + c.s.ctlifc_ovrrd_req = 1); + return 0; +} + diff --git a/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-margin-cn8xxx.c b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-margin-cn8xxx.c new file mode 100644 index 0000000000..c970f2189e --- /dev/null +++ b/src/vendorcode/cavium/bdk/libbdk-hal/qlm/bdk-qlm-margin-cn8xxx.c @@ -0,0 +1,271 @@ +/***********************license start*********************************** +* Copyright (c) 2003-2017 Cavium Inc. (support@cavium.com). All rights +* reserved. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* * Redistributions in binary form must reproduce the above +* copyright notice, this list of conditions and the following +* disclaimer in the documentation and/or other materials provided +* with the distribution. +* +* * Neither the name of Cavium Inc. nor the names of +* its contributors may be used to endorse or promote products +* derived from this software without specific prior written +* permission. +* +* This Software, including technical data, may be subject to U.S. export +* control laws, including the U.S. Export Administration Act and its +* associated regulations, and may be subject to export or import +* regulations in other countries. +* +* TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS" +* AND WITH ALL FAULTS AND CAVIUM INC. MAKES NO PROMISES, REPRESENTATIONS OR +* WARRANTIES, EITHER EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, WITH RESPECT +* TO THE SOFTWARE, INCLUDING ITS CONDITION, ITS CONFORMITY TO ANY +* REPRESENTATION OR DESCRIPTION, OR THE EXISTENCE OF ANY LATENT OR PATENT +* DEFECTS, AND CAVIUM SPECIFICALLY DISCLAIMS ALL IMPLIED (IF ANY) WARRANTIES +* OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, FITNESS FOR A PARTICULAR +* PURPOSE, LACK OF VIRUSES, ACCURACY OR COMPLETENESS, QUIET ENJOYMENT, +* QUIET POSSESSION OR CORRESPONDENCE TO DESCRIPTION. THE ENTIRE RISK +* ARISING OUT OF USE OR PERFORMANCE OF THE SOFTWARE LIES WITH YOU. +***********************license end**************************************/ +#include <bdk.h> +#include "libbdk-arch/bdk-csrs-gser.h" +#include "libbdk-hal/if/bdk-if.h" +#include "libbdk-hal/bdk-qlm.h" +#include "libbdk-hal/bdk-utils.h" + +/* This code is an optional part of the BDK. It is only linked in + if BDK_REQUIRE() needs it */ +BDK_REQUIRE_DEFINE(QLM_MARGIN); + +typedef union +{ + struct + { + uint64_t rx_os_mvalbbd_2 :16; + uint64_t rx_os_mvalbbd_1 :16; + uint64_t reserved_63_32 :32; + + } s; + struct + { + uint64_t Qb :6; + uint64_t Q :6; + uint64_t Lb :6; // Spans the two registers + uint64_t L :6; + uint64_t qerr0 :6; + int64_t reserved_63_30 :34; + } f; + uint64_t u; +} rx_os_mvalbbd_t; + +int __bdk_disable_ccpi_error_report = 0; + +static int convert_to_signed_mag(int source) +{ + /* Synopsis encoded sign in an unexpected way. 0=negative and 1=positive + So bit 5 should be 0 for negative numbers, 1 for positive numbers */ + if (source < 0) + source = -source; + else + source |= 0x20; + return source; +} + +static rx_os_mvalbbd_t get_current_settings(bdk_node_t node, int qlm, int qlm_lane) +{ + rx_os_mvalbbd_t mvalbbd; + mvalbbd.u = 0; + + BDK_CSR_INIT(rx_cfg_1, node, BDK_GSERX_LANEX_RX_CFG_1(qlm, qlm_lane)); + if (!rx_cfg_1.s.pcs_sds_rx_os_men) + { + /* Get the current settings */ + BDK_CSR_INIT(rx_os_out_1, node, BDK_GSERX_LANEX_RX_OS_OUT_1(qlm, qlm_lane)); + BDK_CSR_INIT(rx_os_out_2, node, BDK_GSERX_LANEX_RX_OS_OUT_2(qlm, qlm_lane)); + BDK_CSR_INIT(rx_os_out_3, node, BDK_GSERX_LANEX_RX_OS_OUT_3(qlm, qlm_lane)); + int qerr0 = bdk_extracts(rx_os_out_1.u, 0, 6); + int lb = bdk_extracts(rx_os_out_2.u, 0, 6); + int l = bdk_extracts(rx_os_out_2.u, 6, 6); + int qb = bdk_extracts(rx_os_out_3.u, 0, 6); + int q = bdk_extracts(rx_os_out_3.u, 6, 6); + /* Enable the override with the current values */ + mvalbbd.f.Qb = convert_to_signed_mag(qb); + mvalbbd.f.Q = convert_to_signed_mag(q); + mvalbbd.f.Lb = convert_to_signed_mag(lb); + mvalbbd.f.L = convert_to_signed_mag(l); + mvalbbd.f.qerr0 = convert_to_signed_mag(qerr0); + } + else + { + BDK_CSR_INIT(mvalbbd_1, node, BDK_GSERX_LANEX_RX_OS_MVALBBD_1(qlm, qlm_lane)); + mvalbbd.s.rx_os_mvalbbd_1 = mvalbbd_1.s.pcs_sds_rx_os_mval; + BDK_CSR_INIT(mvalbbd_2, node, BDK_GSERX_LANEX_RX_OS_MVALBBD_2(qlm, qlm_lane)); + mvalbbd.s.rx_os_mvalbbd_2 = mvalbbd_2.s.pcs_sds_rx_os_mval; + } + //printf("qerr0=%d, lb=%d, l=%d, qb=%d, q=%d\n", + // mvalbbd.f.qerr0, mvalbbd.f.Lb, mvalbbd.f.L, mvalbbd.f.Qb, mvalbbd.f.Q); + return mvalbbd; +} + +/** + * Get the current RX margining parameter + * + * @param node Node to read margin value from + * @param qlm QLM to read from + * @param qlm_lane Lane to read + * @param margin_type + * Type of margining parameter to read + * + * @return Current margining parameter value + */ +int64_t bdk_qlm_margin_rx_get(bdk_node_t node, int qlm, int qlm_lane, bdk_qlm_margin_t margin_type) +{ + rx_os_mvalbbd_t mvalbbd = get_current_settings(node, qlm, qlm_lane); + + switch (margin_type) + { + case BDK_QLM_MARGIN_VERTICAL: + if (mvalbbd.f.Q & 0x20) /* Check if sign bit says positive */ + return mvalbbd.f.Q & 0x1f; /* positive, strip off sign */ + else + return -mvalbbd.f.Q; /* negative */ + case BDK_QLM_MARGIN_HORIZONTAL: + return 0; + } + return 0; +} + +/** + * Get the current RX margining parameter minimum value + * + * @param node Node to read margin value from + * @param qlm QLM to read from + * @param qlm_lane Lane to read + * @param margin_type + * Type of margining parameter to read + * + * @return Current margining parameter minimum value + */ +int64_t bdk_qlm_margin_rx_get_min(bdk_node_t node, int qlm, int qlm_lane, bdk_qlm_margin_t margin_type) +{ + switch (margin_type) + { + case BDK_QLM_MARGIN_VERTICAL: + return -31; + case BDK_QLM_MARGIN_HORIZONTAL: + return 0; + } + return 0; +} + +/** + * Get the current RX margining parameter maximum value + * + * @param node Node to read margin value from + * @param qlm QLM to read from + * @param qlm_lane Lane to read + * @param margin_type + * Type of margining parameter to read + * + * @return Current margining parameter maximum value + */ +int64_t bdk_qlm_margin_rx_get_max(bdk_node_t node, int qlm, int qlm_lane, bdk_qlm_margin_t margin_type) +{ + switch (margin_type) + { + case BDK_QLM_MARGIN_VERTICAL: + return 31; + case BDK_QLM_MARGIN_HORIZONTAL: + return 0; + } + return 0; +} + +/** + * Set the current RX margining parameter value + * + * @param node Node to set margin value on + * @param qlm QLM to set + * @param qlm_lane Lane to set + * @param margin_type + * Type of margining parameter to set + * @param value Value of margining parameter + * + * @return Zero on success, negative on failure + */ +int bdk_qlm_margin_rx_set(bdk_node_t node, int qlm, int qlm_lane, bdk_qlm_margin_t margin_type, int value) +{ + rx_os_mvalbbd_t mvalbbd = get_current_settings(node, qlm, qlm_lane); + + switch (margin_type) + { + case BDK_QLM_MARGIN_VERTICAL: + if (value < 0) + mvalbbd.f.Q = -value; /* Sign bit is zero, weird Synopsys */ + else + mvalbbd.f.Q = value | 0x20; /* Sign bit is one, weird Synopsys */ + break; + case BDK_QLM_MARGIN_HORIZONTAL: + return -1; + } + + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_OS_MVALBBD_1(qlm, qlm_lane), + c.s.pcs_sds_rx_os_mval = mvalbbd.s.rx_os_mvalbbd_1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_OS_MVALBBD_2(qlm, qlm_lane), + c.s.pcs_sds_rx_os_mval = mvalbbd.s.rx_os_mvalbbd_2); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_CFG_1(qlm, qlm_lane), + c.s.pcs_sds_rx_os_men = 1); + + /* Disable the DFE(s), gives a better eye measurement */ + BDK_CSR_INIT(pwr_ctrl, node, BDK_GSERX_LANEX_PWR_CTRL(qlm, qlm_lane)); + if (!pwr_ctrl.s.rx_lctrl_ovrrd_en) + { + BDK_CSR_WRITE(node, BDK_GSERX_LANEX_RX_LOOP_CTRL(qlm, qlm_lane), 0xF1); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PWR_CTRL(qlm, qlm_lane), + c.s.rx_lctrl_ovrrd_en = 1); + } + + if (qlm >= 8) + __bdk_disable_ccpi_error_report = 1; + + return 0; +} + +/** + * Restore the supplied RX margining parameter value as if it was never set. This + * disables any overrides in the SERDES need to perform margining + * + * @param node Node to restore margin value on + * @param qlm QLM to restore + * @param qlm_lane Lane to restore + * @param margin_type + * Type of margining parameter to restore + * @param value Value of margining parameter + * + * @return Zero on success, negative on failure + */ +int bdk_qlm_margin_rx_restore(bdk_node_t node, int qlm, int qlm_lane, bdk_qlm_margin_t margin_type, int value) +{ + BDK_CSR_INIT(rx_cfg_1, node, BDK_GSERX_LANEX_RX_CFG_1(qlm, qlm_lane)); + /* Return if no overrides have been applied */ + if (!rx_cfg_1.s.pcs_sds_rx_os_men) + return 0; + bdk_qlm_margin_rx_set(node, qlm, qlm_lane, margin_type, value); + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_RX_CFG_1(qlm, qlm_lane), + c.s.pcs_sds_rx_os_men = 0); + /* Enable the DFE(s) */ + BDK_CSR_MODIFY(c, node, BDK_GSERX_LANEX_PWR_CTRL(qlm, qlm_lane), + c.s.rx_lctrl_ovrrd_en = 0); + __bdk_disable_ccpi_error_report = 0; + return 0; +} + |