* [PATCH v2] PCI: exynos: refactor exynos pcie driver
From: Jaehoon Chung @ 2017-01-05 4:17 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483588418-2551-1-git-send-email-pankaj.dubey@samsung.com>
On 01/05/2017 12:53 PM, Pankaj Dubey wrote:
> From: Niyas Ahmed S T <niyas.ahmed@samsung.com>
>
> Currently Exynos PCIe driver is only supported for Exynos5440 SoC.
> This patch does refactoring of Exynos PCIe driver to extend support
> for other Exynos SoC.
>
> Following are the main changes done via this patch:
>
> 1) It adds separate structs for memory, clock resources.
>
> Reason behind this change is, moving ahead various Exynos SoC will
> have different hardware resources such as iomem, clocks, regmap handles
> etc. for PCIe controller, so keeping these resources in separate struct
> will help us in intiailizing them via per SoC ops, and will avoid too
> many of_machine_is_compatible in code, and help us simplifying
> exynos_pcie struct.
>
> 2) It add exynos_pcie_ops struct which will allow us to support the
> differences in resources in different Exynos SoC.
>
> No functional change intended.
>
> Signed-off-by: Niyas Ahmed S T <niyas.ahmed@samsung.com>
> Signed-off-by: Pankaj Dubey <pankaj.dubey@samsung.com>
> Acked-by: Krzysztof Kozlowski <krzk@kernel.org>
> Reviewed-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
Reviewed-by: Jaehoon Chung <jh80.chung@samsung.com>
Best Regards,
Jaehoon Chung
> ---
> This patch set is prepared on top of Krzysztof's for-next and
> PCIe driver cleanup patch [1] by Jaehoon Chung.
>
> [1]: https://lkml.org/lkml/2016/12/19/44
>
> Changes from v1:
> - Addressed review comments from Krzysztof and Jingoo Han.
>
> drivers/pci/host/pci-exynos.c | 348 ++++++++++++++++++++++++++----------------
> 1 file changed, 219 insertions(+), 129 deletions(-)
>
> diff --git a/drivers/pci/host/pci-exynos.c b/drivers/pci/host/pci-exynos.c
> index 33562cf..d91751b 100644
> --- a/drivers/pci/host/pci-exynos.c
> +++ b/drivers/pci/host/pci-exynos.c
> @@ -17,6 +17,7 @@
> #include <linux/interrupt.h>
> #include <linux/kernel.h>
> #include <linux/init.h>
> +#include <linux/of_device.h>
> #include <linux/of_gpio.h>
> #include <linux/pci.h>
> #include <linux/platform_device.h>
> @@ -28,16 +29,6 @@
>
> #define to_exynos_pcie(x) container_of(x, struct exynos_pcie, pp)
>
> -struct exynos_pcie {
> - struct pcie_port pp;
> - void __iomem *elbi_base; /* DT 0th resource */
> - void __iomem *phy_base; /* DT 1st resource */
> - void __iomem *block_base; /* DT 2nd resource */
> - int reset_gpio;
> - struct clk *clk;
> - struct clk *bus_clk;
> -};
> -
> /* PCIe ELBI registers */
> #define PCIE_IRQ_PULSE 0x000
> #define IRQ_INTA_ASSERT BIT(0)
> @@ -102,6 +93,122 @@ struct exynos_pcie {
> #define PCIE_PHY_TRSV3_PD_TSV BIT(7)
> #define PCIE_PHY_TRSV3_LVCC 0x31c
>
> +struct exynos_pcie_mem_res {
> + void __iomem *elbi_base; /* DT 0th resource: PCIE CTRL */
> + void __iomem *phy_base; /* DT 1st resource: PHY CTRL */
> + void __iomem *block_base; /* DT 2nd resource: PHY ADDITIONAL CTRL */
> +};
> +
> +struct exynos_pcie_clk_res {
> + struct clk *clk;
> + struct clk *bus_clk;
> +};
> +
> +struct exynos_pcie {
> + struct pcie_port pp;
> + struct exynos_pcie_mem_res *mem_res;
> + struct exynos_pcie_clk_res *clk_res;
> + const struct exynos_pcie_ops *ops;
> + int reset_gpio;
> +};
> +
> +struct exynos_pcie_ops {
> + int (*get_mem_resources)(struct platform_device *pdev,
> + struct exynos_pcie *ep);
> + int (*get_clk_resources)(struct exynos_pcie *ep);
> + int (*init_clk_resources)(struct exynos_pcie *ep);
> + void (*deinit_clk_resources)(struct exynos_pcie *ep);
> +};
> +
> +static int exynos5440_pcie_get_mem_resources(struct platform_device *pdev,
> + struct exynos_pcie *ep)
> +{
> + struct resource *res;
> + struct device *dev = ep->pp.dev;
> +
> + ep->mem_res = devm_kzalloc(dev, sizeof(*ep->mem_res), GFP_KERNEL);
> + if (!ep->mem_res)
> + return -ENOMEM;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + ep->mem_res->elbi_base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(ep->mem_res->elbi_base))
> + return PTR_ERR(ep->mem_res->elbi_base);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + ep->mem_res->phy_base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(ep->mem_res->phy_base))
> + return PTR_ERR(ep->mem_res->phy_base);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> + ep->mem_res->block_base = devm_ioremap_resource(dev, res);
> + if (IS_ERR(ep->mem_res->block_base))
> + return PTR_ERR(ep->mem_res->block_base);
> +
> + return 0;
> +}
> +
> +static int exynos5440_pcie_get_clk_resources(struct exynos_pcie *ep)
> +{
> + struct device *dev = ep->pp.dev;
> +
> + ep->clk_res = devm_kzalloc(dev, sizeof(*ep->clk_res), GFP_KERNEL);
> + if (!ep->clk_res)
> + return -ENOMEM;
> +
> + ep->clk_res->clk = devm_clk_get(dev, "pcie");
> + if (IS_ERR(ep->clk_res->clk)) {
> + dev_err(dev, "Failed to get pcie rc clock\n");
> + return PTR_ERR(ep->clk_res->clk);
> + }
> +
> + ep->clk_res->bus_clk = devm_clk_get(dev, "pcie_bus");
> + if (IS_ERR(ep->clk_res->bus_clk)) {
> + dev_err(dev, "Failed to get pcie bus clock\n");
> + return PTR_ERR(ep->clk_res->bus_clk);
> + }
> +
> + return 0;
> +}
> +
> +static int exynos5440_pcie_init_clk_resources(struct exynos_pcie *ep)
> +{
> + struct device *dev = ep->pp.dev;
> + int ret;
> +
> + ret = clk_prepare_enable(ep->clk_res->clk);
> + if (ret) {
> + dev_err(dev, "cannot enable pcie rc clock");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(ep->clk_res->bus_clk);
> + if (ret) {
> + dev_err(dev, "cannot enable pcie bus clock");
> + goto err_bus_clk;
> + }
> +
> + return 0;
> +
> +err_bus_clk:
> + clk_disable_unprepare(ep->clk_res->clk);
> +
> + return ret;
> +}
> +
> +static void exynos5440_pcie_deinit_clk_resources(struct exynos_pcie *ep)
> +{
> + clk_disable_unprepare(ep->clk_res->bus_clk);
> + clk_disable_unprepare(ep->clk_res->clk);
> +}
> +
> +static const struct exynos_pcie_ops exynos5440_pcie_ops = {
> + .get_mem_resources = exynos5440_pcie_get_mem_resources,
> + .get_clk_resources = exynos5440_pcie_get_clk_resources,
> + .init_clk_resources = exynos5440_pcie_init_clk_resources,
> + .deinit_clk_resources = exynos5440_pcie_deinit_clk_resources,
> +};
> +
> static void exynos_pcie_writel(void __iomem *base, u32 val, u32 reg)
> {
> writel(val, base + reg);
> @@ -116,155 +223,155 @@ static void exynos_pcie_sideband_dbi_w_mode(struct exynos_pcie *ep, bool on)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->elbi_base, PCIE_ELBI_SLV_AWMISC);
> + val = exynos_pcie_readl(ep->mem_res->elbi_base, PCIE_ELBI_SLV_AWMISC);
> if (on)
> val |= PCIE_ELBI_SLV_DBI_ENABLE;
> else
> val &= ~PCIE_ELBI_SLV_DBI_ENABLE;
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_ELBI_SLV_AWMISC);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_ELBI_SLV_AWMISC);
> }
>
> static void exynos_pcie_sideband_dbi_r_mode(struct exynos_pcie *ep, bool on)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->elbi_base, PCIE_ELBI_SLV_ARMISC);
> + val = exynos_pcie_readl(ep->mem_res->elbi_base, PCIE_ELBI_SLV_ARMISC);
> if (on)
> val |= PCIE_ELBI_SLV_DBI_ENABLE;
> else
> val &= ~PCIE_ELBI_SLV_DBI_ENABLE;
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_ELBI_SLV_ARMISC);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_ELBI_SLV_ARMISC);
> }
>
> static void exynos_pcie_assert_core_reset(struct exynos_pcie *ep)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->elbi_base, PCIE_CORE_RESET);
> + val = exynos_pcie_readl(ep->mem_res->elbi_base, PCIE_CORE_RESET);
> val &= ~PCIE_CORE_RESET_ENABLE;
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_CORE_RESET);
> - exynos_pcie_writel(ep->elbi_base, 0, PCIE_PWR_RESET);
> - exynos_pcie_writel(ep->elbi_base, 0, PCIE_STICKY_RESET);
> - exynos_pcie_writel(ep->elbi_base, 0, PCIE_NONSTICKY_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_CORE_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 0, PCIE_PWR_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 0, PCIE_STICKY_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 0, PCIE_NONSTICKY_RESET);
> }
>
> static void exynos_pcie_deassert_core_reset(struct exynos_pcie *ep)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->elbi_base, PCIE_CORE_RESET);
> + val = exynos_pcie_readl(ep->mem_res->elbi_base, PCIE_CORE_RESET);
> val |= PCIE_CORE_RESET_ENABLE;
>
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_CORE_RESET);
> - exynos_pcie_writel(ep->elbi_base, 1, PCIE_STICKY_RESET);
> - exynos_pcie_writel(ep->elbi_base, 1, PCIE_NONSTICKY_RESET);
> - exynos_pcie_writel(ep->elbi_base, 1, PCIE_APP_INIT_RESET);
> - exynos_pcie_writel(ep->elbi_base, 0, PCIE_APP_INIT_RESET);
> - exynos_pcie_writel(ep->block_base, 1, PCIE_PHY_MAC_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_CORE_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 1, PCIE_STICKY_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 1, PCIE_NONSTICKY_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 1, PCIE_APP_INIT_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 0, PCIE_APP_INIT_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 1, PCIE_PHY_MAC_RESET);
> }
>
> static void exynos_pcie_assert_phy_reset(struct exynos_pcie *ep)
> {
> - exynos_pcie_writel(ep->block_base, 0, PCIE_PHY_MAC_RESET);
> - exynos_pcie_writel(ep->block_base, 1, PCIE_PHY_GLOBAL_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 0, PCIE_PHY_MAC_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 1, PCIE_PHY_GLOBAL_RESET);
> }
>
> static void exynos_pcie_deassert_phy_reset(struct exynos_pcie *ep)
> {
> - exynos_pcie_writel(ep->block_base, 0, PCIE_PHY_GLOBAL_RESET);
> - exynos_pcie_writel(ep->elbi_base, 1, PCIE_PWR_RESET);
> - exynos_pcie_writel(ep->block_base, 0, PCIE_PHY_COMMON_RESET);
> - exynos_pcie_writel(ep->block_base, 0, PCIE_PHY_CMN_REG);
> - exynos_pcie_writel(ep->block_base, 0, PCIE_PHY_TRSVREG_RESET);
> - exynos_pcie_writel(ep->block_base, 0, PCIE_PHY_TRSV_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 0, PCIE_PHY_GLOBAL_RESET);
> + exynos_pcie_writel(ep->mem_res->elbi_base, 1, PCIE_PWR_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 0, PCIE_PHY_COMMON_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 0, PCIE_PHY_CMN_REG);
> + exynos_pcie_writel(ep->mem_res->block_base, 0, PCIE_PHY_TRSVREG_RESET);
> + exynos_pcie_writel(ep->mem_res->block_base, 0, PCIE_PHY_TRSV_RESET);
> }
>
> static void exynos_pcie_power_on_phy(struct exynos_pcie *ep)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_COMMON_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_COMMON_POWER);
> val &= ~PCIE_PHY_COMMON_PD_CMN;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_COMMON_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV0_POWER);
> val &= ~PCIE_PHY_TRSV0_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV0_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV1_POWER);
> val &= ~PCIE_PHY_TRSV1_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV1_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV2_POWER);
> val &= ~PCIE_PHY_TRSV2_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV2_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV3_POWER);
> val &= ~PCIE_PHY_TRSV3_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV3_POWER);
> }
>
> static void exynos_pcie_power_off_phy(struct exynos_pcie *ep)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_COMMON_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_COMMON_POWER);
> val |= PCIE_PHY_COMMON_PD_CMN;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_COMMON_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV0_POWER);
> val |= PCIE_PHY_TRSV0_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV0_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV1_POWER);
> val |= PCIE_PHY_TRSV1_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV1_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV2_POWER);
> val |= PCIE_PHY_TRSV2_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV2_POWER);
>
> - val = exynos_pcie_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER);
> + val = exynos_pcie_readl(ep->mem_res->phy_base, PCIE_PHY_TRSV3_POWER);
> val |= PCIE_PHY_TRSV3_PD_TSV;
> - exynos_pcie_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER);
> + exynos_pcie_writel(ep->mem_res->phy_base, val, PCIE_PHY_TRSV3_POWER);
> }
>
> static void exynos_pcie_init_phy(struct exynos_pcie *ep)
> {
> /* DCC feedback control off */
> - exynos_pcie_writel(ep->phy_base, 0x29, PCIE_PHY_DCC_FEEDBACK);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x29, PCIE_PHY_DCC_FEEDBACK);
>
> /* set TX/RX impedance */
> - exynos_pcie_writel(ep->phy_base, 0xd5, PCIE_PHY_IMPEDANCE);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0xd5, PCIE_PHY_IMPEDANCE);
>
> /* set 50Mhz PHY clock */
> - exynos_pcie_writel(ep->phy_base, 0x14, PCIE_PHY_PLL_DIV_0);
> - exynos_pcie_writel(ep->phy_base, 0x12, PCIE_PHY_PLL_DIV_1);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x14, PCIE_PHY_PLL_DIV_0);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x12, PCIE_PHY_PLL_DIV_1);
>
> /* set TX Differential output for lane 0 */
> - exynos_pcie_writel(ep->phy_base, 0x7f, PCIE_PHY_TRSV0_DRV_LVL);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x7f, PCIE_PHY_TRSV0_DRV_LVL);
>
> /* set TX Pre-emphasis Level Control for lane 0 to minimum */
> - exynos_pcie_writel(ep->phy_base, 0x0, PCIE_PHY_TRSV0_EMP_LVL);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x0, PCIE_PHY_TRSV0_EMP_LVL);
>
> /* set RX clock and data recovery bandwidth */
> - exynos_pcie_writel(ep->phy_base, 0xe7, PCIE_PHY_PLL_BIAS);
> - exynos_pcie_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV0_RXCDR);
> - exynos_pcie_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV1_RXCDR);
> - exynos_pcie_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV2_RXCDR);
> - exynos_pcie_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV3_RXCDR);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0xe7, PCIE_PHY_PLL_BIAS);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x82, PCIE_PHY_TRSV0_RXCDR);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x82, PCIE_PHY_TRSV1_RXCDR);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x82, PCIE_PHY_TRSV2_RXCDR);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x82, PCIE_PHY_TRSV3_RXCDR);
>
> /* change TX Pre-emphasis Level Control for lanes */
> - exynos_pcie_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV0_EMP_LVL);
> - exynos_pcie_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV1_EMP_LVL);
> - exynos_pcie_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV2_EMP_LVL);
> - exynos_pcie_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV3_EMP_LVL);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x39, PCIE_PHY_TRSV0_EMP_LVL);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x39, PCIE_PHY_TRSV1_EMP_LVL);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x39, PCIE_PHY_TRSV2_EMP_LVL);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x39, PCIE_PHY_TRSV3_EMP_LVL);
>
> /* set LVCC */
> - exynos_pcie_writel(ep->phy_base, 0x20, PCIE_PHY_TRSV0_LVCC);
> - exynos_pcie_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV1_LVCC);
> - exynos_pcie_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV2_LVCC);
> - exynos_pcie_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV3_LVCC);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0x20, PCIE_PHY_TRSV0_LVCC);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0xa0, PCIE_PHY_TRSV1_LVCC);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0xa0, PCIE_PHY_TRSV2_LVCC);
> + exynos_pcie_writel(ep->mem_res->phy_base, 0xa0, PCIE_PHY_TRSV3_LVCC);
> }
>
> static void exynos_pcie_assert_reset(struct exynos_pcie *exynos_pcie)
> @@ -295,25 +402,27 @@ static int exynos_pcie_establish_link(struct exynos_pcie *exynos_pcie)
> exynos_pcie_init_phy(exynos_pcie);
>
> /* pulse for common reset */
> - exynos_pcie_writel(exynos_pcie->block_base, 1, PCIE_PHY_COMMON_RESET);
> + exynos_pcie_writel(exynos_pcie->mem_res->block_base, 1,
> + PCIE_PHY_COMMON_RESET);
> udelay(500);
> - exynos_pcie_writel(exynos_pcie->block_base, 0, PCIE_PHY_COMMON_RESET);
> + exynos_pcie_writel(exynos_pcie->mem_res->block_base, 0,
> + PCIE_PHY_COMMON_RESET);
>
> exynos_pcie_deassert_core_reset(exynos_pcie);
> dw_pcie_setup_rc(pp);
> exynos_pcie_assert_reset(exynos_pcie);
>
> /* assert LTSSM enable */
> - exynos_pcie_writel(exynos_pcie->elbi_base, PCIE_ELBI_LTSSM_ENABLE,
> - PCIE_APP_LTSSM_ENABLE);
> + exynos_pcie_writel(exynos_pcie->mem_res->elbi_base,
> + PCIE_ELBI_LTSSM_ENABLE, PCIE_APP_LTSSM_ENABLE);
>
> /* check if the link is up or not */
> if (!dw_pcie_wait_for_link(pp))
> return 0;
>
> - while (exynos_pcie_readl(exynos_pcie->phy_base,
> + while (exynos_pcie_readl(exynos_pcie->mem_res->phy_base,
> PCIE_PHY_PLL_LOCKED) == 0) {
> - val = exynos_pcie_readl(exynos_pcie->block_base,
> + val = exynos_pcie_readl(exynos_pcie->mem_res->block_base,
> PCIE_PHY_PLL_LOCKED);
> dev_info(dev, "PLL Locked: 0x%x\n", val);
> }
> @@ -325,8 +434,8 @@ static void exynos_pcie_clear_irq_pulse(struct exynos_pcie *ep)
> {
> u32 val;
>
> - val = exynos_pcie_readl(ep->elbi_base, PCIE_IRQ_PULSE);
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_IRQ_PULSE);
> + val = exynos_pcie_readl(ep->mem_res->elbi_base, PCIE_IRQ_PULSE);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_IRQ_PULSE);
> }
>
> static void exynos_pcie_enable_irq_pulse(struct exynos_pcie *ep)
> @@ -336,7 +445,7 @@ static void exynos_pcie_enable_irq_pulse(struct exynos_pcie *ep)
> /* enable INTX interrupt */
> val = IRQ_INTA_ASSERT | IRQ_INTB_ASSERT |
> IRQ_INTC_ASSERT | IRQ_INTD_ASSERT;
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_IRQ_EN_PULSE);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_IRQ_EN_PULSE);
> }
>
> static irqreturn_t exynos_pcie_irq_handler(int irq, void *arg)
> @@ -363,9 +472,9 @@ static void exynos_pcie_msi_init(struct exynos_pcie *ep)
> dw_pcie_msi_init(pp);
>
> /* enable MSI interrupt */
> - val = exynos_pcie_readl(ep->elbi_base, PCIE_IRQ_EN_LEVEL);
> + val = exynos_pcie_readl(ep->mem_res->elbi_base, PCIE_IRQ_EN_LEVEL);
> val |= IRQ_MSI_ENABLE;
> - exynos_pcie_writel(ep->elbi_base, val, PCIE_IRQ_EN_LEVEL);
> + exynos_pcie_writel(ep->mem_res->elbi_base, val, PCIE_IRQ_EN_LEVEL);
> }
>
> static void exynos_pcie_enable_interrupts(struct exynos_pcie *exynos_pcie)
> @@ -425,7 +534,8 @@ static int exynos_pcie_link_up(struct pcie_port *pp)
> struct exynos_pcie *exynos_pcie = to_exynos_pcie(pp);
> u32 val;
>
> - val = exynos_pcie_readl(exynos_pcie->elbi_base, PCIE_ELBI_RDLH_LINKUP);
> + val = exynos_pcie_readl(exynos_pcie->mem_res->elbi_base,
> + PCIE_ELBI_RDLH_LINKUP);
> if (val == PCIE_ELBI_LTSSM_ENABLE)
> return 1;
>
> @@ -503,7 +613,6 @@ static int __init exynos_pcie_probe(struct platform_device *pdev)
> struct exynos_pcie *exynos_pcie;
> struct pcie_port *pp;
> struct device_node *np = dev->of_node;
> - struct resource *res;
> int ret;
>
> exynos_pcie = devm_kzalloc(dev, sizeof(*exynos_pcie), GFP_KERNEL);
> @@ -513,59 +622,37 @@ static int __init exynos_pcie_probe(struct platform_device *pdev)
> pp = &exynos_pcie->pp;
> pp->dev = dev;
>
> - exynos_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
> -
> - exynos_pcie->clk = devm_clk_get(dev, "pcie");
> - if (IS_ERR(exynos_pcie->clk)) {
> - dev_err(dev, "Failed to get pcie rc clock\n");
> - return PTR_ERR(exynos_pcie->clk);
> - }
> - ret = clk_prepare_enable(exynos_pcie->clk);
> - if (ret)
> - return ret;
> + exynos_pcie->ops = (const struct exynos_pcie_ops *)
> + of_device_get_match_data(dev);
>
> - exynos_pcie->bus_clk = devm_clk_get(dev, "pcie_bus");
> - if (IS_ERR(exynos_pcie->bus_clk)) {
> - dev_err(dev, "Failed to get pcie bus clock\n");
> - ret = PTR_ERR(exynos_pcie->bus_clk);
> - goto fail_clk;
> - }
> - ret = clk_prepare_enable(exynos_pcie->bus_clk);
> - if (ret)
> - goto fail_clk;
> + exynos_pcie->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
>
> - res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> - exynos_pcie->elbi_base = devm_ioremap_resource(dev, res);
> - if (IS_ERR(exynos_pcie->elbi_base)) {
> - ret = PTR_ERR(exynos_pcie->elbi_base);
> - goto fail_bus_clk;
> + if (exynos_pcie->ops && exynos_pcie->ops->get_mem_resources) {
> + ret = exynos_pcie->ops->get_mem_resources(pdev, exynos_pcie);
> + if (ret)
> + return ret;
> }
>
> - res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> - exynos_pcie->phy_base = devm_ioremap_resource(dev, res);
> - if (IS_ERR(exynos_pcie->phy_base)) {
> - ret = PTR_ERR(exynos_pcie->phy_base);
> - goto fail_bus_clk;
> - }
> + if (exynos_pcie->ops && exynos_pcie->ops->get_clk_resources) {
> + ret = exynos_pcie->ops->get_clk_resources(exynos_pcie);
> + if (ret)
> + return ret;
>
> - res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
> - exynos_pcie->block_base = devm_ioremap_resource(dev, res);
> - if (IS_ERR(exynos_pcie->block_base)) {
> - ret = PTR_ERR(exynos_pcie->block_base);
> - goto fail_bus_clk;
> + ret = exynos_pcie->ops->init_clk_resources(exynos_pcie);
> + if (ret)
> + return ret;
> }
>
> ret = exynos_add_pcie_port(exynos_pcie, pdev);
> if (ret < 0)
> - goto fail_bus_clk;
> + goto fail_probe;
>
> platform_set_drvdata(pdev, exynos_pcie);
> return 0;
>
> -fail_bus_clk:
> - clk_disable_unprepare(exynos_pcie->bus_clk);
> -fail_clk:
> - clk_disable_unprepare(exynos_pcie->clk);
> +fail_probe:
> + if (exynos_pcie->ops && exynos_pcie->ops->deinit_clk_resources)
> + exynos_pcie->ops->deinit_clk_resources(exynos_pcie);
> return ret;
> }
>
> @@ -573,14 +660,17 @@ static int __exit exynos_pcie_remove(struct platform_device *pdev)
> {
> struct exynos_pcie *exynos_pcie = platform_get_drvdata(pdev);
>
> - clk_disable_unprepare(exynos_pcie->bus_clk);
> - clk_disable_unprepare(exynos_pcie->clk);
> + if (exynos_pcie->ops && exynos_pcie->ops->deinit_clk_resources)
> + exynos_pcie->ops->deinit_clk_resources(exynos_pcie);
>
> return 0;
> }
>
> static const struct of_device_id exynos_pcie_of_match[] = {
> - { .compatible = "samsung,exynos5440-pcie", },
> + {
> + .compatible = "samsung,exynos5440-pcie",
> + .data = &exynos5440_pcie_ops
> + },
> {},
> };
>
>
^ permalink raw reply
* [PATCHv2 0/5] Support for Marvell switches with integrated CPUs
From: Chris Packham @ 2017-01-05 4:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <6c6795d3-06ab-a4f0-78ad-5ecc78c1aa2f@gmail.com>
On 05/01/17 17:07, Florian Fainelli wrote:
> Le 01/04/17 ? 19:36, Chris Packham a ?crit :
>> The 98DX3236, 98DX3336 and 98DX4251 are a set of switch ASICs with
>> integrated CPUs. They CPU block is common within these product lines and
>> (as far as I can tell/have been told) is based on the Armada XP. There
>> are a few differences due to the fact they have to squeeze the CPU into
>> the same package as the switch.
>
> It's really great to see these changes, do you have a plan to also add
> support for the integrated switch using a DSA/switchdev driver?
I'd love to see a switchdev driver but it's a huge task (and no I'm not
committing to writing it). As it stands Marvell ship a switch SDK
largely executes in userspace with a small kernel module providing some
linkage to the underlying hardware.
We (a few of us here at Allied Telesis NZ) have discussed switchdev and
how we get from using Marvell's SDK in our products to using switchdev
proper.
The first step would probably be some kind of trampoline driver which
communicates with a userspace helper to do the actual work.
Alternatively there is some support in Marvell's SDK for compilation as
a binary blob so a proprietary kernel module is another option. Neither
of these are particularly nice in a free software world.
A full "free" implementation would be a large undertaking. Ideally I'd
like to see Marvell involved with producing one but so far they've not
been interested whenever I've brought it up.
^ permalink raw reply
* [PATCHv2 4/5] arm: mvebu: Add device tree for 98DX3236 SoCs
From: Chris Packham @ 2017-01-05 4:34 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1ee850ee-f771-1aeb-6b96-dfbe3118f2fc@gmail.com>
On 05/01/17 17:06, Florian Fainelli wrote:
> Le 01/04/17 ? 19:36, Chris Packham a ?crit :
>> The Marvell 98DX3236, 98DX3336, 98DX4521 and variants are switch ASICs
>> with integrated CPUs. They are similar to the Armada XP SoCs but have
>> different I/O interfaces.
>>
>> Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
>> ---
>> +
>> + switch {
>> + packet-processor at 0 {
>> + compatible = "marvell,prestera-98dx4521";
>> + };
>> + };
>
> This may be a bit premature if you are not providing a binding document
> for this sub-node, you might as well add it once you also add a
> corresponding driver (or if this will remain out of tree, at least
> submitting a separate binding document).
I also see that I made a typo 4521 should be 4251.
The driver that uses this is an out of tree one so I will add a binding
document for it for now.
It wouldn't he hard to whip up a simple uio based driver that provides
mmap access to the switch register space so I might look at that if I
get time. It would be useful for testing if nothing else.
>
> Also, if this node's unit address is 0, you would expect at least a reg
> property whose address cell is 0 to be present.
>
The reg property is in the base 98dx3236 dsti file. The 3 devices have
the same IO footprint but the switching functionality is quite different
between them hence the different compatible strings (all of which I need
to add binding documentation for). I guess I could make the 3236 entry
pp0: packet-processor at 0 {
compatible = "marvell,prestera-98dx3236";
reg = <0 0x4000000>;
...
}
Then the 3336 and 4251 could just override the compatible string
&pp0 {
compatible = "marvell,prestera-98dx4251";
};
Would that be clearer?
^ permalink raw reply
* [PATCHv2 2/5] arm: mvebu: support for SMP on 98DX3336 SoC
From: Chris Packham @ 2017-01-05 4:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <5edb6751-d15b-6654-f4ae-adf38fafb8a4@gmail.com>
On 05/01/17 17:04, Florian Fainelli wrote:
> Le 01/04/17 ? 19:36, Chris Packham a ?crit :
>> Compared to the armada-xp the 98DX3336 uses different registers to set
>> the boot address for the secondary CPU so a new enable-method is needed.
>> This will only work if the machine definition doesn't define an overall
>> smp_ops because there is not currently a way of overriding this from the
>> device tree if it is set in the machine definition.
>
> Not sure I follow you here, in theory, each individual CPU could have a
> different enable-method property, and considering how you leverage
> existing DTS include files, you should be able to override the DTS with
> the appropriate enable-method, or do you have a different problem?
>
The problem is I can't override the enable method if it's set in the
machine definition. It's because the devicetree is looked up first then
the machine definition overrides it.
Heres an old thread that basically outlines the problem.
http://lists.infradead.org/pipermail/linux-arm-kernel/2014-November/300371.html
I posted a few attempts to work around it but there never really was any
consensus reached.
> mv98dx3236_resume_set_cpu_boot_addr(int hw_cpu, void *boot_addr)
>> +{
>> + WARN_ON(hw_cpu != 1);
>> +
>> + writel(0, mv98dx3236_resume_base + MV98DX3236_CPU_RESUME_CTRL_OFFSET);
>> + writel(virt_to_phys(boot_addr), mv98dx3236_resume_base +
>> + MV98DX3236_CPU_RESUME_ADDR_OFFSET);
>
> I just submitted a patch series that switches such users to
> __pa_symbol() instead of virt_to_phys() because this is a kernel image
> symbol. For now this will work as-is, but depending on which patch
> series makes it first, it may be a good idea to keep this on someone's
> TODO list (yours or mine). I do expect having to make a second pass of
> conversions anyway.
>
> [1]: https://lkml.org/lkml/2017/1/4/1101
OK I'll keep that in mind.
>
>> +}
>> +
>> +static int __init mv98dx3236_resume_init(void)
>> +{
>> + struct device_node *np;
>> + struct resource res;
>> + int ret = 0;
>> +
>> + np = of_find_matching_node(NULL, of_mv98dx3236_resume_table);
>> + if (!np)
>> + return 0;
>> +
>> + pr_info("Initializing 98DX3236 Resume\n");
>
> This can be probably be dropped, you should know fairly quickly if this
> succeeded or not.
Will do.
>
> Can't this function be implemented as a smp_ops::smp_init_cpus instead
> of having this initialization done at arch_initcall time?
>
I'll look into it. My initial reaction is no because I still need
armada_xp_smp_init_cpus().
>> +
>> + if (of_address_to_resource(np, 0, &res)) {
>> + pr_err("unable to get resource\n");
>> + ret = -ENOENT;
>> + goto out;
>> + }
>> +
>> + if (!request_mem_region(res.start, resource_size(&res),
>> + np->full_name)) {
>> + pr_err("unable to request region\n");
>> + ret = -EBUSY;
>> + goto out;
>> + }
>
> You should be able to use of_io_request_and_map() and here.
>
Will do.
^ permalink raw reply
* [PATCH v3 02/10] dt-bindings: hisi: Add Hisilicon HiP05/06/07 Djtag dts bindings
From: Anurup M @ 2017-01-05 4:58 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170103225623.ynhc7n267jpzu44z@rob-hp-laptop>
On Wednesday 04 January 2017 04:26 AM, Rob Herring wrote:
> On Mon, Jan 02, 2017 at 01:49:03AM -0500, Anurup M wrote:
>> From: Tan Xiaojun <tanxiaojun@huawei.com>
>>
>> Add Hisilicon HiP05/06/07 Djtag dts bindings for CPU and IO Die
>>
>> Signed-off-by: Tan Xiaojun <tanxiaojun@huawei.com>
>> Signed-off-by: Anurup M <anurup.m@huawei.com>
>> ---
>> .../devicetree/bindings/arm/hisilicon/djtag.txt | 41 ++++++++++++++++++++++
>> 1 file changed, 41 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/arm/hisilicon/djtag.txt
>>
>> diff --git a/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt b/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt
>> new file mode 100644
>> index 0000000..bbe8b45
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt
>> @@ -0,0 +1,41 @@
>> +The Hisilicon Djtag is an independent component which connects with some other
>> +components in the SoC by Debug Bus. The djtag is available in CPU and IO dies
>> +in the chip. The djtag controls access to connecting modules of CPU and IO
>> +dies.
>> +The various connecting components in CPU die (like L3 cache, L3 cache PMU etc.)
>> +are accessed by djtag during real time debugging. In IO die there are connecting
>> +components like RSA. These components appear as devices attached to djtag bus.
>> +
>> +Hisilicon HiP05/06/07 djtag for CPU and IO die
>> +Required properties:
>> + - compatible : The value should be as follows
>> + (a) "hisilicon,hip05-djtag-v1" for CPU and IO die which use v1 hw in
>> + HiP05 chipset.
> You don't need to distinguish the CPU and IO blocks?
The CPU and IO djtag nodes will have different base address(in reg
property).
There is no difference in handling of CPU and IO dies in the djtag driver.
So there is currently no need to distinguish them.
>> + (b) "hisilicon,hip06-djtag-v1" for CPU die which use v1 hw in HiP06 chipset.
>> + (c) "hisilicon,hip06-djtag-v2" for IO die which use v2 hw in HiP06 chipset.
>> + (d) "hisilicon,hip07-djtag-v2" for CPU and IO die which use v2 hw in
>> + HiP07 chipset.
>> + - reg : Register address and size
>> + - hisi-scl-id : The Super Cluster ID for CPU or IO die
> Still needs a vendor prefix. i.e. hisilicon,scl-id
>
Ok. I shall modify it.
Thanks,
Anurup
>> +
>> +Example 1: Djtag for CPU die
>> +
>> + /* for Hisilicon HiP05 djtag for CPU Die */
>> + djtag0: djtag at 80010000 {
>> + compatible = "hisilicon,hip05-djtag-v1";
>> + reg = <0x0 0x80010000 0x0 0x10000>;
>> + hisi-scl-id = <0x02>;
>> +
>> + /* All connecting components will appear as child nodes */
>> + };
>> +
>> +Example 2: Djtag for IO die
>> +
>> + /* for Hisilicon HiP05 djtag for IO Die */
>> + djtag1: djtag at d0000000 {
>> + compatible = "hisilicon,hip05-djtag-v1";
>> + reg = <0x0 0xd0000000 0x0 0x10000>;
>> + hisi-scl-id = <0x01>;
>> +
>> + /* All connecting components will appear as child nodes */
>> + };
>> --
>> 2.1.4
>>
^ permalink raw reply
* [PATCH v3 03/10] dt-bindings: perf: hisi: Add Devicetree bindings for Hisilicon SoC PMU
From: Anurup M @ 2017-01-05 5:00 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170103225914.w3drvtpvupnpfa4x@rob-hp-laptop>
On Wednesday 04 January 2017 04:29 AM, Rob Herring wrote:
> On Mon, Jan 02, 2017 at 01:49:21AM -0500, Anurup M wrote:
>> 1) Device tree bindings for Hisilicon SoC PMU.
>> 2) Add example for Hisilicon L3 cache and MN PMU.
>> 3) Add child nodes of L3C and MN in djtag bindings example.
>>
>> Signed-off-by: Anurup M <anurup.m@huawei.com>
>> Signed-off-by: Shaokun Zhang <zhangshaokun@hisilicon.com>
>> ---
>> .../devicetree/bindings/arm/hisilicon/djtag.txt | 25 ++++++
>> .../devicetree/bindings/arm/hisilicon/pmu.txt | 100 +++++++++++++++++++++
>> 2 files changed, 125 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/arm/hisilicon/pmu.txt
>>
>> diff --git a/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt b/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt
>> index bbe8b45..653fdb7 100644
>> --- a/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt
>> +++ b/Documentation/devicetree/bindings/arm/hisilicon/djtag.txt
>> @@ -27,6 +27,31 @@ Example 1: Djtag for CPU die
>> hisi-scl-id = <0x02>;
>>
>> /* All connecting components will appear as child nodes */
>> +
>> + pmul3c0 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x02>;
>> + };
>> +
>> + pmul3c1 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x04>;
>> + };
>> +
>> + pmul3c2 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x01>;
>> + };
>> +
>> + pmul3c3 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x08>;
>> + };
>> +
>> + pmumn0 {
>> + compatible = "hisilicon,hip05-pmu-mn-v1";
>> + hisi-module-id = <0x0b>;
>> + };
>> };
>>
>> Example 2: Djtag for IO die
>> diff --git a/Documentation/devicetree/bindings/arm/hisilicon/pmu.txt b/Documentation/devicetree/bindings/arm/hisilicon/pmu.txt
>> new file mode 100644
>> index 0000000..fceef8d
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/arm/hisilicon/pmu.txt
>> @@ -0,0 +1,100 @@
>> +Hisilicon SoC HiP05/06/07 ARMv8 PMU
>> +===================================
>> +
>> +The Hisilicon SoC chips like HiP05/06/07 etc. consist of various independent
>> +system device PMUs such as L3 cache (L3C) and Miscellaneous Nodes(MN). These
>> +PMU devices are independent and have hardware logic to gather statistics and
>> +performance information.
>> +
>> +HiSilicon SoC chip is encapsulated by multiple CPU and IO dies. The CPU die
>> +is called as Super CPU cluster (SCCL) which includes 16 cpu-cores. Every SCCL
>> +in HiP05/06/07 chips are further grouped as CPU clusters (CCL) which includes
>> +4 cpu-cores each.
>> +e.g. In the case of HiP05/06/07, each SCCL has 1 L3 cache and 1 MN PMU device.
>> +The L3 cache is further grouped as 4 L3 cache banks in a SCCL.
>> +
>> +The Hisilicon SoC PMU DT node bindings for uncore PMU devices are as below.
>> +For PMU devices like L3 cache. MN etc. which are accessed using the djtag,
>> +the parent node will be the djtag node of the corresponding CPU die (SCCL).
>> +
>> +L3 cache
>> +---------
>> +The L3 cache is dedicated for each SCCL. Each SCCL in HiP05/06/07 chips have 4
>> +L3 cache banks. Each L3 cache bank have separate DT nodes.
>> +
>> +Required properties:
>> +
>> + - compatible : This value should be as follows
>> + (a) "hisilicon,hip05-pmu-l3c-v1" for v1 hw in HiP05 chipset
>> + (b) "hisilicon,hip06-pmu-l3c-v1" for v1 hw in HiP06 chipset
>> + (c) "hisilicon,hip07-pmu-l3c-v2" for v2 hw in HiP07 chipset
>> +
>> + - hisi-module-id : This property is a combination of two values in the below order.
> Vendor prefix: hisilicon,module-id
Ok. I shall modify it.
>> + a) Module ID: The module identifier for djtag.
>> + b) Instance or Bank ID: This will identify the L3 cache bank
>> + or instance.
>> +
>> +Optional properties:
>> +
>> + - interrupt-parent : A phandle indicating which interrupt controller
>> + this PMU signals interrupts to.
>> +
>> + - interrupts : Interrupt line used by this L3 cache bank.
>> +
>> + *The counter overflow IRQ is not supported in v1 hardware (HiP05/06).
>> +
>> +Miscellaneous Node
>> +------------------
>> +The MN is dedicated for each SCCL and hence there are separate DT nodes for MN
>> +for each SCCL.
>> +
>> +Required properties:
>> +
>> + - compatible : This value should be as follows
>> + (a) "hisilicon,hip05-pmu-mn-v1" for v1 hw in HiP05 chipset
>> + (b) "hisilicon,hip06-pmu-mn-v1" for v1 hw in HiP06 chipset
>> + (c) "hisilicon,hip07-pmu-mn-v2" for v2 hw in HiP07 chipset
>> +
>> + - hisi-module-id : Module ID to input for djtag.
> ditto
Ok. I shall modify it.
Thanks,
Anurup
>> +
>> +Optional properties:
>> +
>> + - interrupt-parent : A phandle indicating which interrupt controller
>> + this PMU signals interrupts to.
>> +
>> + - interrupts : Interrupt line used by this PMU.
>> +
>> + *The counter overflow IRQ is not supported in v1 hardware (HiP05/06).
>> +
>> +Example:
>> +
>> + djtag0: djtag at 80010000 {
>> + compatible = "hisilicon,hip05-djtag-v1";
>> + reg = <0x0 0x80010000 0x0 0x10000>;
>> + scl-id = <0x02>;
>> +
>> + pmul3c0 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x02>;
>> + };
>> +
>> + pmul3c1 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x04>;
>> + };
>> +
>> + pmul3c2 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x01>;
>> + };
>> +
>> + pmul3c3 {
>> + compatible = "hisilicon,hip05-pmu-l3c-v1";
>> + hisi-module-id = <0x04 0x08>;
>> + };
>> +
>> + pmumn0 {
>> + compatible = "hisilicon,hip05-pmu-mn-v1";
>> + hisi-module-id = <0x0b>;
>> + };
>> + };
>> --
>> 2.1.4
>>
^ permalink raw reply
* [RFC PATCH] usb: dwc3: add support for OTG driver compilation
From: Manish Narani @ 2017-01-05 5:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <87r34jvtn5.fsf@linux.intel.com>
Hi Felipe,
> From: Felipe Balbi [mailto:balbi at kernel.org]
> Sent: Wednesday, January 04, 2017 7:01 PM
> Hi,
>
> Manish Narani <manish.narani@xilinx.com> writes:
> > This patch adds support for OTG driver compilation and object file
> > creation
> >
> > Signed-off-by: Manish Narani <mnarani@xilinx.com>
> > ---
> > drivers/usb/dwc3/Makefile | 4 ++++
> > 1 file changed, 4 insertions(+)
> >
> > diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> > index ffca340..2d269ad 100644
> > --- a/drivers/usb/dwc3/Makefile
> > +++ b/drivers/usb/dwc3/Makefile
> > @@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET)
> $(CONFIG_USB_DWC3_DUAL_ROLE)),)
> > dwc3-y += gadget.o ep0.o
> > endif
> >
> > +ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
> > + dwc3-y += otg.o
> > +endif
>
> you just broke compilation
Should I add new USB_DWC3_OTG macro in Kconfig?
Thanks,
Manish
This email and any attachments are intended for the sole use of the named recipient(s) and contain(s) confidential information that may be proprietary, privileged or copyrighted under applicable law. If you are not the intended recipient, do not read, copy, or forward this email message or any attachments. Delete this email message and any attachments immediately.
^ permalink raw reply
* [PATCH v6 0/4] arm64: arch_timer: Add workaround for hisilicon-161601 erratum
From: Ding Tianhong @ 2017-01-05 5:31 UTC (permalink / raw)
To: linux-arm-kernel
Erratum Hisilicon-161601 says that the ARM generic timer counter "has the
potential to contain an erroneous value when the timer value changes".
Accesses to TVAL (both read and write) are also affected due to the implicit counter
read. Accesses to CVAL are not affected.
The workaround is to reread the system count registers until the value of the second
read is larger than the first one by less than 32, the system counter can be guaranteed
not to return wrong value twice by back-to-back read and the error value is always larger
than the correct one by 32. Writes to TVAL are replaced with an equivalent write to CVAL.
v2: Introducing a new generic erratum handling mechanism for fsl,a008585 and hisilicon,161601.
Significant rework based on feedback, including seperate the fsl erratum a008585
to another patch, update the erratum name and remove unwanted code.
v3: Introducing the erratum_workaround_set_sne generic function for fsl erratum a008585
and make the #define __fsl_a008585_read_reg to be private to the .c file instead of
being globally visible. After discussion with Marc and Will, a consensus decision was
made to remove the commandline parameter for enabling fsl,erratum-a008585 erratum,
and make some generic name more specific, export timer_unstable_counter_workaround
for module access.
Significant rework based on feedback, including fix some alignment problem, make the
#define __hisi_161601_read_reg to be private to the .c file instead of being globally
visible, add more accurate annotation and modify a bit of logical format to enable
arch_timer_read_ool_enabled, remove the kernel commandline parameter
clocksource.arm_arch_timer.hisilicon-161601.
Introduce a generic aquick framework for erratum in ACPI mode.
v4: rename the quirk handler parameter to make it more generic, and
avoid break loop when handling the quirk becasue it need to
support multi quirks handler.
update some data structures for acpi mode.
v5: Adapt the new kernel-parameters.txt for latest kernel version.
Set the retries of reread system counter to 50, because it is possible
that some interrupts may lead to more than twice read errors and break the loop,
it will trigger the warning, so we set the number of retries far beyond the number of
iterations the loop has been observed to take.
v6: The last 2 patches in the previous version about the ACPI mode will conflict witch Fuwei's
GTDT patches, so remove the ACPI part and only support the DT base code for this patch set.
We have trigger a bug when select the CONFIG_FUNCTION_GRAPH_TRACER and enable function_graph
to /sys/kernel/debug/tracing/current_tracer, the system will stall into an endless loop, it looks
like that the ftrace_graph_caller will be related to xxx.read_cntvct_el0 and read the system counter
again, so mark the xxx.read_cntvct_el0 with notrace to fix the problem.
Ding Tianhong (4):
arm64: arch_timer: Add device tree binding for hisilicon-161601
erratum
arm64: arch_timer: Introduce a generic erratum handing mechanism for
fsl-a008585
arm64: arch_timer: Work around Erratum Hisilicon-161601
arm64: arch timer: Add timer erratum property for Hip05-d02 and
Hip06-d03
Documentation/admin-guide/kernel-parameters.txt | 9 --
Documentation/arm64/silicon-errata.txt | 1 +
.../devicetree/bindings/arm/arch_timer.txt | 8 ++
arch/arm64/boot/dts/hisilicon/hip05.dtsi | 1 +
arch/arm64/boot/dts/hisilicon/hip06.dtsi | 1 +
arch/arm64/include/asm/arch_timer.h | 38 ++----
drivers/clocksource/Kconfig | 9 ++
drivers/clocksource/arm_arch_timer.c | 146 ++++++++++++++++-----
8 files changed, 143 insertions(+), 70 deletions(-)
--
1.9.0
^ permalink raw reply
* [PATCH v6 1/4] arm64: arch_timer: Add device tree binding for hisilicon-161601 erratum
From: Ding Tianhong @ 2017-01-05 5:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483594317-10732-1-git-send-email-dingtianhong@huawei.com>
This erratum describes a bug in logic outside the core, so MIDR can't be
used to identify its presence, and reading an SoC-specific revision
register from common arch timer code would be awkward. So, describe it
in the device tree.
v2: Use the new erratum name and update the description.
Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
Acked-by: Rob Herring <robh@kernel.org>
---
Documentation/devicetree/bindings/arm/arch_timer.txt | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/arch_timer.txt b/Documentation/devicetree/bindings/arm/arch_timer.txt
index ad440a2..935f142 100644
--- a/Documentation/devicetree/bindings/arm/arch_timer.txt
+++ b/Documentation/devicetree/bindings/arm/arch_timer.txt
@@ -31,6 +31,14 @@ to deliver its interrupts via SPIs.
This also affects writes to the tval register, due to the implicit
counter read.
+- hisilicon,erratum-161601 : A boolean property. Indicates the presence of
+ erratum 161601, which says that reading the counter is unreliable unless
+ reading twice on the register and the value of the second read is larger
+ than the first by less than 32. If the verification is unsuccessful, then
+ discard the value of this read and repeat this procedure until the verification
+ is successful. This also affects writes to the tval register, due to the
+ implicit counter read.
+
** Optional properties:
- arm,cpu-registers-not-fw-configured : Firmware does not initialize
--
1.9.0
^ permalink raw reply related
* [PATCH v6 2/4] arm64: arch_timer: Introduce a generic erratum handing mechanism for fsl-a008585
From: Ding Tianhong @ 2017-01-05 5:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483594317-10732-1-git-send-email-dingtianhong@huawei.com>
The workaround for hisilicon,161601 will check the return value of the system counter
by different way, in order to distinguish with the fsl-a008585 workaround, introduce
a new generic erratum handing mechanism for fsl-a008585 and rename some functions.
v2: Introducing a new generic erratum handling mechanism for fsl erratum a008585.
v3: Introducing the erratum_workaround_set_sne generic function for fsl erratum a008585
and make the #define __fsl_a008585_read_reg to be private to the .c file instead of
being globally visible. After discussion with Marc and Will, a consensus decision was
made to remove the commandline parameter for enabling fsl,erratum-a008585 erratum,
and make some generic name more specific, export timer_unstable_counter_workaround
for module access.
v5: Adapt the new documentation for kernel-parameters.txt.
v6: Mark the fsl_a008585_read_xxx_el0 with notrace, if CONFIG_FUNCTION_GRAPH_TRACER selected,
fsl_a008585_read_xxx_el0 will be related to ftrace_graph_caller which will calling sched_clock
to read system counter again, it will cause the system stall into an endless loop.
Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
---
Documentation/admin-guide/kernel-parameters.txt | 9 ---
arch/arm64/include/asm/arch_timer.h | 36 ++++--------
drivers/clocksource/arm_arch_timer.c | 78 +++++++++++++++----------
3 files changed, 58 insertions(+), 65 deletions(-)
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 21e2d88..76437ad 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -539,15 +539,6 @@
loops can be debugged more effectively on production
systems.
- clocksource.arm_arch_timer.fsl-a008585=
- [ARM64]
- Format: <bool>
- Enable/disable the workaround of Freescale/NXP
- erratum A-008585. This can be useful for KVM
- guests, if the guest device tree doesn't show the
- erratum. If unspecified, the workaround is
- enabled based on the device tree.
-
clearcpuid=BITNUM [X86]
Disable CPUID feature X for the kernel. See
arch/x86/include/asm/cpufeatures.h for the valid bit
diff --git a/arch/arm64/include/asm/arch_timer.h b/arch/arm64/include/asm/arch_timer.h
index eaa5bbe..f882c7c 100644
--- a/arch/arm64/include/asm/arch_timer.h
+++ b/arch/arm64/include/asm/arch_timer.h
@@ -31,39 +31,27 @@
#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585)
extern struct static_key_false arch_timer_read_ool_enabled;
-#define needs_fsl_a008585_workaround() \
+#define needs_unstable_timer_counter_workaround() \
static_branch_unlikely(&arch_timer_read_ool_enabled)
#else
-#define needs_fsl_a008585_workaround() false
+#define needs_unstable_timer_counter_workaround() false
#endif
-u32 __fsl_a008585_read_cntp_tval_el0(void);
-u32 __fsl_a008585_read_cntv_tval_el0(void);
-u64 __fsl_a008585_read_cntvct_el0(void);
-/*
- * The number of retries is an arbitrary value well beyond the highest number
- * of iterations the loop has been observed to take.
- */
-#define __fsl_a008585_read_reg(reg) ({ \
- u64 _old, _new; \
- int _retries = 200; \
- \
- do { \
- _old = read_sysreg(reg); \
- _new = read_sysreg(reg); \
- _retries--; \
- } while (unlikely(_old != _new) && _retries); \
- \
- WARN_ON_ONCE(!_retries); \
- _new; \
-})
+struct arch_timer_erratum_workaround {
+ int erratum; /* Indicate the Erratum ID */
+ u32 (*read_cntp_tval_el0)(void);
+ u32 (*read_cntv_tval_el0)(void);
+ u64 (*read_cntvct_el0)(void);
+};
+
+extern struct arch_timer_erratum_workaround *timer_unstable_counter_workaround;
#define arch_timer_reg_read_stable(reg) \
({ \
u64 _val; \
- if (needs_fsl_a008585_workaround()) \
- _val = __fsl_a008585_read_##reg(); \
+ if (needs_unstable_timer_counter_workaround()) \
+ _val = timer_unstable_counter_workaround->read_##reg();\
else \
_val = read_sysreg(reg); \
_val; \
diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c
index 02fef68..f9097b6 100644
--- a/drivers/clocksource/arm_arch_timer.c
+++ b/drivers/clocksource/arm_arch_timer.c
@@ -96,40 +96,53 @@ static int __init early_evtstrm_cfg(char *buf)
*/
#ifdef CONFIG_FSL_ERRATUM_A008585
-DEFINE_STATIC_KEY_FALSE(arch_timer_read_ool_enabled);
-EXPORT_SYMBOL_GPL(arch_timer_read_ool_enabled);
-
-static int fsl_a008585_enable = -1;
+struct arch_timer_erratum_workaround *timer_unstable_counter_workaround = NULL;
+EXPORT_SYMBOL_GPL(timer_unstable_counter_workaround);
-static int __init early_fsl_a008585_cfg(char *buf)
-{
- int ret;
- bool val;
+#define FSL_A008585 0x0001
- ret = strtobool(buf, &val);
- if (ret)
- return ret;
-
- fsl_a008585_enable = val;
- return 0;
-}
-early_param("clocksource.arm_arch_timer.fsl-a008585", early_fsl_a008585_cfg);
+DEFINE_STATIC_KEY_FALSE(arch_timer_read_ool_enabled);
+EXPORT_SYMBOL_GPL(arch_timer_read_ool_enabled);
-u32 __fsl_a008585_read_cntp_tval_el0(void)
+/*
+ * The number of retries is an arbitrary value well beyond the highest number
+ * of iterations the loop has been observed to take.
+ */
+#define __fsl_a008585_read_reg(reg) ({ \
+ u64 _old, _new; \
+ int _retries = 200; \
+ \
+ do { \
+ _old = read_sysreg(reg); \
+ _new = read_sysreg(reg); \
+ _retries--; \
+ } while (unlikely(_old != _new) && _retries); \
+ \
+ WARN_ON_ONCE(!_retries); \
+ _new; \
+})
+
+static u32 notrace fsl_a008585_read_cntp_tval_el0(void)
{
return __fsl_a008585_read_reg(cntp_tval_el0);
}
-u32 __fsl_a008585_read_cntv_tval_el0(void)
+static u32 notrace fsl_a008585_read_cntv_tval_el0(void)
{
return __fsl_a008585_read_reg(cntv_tval_el0);
}
-u64 __fsl_a008585_read_cntvct_el0(void)
+static u64 notrace fsl_a008585_read_cntvct_el0(void)
{
return __fsl_a008585_read_reg(cntvct_el0);
}
-EXPORT_SYMBOL(__fsl_a008585_read_cntvct_el0);
+
+static struct arch_timer_erratum_workaround arch_timer_fsl_a008585 = {
+ .erratum = FSL_A008585,
+ .read_cntp_tval_el0 = fsl_a008585_read_cntp_tval_el0,
+ .read_cntv_tval_el0 = fsl_a008585_read_cntv_tval_el0,
+ .read_cntvct_el0 = fsl_a008585_read_cntvct_el0,
+};
#endif /* CONFIG_FSL_ERRATUM_A008585 */
static __always_inline
@@ -282,7 +295,7 @@ static __always_inline void set_next_event(const int access, unsigned long evt,
}
#ifdef CONFIG_FSL_ERRATUM_A008585
-static __always_inline void fsl_a008585_set_next_event(const int access,
+static __always_inline void erratum_set_next_event_generic(const int access,
unsigned long evt, struct clock_event_device *clk)
{
unsigned long ctrl;
@@ -300,17 +313,17 @@ static __always_inline void fsl_a008585_set_next_event(const int access,
arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, clk);
}
-static int fsl_a008585_set_next_event_virt(unsigned long evt,
+static int erratum_set_next_event_virt(unsigned long evt,
struct clock_event_device *clk)
{
- fsl_a008585_set_next_event(ARCH_TIMER_VIRT_ACCESS, evt, clk);
+ erratum_set_next_event_generic(ARCH_TIMER_VIRT_ACCESS, evt, clk);
return 0;
}
-static int fsl_a008585_set_next_event_phys(unsigned long evt,
+static int erratum_set_next_event_phys(unsigned long evt,
struct clock_event_device *clk)
{
- fsl_a008585_set_next_event(ARCH_TIMER_PHYS_ACCESS, evt, clk);
+ erratum_set_next_event_generic(ARCH_TIMER_PHYS_ACCESS, evt, clk);
return 0;
}
#endif /* CONFIG_FSL_ERRATUM_A008585 */
@@ -343,16 +356,16 @@ static int arch_timer_set_next_event_phys_mem(unsigned long evt,
return 0;
}
-static void fsl_a008585_set_sne(struct clock_event_device *clk)
+static void erratum_workaround_set_sne(struct clock_event_device *clk)
{
#ifdef CONFIG_FSL_ERRATUM_A008585
if (!static_branch_unlikely(&arch_timer_read_ool_enabled))
return;
if (arch_timer_uses_ppi == VIRT_PPI)
- clk->set_next_event = fsl_a008585_set_next_event_virt;
+ clk->set_next_event = erratum_set_next_event_virt;
else
- clk->set_next_event = fsl_a008585_set_next_event_phys;
+ clk->set_next_event = erratum_set_next_event_phys;
#endif
}
@@ -385,7 +398,7 @@ static void __arch_timer_setup(unsigned type,
BUG();
}
- fsl_a008585_set_sne(clk);
+ erratum_workaround_set_sne(clk);
} else {
clk->features |= CLOCK_EVT_FEAT_DYNIRQ;
clk->name = "arch_mem_timer";
@@ -894,9 +907,10 @@ static int __init arch_timer_of_init(struct device_node *np)
arch_timer_c3stop = !of_property_read_bool(np, "always-on");
#ifdef CONFIG_FSL_ERRATUM_A008585
- if (fsl_a008585_enable < 0)
- fsl_a008585_enable = of_property_read_bool(np, "fsl,erratum-a008585");
- if (fsl_a008585_enable) {
+ if (!timer_unstable_counter_workaround && of_property_read_bool(np, "fsl,erratum-a008585"))
+ timer_unstable_counter_workaround = &arch_timer_fsl_a008585;
+
+ if (timer_unstable_counter_workaround) {
static_branch_enable(&arch_timer_read_ool_enabled);
pr_info("Enabling workaround for FSL erratum A-008585\n");
}
--
1.9.0
^ permalink raw reply related
* [PATCH v6 3/4] arm64: arch_timer: Work around Erratum Hisilicon-161601
From: Ding Tianhong @ 2017-01-05 5:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483594317-10732-1-git-send-email-dingtianhong@huawei.com>
Erratum Hisilicon-161601 says that the ARM generic timer counter "has the
potential to contain an erroneous value when the timer value changes".
Accesses to TVAL (both read and write) are also affected due to the implicit counter
read. Accesses to CVAL are not affected.
The workaround is to reread the system count registers until the value of the second
read is larger than the first one by less than 32, the system counter can be guaranteed
not to return wrong value twice by back-to-back read and the error value is always larger
than the correct one by 32. Writes to TVAL are replaced with an equivalent write to CVAL.
The workaround is enabled if the hisilicon,erratum-161601 property is found in
the timer node in the device tree. This can be overridden with the
clocksource.arm_arch_timer.hisilicon-161601 boot parameter, which allows KVM
users to enable the workaround until a mechanism is implemented to
automatically communicate this information.
Fix some description for fsl erratum a008585.
v2: Significant rework based on feedback, including seperate the fsl erratum a008585
to another patch, update the erratum name and remove unwanted code.
v3: Significant rework based on feedback, including fix some alignment problem, make the
#define __hisi_161601_read_reg to be private to the .c file instead of being globally
visible, add more accurate annotation and modify a bit of logical format to enable
arch_timer_read_ool_enabled, remove the kernel commandline parameter
clocksource.arm_arch_timer.hisilicon-161601.
v5: Theoretically the erratum should not occur more than twice in succession when reading
the system counter, but it is possible that some interrupts may lead to more than twice
read errors, triggering the warning, so setting the number of retries to 50 which is far
beyond the number of iterations the loop has been observed to take.
v6: Mark the hisi_161601_read_xxx_el0 with notrace, if CONFIG_FUNCTION_GRAPH_TRACER selected,
hisi_161601_read_xxx_el0 will be related to ftrace_graph_caller which will calling sched_clock
to read system counter again, it will cause the system stall into an endless loop.
Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
---
Documentation/arm64/silicon-errata.txt | 1 +
arch/arm64/include/asm/arch_timer.h | 2 +-
drivers/clocksource/Kconfig | 9 +++++
drivers/clocksource/arm_arch_timer.c | 70 +++++++++++++++++++++++++++++++---
4 files changed, 76 insertions(+), 6 deletions(-)
diff --git a/Documentation/arm64/silicon-errata.txt b/Documentation/arm64/silicon-errata.txt
index 405da11..1c1a95f 100644
--- a/Documentation/arm64/silicon-errata.txt
+++ b/Documentation/arm64/silicon-errata.txt
@@ -63,3 +63,4 @@ stable kernels.
| Cavium | ThunderX SMMUv2 | #27704 | N/A |
| | | | |
| Freescale/NXP | LS2080A/LS1043A | A-008585 | FSL_ERRATUM_A008585 |
+| Hisilicon | Hip0{5,6,7} | #161601 | HISILICON_ERRATUM_161601|
diff --git a/arch/arm64/include/asm/arch_timer.h b/arch/arm64/include/asm/arch_timer.h
index f882c7c..ebf4cde 100644
--- a/arch/arm64/include/asm/arch_timer.h
+++ b/arch/arm64/include/asm/arch_timer.h
@@ -29,7 +29,7 @@
#include <clocksource/arm_arch_timer.h>
-#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585)
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
extern struct static_key_false arch_timer_read_ool_enabled;
#define needs_unstable_timer_counter_workaround() \
static_branch_unlikely(&arch_timer_read_ool_enabled)
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 4866f7a..162d820 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -335,6 +335,15 @@ config FSL_ERRATUM_A008585
value"). The workaround will only be active if the
fsl,erratum-a008585 property is found in the timer node.
+config HISILICON_ERRATUM_161601
+ bool "Workaround for Hisilicon Erratum 161601"
+ default y
+ depends on ARM_ARCH_TIMER && ARM64
+ help
+ This option enables a workaround for Hisilicon Erratum
+ 161601. The workaround will be active if the hisilicon,erratum-161601
+ property is found in the timer node.
+
config ARM_GLOBAL_TIMER
bool "Support for the ARM global timer" if COMPILE_TEST
select CLKSRC_OF if OF
diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c
index f9097b6..078d38f 100644
--- a/drivers/clocksource/arm_arch_timer.c
+++ b/drivers/clocksource/arm_arch_timer.c
@@ -95,15 +95,18 @@ static int __init early_evtstrm_cfg(char *buf)
* Architected system timer support.
*/
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if CONFIG_FSL_ERRATUM_A008585 || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
struct arch_timer_erratum_workaround *timer_unstable_counter_workaround = NULL;
EXPORT_SYMBOL_GPL(timer_unstable_counter_workaround);
#define FSL_A008585 0x0001
+#define HISILICON_161601 0x0002
DEFINE_STATIC_KEY_FALSE(arch_timer_read_ool_enabled);
EXPORT_SYMBOL_GPL(arch_timer_read_ool_enabled);
+#endif
+#ifdef CONFIG_FSL_ERRATUM_A008585
/*
* The number of retries is an arbitrary value well beyond the highest number
* of iterations the loop has been observed to take.
@@ -145,6 +148,54 @@ static u64 notrace fsl_a008585_read_cntvct_el0(void)
};
#endif /* CONFIG_FSL_ERRATUM_A008585 */
+#ifdef CONFIG_HISILICON_ERRATUM_161601
+/*
+ * Verify whether the value of the second read is larger than the first by
+ * less than 32 is the only way to confirm the value is correct, so clear the
+ * lower 5 bits to check whether the difference is greater than 32 or not.
+ * Theoretically the erratum should not occur more than twice in succession
+ * when reading the system counter, but it is possible that some interrupts
+ * may lead to more than twice read errors, triggering the warning, so setting
+ * the number of retries far beyond the number of iterations the loop has been
+ * observed to take.
+ */
+#define __hisi_161601_read_reg(reg) ({ \
+ u64 _old, _new; \
+ int _retries = 50; \
+ \
+ do { \
+ _old = read_sysreg(reg); \
+ _new = read_sysreg(reg); \
+ _retries--; \
+ } while (unlikely((_new - _old) >> 5) && _retries); \
+ \
+ WARN_ON_ONCE(!_retries); \
+ _new; \
+})
+
+static u32 notrace hisi_161601_read_cntp_tval_el0(void)
+{
+ return __hisi_161601_read_reg(cntp_tval_el0);
+}
+
+static u32 notrace hisi_161601_read_cntv_tval_el0(void)
+{
+ return __hisi_161601_read_reg(cntv_tval_el0);
+}
+
+static u64 notrace hisi_161601_read_cntvct_el0(void)
+{
+ return __hisi_161601_read_reg(cntvct_el0);
+}
+
+static struct arch_timer_erratum_workaround arch_timer_hisi_161601 = {
+ .erratum = HISILICON_161601,
+ .read_cntp_tval_el0 = hisi_161601_read_cntp_tval_el0,
+ .read_cntv_tval_el0 = hisi_161601_read_cntv_tval_el0,
+ .read_cntvct_el0 = hisi_161601_read_cntvct_el0,
+};
+#endif /* CONFIG_HISILICON_ERRATUM_161601 */
+
static __always_inline
void arch_timer_reg_write(int access, enum arch_timer_reg reg, u32 val,
struct clock_event_device *clk)
@@ -294,7 +345,7 @@ static __always_inline void set_next_event(const int access, unsigned long evt,
arch_timer_reg_write(access, ARCH_TIMER_REG_CTRL, ctrl, clk);
}
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
static __always_inline void erratum_set_next_event_generic(const int access,
unsigned long evt, struct clock_event_device *clk)
{
@@ -358,7 +409,7 @@ static int arch_timer_set_next_event_phys_mem(unsigned long evt,
static void erratum_workaround_set_sne(struct clock_event_device *clk)
{
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
if (!static_branch_unlikely(&arch_timer_read_ool_enabled))
return;
@@ -618,7 +669,7 @@ static void __init arch_counter_register(unsigned type)
clocksource_counter.archdata.vdso_direct = true;
-#ifdef CONFIG_FSL_ERRATUM_A008585
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
/*
* Don't use the vdso fastpath if errata require using
* the out-of-line counter accessor.
@@ -909,10 +960,19 @@ static int __init arch_timer_of_init(struct device_node *np)
#ifdef CONFIG_FSL_ERRATUM_A008585
if (!timer_unstable_counter_workaround && of_property_read_bool(np, "fsl,erratum-a008585"))
timer_unstable_counter_workaround = &arch_timer_fsl_a008585;
+#endif
+
+#ifdef CONFIG_HISILICON_ERRATUM_161601
+ if (!timer_unstable_counter_workaround && of_property_read_bool(np, "hisilicon,erratum-161601"))
+ timer_unstable_counter_workaround = &arch_timer_hisi_161601;
+#endif
+#if IS_ENABLED(CONFIG_FSL_ERRATUM_A008585) || IS_ENABLED(CONFIG_HISILICON_ERRATUM_161601)
if (timer_unstable_counter_workaround) {
static_branch_enable(&arch_timer_read_ool_enabled);
- pr_info("Enabling workaround for FSL erratum A-008585\n");
+ pr_info("Enabling workaround for %s\n",
+ timer_unstable_counter_workaround->erratum == FSL_A008585 ?
+ "FSL ERRATUM A-008585" : "HISILICON ERRATUM 161601");
}
#endif
--
1.9.0
^ permalink raw reply related
* [PATCH v6 4/4] arm64: arch timer: Add timer erratum property for Hip05-d02 and Hip06-d03
From: Ding Tianhong @ 2017-01-05 5:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483594317-10732-1-git-send-email-dingtianhong@huawei.com>
Enable workaround for hisilicon erratum 161601 on Hip05-d02 and Hip06-d03 board.
Signed-off-by: Ding Tianhong <dingtianhong@huawei.com>
---
arch/arm64/boot/dts/hisilicon/hip05.dtsi | 1 +
arch/arm64/boot/dts/hisilicon/hip06.dtsi | 1 +
2 files changed, 2 insertions(+)
diff --git a/arch/arm64/boot/dts/hisilicon/hip05.dtsi b/arch/arm64/boot/dts/hisilicon/hip05.dtsi
index 4b472a3..a8e9969 100644
--- a/arch/arm64/boot/dts/hisilicon/hip05.dtsi
+++ b/arch/arm64/boot/dts/hisilicon/hip05.dtsi
@@ -281,6 +281,7 @@
<GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+ hisilicon,erratum-161601;
};
pmu {
diff --git a/arch/arm64/boot/dts/hisilicon/hip06.dtsi b/arch/arm64/boot/dts/hisilicon/hip06.dtsi
index a049b64..344e0f0 100644
--- a/arch/arm64/boot/dts/hisilicon/hip06.dtsi
+++ b/arch/arm64/boot/dts/hisilicon/hip06.dtsi
@@ -260,6 +260,7 @@
<GIC_PPI 14 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
+ hisilicon,erratum-161601;
};
pmu {
--
1.9.0
^ permalink raw reply related
* [PATCH 03/22] iio: adc: add support for X-Powers AXP20X and AXP22X PMICs ADCs
From: Chen-Yu Tsai @ 2017-01-05 5:42 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-4-quentin.schulz@free-electrons.com>
On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> The X-Powers AXP20X and AXP22X PMICs have multiple ADCs. They expose the
> battery voltage, battery charge and discharge currents, AC-in and VBUS
> voltages and currents, 2 GPIOs muxable in ADC mode and PMIC temperature.
>
> This adds support for most of AXP20X and AXP22X ADCs.
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
> drivers/iio/adc/Kconfig | 10 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/axp20x_adc.c | 490 +++++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/axp20x.h | 4 +
> 4 files changed, 505 insertions(+)
> create mode 100644 drivers/iio/adc/axp20x_adc.c
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 38bc319..5c5b51f 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -154,6 +154,16 @@ config AT91_SAMA5D2_ADC
> To compile this driver as a module, choose M here: the module will be
> called at91-sama5d2_adc.
>
> +config AXP20X_ADC
> + tristate "X-Powers AXP20X and AXP22X ADC driver"
> + depends on MFD_AXP20X
> + help
> + Say yes here to have support for X-Powers power management IC (PMIC)
> + AXP20X and AXP22X ADC devices.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called axp20x_adc.
> +
> config AXP288_ADC
> tristate "X-Powers AXP288 ADC driver"
> depends on MFD_AXP20X
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index d36c4be..f5c28a5 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD7887) += ad7887.o
> obj-$(CONFIG_AD799X) += ad799x.o
> obj-$(CONFIG_AT91_ADC) += at91_adc.o
> obj-$(CONFIG_AT91_SAMA5D2_ADC) += at91-sama5d2_adc.o
> +obj-$(CONFIG_AXP20X_ADC) += axp20x_adc.o
> obj-$(CONFIG_AXP288_ADC) += axp288_adc.o
> obj-$(CONFIG_BCM_IPROC_ADC) += bcm_iproc_adc.o
> obj-$(CONFIG_BERLIN2_ADC) += berlin2-adc.o
> diff --git a/drivers/iio/adc/axp20x_adc.c b/drivers/iio/adc/axp20x_adc.c
> new file mode 100644
> index 0000000..8df972a
> --- /dev/null
> +++ b/drivers/iio/adc/axp20x_adc.c
> @@ -0,0 +1,490 @@
> +/* ADC driver for AXP20X and AXP22X PMICs
> + *
> + * Copyright (c) 2016 Free Electrons NextThing Co.
> + * Quentin Schulz <quentin.schulz@free-electrons.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it under
> + * the terms of the GNU General Public License version 2 as published by the
> + * Free Software Foundation.
> + *
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/thermal.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/mfd/axp20x.h>
> +
> +#define AXP20X_ADC_EN1_MASK GENMASK(7, 0)
> +
> +#define AXP20X_ADC_EN2_MASK (GENMASK(3, 2) | BIT(7))
> +#define AXP22X_ADC_EN1_MASK (GENMASK(7, 5) | BIT(0))
> +#define AXP20X_ADC_EN2_TEMP_ADC BIT(7)
> +#define AXP20X_ADC_EN2_GPIO0_ADC BIT(3)
> +#define AXP20X_ADC_EN2_GPIO1_ADC BIT(2)
The latter 3 individual bits aren't used anywhere.
Please remove them for now.
> +
> +#define AXP20X_GPIO10_IN_RANGE_GPIO0 BIT(0)
> +#define AXP20X_GPIO10_IN_RANGE_GPIO1 BIT(1)
> +#define AXP20X_GPIO10_IN_RANGE_GPIO0_VAL(x) ((x) & BIT(0))
> +#define AXP20X_GPIO10_IN_RANGE_GPIO1_VAL(x) (((x) & BIT(0)) << 1)
> +
> +#define AXP20X_ADC_RATE_MASK (3 << 6)
> +#define AXP20X_ADC_RATE_25HZ (0 << 6)
> +#define AXP20X_ADC_RATE_50HZ BIT(6)
Please be consistent with the format.
> +#define AXP20X_ADC_RATE_100HZ (2 << 6)
> +#define AXP20X_ADC_RATE_200HZ (3 << 6)
> +
> +#define AXP22X_ADC_RATE_100HZ (0 << 6)
> +#define AXP22X_ADC_RATE_200HZ BIT(6)
> +#define AXP22X_ADC_RATE_400HZ (2 << 6)
> +#define AXP22X_ADC_RATE_800HZ (3 << 6)
These are power-of-2 multiples of some base rate. May I suggest
a formula macro instead. Either way, you seem to be using only
one value. Will this be made configurable in the future?
> +
> +#define AXP20X_ADC_CHANNEL(_channel, _name, _type, _reg) \
> + { \
> + .type = _type, \
> + .indexed = 1, \
> + .channel = _channel, \
> + .address = _reg, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE), \
> + .datasheet_name = _name, \
> + }
> +
> +#define AXP20X_ADC_CHANNEL_OFFSET(_channel, _name, _type, _reg) \
> + { \
> + .type = _type, \
> + .indexed = 1, \
> + .channel = _channel, \
> + .address = _reg, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE) |\
> + BIT(IIO_CHAN_INFO_OFFSET),\
> + .datasheet_name = _name, \
> + }
> +
> +struct axp20x_adc_iio {
> + struct iio_dev *indio_dev;
> + struct regmap *regmap;
> +};
> +
> +enum axp20x_adc_channel {
> + AXP20X_ACIN_V = 0,
> + AXP20X_ACIN_I,
> + AXP20X_VBUS_V,
> + AXP20X_VBUS_I,
> + AXP20X_TEMP_ADC,
PMIC_TEMP would be better. And please save a slot for TS input.
> + AXP20X_GPIO0_V,
> + AXP20X_GPIO1_V,
Please skip a slot for "battery instantaneous power".
> + AXP20X_BATT_V,
> + AXP20X_BATT_CHRG_I,
> + AXP20X_BATT_DISCHRG_I,
> + AXP20X_IPSOUT_V,
> +};
> +
> +enum axp22x_adc_channel {
> + AXP22X_TEMP_ADC = 0,
Same comments as AXP20X_TEMP_ADC.
> + AXP22X_BATT_V,
> + AXP22X_BATT_CHRG_I,
> + AXP22X_BATT_DISCHRG_I,
> +};
Shouldn't these channel numbers be exported as part of the device tree
bindings? At the very least, they shouldn't be changed.
Also please add a comment saying that the channels are numbered
in the order of their respective registers, and not the table
describing the ADCs in the datasheet (9.7 Signal Capture for AXP209
and 9.5 E-Gauge for AXP221).
> +
> +static const struct iio_chan_spec axp20x_adc_channels[] = {
> + AXP20X_ADC_CHANNEL(AXP20X_ACIN_V, "acin_v", IIO_VOLTAGE,
> + AXP20X_ACIN_V_ADC_H),
> + AXP20X_ADC_CHANNEL(AXP20X_ACIN_I, "acin_i", IIO_CURRENT,
> + AXP20X_ACIN_I_ADC_H),
> + AXP20X_ADC_CHANNEL(AXP20X_VBUS_V, "vbus_v", IIO_VOLTAGE,
> + AXP20X_VBUS_V_ADC_H),
> + AXP20X_ADC_CHANNEL(AXP20X_VBUS_I, "vbus_i", IIO_CURRENT,
> + AXP20X_VBUS_I_ADC_H),
> + AXP20X_ADC_CHANNEL_OFFSET(AXP20X_TEMP_ADC, "temp_adc", IIO_TEMP,
> + AXP20X_TEMP_ADC_H),
> + AXP20X_ADC_CHANNEL_OFFSET(AXP20X_GPIO0_V, "gpio0_v", IIO_VOLTAGE,
> + AXP20X_GPIO0_V_ADC_H),
> + AXP20X_ADC_CHANNEL_OFFSET(AXP20X_GPIO1_V, "gpio1_v", IIO_VOLTAGE,
> + AXP20X_GPIO1_V_ADC_H),
> + AXP20X_ADC_CHANNEL(AXP20X_BATT_V, "batt_v", IIO_VOLTAGE,
> + AXP20X_BATT_V_H),
> + AXP20X_ADC_CHANNEL(AXP20X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT,
> + AXP20X_BATT_CHRG_I_H),
> + AXP20X_ADC_CHANNEL(AXP20X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT,
> + AXP20X_BATT_DISCHRG_I_H),
> + AXP20X_ADC_CHANNEL(AXP20X_IPSOUT_V, "ipsout_v", IIO_VOLTAGE,
> + AXP20X_IPSOUT_V_HIGH_H),
> +};
> +
> +static const struct iio_chan_spec axp22x_adc_channels[] = {
> + AXP20X_ADC_CHANNEL_OFFSET(AXP22X_TEMP_ADC, "temp_adc", IIO_TEMP,
> + AXP22X_TEMP_ADC_H),
> + AXP20X_ADC_CHANNEL(AXP22X_BATT_V, "batt_v", IIO_VOLTAGE,
> + AXP20X_BATT_V_H),
> + AXP20X_ADC_CHANNEL(AXP22X_BATT_CHRG_I, "batt_chrg_i", IIO_CURRENT,
> + AXP20X_BATT_CHRG_I_H),
> + AXP20X_ADC_CHANNEL(AXP22X_BATT_DISCHRG_I, "batt_dischrg_i", IIO_CURRENT,
> + AXP20X_BATT_DISCHRG_I_H),
> +};
> +
> +static int axp20x_adc_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *channel, int *val,
> + int *val2)
> +{
> + struct axp20x_adc_iio *info = iio_priv(indio_dev);
> + int size = 12, ret;
> +
> + switch (channel->channel) {
> + case AXP20X_BATT_DISCHRG_I:
> + size = 13;
> + case AXP20X_ACIN_V:
> + case AXP20X_ACIN_I:
> + case AXP20X_VBUS_V:
> + case AXP20X_VBUS_I:
> + case AXP20X_TEMP_ADC:
> + case AXP20X_BATT_V:
> + case AXP20X_BATT_CHRG_I:
> + case AXP20X_IPSOUT_V:
> + case AXP20X_GPIO0_V:
> + case AXP20X_GPIO1_V:
> + ret = axp20x_read_variable_width(info->regmap, channel->address,
> + size);
> + if (ret < 0)
> + return ret;
> + *val = ret;
> + return IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp22x_adc_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *channel, int *val,
> + int *val2)
> +{
> + struct axp20x_adc_iio *info = iio_priv(indio_dev);
> + int size = 12, ret;
> +
> + switch (channel->channel) {
> + case AXP22X_BATT_DISCHRG_I:
> + size = 13;
> + case AXP22X_TEMP_ADC:
> + case AXP22X_BATT_V:
> + case AXP22X_BATT_CHRG_I:
According to the datasheet, AXP22X_BATT_CHRG_I is also 13 bits wide.
> + ret = axp20x_read_variable_width(info->regmap, channel->address,
> + size);
> + if (ret < 0)
> + return ret;
> + *val = ret;
> + return IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp20x_adc_scale(int channel, int *val, int *val2)
> +{
> + switch (channel) {
> + case AXP20X_ACIN_V:
> + case AXP20X_VBUS_V:
> + *val = 1;
> + *val2 = 700000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP20X_ACIN_I:
> + *val = 0;
> + *val2 = 625000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP20X_VBUS_I:
> + *val = 0;
> + *val2 = 375000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP20X_TEMP_ADC:
> + *val = 100;
> + return IIO_VAL_INT;
> +
> + case AXP20X_GPIO0_V:
> + case AXP20X_GPIO1_V:
> + *val = 0;
> + *val2 = 500000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP20X_BATT_V:
> + *val = 1;
> + *val2 = 100000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP20X_BATT_DISCHRG_I:
> + case AXP20X_BATT_CHRG_I:
> + *val = 0;
> + *val2 = 500000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP20X_IPSOUT_V:
> + *val = 1;
> + *val2 = 400000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp22x_adc_scale(int channel, int *val, int *val2)
> +{
> + switch (channel) {
> + case AXP22X_TEMP_ADC:
> + *val = 100;
> + return IIO_VAL_INT;
> +
> + case AXP22X_BATT_V:
> + *val = 1;
> + *val2 = 100000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + case AXP22X_BATT_DISCHRG_I:
> + case AXP22X_BATT_CHRG_I:
> + *val = 0;
> + *val2 = 500000;
> + return IIO_VAL_INT_PLUS_MICRO;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp20x_adc_offset(struct iio_dev *indio_dev, int channel, int *val)
> +{
> + struct axp20x_adc_iio *info = iio_priv(indio_dev);
> + int ret, reg;
> +
> + switch (channel) {
> + case AXP20X_TEMP_ADC:
> + *val = -1447;
> + return IIO_VAL_INT;
> +
> + case AXP20X_GPIO0_V:
> + case AXP20X_GPIO1_V:
> + ret = regmap_read(info->regmap, AXP20X_GPIO10_IN_RANGE, ®);
> + if (ret < 0)
> + return ret;
> +
> + if (channel == AXP20X_GPIO0_V)
> + *val = reg & AXP20X_GPIO10_IN_RANGE_GPIO0;
> + else
> + *val = reg & AXP20X_GPIO10_IN_RANGE_GPIO1;
> +
> + *val = !!(*val) * 700000;
> +
> + return IIO_VAL_INT;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp20x_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_OFFSET:
> + return axp20x_adc_offset(indio_dev, chan->channel, val);
> +
> + case IIO_CHAN_INFO_SCALE:
> + return axp20x_adc_scale(chan->channel, val, val2);
> +
> + case IIO_CHAN_INFO_RAW:
> + return axp20x_adc_read_raw(indio_dev, chan, val, val2);
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp22x_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_OFFSET:
> + *val = -2667;
Datasheet says -267.7 C, or -2677 here.
> + return IIO_VAL_INT;
> +
> + case IIO_CHAN_INFO_SCALE:
> + return axp22x_adc_scale(chan->channel, val, val2);
> +
> + case IIO_CHAN_INFO_RAW:
> + return axp22x_adc_read_raw(indio_dev, chan, val, val2);
> +
> + default:
> + return -EINVAL;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int axp20x_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int val, int val2,
> + long mask)
> +{
> + struct axp20x_adc_iio *info = iio_priv(indio_dev);
> +
> + /*
> + * The AXP20X PMIC allows the user to choose between 0V and 0.7V offsets
> + * for (independently) GPIO0 and GPIO1 when in ADC mode.
> + */
> + if (mask != IIO_CHAN_INFO_OFFSET)
> + return -EINVAL;
> +
> + if (chan->channel != AXP20X_GPIO0_V && chan->channel != AXP20X_GPIO1_V)
> + return -EINVAL;
> +
> + if (val != 0 && val != 700000)
> + return -EINVAL;
> +
> + if (chan->channel == AXP20X_GPIO0_V)
> + return regmap_update_bits(info->regmap, AXP20X_GPIO10_IN_RANGE,
> + AXP20X_GPIO10_IN_RANGE_GPIO0,
> + AXP20X_GPIO10_IN_RANGE_GPIO0_VAL(!!val));
> +
> + return regmap_update_bits(info->regmap, AXP20X_GPIO10_IN_RANGE,
> + AXP20X_GPIO10_IN_RANGE_GPIO1,
> + AXP20X_GPIO10_IN_RANGE_GPIO1_VAL(!!val));
> +}
> +
> +static const struct iio_info axp20x_adc_iio_info = {
> + .read_raw = axp20x_read_raw,
> + .write_raw = axp20x_write_raw,
> + .driver_module = THIS_MODULE,
> +};
> +
> +static const struct iio_info axp22x_adc_iio_info = {
> + .read_raw = axp22x_read_raw,
> + .driver_module = THIS_MODULE,
> +};
> +
> +static const struct of_device_id axp20x_adc_of_match[] = {
> + { .compatible = "x-powers,axp209-adc", .data = (void *)AXP209_ID, },
> + { .compatible = "x-powers,axp221-adc", .data = (void *)AXP221_ID, },
> + { /* sentinel */ },
> +};
> +
> +static int axp20x_probe(struct platform_device *pdev)
> +{
> + struct axp20x_adc_iio *info;
> + struct iio_dev *indio_dev;
> + struct axp20x_dev *axp20x_dev;
> + int ret, axp20x_id;
> +
> + axp20x_dev = dev_get_drvdata(pdev->dev.parent);
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + info = iio_priv(indio_dev);
> + platform_set_drvdata(pdev, indio_dev);
> +
> + info->regmap = axp20x_dev->regmap;
> + info->indio_dev = indio_dev;
> + indio_dev->name = dev_name(&pdev->dev);
> + indio_dev->dev.parent = &pdev->dev;
> + indio_dev->dev.of_node = pdev->dev.of_node;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + axp20x_id = (int)of_device_get_match_data(&pdev->dev);
> +
> + switch (axp20x_id) {
> + case AXP209_ID:
> + indio_dev->info = &axp20x_adc_iio_info;
> + indio_dev->num_channels = ARRAY_SIZE(axp20x_adc_channels);
> + indio_dev->channels = axp20x_adc_channels;
> +
> + /* Enable the ADCs on IP */
> + regmap_write(info->regmap, AXP20X_ADC_EN1, AXP20X_ADC_EN1_MASK);
> +
> + /* Enable GPIO0/1 and internal temperature ADCs */
> + regmap_update_bits(info->regmap, AXP20X_ADC_EN2,
> + AXP20X_ADC_EN2_MASK, AXP20X_ADC_EN2_MASK);
> +
> + /* Configure ADCs rate */
> + regmap_update_bits(info->regmap, AXP20X_ADC_RATE,
> + AXP20X_ADC_RATE_MASK, AXP20X_ADC_RATE_50HZ);
> + break;
> +
> + case AXP221_ID:
> + indio_dev->info = &axp22x_adc_iio_info;
> + indio_dev->num_channels = ARRAY_SIZE(axp22x_adc_channels);
> + indio_dev->channels = axp22x_adc_channels;
> +
> + /* Enable the ADCs on IP */
> + regmap_write(info->regmap, AXP20X_ADC_EN1, AXP22X_ADC_EN1_MASK);
> +
> + /* Configure ADCs rate */
> + regmap_update_bits(info->regmap, AXP20X_ADC_RATE,
> + AXP20X_ADC_RATE_MASK, AXP22X_ADC_RATE_200HZ);
> + break;
> +
> + default:
> + return -EINVAL;
> + }
> +
> + ret = devm_iio_device_register(&pdev->dev, indio_dev);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "could not register the device\n");
> + regmap_write(info->regmap, AXP20X_ADC_EN1, 0);
> + regmap_write(info->regmap, AXP20X_ADC_EN2, 0);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int axp20x_remove(struct platform_device *pdev)
> +{
> + struct axp20x_adc_iio *info;
> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> +
> + info = iio_priv(indio_dev);
Nit: you could just reverse the 2 declarations above and join this
line after struct axp20x_adc_iio *info;
> + regmap_write(info->regmap, AXP20X_ADC_EN1, 0);
> + regmap_write(info->regmap, AXP20X_ADC_EN2, 0);
The existing VBUS power supply driver enables the VBUS ADC bits itself,
and does not check them later on. This means if one were to remove this
axp20x-adc module, the voltage/current readings in the VBUS power supply
would be invalid. Some sort of workaround would be needed here in this
driver of the VBUS driver.
> +
> + return 0;
> +}
> +
> +static struct platform_driver axp20x_adc_driver = {
> + .driver = {
> + .name = "axp20x-adc",
> + .of_match_table = axp20x_adc_of_match,
> + },
> + .probe = axp20x_probe,
> + .remove = axp20x_remove,
> +};
> +
> +module_platform_driver(axp20x_adc_driver);
> +
> +MODULE_DESCRIPTION("ADC driver for AXP20X and AXP22X PMICs");
> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h
> index a4860bc..650c6f6 100644
> --- a/include/linux/mfd/axp20x.h
> +++ b/include/linux/mfd/axp20x.h
> @@ -150,6 +150,10 @@ enum {
> #define AXP20X_VBUS_I_ADC_L 0x5d
> #define AXP20X_TEMP_ADC_H 0x5e
> #define AXP20X_TEMP_ADC_L 0x5f
> +
> +#define AXP22X_TEMP_ADC_H 0x56
> +#define AXP22X_TEMP_ADC_L 0x57
> +
This is in the wrong patch. Also we already have
/* AXP22X specific registers */
#define AXP22X_PMIC_ADC_H 0x56
#define AXP22X_PMIC_ADC_L 0x57
#define AXP22X_TS_ADC_H 0x58
#define AXP22X_TS_ADC_L 0x59
If you want, you could just rename them to be consistent.
Regards
ChenYu
> #define AXP20X_TS_IN_H 0x62
> #define AXP20X_TS_IN_L 0x63
> #define AXP20X_GPIO0_V_ADC_H 0x64
> --
> 2.9.3
>
^ permalink raw reply
* [PATCH 04/22] mfd: axp20x: add ADC cells for AXP20X and AXP22X PMICs
From: Chen-Yu Tsai @ 2017-01-05 5:51 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170104115637.GC24058@dell>
On Wed, Jan 4, 2017 at 7:56 PM, Lee Jones <lee.jones@linaro.org> wrote:
> On Wed, 04 Jan 2017, Lee Jones wrote:
>
>> On Mon, 02 Jan 2017, Quentin Schulz wrote:
>>
>> > This adds the AXP20X/AXP22x ADCs driver to the mfd cells of the AXP209,
>> > AXP221 and AXP223 MFD.
>> >
>> > Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>> > ---
>> > drivers/mfd/axp20x.c | 9 +++++++++
>> > 1 file changed, 9 insertions(+)
>>
>> Applied, thanks.
>
> Whoops, wrong key combo! Should have been:
>
> For my own reference:
> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
Acked-by: Chen-Yu Tsai <wens@csie.org>
>
>> > diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c
>> > index a33db5e..31a84d81 100644
>> > --- a/drivers/mfd/axp20x.c
>> > +++ b/drivers/mfd/axp20x.c
>> > @@ -582,6 +582,9 @@ static struct mfd_cell axp20x_cells[] = {
>> > }, {
>> > .name = "axp20x-regulator",
>> > }, {
>> > + .name = "axp20x-adc",
>> > + .of_compatible = "x-powers,axp209-adc",
>> > + }, {
>> > .name = "axp20x-ac-power-supply",
>> > .of_compatible = "x-powers,axp202-ac-power-supply",
>> > .num_resources = ARRAY_SIZE(axp20x_ac_power_supply_resources),
>> > @@ -602,6 +605,9 @@ static struct mfd_cell axp221_cells[] = {
>> > }, {
>> > .name = "axp20x-regulator",
>> > }, {
>> > + .name = "axp20x-adc",
>> > + .of_compatible = "x-powers,axp221-adc"
>> > + }, {
>> > .name = "axp20x-usb-power-supply",
>> > .of_compatible = "x-powers,axp221-usb-power-supply",
>> > .num_resources = ARRAY_SIZE(axp22x_usb_power_supply_resources),
>> > @@ -615,6 +621,9 @@ static struct mfd_cell axp223_cells[] = {
>> > .num_resources = ARRAY_SIZE(axp22x_pek_resources),
>> > .resources = axp22x_pek_resources,
>> > }, {
>> > + .name = "axp20x-adc",
>> > + .of_compatible = "x-powers,axp221-adc"
>> > + }, {
>> > .name = "axp20x-regulator",
>> > }, {
>> > .name = "axp20x-usb-power-supply",
>>
>
> --
> Lee Jones
> Linaro STMicroelectronics Landing Team Lead
> Linaro.org ? Open source software for ARM SoCs
> Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply
* [PATCH 05/22] ARM: dtsi: axp209: add AXP209 ADC subnode
From: Chen-Yu Tsai @ 2017-01-05 5:51 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-6-quentin.schulz@free-electrons.com>
On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> X-Powers AXP209 PMIC has multiple ADCs, each one exposing data from the
> different power supplies connected to the PMIC.
>
> This adds the ADC subnode for AXP20X PMIC.
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
> arch/arm/boot/dts/axp209.dtsi | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/arch/arm/boot/dts/axp209.dtsi b/arch/arm/boot/dts/axp209.dtsi
> index 675bb0f..2a4e8ee 100644
> --- a/arch/arm/boot/dts/axp209.dtsi
> +++ b/arch/arm/boot/dts/axp209.dtsi
> @@ -53,6 +53,11 @@
> interrupt-controller;
> #interrupt-cells = <1>;
>
> + axp209_adc: axp209_adc {
Node name should be generic. Please change it to "adc".
ChenYu
> + compatible = "x-powers,axp209-adc";
> + #io-channel-cells = <1>;
> + };
> +
> axp_gpio: gpio {
> compatible = "x-powers,axp209-gpio";
> gpio-controller;
> --
> 2.9.3
>
^ permalink raw reply
* [PATCH 06/22] ARM: dtsi: axp22x: add AXP22X ADC subnode
From: Chen-Yu Tsai @ 2017-01-05 5:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-7-quentin.schulz@free-electrons.com>
On Tue, Jan 3, 2017 at 12:37 AM, Quentin Schulz
<quentin.schulz@free-electrons.com> wrote:
> X-Powers AXP22X PMIC has multiple ADCs, each one exposing data from the
> different power supplies connected to the PMIC.
>
> This adds the ADC subnode for AXP22X PMIC.
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
> arch/arm/boot/dts/axp22x.dtsi | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/arch/arm/boot/dts/axp22x.dtsi b/arch/arm/boot/dts/axp22x.dtsi
> index 458b668..a2c4401 100644
> --- a/arch/arm/boot/dts/axp22x.dtsi
> +++ b/arch/arm/boot/dts/axp22x.dtsi
> @@ -52,6 +52,11 @@
> interrupt-controller;
> #interrupt-cells = <1>;
>
> + axp221_adc: axp221_adc {
Same as the last patch. Please change to "adc".
ChenYu
> + compatible = "x-powers,axp221-adc";
> + #io-channel-cells = <1>;
> + };
> +
> regulators {
> /* Default work frequency for buck regulators */
> x-powers,dcdc-freq = <3000>;
> --
> 2.9.3
>
^ permalink raw reply
* [PATCH v11 0/8] power: add power sequence library
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
Hi all,
This is a follow-up for my last power sequence framework patch set [1].
According to Rob Herring and Ulf Hansson's comments[2]. The kinds of
power sequence instances will be added at postcore_initcall, the match
criteria is compatible string first, if the compatible string is not
matched between dts and library, it will try to use generic power sequence.
The host driver just needs to call of_pwrseq_on/of_pwrseq_off
if only one power sequence instance is needed, for more power sequences
are used, using of_pwrseq_on_list/of_pwrseq_off_list instead (eg, USB hub driver).
In future, if there are special power sequence requirements, the special
power sequence library can be created.
This patch set is tested on i.mx6 sabresx evk using a dts change, I use
two hot-plug devices to simulate this use case, the related binding
change is updated at patch [1/6], The udoo board changes were tested
using my last power sequence patch set.[3]
Except for hard-wired MMC and USB devices, I find the USB ULPI PHY also
need to power on itself before it can be found by ULPI bus.
[1] http://www.spinics.net/lists/linux-usb/msg142755.html
[2] http://www.spinics.net/lists/linux-usb/msg143106.html
[3] http://www.spinics.net/lists/linux-usb/msg142815.html
Changes for v11:
- Fix warning: (USB) selects POWER_SEQUENCE which has unmet direct dependencies (OF)
- Delete redundant copyright statement.
- Change pr_warn to pr_debug at wrseq_find_available_instance
- Refine kerneldoc
- %s/ENONET/ENOENT
- Allocate pwrseq list node before than carry out power sequence on
- Add mutex_lock/mutex_lock for pwrseq node browse at pwrseq_find_available_instance
- Add pwrseq_suspend/resume for API both single instance and list
- Add .pwrseq_suspend/resume for pwrseq_generic.c
- Add pwrseq_suspend_list and pwrseq_resume_list for USB hub suspend
and resume routine
Changes for v10:
- Improve the kernel-doc for power sequence core, including exported APIs and
main structure. [Patch 2/8]
- Change Kconfig, and let the user choose power sequence. [Patch 2/8]
- Delete EXPORT_SYMBOL and change related APIs as local, these APIs do not
be intended to export currently. [Patch 2/8]
- Selete POWER_SEQUENCE at USB core's Kconfig. [Patch 4/8]
Changes for v9:
- Add Vaibhav Hiremath's reviewed-by [Patch 4/8]
- Rebase to v4.9-rc1
Changes for v8:
- Allocate one extra pwrseq instance if pwrseq_get has succeed, it can avoid
preallocate instances problem which the number of instance is decided at
compile time, thanks for Heiko Stuebner's suggestion [Patch 2/8]
- Delete pwrseq_compatible_sample.c which is the demo purpose to show compatible
match method. [Patch 2/8]
- Add Maciej S. Szmigiero's tested-by. [Patch 7/8]
Changes for v7:
- Create kinds of power sequence instance at postcore_initcall, and match
the instance with node using compatible string, the beneit of this is
the host driver doesn't need to consider which pwrseq instance needs
to be used, and pwrseq core will match it, however, it eats some memories
if less power sequence instances are used. [Patch 2/8]
- Add pwrseq_compatible_sample.c to test match pwrseq using device_id. [Patch 2/8]
- Fix the comments Vaibhav Hiremath adds for error path for clock and do not
use device_node for parameters at pwrseq_on. [Patch 2/8]
- Simplify the caller to use power sequence, follows Alan's commnets [Patch 4/8]
- Tested three pwrseq instances together using both specific compatible string and
generic libraries.
Changes for v6:
- Add Matthias Kaehlcke's Reviewed-by and Tested-by. (patch [2/6])
- Change chipidea core of_node assignment for coming user. (patch [5/6])
- Applies Joshua Clayton's three dts changes for two boards,
the USB device's reg has only #address-cells, but without #size-cells.
Changes for v5:
- Delete pwrseq_register/pwrseq_unregister, which is useless currently
- Fix the linker error when the pwrseq user is compiled as module
Changes for v4:
- Create the patch on next-20160722
- Fix the of_node is not NULL after chipidea driver is unbinded [Patch 5/6]
- Using more friendly wait method for reset gpio [Patch 2/6]
- Support multiple input clocks [Patch 2/6]
- Add Rob Herring's ack for DT changes
- Add Joshua Clayton's Tested-by
Changes for v3:
- Delete "power-sequence" property at binding-doc, and change related code
at both library and user code.
- Change binding-doc example node name with Rob's comments
- of_get_named_gpio_flags only gets the gpio, but without setting gpio flags,
add additional code request gpio with proper gpio flags
- Add Philipp Zabel's Ack and MAINTAINER's entry
Changes for v2:
- Delete "pwrseq" prefix and clock-names for properties at dt binding
- Should use structure not but its pointer for kzalloc
- Since chipidea core has no of_node, let core's of_node equals glue
layer's at core's probe
Joshua Clayton (2):
ARM: dts: imx6qdl: Enable usb node children with <reg>
ARM: dts: imx6q-evi: Fix onboard hub reset line
Peter Chen (6):
binding-doc: power: pwrseq-generic: add binding doc for generic power
sequence library
power: add power sequence library
binding-doc: usb: usb-device: add optional properties for power
sequence
usb: core: add power sequence handling for USB devices
usb: chipidea: let chipidea core device of_node equal's glue layer
device of_node
ARM: dts: imx6qdl-udoo.dtsi: fix onboard USB HUB property
.../bindings/power/pwrseq/pwrseq-generic.txt | 48 +++
.../devicetree/bindings/usb/usb-device.txt | 10 +-
MAINTAINERS | 9 +
arch/arm/boot/dts/imx6q-evi.dts | 25 +-
arch/arm/boot/dts/imx6qdl-udoo.dtsi | 26 +-
arch/arm/boot/dts/imx6qdl.dtsi | 6 +
drivers/power/Kconfig | 1 +
drivers/power/Makefile | 1 +
drivers/power/pwrseq/Kconfig | 20 ++
drivers/power/pwrseq/Makefile | 2 +
drivers/power/pwrseq/core.c | 335 +++++++++++++++++++++
drivers/power/pwrseq/pwrseq_generic.c | 224 ++++++++++++++
drivers/usb/Kconfig | 1 +
drivers/usb/chipidea/core.c | 27 +-
drivers/usb/core/hub.c | 48 ++-
drivers/usb/core/hub.h | 1 +
include/linux/power/pwrseq.h | 81 +++++
17 files changed, 823 insertions(+), 42 deletions(-)
create mode 100644 Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
create mode 100644 drivers/power/pwrseq/Kconfig
create mode 100644 drivers/power/pwrseq/Makefile
create mode 100644 drivers/power/pwrseq/core.c
create mode 100644 drivers/power/pwrseq/pwrseq_generic.c
create mode 100644 include/linux/power/pwrseq.h
--
2.7.4
^ permalink raw reply
* [PATCH v11 1/8] binding-doc: power: pwrseq-generic: add binding doc for generic power sequence library
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
Add binding doc for generic power sequence library.
Signed-off-by: Peter Chen <peter.chen@nxp.com>
Acked-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Rob Herring <robh@kernel.org>
---
.../bindings/power/pwrseq/pwrseq-generic.txt | 48 ++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
diff --git a/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
new file mode 100644
index 0000000..ebf0d47
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
@@ -0,0 +1,48 @@
+The generic power sequence library
+
+Some hard-wired devices (eg USB/MMC) need to do power sequence before
+the device can be enumerated on the bus, the typical power sequence
+like: enable USB PHY clock, toggle reset pin, etc. But current
+Linux device driver lacks of such code to do it, it may cause some
+hard-wired devices works abnormal or can't be recognized by
+controller at all. The power sequence will be done before this device
+can be found at the bus.
+
+The power sequence properties is under the device node.
+
+Optional properties:
+- clocks: the input clocks for device.
+- reset-gpios: Should specify the GPIO for reset.
+- reset-duration-us: the duration in microsecond for assert reset signal.
+
+Below is the example of USB power sequence properties on USB device
+nodes which have two level USB hubs.
+
+&usbotg1 {
+ vbus-supply = <®_usb_otg1_vbus>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_usb_otg1_id>;
+ status = "okay";
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ genesys: hub at 1 {
+ compatible = "usb5e3,608";
+ reg = <1>;
+
+ clocks = <&clks IMX6SX_CLK_CKO>;
+ reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* hub reset pin */
+ reset-duration-us = <10>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+ asix: ethernet at 1 {
+ compatible = "usbb95,1708";
+ reg = <1>;
+
+ clocks = <&clks IMX6SX_CLK_IPG>;
+ reset-gpios = <&gpio4 6 GPIO_ACTIVE_LOW>; /* ethernet_rst */
+ reset-duration-us = <15>;
+ };
+ };
+};
--
2.7.4
^ permalink raw reply related
* [PATCH v11 2/8] power: add power sequence library
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
We have an well-known problem that the device needs to do some power
sequence before it can be recognized by related host, the typical
example like hard-wired mmc devices and usb devices.
This power sequence is hard to be described at device tree and handled by
related host driver, so we have created a common power sequence
library to cover this requirement. The core code has supplied
some common helpers for host driver, and individual power sequence
libraries handle kinds of power sequence for devices. The pwrseq
librares always need to allocate extra instance for compatible
string match.
pwrseq_generic is intended for general purpose of power sequence, which
handles gpios and clocks currently, and can cover other controls in
future. The host driver just needs to call of_pwrseq_on/of_pwrseq_off
if only one power sequence is needed, else call of_pwrseq_on_list
/of_pwrseq_off_list instead (eg, USB hub driver).
For new power sequence library, it can add its compatible string
to pwrseq_of_match_table, then the pwrseq core will match it with
DT's, and choose this library at runtime.
Signed-off-by: Peter Chen <peter.chen@nxp.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
Tested-by: Matthias Kaehlcke <mka@chromium.org>
---
MAINTAINERS | 9 +
drivers/power/Kconfig | 1 +
drivers/power/Makefile | 1 +
drivers/power/pwrseq/Kconfig | 20 ++
drivers/power/pwrseq/Makefile | 2 +
drivers/power/pwrseq/core.c | 335 ++++++++++++++++++++++++++++++++++
drivers/power/pwrseq/pwrseq_generic.c | 224 +++++++++++++++++++++++
include/linux/power/pwrseq.h | 81 ++++++++
8 files changed, 673 insertions(+)
create mode 100644 drivers/power/pwrseq/Kconfig
create mode 100644 drivers/power/pwrseq/Makefile
create mode 100644 drivers/power/pwrseq/core.c
create mode 100644 drivers/power/pwrseq/pwrseq_generic.c
create mode 100644 include/linux/power/pwrseq.h
diff --git a/MAINTAINERS b/MAINTAINERS
index cfff2c9..ae2aa25 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9828,6 +9828,15 @@ F: include/linux/pm_*
F: include/linux/powercap.h
F: drivers/powercap/
+POWER SEQUENCE LIBRARY
+M: Peter Chen <Peter.Chen@nxp.com>
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git
+L: linux-pm at vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/power/pwrseq/
+F: drivers/power/pwrseq/
+F: include/linux/power/pwrseq.h
+
POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS
M: Sebastian Reichel <sre@kernel.org>
L: linux-pm at vger.kernel.org
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 63454b5..c1bb046 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -1,3 +1,4 @@
source "drivers/power/avs/Kconfig"
source "drivers/power/reset/Kconfig"
source "drivers/power/supply/Kconfig"
+source "drivers/power/pwrseq/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index ff35c71..7db8035 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_POWER_RESET) += reset/
obj-$(CONFIG_POWER_SUPPLY) += supply/
+obj-$(CONFIG_POWER_SEQUENCE) += pwrseq/
diff --git a/drivers/power/pwrseq/Kconfig b/drivers/power/pwrseq/Kconfig
new file mode 100644
index 0000000..c6b3569
--- /dev/null
+++ b/drivers/power/pwrseq/Kconfig
@@ -0,0 +1,20 @@
+#
+# Power Sequence library
+#
+
+menuconfig POWER_SEQUENCE
+ bool "Power sequence control"
+ help
+ It is used for drivers which needs to do power sequence
+ (eg, turn on clock, toggle reset gpio) before the related
+ devices can be found by hardware, eg, USB bus.
+
+if POWER_SEQUENCE
+
+config PWRSEQ_GENERIC
+ bool "Generic power sequence control"
+ depends on OF
+ help
+ This is the generic power sequence control library, and is
+ supposed to support common power sequence usage.
+endif
diff --git a/drivers/power/pwrseq/Makefile b/drivers/power/pwrseq/Makefile
new file mode 100644
index 0000000..ad82389
--- /dev/null
+++ b/drivers/power/pwrseq/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_POWER_SEQUENCE) += core.o
+obj-$(CONFIG_PWRSEQ_GENERIC) += pwrseq_generic.o
diff --git a/drivers/power/pwrseq/core.c b/drivers/power/pwrseq/core.c
new file mode 100644
index 0000000..3d19e62
--- /dev/null
+++ b/drivers/power/pwrseq/core.c
@@ -0,0 +1,335 @@
+/*
+ * core.c power sequence core file
+ *
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Author: Peter Chen <peter.chen@nxp.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.
+ */
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/power/pwrseq.h>
+
+static DEFINE_MUTEX(pwrseq_list_mutex);
+static LIST_HEAD(pwrseq_list);
+
+static int pwrseq_get(struct device_node *np, struct pwrseq *p)
+{
+ if (p && p->get)
+ return p->get(np, p);
+
+ return -ENOTSUPP;
+}
+
+static int pwrseq_on(struct pwrseq *p)
+{
+ if (p && p->on)
+ return p->on(p);
+
+ return -ENOTSUPP;
+}
+
+static void pwrseq_off(struct pwrseq *p)
+{
+ if (p && p->off)
+ p->off(p);
+}
+
+static void pwrseq_put(struct pwrseq *p)
+{
+ if (p && p->put)
+ p->put(p);
+}
+
+/**
+ * pwrseq_register - Add pwrseq instance to global pwrseq list
+ *
+ * @pwrseq: the pwrseq instance
+ */
+void pwrseq_register(struct pwrseq *pwrseq)
+{
+ mutex_lock(&pwrseq_list_mutex);
+ list_add(&pwrseq->node, &pwrseq_list);
+ mutex_unlock(&pwrseq_list_mutex);
+}
+EXPORT_SYMBOL_GPL(pwrseq_register);
+
+/**
+ * pwrseq_unregister - Remove pwrseq instance from global pwrseq list
+ *
+ * @pwrseq: the pwrseq instance
+ */
+void pwrseq_unregister(struct pwrseq *pwrseq)
+{
+ mutex_lock(&pwrseq_list_mutex);
+ list_del(&pwrseq->node);
+ mutex_unlock(&pwrseq_list_mutex);
+}
+EXPORT_SYMBOL_GPL(pwrseq_unregister);
+
+static struct pwrseq *pwrseq_find_available_instance(struct device_node *np)
+{
+ struct pwrseq *pwrseq;
+
+ mutex_lock(&pwrseq_list_mutex);
+ list_for_each_entry(pwrseq, &pwrseq_list, node) {
+ if (pwrseq->used)
+ continue;
+
+ /* compare compatible string for pwrseq node */
+ if (of_match_node(pwrseq->pwrseq_of_match_table, np)) {
+ pwrseq->used = true;
+ mutex_unlock(&pwrseq_list_mutex);
+ return pwrseq;
+ }
+
+ /* return generic pwrseq instance */
+ if (!strcmp(pwrseq->pwrseq_of_match_table->compatible,
+ "generic")) {
+ pr_debug("using generic pwrseq instance for %s\n",
+ np->full_name);
+ pwrseq->used = true;
+ mutex_unlock(&pwrseq_list_mutex);
+ return pwrseq;
+ }
+ }
+ mutex_unlock(&pwrseq_list_mutex);
+ pr_debug("Can't find any pwrseq instances for %s\n", np->full_name);
+
+ return NULL;
+}
+
+/**
+ * of_pwrseq_on - Carry out power sequence on for device node
+ *
+ * @np: the device node would like to power on
+ *
+ * Carry out a single device power on. If multiple devices
+ * need to be handled, use of_pwrseq_on_list() instead.
+ *
+ * Return a pointer to the power sequence instance on success,
+ * or an error code otherwise.
+ */
+struct pwrseq *of_pwrseq_on(struct device_node *np)
+{
+ struct pwrseq *pwrseq;
+ int ret;
+
+ pwrseq = pwrseq_find_available_instance(np);
+ if (!pwrseq)
+ return ERR_PTR(-ENOENT);
+
+ ret = pwrseq_get(np, pwrseq);
+ if (ret) {
+ /* Mark current pwrseq as unused */
+ pwrseq->used = false;
+ return ERR_PTR(ret);
+ }
+
+ ret = pwrseq_on(pwrseq);
+ if (ret)
+ goto pwr_put;
+
+ return pwrseq;
+
+pwr_put:
+ pwrseq_put(pwrseq);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_on);
+
+/**
+ * of_pwrseq_off - Carry out power sequence off for this pwrseq instance
+ *
+ * @pwrseq: the pwrseq instance which related device would like to be off
+ *
+ * This API is used to power off single device, it is the opposite
+ * operation for of_pwrseq_on.
+ */
+void of_pwrseq_off(struct pwrseq *pwrseq)
+{
+ pwrseq_off(pwrseq);
+ pwrseq_put(pwrseq);
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_off);
+
+/**
+ * of_pwrseq_on_list - Carry out power sequence on for list
+ *
+ * @np: the device node would like to power on
+ * @head: the list head for pwrseq list on this bus
+ *
+ * This API is used to power on multiple devices at single bus.
+ * If there are several devices on bus (eg, USB bus), uses this
+ * this API. Otherwise, use of_pwrseq_on instead. After the device
+ * is powered on successfully, it will be added to pwrseq list for
+ * this bus. The caller needs to use mutex_lock for concurrent.
+ *
+ * Return 0 on success, or an error value otherwise.
+ */
+int of_pwrseq_on_list(struct device_node *np, struct list_head *head)
+{
+ struct pwrseq *pwrseq;
+ struct pwrseq_list_per_dev *pwrseq_list_node;
+
+ pwrseq_list_node = kzalloc(sizeof(*pwrseq_list_node), GFP_KERNEL);
+ if (!pwrseq_list_node)
+ return -ENOMEM;
+
+ pwrseq = of_pwrseq_on(np);
+ if (IS_ERR(pwrseq)) {
+ kfree(pwrseq_list_node);
+ return PTR_ERR(pwrseq);
+ }
+
+ pwrseq_list_node->pwrseq = pwrseq;
+ list_add(&pwrseq_list_node->list, head);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_on_list);
+
+/**
+ * of_pwrseq_off_list - Carry out power sequence off for the list
+ *
+ * @head: the list head for pwrseq instance list on this bus
+ *
+ * This API is used to power off all devices on this bus, it is
+ * the opposite operation for of_pwrseq_on_list.
+ * The caller needs to use mutex_lock for concurrent.
+ */
+void of_pwrseq_off_list(struct list_head *head)
+{
+ struct pwrseq *pwrseq;
+ struct pwrseq_list_per_dev *pwrseq_list_node, *tmp_node;
+
+ list_for_each_entry_safe(pwrseq_list_node, tmp_node, head, list) {
+ pwrseq = pwrseq_list_node->pwrseq;
+ of_pwrseq_off(pwrseq);
+ list_del(&pwrseq_list_node->list);
+ kfree(pwrseq_list_node);
+ }
+}
+EXPORT_SYMBOL_GPL(of_pwrseq_off_list);
+
+/**
+ * pwrseq_suspend - Carry out power sequence suspend for this pwrseq instance
+ *
+ * @pwrseq: the pwrseq instance
+ *
+ * This API is used to do suspend operation on pwrseq instance.
+ *
+ * Return 0 on success, or an error value otherwise.
+ */
+int pwrseq_suspend(struct pwrseq *p)
+{
+ int ret = 0;
+
+ if (p && p->suspend)
+ ret = p->suspend(p);
+ else
+ return ret;
+
+ if (!ret)
+ p->suspended = true;
+ else
+ pr_err("%s failed\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_suspend);
+
+/**
+ * pwrseq_resume - Carry out power sequence resume for this pwrseq instance
+ *
+ * @pwrseq: the pwrseq instance
+ *
+ * This API is used to do resume operation on pwrseq instance.
+ *
+ * Return 0 on success, or an error value otherwise.
+ */
+int pwrseq_resume(struct pwrseq *p)
+{
+ int ret = 0;
+
+ if (p && p->resume)
+ ret = p->resume(p);
+ else
+ return ret;
+
+ if (!ret)
+ p->suspended = false;
+ else
+ pr_err("%s failed\n", __func__);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_resume);
+
+/**
+ * pwrseq_suspend_list - Carry out power sequence suspend for list
+ *
+ * @head: the list head for pwrseq instance list on this bus
+ *
+ * This API is used to do suspend on all power sequence instances on this bus.
+ * The caller needs to use mutex_lock for concurrent.
+ */
+int pwrseq_suspend_list(struct list_head *head)
+{
+ struct pwrseq *pwrseq;
+ struct pwrseq_list_per_dev *pwrseq_list_node;
+ int ret = 0;
+
+ list_for_each_entry(pwrseq_list_node, head, list) {
+ ret = pwrseq_suspend(pwrseq_list_node->pwrseq);
+ if (ret)
+ break;
+ }
+
+ if (ret) {
+ list_for_each_entry(pwrseq_list_node, head, list) {
+ pwrseq = pwrseq_list_node->pwrseq;
+ if (pwrseq->suspended)
+ pwrseq_resume(pwrseq);
+ }
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_suspend_list);
+
+/**
+ * pwrseq_resume_list - Carry out power sequence resume for the list
+ *
+ * @head: the list head for pwrseq instance list on this bus
+ *
+ * This API is used to do resume on all power sequence instances on this bus.
+ * The caller needs to use mutex_lock for concurrent.
+ */
+int pwrseq_resume_list(struct list_head *head)
+{
+ struct pwrseq_list_per_dev *pwrseq_list_node;
+ int ret = 0;
+
+ list_for_each_entry(pwrseq_list_node, head, list) {
+ ret = pwrseq_resume(pwrseq_list_node->pwrseq);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwrseq_resume_list);
diff --git a/drivers/power/pwrseq/pwrseq_generic.c b/drivers/power/pwrseq/pwrseq_generic.c
new file mode 100644
index 0000000..0e70a38
--- /dev/null
+++ b/drivers/power/pwrseq/pwrseq_generic.c
@@ -0,0 +1,224 @@
+/*
+ * pwrseq_generic.c Generic power sequence handling
+ *
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Author: Peter Chen <peter.chen@nxp.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+
+#include <linux/power/pwrseq.h>
+
+struct pwrseq_generic {
+ struct pwrseq pwrseq;
+ struct gpio_desc *gpiod_reset;
+ struct clk *clks[PWRSEQ_MAX_CLKS];
+ u32 duration_us;
+ bool suspended;
+};
+
+#define to_generic_pwrseq(p) container_of(p, struct pwrseq_generic, pwrseq)
+
+static int pwrseq_generic_alloc_instance(void);
+static const struct of_device_id generic_id_table[] = {
+ { .compatible = "generic",},
+ { /* sentinel */ }
+};
+
+static int pwrseq_generic_suspend(struct pwrseq *pwrseq)
+{
+ struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+ int clk;
+
+ for (clk = PWRSEQ_MAX_CLKS - 1; clk >= 0; clk--)
+ clk_disable_unprepare(pwrseq_gen->clks[clk]);
+
+ pwrseq_gen->suspended = true;
+ return 0;
+}
+
+static int pwrseq_generic_resume(struct pwrseq *pwrseq)
+{
+ struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+ int clk, ret = 0;
+
+ for (clk = 0; clk < PWRSEQ_MAX_CLKS && pwrseq_gen->clks[clk]; clk++) {
+ ret = clk_prepare_enable(pwrseq_gen->clks[clk]);
+ if (ret) {
+ pr_err("Can't enable clock, ret=%d\n", ret);
+ goto err_disable_clks;
+ }
+ }
+
+ pwrseq_gen->suspended = false;
+ return ret;
+
+err_disable_clks:
+ while (--clk >= 0)
+ clk_disable_unprepare(pwrseq_gen->clks[clk]);
+
+ return ret;
+}
+
+static void pwrseq_generic_put(struct pwrseq *pwrseq)
+{
+ struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+ int clk;
+
+ if (pwrseq_gen->gpiod_reset)
+ gpiod_put(pwrseq_gen->gpiod_reset);
+
+ for (clk = 0; clk < PWRSEQ_MAX_CLKS; clk++)
+ clk_put(pwrseq_gen->clks[clk]);
+
+ pwrseq_unregister(&pwrseq_gen->pwrseq);
+ kfree(pwrseq_gen);
+}
+
+static void pwrseq_generic_off(struct pwrseq *pwrseq)
+{
+ struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+ int clk;
+
+ if (pwrseq_gen->suspended)
+ return;
+
+ for (clk = PWRSEQ_MAX_CLKS - 1; clk >= 0; clk--)
+ clk_disable_unprepare(pwrseq_gen->clks[clk]);
+}
+
+static int pwrseq_generic_on(struct pwrseq *pwrseq)
+{
+ struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+ int clk, ret = 0;
+ struct gpio_desc *gpiod_reset = pwrseq_gen->gpiod_reset;
+
+ for (clk = 0; clk < PWRSEQ_MAX_CLKS && pwrseq_gen->clks[clk]; clk++) {
+ ret = clk_prepare_enable(pwrseq_gen->clks[clk]);
+ if (ret) {
+ pr_err("Can't enable clock, ret=%d\n", ret);
+ goto err_disable_clks;
+ }
+ }
+
+ if (gpiod_reset) {
+ u32 duration_us = pwrseq_gen->duration_us;
+
+ if (duration_us <= 10)
+ udelay(10);
+ else
+ usleep_range(duration_us, duration_us + 100);
+ gpiod_set_value(gpiod_reset, 0);
+ }
+
+ return ret;
+
+err_disable_clks:
+ while (--clk >= 0)
+ clk_disable_unprepare(pwrseq_gen->clks[clk]);
+
+ return ret;
+}
+
+static int pwrseq_generic_get(struct device_node *np, struct pwrseq *pwrseq)
+{
+ struct pwrseq_generic *pwrseq_gen = to_generic_pwrseq(pwrseq);
+ enum of_gpio_flags flags;
+ int reset_gpio, clk, ret = 0;
+
+ for (clk = 0; clk < PWRSEQ_MAX_CLKS; clk++) {
+ pwrseq_gen->clks[clk] = of_clk_get(np, clk);
+ if (IS_ERR(pwrseq_gen->clks[clk])) {
+ ret = PTR_ERR(pwrseq_gen->clks[clk]);
+ if (ret != -ENOENT)
+ goto err_put_clks;
+ pwrseq_gen->clks[clk] = NULL;
+ break;
+ }
+ }
+
+ reset_gpio = of_get_named_gpio_flags(np, "reset-gpios", 0, &flags);
+ if (gpio_is_valid(reset_gpio)) {
+ unsigned long gpio_flags;
+
+ if (flags & OF_GPIO_ACTIVE_LOW)
+ gpio_flags = GPIOF_ACTIVE_LOW | GPIOF_OUT_INIT_LOW;
+ else
+ gpio_flags = GPIOF_OUT_INIT_HIGH;
+
+ ret = gpio_request_one(reset_gpio, gpio_flags,
+ "pwrseq-reset-gpios");
+ if (ret)
+ goto err_put_clks;
+
+ pwrseq_gen->gpiod_reset = gpio_to_desc(reset_gpio);
+ of_property_read_u32(np, "reset-duration-us",
+ &pwrseq_gen->duration_us);
+ } else if (reset_gpio == -ENOENT) {
+ ; /* no such gpio */
+ } else {
+ ret = reset_gpio;
+ pr_err("Failed to get reset gpio on %s, err = %d\n",
+ np->full_name, reset_gpio);
+ goto err_put_clks;
+ }
+
+ /* allocate new one for later pwrseq instance request */
+ ret = pwrseq_generic_alloc_instance();
+ if (ret)
+ goto err_put_gpio;
+
+ return 0;
+
+err_put_gpio:
+ if (pwrseq_gen->gpiod_reset)
+ gpiod_put(pwrseq_gen->gpiod_reset);
+err_put_clks:
+ while (--clk >= 0)
+ clk_put(pwrseq_gen->clks[clk]);
+ return ret;
+}
+
+static int pwrseq_generic_alloc_instance(void)
+{
+ struct pwrseq_generic *pwrseq_gen;
+
+ pwrseq_gen = kzalloc(sizeof(*pwrseq_gen), GFP_KERNEL);
+ if (!pwrseq_gen)
+ return -ENOMEM;
+
+ pwrseq_gen->pwrseq.pwrseq_of_match_table = generic_id_table;
+ pwrseq_gen->pwrseq.get = pwrseq_generic_get;
+ pwrseq_gen->pwrseq.on = pwrseq_generic_on;
+ pwrseq_gen->pwrseq.off = pwrseq_generic_off;
+ pwrseq_gen->pwrseq.put = pwrseq_generic_put;
+ pwrseq_gen->pwrseq.suspend = pwrseq_generic_suspend;
+ pwrseq_gen->pwrseq.resume = pwrseq_generic_resume;
+
+ pwrseq_register(&pwrseq_gen->pwrseq);
+ return 0;
+}
+
+static int __init pwrseq_generic_register(void)
+{
+ return pwrseq_generic_alloc_instance();
+}
+postcore_initcall(pwrseq_generic_register)
diff --git a/include/linux/power/pwrseq.h b/include/linux/power/pwrseq.h
new file mode 100644
index 0000000..cbc344c
--- /dev/null
+++ b/include/linux/power/pwrseq.h
@@ -0,0 +1,81 @@
+#ifndef __LINUX_PWRSEQ_H
+#define __LINUX_PWRSEQ_H
+
+#include <linux/of.h>
+
+#define PWRSEQ_MAX_CLKS 3
+
+/**
+ * struct pwrseq - the power sequence structure
+ * @pwrseq_of_match_table: the OF device id table this pwrseq library supports
+ * @node: the list pointer to be added to pwrseq list
+ * @get: the API is used to get pwrseq instance from the device node
+ * @on: do power on for this pwrseq instance
+ * @off: do power off for this pwrseq instance
+ * @put: release the resources on this pwrseq instance
+ * @suspend: do suspend operation on this pwrseq instance
+ * @resume: do resume operation on this pwrseq instance
+ * @used: this pwrseq instance is used by device
+ */
+struct pwrseq {
+ const struct of_device_id *pwrseq_of_match_table;
+ struct list_head node;
+ int (*get)(struct device_node *np, struct pwrseq *p);
+ int (*on)(struct pwrseq *p);
+ void (*off)(struct pwrseq *p);
+ void (*put)(struct pwrseq *p);
+ int (*suspend)(struct pwrseq *p);
+ int (*resume)(struct pwrseq *p);
+ bool used;
+ bool suspended;
+};
+
+/* used for power sequence instance list in one driver */
+struct pwrseq_list_per_dev {
+ struct pwrseq *pwrseq;
+ struct list_head list;
+};
+
+#if IS_ENABLED(CONFIG_POWER_SEQUENCE)
+void pwrseq_register(struct pwrseq *pwrseq);
+void pwrseq_unregister(struct pwrseq *pwrseq);
+struct pwrseq *of_pwrseq_on(struct device_node *np);
+void of_pwrseq_off(struct pwrseq *pwrseq);
+int of_pwrseq_on_list(struct device_node *np, struct list_head *head);
+void of_pwrseq_off_list(struct list_head *head);
+int pwrseq_suspend(struct pwrseq *p);
+int pwrseq_resume(struct pwrseq *p);
+int pwrseq_suspend_list(struct list_head *head);
+int pwrseq_resume_list(struct list_head *head);
+#else
+static inline void pwrseq_register(struct pwrseq *pwrseq) {}
+static inline void pwrseq_unregister(struct pwrseq *pwrseq) {}
+static inline struct pwrseq *of_pwrseq_on(struct device_node *np)
+{
+ return NULL;
+}
+static void of_pwrseq_off(struct pwrseq *pwrseq) {}
+static int of_pwrseq_on_list(struct device_node *np, struct list_head *head)
+{
+ return 0;
+}
+static void of_pwrseq_off_list(struct list_head *head) {}
+static int pwrseq_suspend(struct pwrseq *p)
+{
+ return 0;
+}
+static int pwrseq_resume(struct pwrseq *p)
+{
+ return 0;
+}
+static int pwrseq_suspend_list(struct list_head *head)
+{
+ return 0;
+}
+static int pwrseq_resume_list(struct list_head *head)
+{
+ return 0;
+}
+#endif /* CONFIG_POWER_SEQUENCE */
+
+#endif /* __LINUX_PWRSEQ_H */
--
2.7.4
^ permalink raw reply related
* [PATCH v11 3/8] binding-doc: usb: usb-device: add optional properties for power sequence
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
Add optional properties for power sequence.
Signed-off-by: Peter Chen <peter.chen@nxp.com>
Acked-by: Rob Herring <robh@kernel.org>
---
Documentation/devicetree/bindings/usb/usb-device.txt | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/usb/usb-device.txt b/Documentation/devicetree/bindings/usb/usb-device.txt
index 1c35e7b..3661dd2 100644
--- a/Documentation/devicetree/bindings/usb/usb-device.txt
+++ b/Documentation/devicetree/bindings/usb/usb-device.txt
@@ -13,6 +13,10 @@ Required properties:
- reg: the port number which this device is connecting to, the range
is 1-31.
+Optional properties:
+power sequence properties, see
+Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt for detail
+
Example:
&usb1 {
@@ -21,8 +25,12 @@ Example:
#address-cells = <1>;
#size-cells = <0>;
- hub: genesys at 1 {
+ genesys: hub at 1 {
compatible = "usb5e3,608";
reg = <1>;
+
+ clocks = <&clks IMX6SX_CLK_CKO>;
+ reset-gpios = <&gpio4 5 GPIO_ACTIVE_LOW>; /* hub reset pin */
+ reset-duration-us = <10>;
};
}
--
2.7.4
^ permalink raw reply related
* [PATCH v11 4/8] usb: core: add power sequence handling for USB devices
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
Some hard-wired USB devices need to do power sequence to let the
device work normally, the typical power sequence like: enable USB
PHY clock, toggle reset pin, etc. But current Linux USB driver
lacks of such code to do it, it may cause some hard-wired USB devices
works abnormal or can't be recognized by controller at all.
In this patch, it calls power sequence library APIs to finish
the power sequence events. It will do power on sequence at hub's
probe for all devices under this hub (includes root hub).
At hub_disconnect, it will do power off sequence which is at powered
on list.
Signed-off-by: Peter Chen <peter.chen@nxp.com>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Reviewed-by: Vaibhav Hiremath <hvaibhav.linux@gmail.com>
---
drivers/usb/Kconfig | 1 +
drivers/usb/core/hub.c | 48 ++++++++++++++++++++++++++++++++++++++++++++----
drivers/usb/core/hub.h | 1 +
3 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index fbe493d..706f261 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -40,6 +40,7 @@ config USB
tristate "Support for Host-side USB"
depends on USB_ARCH_HAS_HCD
select USB_COMMON
+ select POWER_SEQUENCE
select NLS # for UTF-8 strings
---help---
Universal Serial Bus (USB) is a specification for a serial bus
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 1fa5c0f..cec2fad 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -28,6 +28,7 @@
#include <linux/mutex.h>
#include <linux/random.h>
#include <linux/pm_qos.h>
+#include <linux/power/pwrseq.h>
#include <linux/uaccess.h>
#include <asm/byteorder.h>
@@ -1645,6 +1646,7 @@ static void hub_disconnect(struct usb_interface *intf)
hub->error = 0;
hub_quiesce(hub, HUB_DISCONNECT);
+ of_pwrseq_off_list(&hub->pwrseq_on_list);
mutex_lock(&usb_port_peer_mutex);
/* Avoid races with recursively_mark_NOTATTACHED() */
@@ -1672,12 +1674,41 @@ static void hub_disconnect(struct usb_interface *intf)
kref_put(&hub->kref, hub_release);
}
+#ifdef CONFIG_OF
+static int hub_of_pwrseq_on(struct usb_hub *hub)
+{
+ struct device *parent;
+ struct usb_device *hdev = hub->hdev;
+ struct device_node *np;
+ int ret;
+
+ if (hdev->parent)
+ parent = &hdev->dev;
+ else
+ parent = bus_to_hcd(hdev->bus)->self.controller;
+
+ for_each_child_of_node(parent->of_node, np) {
+ ret = of_pwrseq_on_list(np, &hub->pwrseq_on_list);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+#else
+static int hub_of_pwrseq_on(struct usb_hub *hub)
+{
+ return 0;
+}
+#endif
+
static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_host_interface *desc;
struct usb_endpoint_descriptor *endpoint;
struct usb_device *hdev;
struct usb_hub *hub;
+ int ret = -ENODEV;
desc = intf->cur_altsetting;
hdev = interface_to_usbdev(intf);
@@ -1782,6 +1813,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
INIT_DELAYED_WORK(&hub->leds, led_work);
INIT_DELAYED_WORK(&hub->init_work, NULL);
INIT_WORK(&hub->events, hub_event);
+ INIT_LIST_HEAD(&hub->pwrseq_on_list);
usb_get_intf(intf);
usb_get_dev(hdev);
@@ -1795,11 +1827,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
hub->quirk_check_port_auto_suspend = 1;
- if (hub_configure(hub, endpoint) >= 0)
- return 0;
+ if (hub_configure(hub, endpoint) >= 0) {
+ ret = hub_of_pwrseq_on(hub);
+ if (!ret)
+ return 0;
+ }
hub_disconnect(intf);
- return -ENODEV;
+ return ret;
}
static int
@@ -3613,14 +3648,19 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
/* stop hub_wq and related activity */
hub_quiesce(hub, HUB_SUSPEND);
- return 0;
+ return pwrseq_suspend_list(&hub->pwrseq_on_list);
}
static int hub_resume(struct usb_interface *intf)
{
struct usb_hub *hub = usb_get_intfdata(intf);
+ int ret;
dev_dbg(&intf->dev, "%s\n", __func__);
+ ret = pwrseq_resume_list(&hub->pwrseq_on_list);
+ if (ret)
+ return ret;
+
hub_activate(hub, HUB_RESUME);
return 0;
}
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index 34c1a7e..cd86f91 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -78,6 +78,7 @@ struct usb_hub {
struct delayed_work init_work;
struct work_struct events;
struct usb_port **ports;
+ struct list_head pwrseq_on_list; /* powered pwrseq node list */
};
/**
--
2.7.4
^ permalink raw reply related
* [PATCH v11 5/8] usb: chipidea: let chipidea core device of_node equal's glue layer device of_node
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
From: Peter Chen <peter.chen@freescale.com>
At device tree, we have no device node for chipidea core,
the glue layer's node is the parent node for host and udc
device. But in related driver, the parent device is chipidea
core. So, in order to let the common driver get parent's node,
we let the core's device node equals glue layer device node.
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
---
drivers/usb/chipidea/core.c | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 3dbb4a2..fdffc67 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -928,6 +928,16 @@ static int ci_hdrc_probe(struct platform_device *pdev)
return -ENODEV;
}
+ /*
+ * At device tree, we have no device node for chipidea core,
+ * the glue layer's node is the parent node for host and udc
+ * device. But in related driver, the parent device is chipidea
+ * core. So, in order to let the common driver get parent's node,
+ * we let the core's device node equals glue layer's node.
+ */
+ if (dev->parent && dev->parent->of_node)
+ dev->of_node = dev->parent->of_node;
+
if (ci->platdata->phy) {
ci->phy = ci->platdata->phy;
} else if (ci->platdata->usb_phy) {
@@ -938,11 +948,15 @@ static int ci_hdrc_probe(struct platform_device *pdev)
/* if both generic PHY and USB PHY layers aren't enabled */
if (PTR_ERR(ci->phy) == -ENOSYS &&
- PTR_ERR(ci->usb_phy) == -ENXIO)
- return -ENXIO;
+ PTR_ERR(ci->usb_phy) == -ENXIO) {
+ ret = -ENXIO;
+ goto clear_of_node;
+ }
- if (IS_ERR(ci->phy) && IS_ERR(ci->usb_phy))
- return -EPROBE_DEFER;
+ if (IS_ERR(ci->phy) && IS_ERR(ci->usb_phy)) {
+ ret = -EPROBE_DEFER;
+ goto clear_of_node;
+ }
if (IS_ERR(ci->phy))
ci->phy = NULL;
@@ -953,7 +967,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ret = ci_usb_phy_init(ci);
if (ret) {
dev_err(dev, "unable to init phy: %d\n", ret);
- return ret;
+ goto clear_of_node;
}
ci->hw_bank.phys = res->start;
@@ -1059,6 +1073,8 @@ static int ci_hdrc_probe(struct platform_device *pdev)
ci_role_destroy(ci);
deinit_phy:
ci_usb_phy_exit(ci);
+clear_of_node:
+ dev->of_node = NULL;
return ret;
}
@@ -1077,6 +1093,7 @@ static int ci_hdrc_remove(struct platform_device *pdev)
ci_extcon_unregister(ci);
ci_role_destroy(ci);
ci_hdrc_enter_lpm(ci, true);
+ ci->dev->of_node = NULL;
ci_usb_phy_exit(ci);
return 0;
--
2.7.4
^ permalink raw reply related
* [PATCH v11 6/8] ARM: dts: imx6qdl: Enable usb node children with <reg>
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
From: Joshua Clayton <stillcompiling@gmail.com>
Give usb nodes #address and #size attributes, so that a child node
representing a permanently connected device such as an onboard hub may
be addressed with a <reg> attribute
Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Signed-off-by: Peter Chen <peter.chen@nxp.com>
---
arch/arm/boot/dts/imx6qdl.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm/boot/dts/imx6qdl.dtsi b/arch/arm/boot/dts/imx6qdl.dtsi
index 53e6e63..e6725f1 100644
--- a/arch/arm/boot/dts/imx6qdl.dtsi
+++ b/arch/arm/boot/dts/imx6qdl.dtsi
@@ -936,6 +936,8 @@
usbh1: usb at 02184200 {
compatible = "fsl,imx6q-usb", "fsl,imx27-usb";
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <0x02184200 0x200>;
interrupts = <0 40 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_USBOH3>;
@@ -950,6 +952,8 @@
usbh2: usb at 02184400 {
compatible = "fsl,imx6q-usb", "fsl,imx27-usb";
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <0x02184400 0x200>;
interrupts = <0 41 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_USBOH3>;
@@ -963,6 +967,8 @@
usbh3: usb at 02184600 {
compatible = "fsl,imx6q-usb", "fsl,imx27-usb";
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <0x02184600 0x200>;
interrupts = <0 42 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6QDL_CLK_USBOH3>;
--
2.7.4
^ permalink raw reply related
* [PATCH v11 7/8] ARM: dts: imx6qdl-udoo.dtsi: fix onboard USB HUB property
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
The current dts describes USB HUB's property at USB controller's
entry, it is improper. The USB HUB should be the child node
under USB controller, and power sequence properties are under
it. Besides, using gpio pinctrl setting for USB2415's reset pin.
Signed-off-by: Peter Chen <peter.chen@nxp.com>
Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Tested-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
---
arch/arm/boot/dts/imx6qdl-udoo.dtsi | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/arch/arm/boot/dts/imx6qdl-udoo.dtsi b/arch/arm/boot/dts/imx6qdl-udoo.dtsi
index c96c91d..a173de2 100644
--- a/arch/arm/boot/dts/imx6qdl-udoo.dtsi
+++ b/arch/arm/boot/dts/imx6qdl-udoo.dtsi
@@ -9,6 +9,8 @@
*
*/
+#include <dt-bindings/gpio/gpio.h>
+
/ {
aliases {
backlight = &backlight;
@@ -58,17 +60,6 @@
#address-cells = <1>;
#size-cells = <0>;
- reg_usb_h1_vbus: regulator at 0 {
- compatible = "regulator-fixed";
- reg = <0>;
- regulator-name = "usb_h1_vbus";
- regulator-min-microvolt = <5000000>;
- regulator-max-microvolt = <5000000>;
- enable-active-high;
- startup-delay-us = <2>; /* USB2415 requires a POR of 1 us minimum */
- gpio = <&gpio7 12 0>;
- };
-
reg_panel: regulator at 1 {
compatible = "regulator-fixed";
reg = <1>;
@@ -188,7 +179,7 @@
pinctrl_usbh: usbhgrp {
fsl,pins = <
- MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x80000000
+ MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x1b0b0
MX6QDL_PAD_NANDF_CS2__CCM_CLKO2 0x130b0
>;
};
@@ -259,9 +250,16 @@
&usbh1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usbh>;
- vbus-supply = <®_usb_h1_vbus>;
- clocks = <&clks IMX6QDL_CLK_CKO>;
status = "okay";
+
+ usb2415: hub at 1 {
+ compatible = "usb424,2514";
+ reg = <1>;
+
+ clocks = <&clks IMX6QDL_CLK_CKO>;
+ reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>;
+ reset-duration-us = <3000>;
+ };
};
&usdhc3 {
--
2.7.4
^ permalink raw reply related
* [PATCH v11 8/8] ARM: dts: imx6q-evi: Fix onboard hub reset line
From: Peter Chen @ 2017-01-05 6:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483596119-27508-1-git-send-email-peter.chen@nxp.com>
From: Joshua Clayton <stillcompiling@gmail.com>
Previously the onboard hub was made to work by treating its
reset gpio as a regulator enable.
Get rid of that kludge now that pwseq has added reset gpio support
Move pin muxing the hub reset pin into the usbh1 group
Signed-off-by: Joshua Clayton <stillcompiling@gmail.com>
Signed-off-by: Peter Chen <peter.chen@nxp.com>
---
arch/arm/boot/dts/imx6q-evi.dts | 25 +++++++------------------
1 file changed, 7 insertions(+), 18 deletions(-)
diff --git a/arch/arm/boot/dts/imx6q-evi.dts b/arch/arm/boot/dts/imx6q-evi.dts
index 7c7c1a8..79a0bd5 100644
--- a/arch/arm/boot/dts/imx6q-evi.dts
+++ b/arch/arm/boot/dts/imx6q-evi.dts
@@ -54,18 +54,6 @@
reg = <0x10000000 0x40000000>;
};
- reg_usbh1_vbus: regulator-usbhubreset {
- compatible = "regulator-fixed";
- regulator-name = "usbh1_vbus";
- regulator-min-microvolt = <5000000>;
- regulator-max-microvolt = <5000000>;
- enable-active-high;
- startup-delay-us = <2>;
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_usbh1_hubreset>;
- gpio = <&gpio7 12 GPIO_ACTIVE_HIGH>;
- };
-
reg_usb_otg_vbus: regulator-usbotgvbus {
compatible = "regulator-fixed";
regulator-name = "usb_otg_vbus";
@@ -207,12 +195,18 @@
};
&usbh1 {
- vbus-supply = <®_usbh1_vbus>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_usbh1>;
dr_mode = "host";
disable-over-current;
status = "okay";
+
+ usb2415host: hub at 1 {
+ compatible = "usb424,2513";
+ reg = <1>;
+ reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>;
+ reset-duration-us = <3000>;
+ };
};
&usbotg {
@@ -468,11 +462,6 @@
MX6QDL_PAD_GPIO_3__USB_H1_OC 0x1b0b0
/* usbh1_b OC */
MX6QDL_PAD_GPIO_0__GPIO1_IO00 0x1b0b0
- >;
- };
-
- pinctrl_usbh1_hubreset: usbh1hubresetgrp {
- fsl,pins = <
MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x1b0b0
>;
};
--
2.7.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox