diff --git a/src/kboot.c b/src/kboot.c index 85ec5532..19deeb25 100644 --- a/src/kboot.c +++ b/src/kboot.c @@ -18,6 +18,8 @@ #define MAX_CHOSEN_PARAMS 16 +#define MAX_ATC_DEVS 8 + static void *dt = NULL; static int dt_bufsize = 0; static void *initrd_start = NULL; @@ -621,6 +623,165 @@ static int dt_set_uboot(void) return 0; } +struct atc_tunable { + u32 offset : 24; + u32 size : 8; + u32 mask; + u32 value; +} PACKED; +static_assert(sizeof(struct atc_tunable) == 12, "Invalid atc_tunable size"); + +struct atc_tunable_info { + const char *adt_name; + const char *fdt_name; + size_t reg_offset; + size_t reg_size; + bool required; +}; + +static const struct atc_tunable_info atc_tunables[] = { + /* global tunables applied after power on or reset */ + {"tunable_ATC0AXI2AF", "apple,tunable-axi2af", 0x0, 0x4000, true}, + {"tunable_ATC_FABRIC", "apple,tunable-common", 0x45000, 0x4000, true}, + {"tunable_AUS_CMN_TOP", "apple,tunable-common", 0x800, 0x4000, true}, + {"tunable_AUS_CMN_SHM", "apple,tunable-common", 0xa00, 0x4000, true}, + {"tunable_AUSPLL_CORE", "apple,tunable-common", 0x2200, 0x4000, true}, + {"tunable_AUSPLL_TOP", "apple,tunable-common", 0x2000, 0x4000, true}, + {"tunable_CIO3PLL_CORE", "apple,tunable-common", 0x2a00, 0x4000, true}, + {"tunable_CIO3PLL_TOP", "apple,tunable-common", 0x2800, 0x4000, true}, + {"tunable_CIO_CIO3PLL_TOP", "apple,tunable-common", 0x2800, 0x4000, false}, + {"tunable_USB_ACIOPHY_TOP", "apple,tunable-common", 0x0, 0x4000, true}, + /* lane-specific tunables applied after a cable is connected */ + {"tunable_DP_LN0_AUSPMA_TX_TOP", "apple,tunable-lane0-dp", 0xc000, 0x1000, true}, + {"tunable_DP_LN1_AUSPMA_TX_TOP", "apple,tunable-lane1-dp", 0x13000, 0x1000, true}, + {"tunable_USB_LN0_AUSPMA_RX_TOP", "apple,tunable-lane0-usb", 0x9000, 0x1000, true}, + {"tunable_USB_LN0_AUSPMA_RX_EQ", "apple,tunable-lane0-usb", 0xa000, 0x1000, true}, + {"tunable_USB_LN0_AUSPMA_RX_SHM", "apple,tunable-lane0-usb", 0xb000, 0x1000, true}, + {"tunable_USB_LN0_AUSPMA_TX_TOP", "apple,tunable-lane0-usb", 0xc000, 0x1000, true}, + {"tunable_USB_LN1_AUSPMA_RX_TOP", "apple,tunable-lane1-usb", 0x10000, 0x1000, true}, + {"tunable_USB_LN1_AUSPMA_RX_EQ", "apple,tunable-lane1-usb", 0x11000, 0x1000, true}, + {"tunable_USB_LN1_AUSPMA_RX_SHM", "apple,tunable-lane1-usb", 0x12000, 0x1000, true}, + {"tunable_USB_LN1_AUSPMA_TX_TOP", "apple,tunable-lane1-usb", 0x13000, 0x1000, true}, + {"tunable_CIO_LN0_AUSPMA_RX_TOP", "apple,tunable-lane0-cio", 0x9000, 0x1000, true}, + {"tunable_CIO_LN0_AUSPMA_RX_EQ", "apple,tunable-lane0-cio", 0xa000, 0x1000, true}, + {"tunable_CIO_LN0_AUSPMA_RX_SHM", "apple,tunable-lane0-cio", 0xb000, 0x1000, true}, + {"tunable_CIO_LN0_AUSPMA_TX_TOP", "apple,tunable-lane0-cio", 0xc000, 0x1000, true}, + {"tunable_CIO_LN1_AUSPMA_RX_TOP", "apple,tunable-lane1-cio", 0x10000, 0x1000, true}, + {"tunable_CIO_LN1_AUSPMA_RX_EQ", "apple,tunable-lane1-cio", 0x11000, 0x1000, true}, + {"tunable_CIO_LN1_AUSPMA_RX_SHM", "apple,tunable-lane1-cio", 0x12000, 0x1000, true}, + {"tunable_CIO_LN1_AUSPMA_TX_TOP", "apple,tunable-lane1-cio", 0x13000, 0x1000, true}, +}; + +static int dt_append_atc_tunable(int adt_node, int fdt_node, + const struct atc_tunable_info *tunable_info) +{ + u32 tunables_len; + const struct atc_tunable *tunable_adt = + adt_getprop(adt, adt_node, tunable_info->adt_name, &tunables_len); + + if (!tunable_adt) { + printf("ADT: tunable %s not found\n", tunable_info->adt_name); + + if (tunable_info->required) + return -1; + else + return 0; + } + + if (tunables_len % sizeof(*tunable_adt)) { + printf("ADT: tunable %s with invalid length %d\n", tunable_info->adt_name, tunables_len); + return -1; + } + + u32 n_tunables = tunables_len / sizeof(*tunable_adt); + for (size_t j = 0; j < n_tunables; j++) { + const struct atc_tunable *tunable = &tunable_adt[j]; + + if (tunable->size != 32) { + printf("kboot: ATC tunable has invalid size %d\n", tunable->size); + return -1; + } + + if (tunable->offset % (tunable->size / 8)) { + printf("kboot: ATC tunable has unaligned offset %x\n", tunable->offset); + return -1; + } + + if (tunable->offset + (tunable->size / 8) > tunable_info->reg_size) { + printf("kboot: ATC tunable has invalid offset %x\n", tunable->offset); + return -1; + } + + if (fdt_appendprop_u32(dt, fdt_node, tunable_info->fdt_name, + tunable->offset + tunable_info->reg_offset) < 0) + return -1; + if (fdt_appendprop_u32(dt, fdt_node, tunable_info->fdt_name, tunable->mask) < 0) + return -1; + if (fdt_appendprop_u32(dt, fdt_node, tunable_info->fdt_name, tunable->value) < 0) + return -1; + } + + return 0; +} + +static void dt_copy_atc_tunables(const char *adt_path, const char *dt_alias) +{ + int ret; + + int adt_node = adt_path_offset(adt, adt_path); + if (adt_node < 0) + return; + + const char *fdt_path = fdt_get_alias(dt, dt_alias); + if (fdt_path == NULL) { + printf("FDT: Unable to find alias %s\n", dt_alias); + return; + } + + int fdt_node = fdt_path_offset(dt, fdt_path); + if (fdt_node < 0) { + printf("FDT: Unable to find path %s for alias %s\n", fdt_path, dt_alias); + return; + } + + for (size_t i = 0; i < sizeof(atc_tunables) / sizeof(*atc_tunables); ++i) { + ret = dt_append_atc_tunable(adt_node, fdt_node, &atc_tunables[i]); + if (ret) + goto cleanup; + } + + return; + +cleanup: + /* + * USB3 and Thunderbolt won't work if something went wrong. Clean up to make + * sure we don't leave half-filled properties around so that we can at least + * try to boot with USB2 support only. + */ + for (size_t i = 0; i < sizeof(atc_tunables) / sizeof(*atc_tunables); ++i) + fdt_delprop(dt, fdt_node, atc_tunables[i].fdt_name); + + printf("FDT: Unable to setup ATC tunables for %s - USB3/Thunderbolt will not work\n", adt_path); +} + +static int dt_set_atc_tunables(void) +{ + char adt_path[32]; + char fdt_alias[32]; + + for (int i = 0; i < MAX_ATC_DEVS; ++i) { + memset(adt_path, 0, sizeof(adt_path)); + snprintf(adt_path, sizeof(adt_path), "/arm-io/atc-phy%d", i); + + memset(fdt_alias, 0, sizeof(adt_path)); + snprintf(fdt_alias, sizeof(fdt_alias), "atcphy%d", i); + + dt_copy_atc_tunables(adt_path, fdt_alias); + } + + return 0; +} + static int dt_disable_missing_devs(const char *adt_prefix, const char *dt_prefix, int max_devs) { int ret = -1; @@ -801,6 +962,8 @@ int kboot_prepare_dt(void *fdt) return -1; if (dt_set_uboot()) return -1; + if (dt_set_atc_tunables()) + return -1; if (dt_disable_missing_devs("usb-drd", "usb@", 8)) return -1; if (dt_disable_missing_devs("i2c", "i2c@", 8))