diff options
| author | Tom Rini <[email protected]> | 2022-01-15 07:34:46 -0500 |
|---|---|---|
| committer | Tom Rini <[email protected]> | 2022-01-15 07:34:46 -0500 |
| commit | 0962da92a1dfb210eef8c936e33862812fa1b208 (patch) | |
| tree | c72fd2621d37fcff8b834f104ef83200d47fbebf /drivers | |
| parent | 9b72d934c2f7d8ee894f87e082577743877eb76e (diff) | |
| parent | 97f2a749d5126b2908dd282969c02c3632417c68 (diff) | |
Merge branch '2022-01-14-assorted-fixes'
- A number of fixes in various subsystems. This includes having the phy
uclass track power-on and init counts as this should resolve some
tricky functional problems on a number of platforms.
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/mtd/nand/raw/brcmnand/brcmnand.c | 2 | ||||
| -rw-r--r-- | drivers/nvme/nvme_show.c | 35 | ||||
| -rw-r--r-- | drivers/pci/pci_auto.c | 170 | ||||
| -rw-r--r-- | drivers/phy/allwinner/phy-sun4i-usb.c | 9 | ||||
| -rw-r--r-- | drivers/phy/phy-uclass.c | 137 |
5 files changed, 334 insertions, 19 deletions
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c index f8434ca88db..74c9348f7fc 100644 --- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c @@ -1632,7 +1632,7 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip, mtd->oobsize / trans, host->hwcfg.sector_size_1k); - if (!ret) { + if (ret != -EBADMSG) { *err_addr = brcmnand_read_reg(ctrl, BRCMNAND_UNCORR_ADDR) | ((u64)(brcmnand_read_reg(ctrl, diff --git a/drivers/nvme/nvme_show.c b/drivers/nvme/nvme_show.c index 15e459da1ac..72cbac82bcc 100644 --- a/drivers/nvme/nvme_show.c +++ b/drivers/nvme/nvme_show.c @@ -106,24 +106,41 @@ int nvme_print_info(struct udevice *udev) { struct nvme_ns *ns = dev_get_priv(udev); struct nvme_dev *dev = ns->dev; - ALLOC_CACHE_ALIGN_BUFFER(char, buf_ns, sizeof(struct nvme_id_ns)); - struct nvme_id_ns *id = (struct nvme_id_ns *)buf_ns; - ALLOC_CACHE_ALIGN_BUFFER(char, buf_ctrl, sizeof(struct nvme_id_ctrl)); - struct nvme_id_ctrl *ctrl = (struct nvme_id_ctrl *)buf_ctrl; + struct nvme_id_ctrl *ctrl; + struct nvme_id_ns *id; + int ret = 0; - if (nvme_identify(dev, 0, 1, (dma_addr_t)(long)ctrl)) - return -EIO; + ctrl = memalign(dev->page_size, sizeof(struct nvme_id_ctrl)); + if (!ctrl) + return -ENOMEM; + + if (nvme_identify(dev, 0, 1, (dma_addr_t)(long)ctrl)) { + ret = -EIO; + goto free_ctrl; + } print_optional_admin_cmd(le16_to_cpu(ctrl->oacs), ns->devnum); print_optional_nvm_cmd(le16_to_cpu(ctrl->oncs), ns->devnum); print_format_nvme_attributes(ctrl->fna, ns->devnum); - if (nvme_identify(dev, ns->ns_id, 0, (dma_addr_t)(long)id)) - return -EIO; + id = memalign(dev->page_size, sizeof(struct nvme_id_ns)); + if (!id) { + ret = -ENOMEM; + goto free_ctrl; + } + + if (nvme_identify(dev, ns->ns_id, 0, (dma_addr_t)(long)id)) { + ret = -EIO; + goto free_id; + } print_formats(id, ns); print_data_protect_cap(id->dpc, ns->devnum); print_metadata_cap(id->mc, ns->devnum); - return 0; +free_id: + free(id); +free_ctrl: + free(ctrl); + return ret; } diff --git a/drivers/pci/pci_auto.c b/drivers/pci/pci_auto.c index c0acf331398..c7968926a17 100644 --- a/drivers/pci/pci_auto.c +++ b/drivers/pci/pci_auto.c @@ -5,6 +5,7 @@ * Author: Matt Porter <[email protected]> * * Copyright 2000 MontaVista Software Inc. + * Copyright (c) 2021 Maciej W. Rozycki <[email protected]> */ #include <common.h> @@ -12,6 +13,7 @@ #include <errno.h> #include <log.h> #include <pci.h> +#include <time.h> #include "pci_internal.h" /* the user can define CONFIG_SYS_PCI_CACHE_LINE_SIZE to avoid problems */ @@ -180,6 +182,168 @@ static void dm_pciauto_setup_device(struct udevice *dev, dm_pci_write_config8(dev, PCI_LATENCY_TIMER, 0x80); } +/* + * Check if the link of a downstream PCIe port operates correctly. + * + * For that check if the optional Data Link Layer Link Active status gets + * on within a 200ms period or failing that wait until the completion of + * that period and check if link training has shown the completed status + * continuously throughout the second half of that period. + * + * Observation with the ASMedia ASM2824 Gen 3 switch indicates it takes + * 11-44ms to indicate the Data Link Layer Link Active status at 2.5GT/s, + * though it may take a couple of link training iterations. + */ +static bool dm_pciauto_exp_link_stable(struct udevice *dev, int pcie_off) +{ + u64 loops = 0, trcount = 0, ntrcount = 0, flips = 0; + bool dllla, lnktr, plnktr; + u16 exp_lnksta; + pci_dev_t bdf; + u64 end; + + dm_pci_read_config16(dev, pcie_off + PCI_EXP_LNKSTA, &exp_lnksta); + plnktr = !!(exp_lnksta & PCI_EXP_LNKSTA_LT); + + end = get_ticks() + usec_to_tick(200000); + do { + dm_pci_read_config16(dev, pcie_off + PCI_EXP_LNKSTA, + &exp_lnksta); + dllla = !!(exp_lnksta & PCI_EXP_LNKSTA_DLLLA); + lnktr = !!(exp_lnksta & PCI_EXP_LNKSTA_LT); + + flips += plnktr ^ lnktr; + if (lnktr) { + ntrcount = 0; + trcount++; + } else { + ntrcount++; + } + loops++; + + plnktr = lnktr; + } while (!dllla && get_ticks() < end); + + bdf = dm_pci_get_bdf(dev); + debug("PCI Autoconfig: %02x.%02x.%02x: Fixup link: DL active: %u; " + "%3llu flips, %6llu loops of which %6llu while training, " + "final %6llu stable\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), + (unsigned int)dllla, + (unsigned long long)flips, (unsigned long long)loops, + (unsigned long long)trcount, (unsigned long long)ntrcount); + + return dllla || ntrcount >= loops / 2; +} + +/* + * Retrain the link of a downstream PCIe port by hand if necessary. + * + * This is needed at least where a downstream port of the ASMedia ASM2824 + * Gen 3 switch is wired to the upstream port of the Pericom PI7C9X2G304 + * Gen 2 switch, and observed with the Delock Riser Card PCI Express x1 > + * 2 x PCIe x1 device, P/N 41433, plugged into the SiFive HiFive Unmatched + * board. + * + * In such a configuration the switches are supposed to negotiate the link + * speed of preferably 5.0GT/s, falling back to 2.5GT/s. However the link + * continues switching between the two speeds indefinitely and the data + * link layer never reaches the active state, with link training reported + * repeatedly active ~84% of the time. Forcing the target link speed to + * 2.5GT/s with the upstream ASM2824 device makes the two switches talk to + * each other correctly however. And more interestingly retraining with a + * higher target link speed afterwards lets the two successfully negotiate + * 5.0GT/s. + * + * As this can potentially happen with any device and is cheap in the case + * of correctly operating hardware, let's do it for all downstream ports, + * for root complexes, PCIe switches and PCI/PCI-X to PCIe bridges. + * + * First check if automatic link training may have failed to complete, as + * indicated by the optional Data Link Layer Link Active status being off + * and the Link Bandwidth Management Status indicating that hardware has + * changed the link speed or width in an attempt to correct unreliable + * link operation. If this is the case, then check if the link operates + * correctly by seeing whether it is being trained excessively. If it is, + * then conclude the link is broken. + * + * In that case restrict the speed to 2.5GT/s, observing that the Target + * Link Speed field is sticky and therefore the link will stay restricted + * even after a device reset is later made by an OS that is unaware of the + * problem. With the speed restricted request that the link be retrained + * and check again if the link operates correctly. If not, then set the + * Target Link Speed back to the original value. + * + * This requires the presence of the Link Control 2 register, so make sure + * the PCI Express Capability Version is at least 2. Also don't try, for + * obvious reasons, to limit the speed if 2.5GT/s is the only link speed + * supported. + */ +static void dm_pciauto_exp_fixup_link(struct udevice *dev, int pcie_off) +{ + u16 exp_lnksta, exp_lnkctl, exp_lnkctl2; + u16 exp_flags, exp_type, exp_version; + u32 exp_lnkcap; + pci_dev_t bdf; + + dm_pci_read_config16(dev, pcie_off + PCI_EXP_FLAGS, &exp_flags); + exp_version = exp_flags & PCI_EXP_FLAGS_VERS; + if (exp_version < 2) + return; + + exp_type = (exp_flags & PCI_EXP_FLAGS_TYPE) >> 4; + switch (exp_type) { + case PCI_EXP_TYPE_ROOT_PORT: + case PCI_EXP_TYPE_DOWNSTREAM: + case PCI_EXP_TYPE_PCIE_BRIDGE: + break; + default: + return; + } + + dm_pci_read_config32(dev, pcie_off + PCI_EXP_LNKCAP, &exp_lnkcap); + if ((exp_lnkcap & PCI_EXP_LNKCAP_SLS) <= PCI_EXP_LNKCAP_SLS_2_5GB) + return; + + dm_pci_read_config16(dev, pcie_off + PCI_EXP_LNKSTA, &exp_lnksta); + if ((exp_lnksta & (PCI_EXP_LNKSTA_LBMS | PCI_EXP_LNKSTA_DLLLA)) != + PCI_EXP_LNKSTA_LBMS) + return; + + if (dm_pciauto_exp_link_stable(dev, pcie_off)) + return; + + bdf = dm_pci_get_bdf(dev); + printf("PCI Autoconfig: %02x.%02x.%02x: " + "Downstream link non-functional\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + printf("PCI Autoconfig: %02x.%02x.%02x: " + "Retrying with speed restricted to 2.5GT/s...\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + + dm_pci_read_config16(dev, pcie_off + PCI_EXP_LNKCTL, &exp_lnkctl); + dm_pci_read_config16(dev, pcie_off + PCI_EXP_LNKCTL2, &exp_lnkctl2); + + dm_pci_write_config16(dev, pcie_off + PCI_EXP_LNKCTL2, + (exp_lnkctl2 & ~PCI_EXP_LNKCTL2_TLS) | + PCI_EXP_LNKCTL2_TLS_2_5GT); + dm_pci_write_config16(dev, pcie_off + PCI_EXP_LNKCTL, + exp_lnkctl | PCI_EXP_LNKCTL_RL); + + if (dm_pciauto_exp_link_stable(dev, pcie_off)) { + printf("PCI Autoconfig: %02x.%02x.%02x: Succeeded!\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + } else { + printf("PCI Autoconfig: %02x.%02x.%02x: Failed!\n", + PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf)); + + dm_pci_write_config16(dev, pcie_off + PCI_EXP_LNKCTL2, + exp_lnkctl2); + dm_pci_write_config16(dev, pcie_off + PCI_EXP_LNKCTL, + exp_lnkctl | PCI_EXP_LNKCTL_RL); + } +} + void dm_pciauto_prescan_setup_bridge(struct udevice *dev, int sub_bus) { struct pci_region *pci_mem; @@ -189,6 +353,7 @@ void dm_pciauto_prescan_setup_bridge(struct udevice *dev, int sub_bus) u8 io_32; struct udevice *ctlr = pci_get_controller(dev); struct pci_controller *ctlr_hose = dev_get_uclass_priv(ctlr); + int pcie_off; pci_mem = ctlr_hose->pci_mem; pci_prefetch = ctlr_hose->pci_prefetch; @@ -275,6 +440,11 @@ void dm_pciauto_prescan_setup_bridge(struct udevice *dev, int sub_bus) } } + /* For PCIe devices see if we need to retrain the link by hand */ + pcie_off = dm_pci_find_capability(dev, PCI_CAP_ID_EXP); + if (pcie_off) + dm_pciauto_exp_fixup_link(dev, pcie_off); + /* Enable memory and I/O accesses, enable bus master */ dm_pci_write_config16(dev, PCI_COMMAND, cmdstat | PCI_COMMAND_MASTER); } diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c index ab2a5d17fcf..86c589a65fd 100644 --- a/drivers/phy/allwinner/phy-sun4i-usb.c +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -125,7 +125,6 @@ struct sun4i_usb_phy_info { struct sun4i_usb_phy_plat { void __iomem *pmu; - int power_on_count; int gpio_vbus; int gpio_vbus_det; int gpio_id_det; @@ -225,10 +224,6 @@ static int sun4i_usb_phy_power_on(struct phy *phy) initial_usb_scan_delay = 0; } - usb_phy->power_on_count++; - if (usb_phy->power_on_count != 1) - return 0; - if (usb_phy->gpio_vbus >= 0) gpio_set_value(usb_phy->gpio_vbus, SUNXI_GPIO_PULL_UP); @@ -240,10 +235,6 @@ static int sun4i_usb_phy_power_off(struct phy *phy) struct sun4i_usb_phy_data *data = dev_get_priv(phy->dev); struct sun4i_usb_phy_plat *usb_phy = &data->usb_phy[phy->id]; - usb_phy->power_on_count--; - if (usb_phy->power_on_count != 0) - return 0; - if (usb_phy->gpio_vbus >= 0) gpio_set_value(usb_phy->gpio_vbus, SUNXI_GPIO_PULL_DISABLE); diff --git a/drivers/phy/phy-uclass.c b/drivers/phy/phy-uclass.c index 706e9fdf3a1..49e2ec25c28 100644 --- a/drivers/phy/phy-uclass.c +++ b/drivers/phy/phy-uclass.c @@ -11,12 +11,96 @@ #include <dm/device_compat.h> #include <dm/devres.h> #include <generic-phy.h> +#include <linux/list.h> + +/** + * struct phy_counts - Init and power-on counts of a single PHY port + * + * This structure is used to keep track of PHY initialization and power + * state change requests, so that we don't power off and deinitialize a + * PHY instance until all of its users want it done. Otherwise, multiple + * consumers using the same PHY port can cause problems (e.g. one might + * call power_off() after another's exit() and hang indefinitely). + * + * @id: The PHY ID within a PHY provider + * @power_on_count: Times generic_phy_power_on() was called for this ID + * without a matching generic_phy_power_off() afterwards + * @init_count: Times generic_phy_init() was called for this ID + * without a matching generic_phy_exit() afterwards + * @list: Handle for a linked list of these structures corresponding to + * ports of the same PHY provider + */ +struct phy_counts { + unsigned long id; + int power_on_count; + int init_count; + struct list_head list; +}; static inline struct phy_ops *phy_dev_ops(struct udevice *dev) { return (struct phy_ops *)dev->driver->ops; } +static struct phy_counts *phy_get_counts(struct phy *phy) +{ + struct list_head *uc_priv; + struct phy_counts *counts; + + if (!generic_phy_valid(phy)) + return NULL; + + uc_priv = dev_get_uclass_priv(phy->dev); + list_for_each_entry(counts, uc_priv, list) + if (counts->id == phy->id) + return counts; + + return NULL; +} + +static int phy_alloc_counts(struct phy *phy) +{ + struct list_head *uc_priv; + struct phy_counts *counts; + + if (!generic_phy_valid(phy)) + return 0; + if (phy_get_counts(phy)) + return 0; + + uc_priv = dev_get_uclass_priv(phy->dev); + counts = kzalloc(sizeof(*counts), GFP_KERNEL); + if (!counts) + return -ENOMEM; + + counts->id = phy->id; + counts->power_on_count = 0; + counts->init_count = 0; + list_add(&counts->list, uc_priv); + + return 0; +} + +static int phy_uclass_pre_probe(struct udevice *dev) +{ + struct list_head *uc_priv = dev_get_uclass_priv(dev); + + INIT_LIST_HEAD(uc_priv); + + return 0; +} + +static int phy_uclass_pre_remove(struct udevice *dev) +{ + struct list_head *uc_priv = dev_get_uclass_priv(dev); + struct phy_counts *counts, *next; + + list_for_each_entry_safe(counts, next, uc_priv, list) + kfree(counts); + + return 0; +} + static int generic_phy_xlate_offs_flags(struct phy *phy, struct ofnode_phandle_args *args) { @@ -88,6 +172,12 @@ int generic_phy_get_by_index_nodev(ofnode node, int index, struct phy *phy) goto err; } + ret = phy_alloc_counts(phy); + if (ret) { + debug("phy_alloc_counts() failed: %d\n", ret); + goto err; + } + return 0; err: @@ -118,6 +208,7 @@ int generic_phy_get_by_name(struct udevice *dev, const char *phy_name, int generic_phy_init(struct phy *phy) { + struct phy_counts *counts; struct phy_ops const *ops; int ret; @@ -126,10 +217,19 @@ int generic_phy_init(struct phy *phy) ops = phy_dev_ops(phy->dev); if (!ops->init) return 0; + + counts = phy_get_counts(phy); + if (counts->init_count > 0) { + counts->init_count++; + return 0; + } + ret = ops->init(phy); if (ret) dev_err(phy->dev, "PHY: Failed to init %s: %d.\n", phy->dev->name, ret); + else + counts->init_count = 1; return ret; } @@ -154,6 +254,7 @@ int generic_phy_reset(struct phy *phy) int generic_phy_exit(struct phy *phy) { + struct phy_counts *counts; struct phy_ops const *ops; int ret; @@ -162,16 +263,28 @@ int generic_phy_exit(struct phy *phy) ops = phy_dev_ops(phy->dev); if (!ops->exit) return 0; + + counts = phy_get_counts(phy); + if (counts->init_count == 0) + return 0; + if (counts->init_count > 1) { + counts->init_count--; + return 0; + } + ret = ops->exit(phy); if (ret) dev_err(phy->dev, "PHY: Failed to exit %s: %d.\n", phy->dev->name, ret); + else + counts->init_count = 0; return ret; } int generic_phy_power_on(struct phy *phy) { + struct phy_counts *counts; struct phy_ops const *ops; int ret; @@ -180,16 +293,26 @@ int generic_phy_power_on(struct phy *phy) ops = phy_dev_ops(phy->dev); if (!ops->power_on) return 0; + + counts = phy_get_counts(phy); + if (counts->power_on_count > 0) { + counts->power_on_count++; + return 0; + } + ret = ops->power_on(phy); if (ret) dev_err(phy->dev, "PHY: Failed to power on %s: %d.\n", phy->dev->name, ret); + else + counts->power_on_count = 1; return ret; } int generic_phy_power_off(struct phy *phy) { + struct phy_counts *counts; struct phy_ops const *ops; int ret; @@ -198,10 +321,21 @@ int generic_phy_power_off(struct phy *phy) ops = phy_dev_ops(phy->dev); if (!ops->power_off) return 0; + + counts = phy_get_counts(phy); + if (counts->power_on_count == 0) + return 0; + if (counts->power_on_count > 1) { + counts->power_on_count--; + return 0; + } + ret = ops->power_off(phy); if (ret) dev_err(phy->dev, "PHY: Failed to power off %s: %d.\n", phy->dev->name, ret); + else + counts->power_on_count = 0; return ret; } @@ -316,4 +450,7 @@ int generic_phy_power_off_bulk(struct phy_bulk *bulk) UCLASS_DRIVER(phy) = { .id = UCLASS_PHY, .name = "phy", + .pre_probe = phy_uclass_pre_probe, + .pre_remove = phy_uclass_pre_remove, + .per_device_auto = sizeof(struct list_head), }; |
