From 3418f0ec63292d392e64d4d055413204a61ae0ca Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 16 Oct 2019 11:43:44 +0200 Subject: ARM: stm32mp: select ARCH_HAS_RESET_CONTROLLER Many STM32 peripherals, including I2C, SPI, USB and SDMMC use the RCC reset controller for reset. Enable ARCH_HAS_RESET_CONTROLLER, so drivers depending on RESET_CONTROLLER become available for selection. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 652ac24ce3..58f044137b 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -199,6 +199,7 @@ config ARCH_STM32MP select HAS_DEBUG_LL select HAVE_CLK select GPIOLIB + select ARCH_HAS_RESET_CONTROLLER config ARCH_VERSATILE bool "ARM Versatile boards (ARM926EJ-S)" -- cgit v1.2.3 From bad5fbbb6de427e2bc67df6b9ada478d241c6d0c Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 15 Oct 2019 11:33:45 +0200 Subject: ARM: stm32mp: enable ARM_AMBA It is needed for mci/sd/mmc driver. Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 58f044137b..725ea12a8c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -200,6 +200,7 @@ config ARCH_STM32MP select HAVE_CLK select GPIOLIB select ARCH_HAS_RESET_CONTROLLER + select ARM_AMBA config ARCH_VERSATILE bool "ARM Versatile boards (ARM926EJ-S)" -- cgit v1.2.3 From ce99d0c86b32ecc2946e36456695a8aad83ea15e Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 15 Oct 2019 11:33:46 +0200 Subject: mci: add support for stm32mp sd/mmc controller This driver was ported from u-boot v2019.10. Instead of devicetree compatible it is using ARM AMBA id. So, there is no need to patch devicetree with different compatible as it was implemented in u-boot. Signed-off-by: Oleksij Rempel Signed-off-by: Sascha Hauer --- drivers/mci/Kconfig | 9 + drivers/mci/Makefile | 1 + drivers/mci/stm32_sdmmc2.c | 665 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 675 insertions(+) create mode 100644 drivers/mci/stm32_sdmmc2.c diff --git a/drivers/mci/Kconfig b/drivers/mci/Kconfig index 08c8c84e8c..4a71a46097 100644 --- a/drivers/mci/Kconfig +++ b/drivers/mci/Kconfig @@ -153,4 +153,13 @@ config MMC_SPI_CRC_ON help Enable CRC protection for transfers +config MCI_STM32_SDMMC2 + bool "STMicroelectronics STM32H7 SD/MMC Host Controller support" + depends on ARM_AMBA + depends on RESET_CONTROLLER + help + This selects support for the SD/MMC controller on STM32H7 SoCs. + If you have a board based on such a SoC and with a SD/MMC slot, + say Y or M here. + endif diff --git a/drivers/mci/Makefile b/drivers/mci/Makefile index 25a1d073dc..04c1287fee 100644 --- a/drivers/mci/Makefile +++ b/drivers/mci/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_MCI_TEGRA) += tegra-sdmmc.o obj-$(CONFIG_MCI_SPI) += mci_spi.o obj-$(CONFIG_MCI_DW) += dw_mmc.o obj-$(CONFIG_MCI_MMCI) += mmci.o +obj-$(CONFIG_MCI_STM32_SDMMC2) += stm32_sdmmc2.o diff --git a/drivers/mci/stm32_sdmmc2.c b/drivers/mci/stm32_sdmmc2.c new file mode 100644 index 0000000000..7346c8a3f5 --- /dev/null +++ b/drivers/mci/stm32_sdmmc2.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author(s): Patrice Chotard, for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "stm32_sdmmc" + +/* SDMMC REGISTERS OFFSET */ +#define SDMMC_POWER 0x00 /* SDMMC power control */ +#define SDMMC_CLKCR 0x04 /* SDMMC clock control */ +#define SDMMC_ARG 0x08 /* SDMMC argument */ +#define SDMMC_CMD 0x0C /* SDMMC command */ +#define SDMMC_RESP1 0x14 /* SDMMC response 1 */ +#define SDMMC_RESP2 0x18 /* SDMMC response 2 */ +#define SDMMC_RESP3 0x1C /* SDMMC response 3 */ +#define SDMMC_RESP4 0x20 /* SDMMC response 4 */ +#define SDMMC_DTIMER 0x24 /* SDMMC data timer */ +#define SDMMC_DLEN 0x28 /* SDMMC data length */ +#define SDMMC_DCTRL 0x2C /* SDMMC data control */ +#define SDMMC_DCOUNT 0x30 /* SDMMC data counter */ +#define SDMMC_STA 0x34 /* SDMMC status */ +#define SDMMC_ICR 0x38 /* SDMMC interrupt clear */ +#define SDMMC_MASK 0x3C /* SDMMC mask */ +#define SDMMC_IDMACTRL 0x50 /* SDMMC DMA control */ +#define SDMMC_IDMABASE0 0x58 /* SDMMC DMA buffer 0 base address */ + +/* SDMMC_POWER register */ +#define SDMMC_POWER_PWRCTRL_MASK GENMASK(1, 0) +#define SDMMC_POWER_PWRCTRL_OFF 0 +#define SDMMC_POWER_PWRCTRL_CYCLE 2 +#define SDMMC_POWER_PWRCTRL_ON 3 +#define SDMMC_POWER_VSWITCH BIT(2) +#define SDMMC_POWER_VSWITCHEN BIT(3) +#define SDMMC_POWER_DIRPOL BIT(4) + +/* SDMMC_CLKCR register */ +#define SDMMC_CLKCR_CLKDIV GENMASK(9, 0) +#define SDMMC_CLKCR_CLKDIV_MAX SDMMC_CLKCR_CLKDIV +#define SDMMC_CLKCR_PWRSAV BIT(12) +#define SDMMC_CLKCR_WIDBUS_4 BIT(14) +#define SDMMC_CLKCR_WIDBUS_8 BIT(15) +#define SDMMC_CLKCR_NEGEDGE BIT(16) +#define SDMMC_CLKCR_HWFC_EN BIT(17) +#define SDMMC_CLKCR_DDR BIT(18) +#define SDMMC_CLKCR_BUSSPEED BIT(19) +#define SDMMC_CLKCR_SELCLKRX_MASK GENMASK(21, 20) +#define SDMMC_CLKCR_SELCLKRX_CK 0 +#define SDMMC_CLKCR_SELCLKRX_CKIN BIT(20) +#define SDMMC_CLKCR_SELCLKRX_FBCK BIT(21) + +/* SDMMC_CMD register */ +#define SDMMC_CMD_CMDINDEX GENMASK(5, 0) +#define SDMMC_CMD_CMDTRANS BIT(6) +#define SDMMC_CMD_CMDSTOP BIT(7) +#define SDMMC_CMD_WAITRESP GENMASK(9, 8) +#define SDMMC_CMD_WAITRESP_0 BIT(8) +#define SDMMC_CMD_WAITRESP_1 BIT(9) +#define SDMMC_CMD_WAITINT BIT(10) +#define SDMMC_CMD_WAITPEND BIT(11) +#define SDMMC_CMD_CPSMEN BIT(12) +#define SDMMC_CMD_DTHOLD BIT(13) +#define SDMMC_CMD_BOOTMODE BIT(14) +#define SDMMC_CMD_BOOTEN BIT(15) +#define SDMMC_CMD_CMDSUSPEND BIT(16) + +/* SDMMC_DCTRL register */ +#define SDMMC_DCTRL_DTEN BIT(0) +#define SDMMC_DCTRL_DTDIR BIT(1) +#define SDMMC_DCTRL_DTMODE GENMASK(3, 2) +#define SDMMC_DCTRL_DBLOCKSIZE GENMASK(7, 4) +#define SDMMC_DCTRL_DBLOCKSIZE_SHIFT 4 +#define SDMMC_DCTRL_RWSTART BIT(8) +#define SDMMC_DCTRL_RWSTOP BIT(9) +#define SDMMC_DCTRL_RWMOD BIT(10) +#define SDMMC_DCTRL_SDMMCEN BIT(11) +#define SDMMC_DCTRL_BOOTACKEN BIT(12) +#define SDMMC_DCTRL_FIFORST BIT(13) + +/* SDMMC_STA register */ +#define SDMMC_STA_CCRCFAIL BIT(0) +#define SDMMC_STA_DCRCFAIL BIT(1) +#define SDMMC_STA_CTIMEOUT BIT(2) +#define SDMMC_STA_DTIMEOUT BIT(3) +#define SDMMC_STA_TXUNDERR BIT(4) +#define SDMMC_STA_RXOVERR BIT(5) +#define SDMMC_STA_CMDREND BIT(6) +#define SDMMC_STA_CMDSENT BIT(7) +#define SDMMC_STA_DATAEND BIT(8) +#define SDMMC_STA_DHOLD BIT(9) +#define SDMMC_STA_DBCKEND BIT(10) +#define SDMMC_STA_DABORT BIT(11) +#define SDMMC_STA_DPSMACT BIT(12) +#define SDMMC_STA_CPSMACT BIT(13) +#define SDMMC_STA_TXFIFOHE BIT(14) +#define SDMMC_STA_RXFIFOHF BIT(15) +#define SDMMC_STA_TXFIFOF BIT(16) +#define SDMMC_STA_RXFIFOF BIT(17) +#define SDMMC_STA_TXFIFOE BIT(18) +#define SDMMC_STA_RXFIFOE BIT(19) +#define SDMMC_STA_BUSYD0 BIT(20) +#define SDMMC_STA_BUSYD0END BIT(21) +#define SDMMC_STA_SDMMCIT BIT(22) +#define SDMMC_STA_ACKFAIL BIT(23) +#define SDMMC_STA_ACKTIMEOUT BIT(24) +#define SDMMC_STA_VSWEND BIT(25) +#define SDMMC_STA_CKSTOP BIT(26) +#define SDMMC_STA_IDMATE BIT(27) +#define SDMMC_STA_IDMABTC BIT(28) + +/* SDMMC_ICR register */ +#define SDMMC_ICR_CCRCFAILC BIT(0) +#define SDMMC_ICR_DCRCFAILC BIT(1) +#define SDMMC_ICR_CTIMEOUTC BIT(2) +#define SDMMC_ICR_DTIMEOUTC BIT(3) +#define SDMMC_ICR_TXUNDERRC BIT(4) +#define SDMMC_ICR_RXOVERRC BIT(5) +#define SDMMC_ICR_CMDRENDC BIT(6) +#define SDMMC_ICR_CMDSENTC BIT(7) +#define SDMMC_ICR_DATAENDC BIT(8) +#define SDMMC_ICR_DHOLDC BIT(9) +#define SDMMC_ICR_DBCKENDC BIT(10) +#define SDMMC_ICR_DABORTC BIT(11) +#define SDMMC_ICR_BUSYD0ENDC BIT(21) +#define SDMMC_ICR_SDMMCITC BIT(22) +#define SDMMC_ICR_ACKFAILC BIT(23) +#define SDMMC_ICR_ACKTIMEOUTC BIT(24) +#define SDMMC_ICR_VSWENDC BIT(25) +#define SDMMC_ICR_CKSTOPC BIT(26) +#define SDMMC_ICR_IDMATEC BIT(27) +#define SDMMC_ICR_IDMABTCC BIT(28) +#define SDMMC_ICR_STATIC_FLAGS ((GENMASK(28, 21)) | (GENMASK(11, 0))) + +/* SDMMC_MASK register */ +#define SDMMC_MASK_CCRCFAILIE BIT(0) +#define SDMMC_MASK_DCRCFAILIE BIT(1) +#define SDMMC_MASK_CTIMEOUTIE BIT(2) +#define SDMMC_MASK_DTIMEOUTIE BIT(3) +#define SDMMC_MASK_TXUNDERRIE BIT(4) +#define SDMMC_MASK_RXOVERRIE BIT(5) +#define SDMMC_MASK_CMDRENDIE BIT(6) +#define SDMMC_MASK_CMDSENTIE BIT(7) +#define SDMMC_MASK_DATAENDIE BIT(8) +#define SDMMC_MASK_DHOLDIE BIT(9) +#define SDMMC_MASK_DBCKENDIE BIT(10) +#define SDMMC_MASK_DABORTIE BIT(11) +#define SDMMC_MASK_TXFIFOHEIE BIT(14) +#define SDMMC_MASK_RXFIFOHFIE BIT(15) +#define SDMMC_MASK_RXFIFOFIE BIT(17) +#define SDMMC_MASK_TXFIFOEIE BIT(18) +#define SDMMC_MASK_BUSYD0ENDIE BIT(21) +#define SDMMC_MASK_SDMMCITIE BIT(22) +#define SDMMC_MASK_ACKFAILIE BIT(23) +#define SDMMC_MASK_ACKTIMEOUTIE BIT(24) +#define SDMMC_MASK_VSWENDIE BIT(25) +#define SDMMC_MASK_CKSTOPIE BIT(26) +#define SDMMC_MASK_IDMABTCIE BIT(28) + +/* SDMMC_IDMACTRL register */ +#define SDMMC_IDMACTRL_IDMAEN BIT(0) + +#define SDMMC_CMD_TIMEOUT 0xFFFFFFFF +#define SDMMC_BUSYD0END_TIMEOUT_US 2000000 + +#define IS_RISING_EDGE(reg) ((reg) & SDMMC_CLKCR_NEGEDGE ? 0 : 1) + +struct stm32_sdmmc2_priv { + void __iomem *base; + struct mci_host mci; + struct device_d *dev; + struct clk *clk; + struct reset_control *reset_ctl; + u32 clk_reg_msk; + u32 pwr_reg_msk; +}; + +#define to_mci_host(mci) container_of(mci, struct stm32_sdmmc2_priv, mci) + +/* + * Reset the SDMMC with the RCC.SDMMCxRST register bit. + * This will reset the SDMMC to the reset state and the CPSM and DPSM + * to the Idle state. SDMMC is disabled, Signals Hiz. + */ +static int stm32_sdmmc2_reset(struct mci_host *mci, struct device_d *mci_dev) +{ + struct stm32_sdmmc2_priv *priv = to_mci_host(mci); + + reset_control_assert(priv->reset_ctl); + udelay(2); + reset_control_deassert(priv->reset_ctl); + + /* init the needed SDMMC register after reset */ + writel(priv->pwr_reg_msk, priv->base + SDMMC_POWER); + + return 0; +} + +/* + * Set the SDMMC in power-cycle state. + * This will make that the SDMMC_D[7:0], + * SDMMC_CMD and SDMMC_CK are driven low, to prevent the card from being + * supplied through the signal lines. + */ +static void stm32_sdmmc2_pwrcycle(struct stm32_sdmmc2_priv *priv) +{ + if ((readl(priv->base + SDMMC_POWER) & SDMMC_POWER_PWRCTRL_MASK) == + SDMMC_POWER_PWRCTRL_CYCLE) + return; + + stm32_sdmmc2_reset(&priv->mci, priv->dev); + writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); +} + +/* + * set the SDMMC state Power-on: the card is clocked + * manage the SDMMC state control: + * Reset => Power-Cycle => Power-Off => Power + * PWRCTRL=10 PWCTRL=00 PWCTRL=11 + */ +static void stm32_sdmmc2_pwron(struct stm32_sdmmc2_priv *priv) +{ + u32 pwrctrl = + readl(priv->base + SDMMC_POWER) & SDMMC_POWER_PWRCTRL_MASK; + + if (pwrctrl == SDMMC_POWER_PWRCTRL_ON) + return; + + /* warning: same PWRCTRL value after reset and for power-off state + * it is the reset state here = the only managed by the driver + */ + if (pwrctrl == SDMMC_POWER_PWRCTRL_OFF) { + writel(SDMMC_POWER_PWRCTRL_CYCLE | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + } + + /* + * the remaining case is SDMMC_POWER_PWRCTRL_CYCLE + * switch to Power-Off state: SDMCC disable, signals drive 1 + */ + writel(SDMMC_POWER_PWRCTRL_OFF | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + + /* After the 1ms delay set the SDMMC to power-on */ + mdelay(1); + writel(SDMMC_POWER_PWRCTRL_ON | priv->pwr_reg_msk, + priv->base + SDMMC_POWER); + + /* during the first 74 SDMMC_CK cycles the SDMMC is still disabled. */ +} + +static void stm32_sdmmc2_start_data(struct stm32_sdmmc2_priv *priv, + struct mci_data *data, u32 data_length) +{ + unsigned int num_bytes = data->blocks * data->blocksize; + u32 data_ctrl, idmabase0; + + /* Configure the SDMMC DPSM (Data Path State Machine) */ + data_ctrl = (__ilog2_u32(data->blocksize) << + SDMMC_DCTRL_DBLOCKSIZE_SHIFT) & + SDMMC_DCTRL_DBLOCKSIZE; + + if (data->flags & MMC_DATA_READ) { + data_ctrl |= SDMMC_DCTRL_DTDIR; + idmabase0 = (u32)data->dest; + } else { + idmabase0 = (u32)data->src; + } + + /* Set the SDMMC DataLength value */ + writel(data_length, priv->base + SDMMC_DLEN); + + /* Write to SDMMC DCTRL */ + writel(data_ctrl, priv->base + SDMMC_DCTRL); + + if (data->flags & MMC_DATA_WRITE) + dma_sync_single_for_device((unsigned long)idmabase0, + num_bytes, DMA_TO_DEVICE); + else + dma_sync_single_for_device((unsigned long)idmabase0, + num_bytes, DMA_FROM_DEVICE); + + /* Enable internal DMA */ + writel(idmabase0, priv->base + SDMMC_IDMABASE0); + writel(SDMMC_IDMACTRL_IDMAEN, priv->base + SDMMC_IDMACTRL); +} + +static void stm32_sdmmc2_start_cmd(struct stm32_sdmmc2_priv *priv, + struct mci_cmd *cmd, u32 cmd_param, + u32 data_length) +{ + u32 timeout = 0; + + if (readl(priv->base + SDMMC_CMD) & SDMMC_CMD_CPSMEN) + writel(0, priv->base + SDMMC_CMD); + + cmd_param |= cmd->cmdidx | SDMMC_CMD_CPSMEN; + if (cmd->resp_type & MMC_RSP_PRESENT) { + if (cmd->resp_type & MMC_RSP_136) + cmd_param |= SDMMC_CMD_WAITRESP; + else if (cmd->resp_type & MMC_RSP_CRC) + cmd_param |= SDMMC_CMD_WAITRESP_0; + else + cmd_param |= SDMMC_CMD_WAITRESP_1; + } + + /* + * SDMMC_DTIME must be set in two case: + * - on data transfert. + * - on busy request. + * If not done or too short, the dtimeout flag occurs and DPSM stays + * enabled/busy and waits for abort (stop transmission cmd). + * Next data command is not possible whereas DPSM is activated. + */ + if (data_length) { + timeout = SDMMC_CMD_TIMEOUT; + } else { + writel(0, priv->base + SDMMC_DCTRL); + + if (cmd->resp_type & MMC_RSP_BUSY) + timeout = SDMMC_CMD_TIMEOUT; + } + + /* Set the SDMMC Data TimeOut value */ + writel(timeout, priv->base + SDMMC_DTIMER); + + /* Clear flags */ + writel(SDMMC_ICR_STATIC_FLAGS, priv->base + SDMMC_ICR); + + /* Set SDMMC argument value */ + writel(cmd->cmdarg, priv->base + SDMMC_ARG); + + /* Set SDMMC command parameters */ + writel(cmd_param, priv->base + SDMMC_CMD); +} + +static int stm32_sdmmc2_end_cmd(struct stm32_sdmmc2_priv *priv, + struct mci_cmd *cmd) +{ + u32 mask = SDMMC_STA_CTIMEOUT; + u32 status; + int ret; + + if (cmd->resp_type & MMC_RSP_PRESENT) { + mask |= SDMMC_STA_CMDREND; + if (cmd->resp_type & MMC_RSP_CRC) + mask |= SDMMC_STA_CCRCFAIL; + } else { + mask |= SDMMC_STA_CMDSENT; + } + + /* Polling status register */ + ret = readl_poll_timeout(priv->base + SDMMC_STA, status, status & mask, + SDMMC_BUSYD0END_TIMEOUT_US); + if (ret < 0) { + dev_err(priv->dev, "timeout reading SDMMC_STA register\n"); + return ret; + } + + /* Check status */ + if (status & SDMMC_STA_CTIMEOUT) { + dev_err(priv->dev, "%s: error SDMMC_STA_CTIMEOUT (0x%x) for cmd %d\n", + __func__, status, cmd->cmdidx); + return -ETIMEDOUT; + } + + if (status & SDMMC_STA_CCRCFAIL && cmd->resp_type & MMC_RSP_CRC) { + dev_err(priv->dev, "%s: error SDMMC_STA_CCRCFAIL (0x%x) for cmd %d\n", + __func__, status, cmd->cmdidx); + return -EILSEQ; + } + + if (status & SDMMC_STA_CMDREND && cmd->resp_type & MMC_RSP_PRESENT) { + cmd->response[0] = readl(priv->base + SDMMC_RESP1); + if (cmd->resp_type & MMC_RSP_136) { + cmd->response[1] = readl(priv->base + SDMMC_RESP2); + cmd->response[2] = readl(priv->base + SDMMC_RESP3); + cmd->response[3] = readl(priv->base + SDMMC_RESP4); + } + + /* Wait for BUSYD0END flag if busy status is detected */ + if (cmd->resp_type & MMC_RSP_BUSY && + status & SDMMC_STA_BUSYD0) { + mask = SDMMC_STA_DTIMEOUT | SDMMC_STA_BUSYD0END; + + /* Polling status register */ + ret = readl_poll_timeout(priv->base + SDMMC_STA, + status, status & mask, + SDMMC_BUSYD0END_TIMEOUT_US); + + if (ret < 0) { + dev_err(priv->dev, "%s: timeout reading SDMMC_STA\n", + __func__); + return ret; + } + + if (status & SDMMC_STA_DTIMEOUT) { + dev_err(priv->dev, "%s: error SDMMC_STA_DTIMEOUT (0x%x)\n", + __func__, status); + return -ETIMEDOUT; + } + } + } + + return 0; +} + +static int stm32_sdmmc2_end_data(struct stm32_sdmmc2_priv *priv, + struct mci_cmd *cmd, + struct mci_data *data) +{ + u32 mask = SDMMC_STA_DCRCFAIL | SDMMC_STA_DTIMEOUT | + SDMMC_STA_IDMATE | SDMMC_STA_DATAEND; + unsigned int num_bytes = data->blocks * data->blocksize; + u32 status; + int ret; + + if (data->flags & MMC_DATA_READ) + mask |= SDMMC_STA_RXOVERR; + else + mask |= SDMMC_STA_TXUNDERR; + + ret = readl_poll_timeout(priv->base + SDMMC_STA, status, status & mask, + SDMMC_BUSYD0END_TIMEOUT_US); + if (ret < 0) { + dev_err(priv->dev, "Time out on waiting for SDMMC_STA. cmd %d\n", + cmd->cmdidx); + return ret; + } + + if (data->flags & MMC_DATA_WRITE) + dma_sync_single_for_cpu((unsigned long)data->src, + num_bytes, DMA_TO_DEVICE); + else + dma_sync_single_for_cpu((unsigned long)data->dest, + num_bytes, DMA_FROM_DEVICE); + + if (status & SDMMC_STA_DCRCFAIL) { + dev_err(priv->dev, "error SDMMC_STA_DCRCFAIL (0x%x) for cmd %d\n", + status, cmd->cmdidx); + return -EILSEQ; + } + + if (status & SDMMC_STA_DTIMEOUT) { + dev_err(priv->dev, "error SDMMC_STA_DTIMEOUT (0x%x) for cmd %d\n", + status, cmd->cmdidx); + return -ETIMEDOUT; + } + + if (status & SDMMC_STA_TXUNDERR) { + dev_err(priv->dev, "error SDMMC_STA_TXUNDERR (0x%x) for cmd %d\n", + status, cmd->cmdidx); + return -EIO; + } + + if (status & SDMMC_STA_RXOVERR) { + dev_err(priv->dev, "error SDMMC_STA_RXOVERR (0x%x) for cmd %d\n", + status, cmd->cmdidx); + return -EIO; + } + + if (status & SDMMC_STA_IDMATE) { + dev_err(priv->dev, "%s: error SDMMC_STA_IDMATE (0x%x) for cmd %d\n", + __func__, status, cmd->cmdidx); + return -EIO; + } + + return 0; +} + +static int stm32_sdmmc2_send_cmd(struct mci_host *mci, struct mci_cmd *cmd, + struct mci_data *data) +{ + struct stm32_sdmmc2_priv *priv = to_mci_host(mci); + u32 cmdat = data ? SDMMC_CMD_CMDTRANS : 0; + u32 data_length; + int ret, retry = 3; + +retry_cmd: + data_length = 0; + + if (data) { + data_length = data->blocks * data->blocksize; + stm32_sdmmc2_start_data(priv, data, data_length); + } + + stm32_sdmmc2_start_cmd(priv, cmd, cmdat, data_length); + + dev_dbg(priv->dev, "%s: send cmd %d data: 0x%x @ 0x%x\n", __func__, + cmd->cmdidx, data ? data_length : 0, (unsigned int)data); + + ret = stm32_sdmmc2_end_cmd(priv, cmd); + + if (data && !ret) + ret = stm32_sdmmc2_end_data(priv, cmd, data); + + /* Clear flags */ + writel(SDMMC_ICR_STATIC_FLAGS, priv->base + SDMMC_ICR); + if (data) + writel(0x0, priv->base + SDMMC_IDMACTRL); + + /* + * To stop Data Path State Machine, a stop_transmission command + * shall be send on cmd or data errors. + */ + if (ret && cmd->cmdidx != MMC_CMD_STOP_TRANSMISSION) { + struct mci_cmd stop_cmd; + + stop_cmd.cmdidx = MMC_CMD_STOP_TRANSMISSION; + stop_cmd.cmdarg = 0; + stop_cmd.resp_type = MMC_RSP_R1b; + + dev_dbg(priv->dev, "%s: send STOP command to abort dpsm treatments\n", + __func__); + + data_length = 0; + + stm32_sdmmc2_start_cmd(priv, &stop_cmd, + SDMMC_CMD_CMDSTOP, data_length); + ret = stm32_sdmmc2_end_cmd(priv, &stop_cmd); + + writel(SDMMC_ICR_STATIC_FLAGS, priv->base + SDMMC_ICR); + } + + if (ret && retry) { + dev_warn(priv->dev, "%s: cmd %d failed, retrying ...\n", + __func__, cmd->cmdidx); + + stm32_sdmmc2_pwrcycle(priv); + stm32_sdmmc2_pwron(priv); + retry--; + + goto retry_cmd; + } + + dev_dbg(priv->dev, "%s: end for CMD %d, ret = %d\n", __func__, + cmd->cmdidx, ret); + + return ret; +} + +static void stm32_sdmmc2_set_ios(struct mci_host *mci, struct mci_ios *ios) +{ + struct stm32_sdmmc2_priv *priv = to_mci_host(mci); + u32 desired = mci->clock; + u32 sys_clock = clk_get_rate(priv->clk); + u32 clk = 0; + + dev_dbg(priv->dev, "%s: bus_with = %d, clock = %d\n", __func__, + mci->bus_width, mci->clock); + + if (mci->clock) + stm32_sdmmc2_pwron(priv); + else + stm32_sdmmc2_pwrcycle(priv); + + /* + * clk_div = 0 => command and data generated on SDMMCCLK falling edge + * clk_div > 0 and NEGEDGE = 0 => command and data generated on + * SDMMCCLK rising edge + * clk_div > 0 and NEGEDGE = 1 => command and data generated on + * SDMMCCLK falling edge + */ + if (desired && (sys_clock > desired || + IS_RISING_EDGE(priv->clk_reg_msk))) { + clk = DIV_ROUND_UP(sys_clock, 2 * desired); + if (clk > SDMMC_CLKCR_CLKDIV_MAX) + clk = SDMMC_CLKCR_CLKDIV_MAX; + } + + if (mci->bus_width == 4) + clk |= SDMMC_CLKCR_WIDBUS_4; + if (mci->bus_width == 8) + clk |= SDMMC_CLKCR_WIDBUS_8; + + writel(clk | priv->clk_reg_msk | SDMMC_CLKCR_HWFC_EN, + priv->base + SDMMC_CLKCR); +} + +static int stm32_sdmmc2_probe(struct amba_device *adev, + const struct amba_id *id) +{ + struct device_d *dev = &adev->dev; + struct device_node *np = dev->device_node; + struct stm32_sdmmc2_priv *priv; + struct mci_host *mci; + int ret; + + priv = xzalloc(sizeof(*priv)); + + priv->base = amba_get_mem_region(adev); + priv->dev = dev; + + mci = &priv->mci; + mci->send_cmd = stm32_sdmmc2_send_cmd, + mci->set_ios = stm32_sdmmc2_set_ios, + mci->init = stm32_sdmmc2_reset; + mci->hw_dev = dev; + + priv->clk = clk_get(dev, NULL); + if (IS_ERR(priv->clk)) { + ret = PTR_ERR(priv->clk); + goto priv_free; + } + + ret = clk_enable(priv->clk); + if (ret) + goto priv_free; + + if (of_get_property(np, "st,neg-edge", NULL)) + priv->clk_reg_msk |= SDMMC_CLKCR_NEGEDGE; + if (of_get_property(np, "st,sig-dir", NULL)) + priv->pwr_reg_msk |= SDMMC_POWER_DIRPOL; + if (of_get_property(np, "st,use-ckin", NULL)) + priv->clk_reg_msk |= SDMMC_CLKCR_SELCLKRX_CKIN; + + priv->reset_ctl = reset_control_get(dev, NULL); + if (IS_ERR(priv->reset_ctl)) + priv->reset_ctl = NULL; + + mci->f_min = 400000; + /* f_max is taken from kernel v5.3 variant_stm32_sdmmc */ + mci->f_max = 208000000; + mci->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + + return mci_register(&priv->mci); + +priv_free: + free(priv); + + return ret; +} + +static struct amba_id stm32_sdmmc2_ids[] = { + /* ST Micro STM32MP157C */ + { + .id = 0x10153180, + .mask = 0xf0ffffff, + }, + { 0, 0 }, +}; + +static struct amba_driver stm32_sdmmc2_driver = { + .drv = { + .name = DRIVER_NAME, + }, + .probe = stm32_sdmmc2_probe, + .id_table = stm32_sdmmc2_ids, +}; + +static int stm32_sdmmc2_init(void) +{ + amba_driver_register(&stm32_sdmmc2_driver); + return 0; +} +device_initcall(stm32_sdmmc2_init); -- cgit v1.2.3 From 0dd17ab1bb2d573cc73c618589f7062b6e6c7c59 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:25 +0100 Subject: pinctrl: stm32: fix debug print of uninitialized variable mode and alt are printed with the dev_dbg before they are initialized. Remedy this by moving the dev_dbg after them. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/pinctrl/pinctrl-stm32.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pinctrl/pinctrl-stm32.c b/drivers/pinctrl/pinctrl-stm32.c index 7f04cea50b..ab121998a3 100644 --- a/drivers/pinctrl/pinctrl-stm32.c +++ b/drivers/pinctrl/pinctrl-stm32.c @@ -157,13 +157,13 @@ static int stm32_pinctrl_set_state(struct pinctrl_device *pdev, struct device_no if (offset < 0) return -ENODEV; + mode = stm32_gpio_get_mode(func); + alt = stm32_gpio_get_alt(func); + dev_dbg(pdev->dev, "configuring port %s pin %u with:\n\t" "fn %u, mode %u, alt %u\n", bank->name, offset, func, mode, alt); - mode = stm32_gpio_get_mode(func); - alt = stm32_gpio_get_alt(func); - clk_enable(bank->clk); __stm32_pmx_set_mode(bank->base, offset, mode, alt); -- cgit v1.2.3 From dc9510eb3524f41168b4837fcf7e253e366cc0d0 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:26 +0100 Subject: pinctrl: demote dev_info on successful probes to dev_dbg The SoC's pin controller is virtually always built and probed, so there's is little information to gain from the fact it was successfully probed. Have the success message show up as debug message to reduce probe clutter like this: NOTICE: stm32-pinctrl soc:pin-controller@50002000.of: pinctrl/gpio driver registered NOTICE: stm32-pinctrl soc:pin-controller-z@54004000.of: pinctrl/gpio driver registered Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/pinctrl/pinctrl-at91-pio4.c | 4 ++-- drivers/pinctrl/pinctrl-bcm2835.c | 2 +- drivers/pinctrl/pinctrl-stm32.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pinctrl/pinctrl-at91-pio4.c b/drivers/pinctrl/pinctrl-at91-pio4.c index 9bc259f84c..b527114f1b 100644 --- a/drivers/pinctrl/pinctrl-at91-pio4.c +++ b/drivers/pinctrl/pinctrl-at91-pio4.c @@ -260,7 +260,7 @@ static int pinctrl_at91_pio4_gpiochip_add(struct device_d *dev, return ret; } - dev_info(dev, "gpio driver registered\n"); + dev_dbg(dev, "gpio driver registered\n"); return 0; } @@ -290,7 +290,7 @@ static int pinctrl_at91_pio4_probe(struct device_d *dev) if (ret) return ret; - dev_info(dev, "pinctrl driver registered\n"); + dev_dbg(dev, "pinctrl driver registered\n"); if (of_get_property(np, "gpio-controller", NULL)) return pinctrl_at91_pio4_gpiochip_add(dev, pinctrl); diff --git a/drivers/pinctrl/pinctrl-bcm2835.c b/drivers/pinctrl/pinctrl-bcm2835.c index 5fd5740e81..b8e9b60372 100644 --- a/drivers/pinctrl/pinctrl-bcm2835.c +++ b/drivers/pinctrl/pinctrl-bcm2835.c @@ -171,7 +171,7 @@ static int bcm2835_gpio_probe(struct device_d *dev) goto err; } - dev_info(dev, "probed gpiochip%d with base %d\n", dev->id, bcmgpio->chip.base); + dev_dbg(dev, "probed gpiochip%d with base %d\n", dev->id, bcmgpio->chip.base); if (IS_ENABLED(CONFIG_PINCTRL)) { ret = pinctrl_register(&bcmgpio->pctl); diff --git a/drivers/pinctrl/pinctrl-stm32.c b/drivers/pinctrl/pinctrl-stm32.c index ab121998a3..c199d74846 100644 --- a/drivers/pinctrl/pinctrl-stm32.c +++ b/drivers/pinctrl/pinctrl-stm32.c @@ -401,7 +401,7 @@ static int stm32_pinctrl_probe(struct device_d *dev) } } - dev_info(dev, "pinctrl/gpio driver registered\n"); + dev_dbg(dev, "pinctrl/gpio driver registered\n"); return 0; } -- cgit v1.2.3 From f2f170ad83d6b41393ed1fef900bebf370bdcce6 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:27 +0100 Subject: pinctrl: stm32: parse pinctrl nodes without subnodes as well The bindings allow the pinmux node to occur directly in the node under the pin controller as well. Check for this and support both types of pinctrl specification. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/pinctrl/pinctrl-stm32.c | 180 ++++++++++++++++++++++------------------ 1 file changed, 99 insertions(+), 81 deletions(-) diff --git a/drivers/pinctrl/pinctrl-stm32.c b/drivers/pinctrl/pinctrl-stm32.c index c199d74846..cdaed510c5 100644 --- a/drivers/pinctrl/pinctrl-stm32.c +++ b/drivers/pinctrl/pinctrl-stm32.c @@ -87,110 +87,128 @@ static inline u32 stm32_gpio_get_alt(u32 function) return 0; } -static int stm32_pinctrl_set_state(struct pinctrl_device *pdev, struct device_node *group) +static int __stm32_pinctrl_set_state(struct device_d *dev, struct device_node *pins) { - struct stm32_pinctrl *pinctrl = to_stm32_pinctrl(pdev); - struct device_node *pins; int ret; - ret = hwspinlock_lock_timeout(&pinctrl->hws, 10); - if (ret == -ETIMEDOUT) { - dev_err(pdev->dev, "hw spinlock timeout\n"); - return ret; + int num_pins = 0, i; + u32 slew_rate; + bool adjust_slew_rate = false; + enum stm32_pin_bias bias = -1; + enum stm32_pin_out_type out_type = -1; + enum { PIN_INPUT, PIN_OUTPUT_LOW, PIN_OUTPUT_HIGH } dir = -1; + + of_get_property(pins, "pinmux", &num_pins); + num_pins /= sizeof(__be32); + if (!num_pins) { + dev_err(dev, "Invalid pinmux property in %s\n", + pins->full_name); + return -EINVAL; } - for_each_child_of_node(group, pins) { - int num_pins = 0, i; - u32 slew_rate; - bool adjust_slew_rate = false; - enum stm32_pin_bias bias = -1; - enum stm32_pin_out_type out_type = -1; - enum { PIN_INPUT, PIN_OUTPUT_LOW, PIN_OUTPUT_HIGH } dir = -1; - - of_get_property(pins, "pinmux", &num_pins); - num_pins /= sizeof(__be32); - if (!num_pins) { - dev_err(pdev->dev, "Invalid pinmux property in %s\n", - pins->full_name); - return -EINVAL; - } - - ret = of_property_read_u32(pins, "slew-rate", &slew_rate); - if (!ret) - adjust_slew_rate = true; - - if (of_get_property(pins, "bias-disable", NULL)) - bias = STM32_PIN_NO_BIAS; - else if (of_get_property(pins, "bias-pull-up", NULL)) - bias = STM32_PIN_PULL_UP; - else if (of_get_property(pins, "bias-pull-down", NULL)) - bias = STM32_PIN_PULL_DOWN; + ret = of_property_read_u32(pins, "slew-rate", &slew_rate); + if (!ret) + adjust_slew_rate = true; + + if (of_get_property(pins, "bias-disable", NULL)) + bias = STM32_PIN_NO_BIAS; + else if (of_get_property(pins, "bias-pull-up", NULL)) + bias = STM32_PIN_PULL_UP; + else if (of_get_property(pins, "bias-pull-down", NULL)) + bias = STM32_PIN_PULL_DOWN; + + if (of_get_property(pins, "drive-push-pull", NULL)) + out_type = STM32_PIN_OUT_PUSHPULL; + else if (of_get_property(pins, "drive-open-drain", NULL)) + out_type = STM32_PIN_OUT_OPENDRAIN; + + if (of_get_property(pins, "input-enable", NULL)) + dir = PIN_INPUT; + else if (of_get_property(pins, "output-low", NULL)) + dir = PIN_OUTPUT_LOW; + else if (of_get_property(pins, "output-high", NULL)) + dir = PIN_OUTPUT_HIGH; + + dev_dbg(dev, "%s: multiplexing %d pins\n", pins->full_name, num_pins); + + for (i = 0; i < num_pins; i++) { + struct stm32_gpio_bank *bank = NULL; + u32 pinfunc, mode, alt; + unsigned func; + int offset; + + ret = of_property_read_u32_index(pins, "pinmux", + i, &pinfunc); + if (ret) + return ret; - if (of_get_property(pins, "drive-push-pull", NULL)) - out_type = STM32_PIN_OUT_PUSHPULL; - else if (of_get_property(pins, "drive-open-drain", NULL)) - out_type = STM32_PIN_OUT_OPENDRAIN; + func = STM32_GET_PIN_FUNC(pinfunc); + offset = stm32_gpio_pin(STM32_GET_PIN_NO(pinfunc), &bank); + if (offset < 0) + return -ENODEV; - if (of_get_property(pins, "input-enable", NULL)) - dir = PIN_INPUT; - else if (of_get_property(pins, "output-low", NULL)) - dir = PIN_OUTPUT_LOW; - else if (of_get_property(pins, "output-high", NULL)) - dir = PIN_OUTPUT_HIGH; + mode = stm32_gpio_get_mode(func); + alt = stm32_gpio_get_alt(func); - dev_dbg(pdev->dev, "%s: multiplexing %d pins\n", - pins->full_name, num_pins); + dev_dbg(dev, "configuring port %s pin %u with:\n\t" + "fn %u, mode %u, alt %u\n", + bank->name, offset, func, mode, alt); - for (i = 0; i < num_pins; i++) { - struct stm32_gpio_bank *bank = NULL; - u32 pinfunc, mode, alt; - unsigned func; - int offset; + clk_enable(bank->clk); - ret = of_property_read_u32_index(pins, "pinmux", - i, &pinfunc); - if (ret) - return ret; + __stm32_pmx_set_mode(bank->base, offset, mode, alt); - func = STM32_GET_PIN_FUNC(pinfunc); - offset = stm32_gpio_pin(STM32_GET_PIN_NO(pinfunc), &bank); - if (offset < 0) - return -ENODEV; + if (adjust_slew_rate) + __stm32_pmx_set_speed(bank->base, offset, slew_rate); - mode = stm32_gpio_get_mode(func); - alt = stm32_gpio_get_alt(func); + if (bias != -1) + __stm32_pmx_set_bias(bank->base, offset, bias); - dev_dbg(pdev->dev, "configuring port %s pin %u with:\n\t" - "fn %u, mode %u, alt %u\n", - bank->name, offset, func, mode, alt); + if (out_type != -1) + __stm32_pmx_set_output_type(bank->base, offset, out_type); - clk_enable(bank->clk); + if (dir == PIN_INPUT) + __stm32_pmx_gpio_input(bank->base, offset); + else if (dir == PIN_OUTPUT_LOW) + __stm32_pmx_gpio_output(bank->base, offset, 0); + else if (dir == PIN_OUTPUT_HIGH) + __stm32_pmx_gpio_output(bank->base, offset, 1); - __stm32_pmx_set_mode(bank->base, offset, mode, alt); + clk_disable(bank->clk); + } - if (adjust_slew_rate) - __stm32_pmx_set_speed(bank->base, offset, slew_rate); + return 0; +} - if (bias != -1) - __stm32_pmx_set_bias(bank->base, offset, bias); +static int stm32_pinctrl_set_state(struct pinctrl_device *pdev, struct device_node *np) +{ + struct stm32_pinctrl *pinctrl = to_stm32_pinctrl(pdev); + struct device_d *dev = pdev->dev; + struct device_node *pins; + void *prop; + int ret; - if (out_type != -1) - __stm32_pmx_set_output_type(bank->base, offset, out_type); + ret = hwspinlock_lock_timeout(&pinctrl->hws, 10); + if (ret == -ETIMEDOUT) { + dev_err(dev, "hw spinlock timeout\n"); + return ret; + } - if (dir == PIN_INPUT) - __stm32_pmx_gpio_input(bank->base, offset); - else if (dir == PIN_OUTPUT_LOW) - __stm32_pmx_gpio_output(bank->base, offset, 0); - else if (dir == PIN_OUTPUT_HIGH) - __stm32_pmx_gpio_output(bank->base, offset, 1); + prop = of_find_property(np, "pinmux", NULL); + if (prop) { + ret = __stm32_pinctrl_set_state(dev, np); + goto out; + } - clk_disable(bank->clk); - } + for_each_child_of_node(np, pins) { + ret = __stm32_pinctrl_set_state(dev, pins); + if (ret) + goto out; } +out: hwspinlock_unlock(&pinctrl->hws); - - return 0; + return ret; } /* GPIO functions */ -- cgit v1.2.3 From 021f8d69104708b095f635a692416c8baa5f46c9 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:28 +0100 Subject: ARM: sm: document SMC/PSCI related options At least to me, the difference between these options were confusing at first. Clear this up. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 725ea12a8c..d4947cef5c 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -427,10 +427,14 @@ config ARM_SEMIHOSTING config ARM_SMCCC bool + help + This option enables barebox to invoke ARM secure monitor calls. config ARM_SECURE_MONITOR select ARM_SMCCC bool + help + This option enables barebox to service ARM secure monitor calls. config ARM_PSCI_OF bool @@ -442,7 +446,7 @@ config ARM_PSCI select ARM_PSCI_OF help PSCI is used for controlling secondary CPU cores on some systems. Say - yes here if you have one of these. + yes here if you want barebox to service PSCI calls on such systems. config ARM_PSCI_DEBUG bool "Enable PSCI debugging" -- cgit v1.2.3 From 6fb978dd7162b4aa708eb415f2bb1278403e041c Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:29 +0100 Subject: ARM: stm32mp: select ARM_SMCCC always ARM_SMCCC compiles in the code for issuing ARM secure monitor calls. We need those on the STM32MP, because barebox runs in non-secure mode and does some operations like reading the BSEC OTP through SMCs. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index d4947cef5c..f82844a83a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -201,6 +201,7 @@ config ARCH_STM32MP select GPIOLIB select ARCH_HAS_RESET_CONTROLLER select ARM_AMBA + select ARM_SMCCC config ARCH_VERSATILE bool "ARM Versatile boards (ARM926EJ-S)" -- cgit v1.2.3 From 3bf910ddb0488689860e8ee6893142a93fdb3cf1 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:30 +0100 Subject: nvmem: add read support for STM32MP1 bsec OTP The bsec on the STM32MP157C provides a 380 byte OTP. Add initial support for reading and writing the shadow copy of the fuses. Direct fuse access is not yet supported. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/dts/stm32mp157c.dtsi | 4 + arch/arm/mach-stm32mp/include/mach/bsec.h | 41 ++++++ arch/arm/mach-stm32mp/include/mach/smc.h | 28 ++++ drivers/nvmem/Kconfig | 8 ++ drivers/nvmem/Makefile | 5 +- drivers/nvmem/bsec.c | 221 ++++++++++++++++++++++++++++++ 6 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-stm32mp/include/mach/bsec.h create mode 100644 arch/arm/mach-stm32mp/include/mach/smc.h create mode 100644 drivers/nvmem/bsec.c diff --git a/arch/arm/dts/stm32mp157c.dtsi b/arch/arm/dts/stm32mp157c.dtsi index 8d9c84a047..771139c28a 100644 --- a/arch/arm/dts/stm32mp157c.dtsi +++ b/arch/arm/dts/stm32mp157c.dtsi @@ -20,3 +20,7 @@ gpio25 = &gpioz; }; }; + +&bsec { + barebox,provide-mac-address = <ðernet0 0x39>; +}; diff --git a/arch/arm/mach-stm32mp/include/mach/bsec.h b/arch/arm/mach-stm32mp/include/mach/bsec.h new file mode 100644 index 0000000000..559faaa2ba --- /dev/null +++ b/arch/arm/mach-stm32mp/include/mach/bsec.h @@ -0,0 +1,41 @@ +#ifndef __MACH_STM32_BSEC_H__ +#define __MACH_STM32_BSEC_H__ + +#include + +/* Return status */ +enum bsec_smc { + BSEC_SMC_OK = 0, + BSEC_SMC_ERROR = -1, + BSEC_SMC_DISTURBED = -2, + BSEC_SMC_INVALID_PARAM = -3, + BSEC_SMC_PROG_FAIL = -4, + BSEC_SMC_LOCK_FAIL = -5, + BSEC_SMC_WRITE_FAIL = -6, + BSEC_SMC_SHADOW_FAIL = -7, + BSEC_SMC_TIMEOUT = -8, +}; + +/* Service for BSEC */ +enum bsec_field { + BSEC_SMC_READ_SHADOW = 1, + BSEC_SMC_PROG_OTP = 2, + BSEC_SMC_WRITE_SHADOW = 3, + BSEC_SMC_READ_OTP = 4, + BSEC_SMC_READ_ALL = 5, + BSEC_SMC_WRITE_ALL = 6, +}; + +static inline enum bsec_smc bsec_read_field(enum bsec_field field, unsigned *val) +{ + return stm32mp_smc(STM32_SMC_BSEC, BSEC_SMC_READ_SHADOW, + field, 0, val); +} + +static inline enum bsec_smc bsec_write_field(enum bsec_field field, unsigned val) +{ + return stm32mp_smc(STM32_SMC_BSEC, BSEC_SMC_WRITE_SHADOW, + field, val, NULL); +} + +#endif diff --git a/arch/arm/mach-stm32mp/include/mach/smc.h b/arch/arm/mach-stm32mp/include/mach/smc.h new file mode 100644 index 0000000000..6b8e62bd53 --- /dev/null +++ b/arch/arm/mach-stm32mp/include/mach/smc.h @@ -0,0 +1,28 @@ +#ifndef __MACH_STM32_SMC_H__ +#define __MACH_STM32_SMC_H__ + +#include + +/* Secure Service access from Non-secure */ +#define STM32_SMC_RCC 0x82001000 +#define STM32_SMC_PWR 0x82001001 +#define STM32_SMC_RTC 0x82001002 +#define STM32_SMC_BSEC 0x82001003 + +/* Register access service use for RCC/RTC/PWR */ +#define STM32_SMC_REG_WRITE 0x1 +#define STM32_SMC_REG_SET 0x2 +#define STM32_SMC_REG_CLEAR 0x3 + +static inline int stm32mp_smc(u32 svc, u8 op, u32 data1, u32 data2, u32 *val) +{ + struct arm_smccc_res res; + + arm_smccc_smc(svc, op, data1, data2, 0, 0, 0, 0, &res); + if (val) + *val = res.a1; + + return (int)res.a0; +} + +#endif diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index c28a6d4e43..968342b281 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -51,4 +51,12 @@ config EEPROM_93XX46 supports both read and write commands and also the command to erase the whole EEPROM. +config STM32_BSEC + tristate "STM32 Boot and security and OTP control" + depends on ARCH_STM32MP + depends on OFDEVICE + help + This adds support for the STM32 OTP controller. Reads and writes + to will go to the shadow RAM, not the OTP fuses themselvers. + endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index abf9dae429..7101c5aca4 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -16,4 +16,7 @@ obj-$(CONFIG_RAVE_SP_EEPROM) += nvmem-rave-sp-eeprom.o nvmem-rave-sp-eeprom-y := rave-sp-eeprom.o obj-$(CONFIG_EEPROM_93XX46) += nvmem_eeprom_93xx46.o -nvmem_eeprom_93xx46-y := eeprom_93xx46.o \ No newline at end of file +nvmem_eeprom_93xx46-y := eeprom_93xx46.o + +obj-$(CONFIG_STM32_BSEC) += nvmem_bsec.o +nvmem_bsec-y := bsec.o diff --git a/drivers/nvmem/bsec.c b/drivers/nvmem/bsec.c new file mode 100644 index 0000000000..8235d468d1 --- /dev/null +++ b/drivers/nvmem/bsec.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + * Copyright (c) 2019 Ahmad Fatoum, Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BSEC_OTP_SERIAL 13 + +struct bsec_priv { + struct regmap *map; + u32 svc_id; + struct device_d dev; + struct regmap_config map_config; + struct nvmem_config config; +}; + +struct stm32_bsec_data { + unsigned long svc_id; + int num_regs; +}; + +static int bsec_smc(struct bsec_priv *priv, u8 op, enum bsec_field field, + unsigned data2, unsigned *val) +{ + enum bsec_smc ret = stm32mp_smc(priv->svc_id, op, field / 4, data2, val); + switch(ret) + { + case BSEC_SMC_OK: + return 0; + case BSEC_SMC_ERROR: + case BSEC_SMC_DISTURBED: + case BSEC_SMC_PROG_FAIL: + case BSEC_SMC_LOCK_FAIL: + case BSEC_SMC_WRITE_FAIL: + case BSEC_SMC_SHADOW_FAIL: + return -EIO; + case BSEC_SMC_INVALID_PARAM: + return -EINVAL; + case BSEC_SMC_TIMEOUT: + return -ETIMEDOUT; + } + + return -ENXIO; +} + +static int st32_bsec_read_shadow(void *ctx, unsigned reg, unsigned *val) +{ + return bsec_smc(ctx, BSEC_SMC_READ_SHADOW, reg, 0, val); +} + +static int stm32_bsec_reg_write_shadow(void *ctx, unsigned reg, unsigned val) +{ + return bsec_smc(ctx, BSEC_SMC_WRITE_SHADOW, reg, val, NULL); +} + +static struct regmap_bus stm32_bsec_regmap_bus = { + .reg_write = stm32_bsec_reg_write_shadow, + .reg_read = st32_bsec_read_shadow, +}; + +static int stm32_bsec_write(struct device_d *dev, int offset, + const void *val, int bytes) +{ + struct bsec_priv *priv = dev->parent->priv; + + return regmap_bulk_write(priv->map, offset, val, bytes); +} + +static int stm32_bsec_read(struct device_d *dev, int offset, + void *val, int bytes) +{ + struct bsec_priv *priv = dev->parent->priv; + + return regmap_bulk_read(priv->map, offset, val, bytes); +} + +static const struct nvmem_bus stm32_bsec_nvmem_bus = { + .write = stm32_bsec_write, + .read = stm32_bsec_read, +}; + +static void stm32_bsec_set_unique_machine_id(struct regmap *map) +{ + u32 unique_id[3]; + int ret; + + ret = regmap_bulk_read(map, BSEC_OTP_SERIAL * 4, + unique_id, sizeof(unique_id)); + if (ret) + return; + + machine_id_set_hashable(unique_id, sizeof(unique_id)); +} + +static int stm32_bsec_read_mac(struct regmap *map, int offset, u8 *mac) +{ + u8 res[8]; + int ret; + + ret = regmap_bulk_read(map, offset * 4, res, 8); + if (ret) + return ret; + + memcpy(mac, res, ETH_ALEN); + return 0; +} + +static void stm32_bsec_init_dt(struct bsec_priv *priv) +{ + struct device_node *node = priv->dev.parent->device_node; + struct device_node *rnode; + u32 phandle, offset; + char mac[ETH_ALEN]; + const __be32 *prop; + + int len; + int ret; + + if (!node) + return; + + prop = of_get_property(node, "barebox,provide-mac-address", &len); + if (!prop) + return; + + if (len != 2 * sizeof(__be32)) + return; + + phandle = be32_to_cpup(prop++); + + rnode = of_find_node_by_phandle(phandle); + offset = be32_to_cpup(prop++); + + ret = stm32_bsec_read_mac(priv->map, offset, mac); + if (ret) { + dev_warn(&priv->dev, "error setting MAC address: %s\n", + strerror(-ret)); + return; + } + + of_eth_register_ethaddr(rnode, mac); +} + +static int stm32_bsec_probe(struct device_d *dev) +{ + struct bsec_priv *priv; + int ret = 0; + const struct stm32_bsec_data *data; + struct nvmem_device *nvmem; + + ret = dev_get_drvdata(dev, (const void **)&data); + if (ret) + return ret; + + priv = xzalloc(sizeof(*priv)); + + priv->svc_id = data->svc_id; + + dev_set_name(&priv->dev, "bsec"); + priv->dev.parent = dev; + register_device(&priv->dev); + + priv->map_config.reg_bits = 32; + priv->map_config.val_bits = 32; + priv->map_config.reg_stride = 4; + priv->map_config.max_register = data->num_regs; + + priv->map = regmap_init(dev, &stm32_bsec_regmap_bus, priv, &priv->map_config); + if (IS_ERR(priv->map)) + return PTR_ERR(priv->map); + + priv->config.name = "stm32-bsec"; + priv->config.dev = dev; + priv->config.stride = 4; + priv->config.word_size = 4; + priv->config.size = data->num_regs; + priv->config.bus = &stm32_bsec_nvmem_bus; + dev->priv = priv; + + nvmem = nvmem_register(&priv->config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + if (IS_ENABLED(CONFIG_MACHINE_ID)) + stm32_bsec_set_unique_machine_id(priv->map); + + stm32_bsec_init_dt(priv); + + return 0; +} + +static struct stm32_bsec_data stm32mp15_bsec_data = { + .num_regs = 95 * 4, + .svc_id = STM32_SMC_BSEC, +}; + +static __maybe_unused struct of_device_id stm32_bsec_dt_ids[] = { + { .compatible = "st,stm32mp15-bsec", .data = &stm32mp15_bsec_data }, + { /* sentinel */ } +}; + +static struct driver_d stm32_bsec_driver = { + .name = "stm32_bsec", + .probe = stm32_bsec_probe, + .of_compatible = DRV_OF_COMPAT(stm32_bsec_dt_ids), +}; +postcore_platform_driver(stm32_bsec_driver); -- cgit v1.2.3 From 530fb27a57d18128e47cd1530470425436a697d1 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 00:18:32 +0100 Subject: ARM: stm32mp: implement SoC and boot source identification The BSEC OTP holds information about SoC type and package. The Tamp registers hold information from the BootROM about boot source. Add support for both. Additionally, the tamp registers can also hold a request from the operating system about what mode to enter after boot, e.g. boot-into-recovery. A global function is exported for this, but unused so far. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/mach-stm32mp/Makefile | 3 +- arch/arm/mach-stm32mp/include/mach/bootsource.h | 33 +++ arch/arm/mach-stm32mp/include/mach/revision.h | 32 +++ arch/arm/mach-stm32mp/init.c | 260 ++++++++++++++++++++++++ 4 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 arch/arm/mach-stm32mp/include/mach/bootsource.h create mode 100644 arch/arm/mach-stm32mp/include/mach/revision.h create mode 100644 arch/arm/mach-stm32mp/init.c diff --git a/arch/arm/mach-stm32mp/Makefile b/arch/arm/mach-stm32mp/Makefile index 204cad608f..6f49528892 100644 --- a/arch/arm/mach-stm32mp/Makefile +++ b/arch/arm/mach-stm32mp/Makefile @@ -1 +1,2 @@ -obj-$(CONFIG_BOOTM) := stm32image.o +obj-y := init.o +obj-$(CONFIG_BOOTM) += stm32image.o diff --git a/arch/arm/mach-stm32mp/include/mach/bootsource.h b/arch/arm/mach-stm32mp/include/mach/bootsource.h new file mode 100644 index 0000000000..1b6f562ac3 --- /dev/null +++ b/arch/arm/mach-stm32mp/include/mach/bootsource.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + */ + +#ifndef __MACH_STM32_BOOTSOURCE_H__ +#define __MACH_STM32_BOOTSOURCE_H__ + +enum stm32mp_boot_device { + STM32MP_BOOT_FLASH_SD = 0x10, /* .. 0x13 */ + STM32MP_BOOT_FLASH_EMMC = 0x20, /* .. 0x23 */ + STM32MP_BOOT_FLASH_NAND = 0x30, + STM32MP_BOOT_FLASH_NAND_FMC = 0x31, + STM32MP_BOOT_FLASH_NOR = 0x40, + STM32MP_BOOT_FLASH_NOR_QSPI = 0x41, + STM32MP_BOOT_SERIAL_UART = 0x50, /* .. 0x58 */ + STM32MP_BOOT_SERIAL_USB = 0x60, + STM32MP_BOOT_SERIAL_USB_OTG = 0x62, +}; + +enum stm32mp_forced_boot_mode { + STM32MP_BOOT_NORMAL = 0x00, + STM32MP_BOOT_FASTBOOT = 0x01, + STM32MP_BOOT_RECOVERY = 0x02, + STM32MP_BOOT_STM32PROG = 0x03, + STM32MP_BOOT_UMS_MMC0 = 0x10, + STM32MP_BOOT_UMS_MMC1 = 0x11, + STM32MP_BOOT_UMS_MMC2 = 0x12, +}; + +enum stm32mp_forced_boot_mode st32mp_get_forced_boot_mode(void); + +#endif diff --git a/arch/arm/mach-stm32mp/include/mach/revision.h b/arch/arm/mach-stm32mp/include/mach/revision.h new file mode 100644 index 0000000000..387201421d --- /dev/null +++ b/arch/arm/mach-stm32mp/include/mach/revision.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause */ +/* + * Copyright (C) 2015-2017, STMicroelectronics - All Rights Reserved + */ + +#ifndef __MACH_CPUTYPE_H__ +#define __MACH_CPUTYPE_H__ + +/* ID = Device Version (bit31:16) + Device Part Number (RPN) (bit15:0)*/ +#define CPU_STM32MP157Cxx 0x05000000 +#define CPU_STM32MP157Axx 0x05000001 +#define CPU_STM32MP153Cxx 0x05000024 +#define CPU_STM32MP153Axx 0x05000025 +#define CPU_STM32MP151Cxx 0x0500002E +#define CPU_STM32MP151Axx 0x0500002F + +/* silicon revisions */ +#define CPU_REV_A 0x1000 +#define CPU_REV_B 0x2000 + +int stm32mp_silicon_revision(void); +int stm32mp_cputype(void); +int stm32mp_package(void); + +#define cpu_is_stm32mp157c() (stm32mp_cputype() == CPU_STM32MP157Cxx) +#define cpu_is_stm32mp157a() (stm32mp_cputype() == CPU_STM32MP157Axx) +#define cpu_is_stm32mp153c() (stm32mp_cputype() == CPU_STM32MP153Cxx) +#define cpu_is_stm32mp153a() (stm32mp_cputype() == CPU_STM32MP153Axx) +#define cpu_is_stm32mp151c() (stm32mp_cputype() == CPU_STM32MP151Cxx) +#define cpu_is_stm32mp151a() (stm32mp_cputype() == CPU_STM32MP151Axx) + +#endif /* __MACH_CPUTYPE_H__ */ diff --git a/arch/arm/mach-stm32mp/init.c b/arch/arm/mach-stm32mp/init.c new file mode 100644 index 0000000000..7bad989a60 --- /dev/null +++ b/arch/arm/mach-stm32mp/init.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2018, STMicroelectronics - All Rights Reserved + * Copyright (C) 2019 Ahmad Fatoum, Pengutronix + */ + +#define pr_fmt(fmt) "stm32mp-init: " fmt + +#include +#include +#include +#include +#include +#include +#include + +/* DBGMCU register */ +#define DBGMCU_IDC (STM32_DBGMCU_BASE + 0x00) +#define DBGMCU_APB4FZ1 (STM32_DBGMCU_BASE + 0x2C) +#define DBGMCU_APB4FZ1_IWDG2 BIT(2) +#define DBGMCU_IDC_DEV_ID_MASK GENMASK(11, 0) +#define DBGMCU_IDC_DEV_ID_SHIFT 0 +#define DBGMCU_IDC_REV_ID_MASK GENMASK(31, 16) +#define DBGMCU_IDC_REV_ID_SHIFT 16 + +#define RCC_DBGCFGR (STM32_RCC_BASE + 0x080C) +#define RCC_DBGCFGR_DBGCKEN BIT(8) + +/* BSEC OTP index */ +#define BSEC_OTP_RPN 1 +#define BSEC_OTP_PKG 16 + +/* Device Part Number (RPN) = OTP_DATA1 lower 8 bits */ +#define RPN_SHIFT 0 +#define RPN_MASK GENMASK(7, 0) + +/* Package = bit 27:29 of OTP16 + * - 100: LBGA448 (FFI) => AA = LFBGA 18x18mm 448 balls p. 0.8mm + * - 011: LBGA354 (LCI) => AB = LFBGA 16x16mm 359 balls p. 0.8mm + * - 010: TFBGA361 (FFC) => AC = TFBGA 12x12mm 361 balls p. 0.5mm + * - 001: TFBGA257 (LCC) => AD = TFBGA 10x10mm 257 balls p. 0.5mm + * - others: Reserved + */ +#define PKG_SHIFT 27 +#define PKG_MASK GENMASK(2, 0) + +#define PKG_AA_LBGA448 4 +#define PKG_AB_LBGA354 3 +#define PKG_AC_TFBGA361 2 +#define PKG_AD_TFBGA257 1 + +/* + * enumerated for boot interface from Bootrom, used in TAMP_BOOT_CONTEXT + * - boot device = bit 8:4 + * - boot instance = bit 3:0 + */ +#define BOOT_TYPE_MASK 0xF0 +#define BOOT_TYPE_SHIFT 4 +#define BOOT_INSTANCE_MASK 0x0F +#define BOOT_INSTANCE_SHIFT 0 + +/* TAMP registers */ +#define TAMP_BACKUP_REGISTER(x) (STM32_TAMP_BASE + 0x100 + 4 * x) +/* secure access */ +#define TAMP_BACKUP_MAGIC_NUMBER TAMP_BACKUP_REGISTER(4) +#define TAMP_BACKUP_BRANCH_ADDRESS TAMP_BACKUP_REGISTER(5) +/* non secure access */ +#define TAMP_BOOT_CONTEXT TAMP_BACKUP_REGISTER(20) +#define TAMP_BOOTCOUNT TAMP_BACKUP_REGISTER(21) + +#define TAMP_BOOT_MODE_MASK GENMASK(15, 8) +#define TAMP_BOOT_MODE_SHIFT 8 +#define TAMP_BOOT_DEVICE_MASK GENMASK(7, 4) +#define TAMP_BOOT_INSTANCE_MASK GENMASK(3, 0) +#define TAMP_BOOT_FORCED_MASK GENMASK(7, 0) +#define TAMP_BOOT_DEBUG_ON BIT(16) + + +static enum stm32mp_forced_boot_mode __stm32mp_forced_boot_mode; +enum stm32mp_forced_boot_mode st32mp_get_forced_boot_mode(void) +{ + return __stm32mp_forced_boot_mode; +} + +static void setup_boot_mode(void) +{ + u32 boot_ctx = readl(TAMP_BOOT_CONTEXT); + u32 boot_mode = + (boot_ctx & TAMP_BOOT_MODE_MASK) >> TAMP_BOOT_MODE_SHIFT; + int instance = (boot_mode & TAMP_BOOT_INSTANCE_MASK) - 1; + enum bootsource src = BOOTSOURCE_UNKNOWN; + + switch (boot_mode & TAMP_BOOT_DEVICE_MASK) { + case STM32MP_BOOT_SERIAL_UART: + src = BOOTSOURCE_SERIAL; + break; + case STM32MP_BOOT_SERIAL_USB: + src = BOOTSOURCE_USB; + break; + case STM32MP_BOOT_FLASH_SD: + case STM32MP_BOOT_FLASH_EMMC: + src = BOOTSOURCE_MMC; + break; + case STM32MP_BOOT_FLASH_NAND: + src = BOOTSOURCE_NAND; + break; + case STM32MP_BOOT_FLASH_NOR: + instance = 0; + src = BOOTSOURCE_NOR; + break; + case STM32MP_BOOT_FLASH_NOR_QSPI: + instance--; + src = BOOTSOURCE_SPI_NOR; + break; + default: + pr_debug("unexpected boot mode\n"); + break; + } + + __stm32mp_forced_boot_mode = boot_ctx & TAMP_BOOT_FORCED_MASK; + + pr_debug("[boot_ctx=0x%x] => mode=0x%x, instance=%d forced=0x%x\n", + boot_ctx, boot_mode, instance, __stm32mp_forced_boot_mode); + + bootsource_set(src); + bootsource_set_instance(instance); + + /* clear TAMP for next reboot */ + clrsetbits_le32(TAMP_BOOT_CONTEXT, TAMP_BOOT_FORCED_MASK, + STM32MP_BOOT_NORMAL); +} + +static int __stm32mp_cputype; +int stm32mp_cputype(void) +{ + return __stm32mp_cputype; +} + +static int __stm32mp_silicon_revision; +int stm32mp_silicon_revision(void) +{ + return __stm32mp_silicon_revision; +} + +static int __stm32mp_package; +int stm32mp_package(void) +{ + return __stm32mp_package; +} + +static inline u32 read_idc(void) +{ + setbits_le32(RCC_DBGCFGR, RCC_DBGCFGR_DBGCKEN); + return readl(IOMEM(DBGMCU_IDC)); +} + +/* Get Device Part Number (RPN) from OTP */ +static u32 get_cpu_rpn(u32 *rpn) +{ + int ret = bsec_read_field(BSEC_OTP_RPN, rpn); + if (ret) + return ret; + + *rpn = (*rpn >> RPN_SHIFT) & RPN_MASK; + return 0; +} + +static u32 get_cpu_revision(void) +{ + return (read_idc() & DBGMCU_IDC_REV_ID_MASK) >> DBGMCU_IDC_REV_ID_SHIFT; +} + +static u32 get_cpu_type(u32 *type) +{ + u32 id; + int ret = get_cpu_rpn(type); + if (ret) + return ret; + + id = (read_idc() & DBGMCU_IDC_DEV_ID_MASK) >> DBGMCU_IDC_DEV_ID_SHIFT; + *type |= id << 16; + return 0; +} + +static int get_cpu_package(u32 *pkg) +{ + int ret = bsec_read_field(BSEC_OTP_PKG, pkg); + if (ret) + return ret; + + *pkg = (*pkg >> PKG_SHIFT) & PKG_MASK; + return 0; +} + +static int setup_cpu_type(void) +{ + const char *cputypestr; + const char *cpupkgstr; + + get_cpu_type(&__stm32mp_cputype); + switch (__stm32mp_cputype) { + case CPU_STM32MP157Cxx: + cputypestr = "157C"; + break; + case CPU_STM32MP157Axx: + cputypestr = "157A"; + break; + case CPU_STM32MP153Cxx: + cputypestr = "153C"; + break; + case CPU_STM32MP153Axx: + cputypestr = "153A"; + break; + case CPU_STM32MP151Cxx: + cputypestr = "151C"; + break; + case CPU_STM32MP151Axx: + cputypestr = "151A"; + break; + default: + cputypestr = "????"; + break; + } + + get_cpu_package(&__stm32mp_package ); + switch (__stm32mp_package) { + case PKG_AA_LBGA448: + cpupkgstr = "AA"; + break; + case PKG_AB_LBGA354: + cpupkgstr = "AB"; + break; + case PKG_AC_TFBGA361: + cpupkgstr = "AC"; + break; + case PKG_AD_TFBGA257: + cpupkgstr = "AD"; + break; + default: + cpupkgstr = "??"; + break; + } + + __stm32mp_silicon_revision = get_cpu_revision(); + + pr_debug("cputype = 0x%x, package = 0x%x, revision = 0x%x\n", + __stm32mp_cputype, __stm32mp_package, __stm32mp_silicon_revision); + pr_info("detected STM32MP%s%s Rev.%c\n", cputypestr, cpupkgstr, + (__stm32mp_silicon_revision >> 12) + 'A' - 1); + return 0; +} + +static int stm32mp_init(void) +{ + setup_cpu_type(); + setup_boot_mode(); + + return 0; +} +postcore_initcall(stm32mp_init); -- cgit v1.2.3 From 4c32eafe27f740d4edddc58be3c6a0e2d73015b7 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 22:39:38 +0100 Subject: mci: stm32_sdmmc2: parse generic MCI oftree properties mci_of_parse takes care of the generic oftree bits, like removable flag, aliases and max-frequency. Insert it, so we can start to refer to the controller's block devices by a fixed name. Sticking in the mci_of_parse shows that the SD/MMC2 driver in barebox doesn't play nicely with the device tree specification of the SD/MMC2 for the dk2 board, however: Specifying the bus-width = <4> property in the device tree now made mmc usage fail when reading the environment: ERROR: error SDMMC_STA_DCRCFAIL (0x81042) for cmd 18 ERROR: stm32_sdmmc2_end_cmd: error SDMMC_STA_CTIMEOUT (0x4) for cmd 12 WARNING: stm32_sdmmc2_send_cmd: cmd 12 failed, retrying ... ERROR: stm32_sdmmc2_end_cmd: error SDMMC_STA_CTIMEOUT (0x4) for cmd 12 WARNING: stm32_sdmmc2_send_cmd: cmd 12 failed, retrying ... ERROR: stm32_sdmmc2_end_cmd: error SDMMC_STA_CTIMEOUT (0x4) for cmd 12 WARNING: stm32_sdmmc2_send_cmd: cmd 12 failed, retrying ... ERROR: stm32_sdmmc2_end_cmd: error SDMMC_STA_CTIMEOUT (0x4) for cmd 12 We'll want to fix this eventually, but for now force the bus width to 1. So we don't forget about this, print a notice to the console that we've done so. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/mci/stm32_sdmmc2.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mci/stm32_sdmmc2.c b/drivers/mci/stm32_sdmmc2.c index 7346c8a3f5..1a41c34d24 100644 --- a/drivers/mci/stm32_sdmmc2.c +++ b/drivers/mci/stm32_sdmmc2.c @@ -632,6 +632,13 @@ static int stm32_sdmmc2_probe(struct amba_device *adev, mci->f_max = 208000000; mci->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + mci_of_parse(&priv->mci); + + if (mci->host_caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA)) { + dev_notice(dev, "Fixing bus-width to 1 due to driver limitation\n"); + mci->host_caps &= ~(MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA); + } + return mci_register(&priv->mci); priv_free: -- cgit v1.2.3 From 859697993bc6849c072d6c9ebbf908316b7d644b Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 22:39:39 +0100 Subject: ARM: stm32mp: dk2: add barebox SD-Card update handler Now with the SD/MMC controller supported, lets add a bbu handler, so we can use it to update the second stage boot loader partition. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/boards/stm32mp157c-dk2/board.c | 13 +++++++++++++ arch/arm/dts/stm32mp157a-dk1.dtsi | 4 ++++ arch/arm/mach-stm32mp/include/mach/bbu.h | 14 ++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 arch/arm/mach-stm32mp/include/mach/bbu.h diff --git a/arch/arm/boards/stm32mp157c-dk2/board.c b/arch/arm/boards/stm32mp157c-dk2/board.c index 9cb861af85..f15ae0b4af 100644 --- a/arch/arm/boards/stm32mp157c-dk2/board.c +++ b/arch/arm/boards/stm32mp157c-dk2/board.c @@ -4,6 +4,7 @@ #include #include #include +#include static int dk2_mem_init(void) { @@ -15,3 +16,15 @@ static int dk2_mem_init(void) return 0; } mem_initcall(dk2_mem_init); + +static int dk2_postcore_init(void) +{ + if (!of_machine_is_compatible("st,stm32mp157c-dk2")) + return 0; + + stm32mp_bbu_mmc_register_handler("sd", "/dev/mmc0.ssbl", + BBU_HANDLER_FLAG_DEFAULT); + + return 0; +} +postcore_initcall(dk2_postcore_init); diff --git a/arch/arm/dts/stm32mp157a-dk1.dtsi b/arch/arm/dts/stm32mp157a-dk1.dtsi index f7fbdcd174..fc946ed9f4 100644 --- a/arch/arm/dts/stm32mp157a-dk1.dtsi +++ b/arch/arm/dts/stm32mp157a-dk1.dtsi @@ -8,6 +8,10 @@ #include / { + aliases { + mmc0 = &sdmmc1; + }; + chosen { environment { compatible = "barebox,environment"; diff --git a/arch/arm/mach-stm32mp/include/mach/bbu.h b/arch/arm/mach-stm32mp/include/mach/bbu.h new file mode 100644 index 0000000000..8b9504400e --- /dev/null +++ b/arch/arm/mach-stm32mp/include/mach/bbu.h @@ -0,0 +1,14 @@ +#ifndef MACH_STM32MP_BBU_H_ +#define MACH_STM32MP_BBU_H_ + +#include + +static inline int stm32mp_bbu_mmc_register_handler(const char *name, + const char *devicefile, + unsigned long flags) +{ + return bbu_register_std_file_update(name, flags, devicefile, + filetype_stm32_image_v1); +} + +#endif /* MACH_STM32MP_BBU_H_ */ -- cgit v1.2.3 From a4f709bbb5ee067f7600ab9ebf90fd5087833658 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 22:39:40 +0100 Subject: net: add Designware Ethernet QoS for STM32MP We already have Designware NIC support in barebox, but for the DWMAC1000, the DWMAC4 (also called GMAC4), no support was mainline so far. The DWMAC4 is different enough that sharing code with the DWMAC1000 is not really that helpful, because even basics like MDIO registers have different layout. Instead of coding bit masks and shifts into the driver data, like Linux does, we'll keep both driver kinds separate. Nevertheless, we collect functions that are not SoC-specific into a separate 'library' file. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/net/Kconfig | 21 +- drivers/net/Makefile | 2 + drivers/net/designware_eqos.c | 876 +++++++++++++++++++++++++++++++++++++++++ drivers/net/designware_eqos.h | 84 ++++ drivers/net/designware_stm32.c | 245 ++++++++++++ 5 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 drivers/net/designware_eqos.c create mode 100644 drivers/net/designware_eqos.h create mode 100644 drivers/net/designware_stm32.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 57f0b57d64..5b66fe84eb 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -61,7 +61,7 @@ config DRIVER_NET_DAVINCI_EMAC select PHYLIB config DRIVER_NET_DESIGNWARE - bool "Designware Universal MAC ethernet platform support" + bool "Designware Universal MAC1000 ethernet platform support" depends on HAS_DMA select PHYLIB help @@ -87,6 +87,25 @@ config DRIVER_NET_DESIGNWARE_SOCFPGA endif +config DRIVER_NET_DESIGNWARE_EQOS + bool "Designware Designware Ethernet QoS support" + depends on HAS_DMA + select PHYLIB + select OFTREE + help + This option enables support for the Synopsys + Designware Ethernet Quality-of-Service (GMAC4). + +if DRIVER_NET_DESIGNWARE_EQOS + +config DRIVER_NET_DESIGNWARE_STM32 + bool "Designware EQOS STM32 driver" + select MFD_SYSCON + help + This option enables support for the ethernet MAC on the STM32MP platforms. + +endif + config DRIVER_NET_DM9K bool "Davicom dm9k[E|A|B] ethernet driver" depends on HAS_DM9000 diff --git a/drivers/net/Makefile b/drivers/net/Makefile index f6a8213613..7ecf4e2851 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_DRIVER_NET_DAVINCI_EMAC) += davinci_emac.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE) += designware.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_GENERIC) += designware_generic.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_SOCFPGA) += designware_socfpga.o +obj-$(CONFIG_DRIVER_NET_DESIGNWARE_EQOS) += designware_eqos.o +obj-$(CONFIG_DRIVER_NET_DESIGNWARE_STM32) += designware_stm32.o obj-$(CONFIG_DRIVER_NET_DM9K) += dm9k.o obj-$(CONFIG_DRIVER_NET_E1000) += e1000/regio.o e1000/main.o e1000/eeprom.o obj-$(CONFIG_DRIVER_NET_ENC28J60) += enc28j60.o diff --git a/drivers/net/designware_eqos.c b/drivers/net/designware_eqos.c new file mode 100644 index 0000000000..a49239e057 --- /dev/null +++ b/drivers/net/designware_eqos.c @@ -0,0 +1,876 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * Copyright (c) 2019, Ahmad Fatoum, Pengutronix + * + * Portions based on U-Boot's rtl8169.c and dwc_eth_qos. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "designware_eqos.h" + +/* Core registers */ + +#define EQOS_MAC_REGS_BASE 0x000 +struct eqos_mac_regs { + u32 config; /* 0x000 */ + u32 ext_config; /* 0x004 */ + u32 unused_004[(0x070 - 0x008) / 4]; /* 0x008 */ + u32 q0_tx_flow_ctrl; /* 0x070 */ + u32 unused_070[(0x090 - 0x074) / 4]; /* 0x074 */ + u32 rx_flow_ctrl; /* 0x090 */ + u32 unused_094; /* 0x094 */ + u32 txq_prty_map0; /* 0x098 */ + u32 unused_09c; /* 0x09c */ + u32 rxq_ctrl0; /* 0x0a0 */ + u32 unused_0a4; /* 0x0a4 */ + u32 rxq_ctrl2; /* 0x0a8 */ + u32 unused_0ac[(0x0dc - 0x0ac) / 4]; /* 0x0ac */ + u32 us_tic_counter; /* 0x0dc */ + u32 unused_0e0[(0x11c - 0x0e0) / 4]; /* 0x0e0 */ + u32 hw_feature0; /* 0x11c */ + u32 hw_feature1; /* 0x120 */ + u32 hw_feature2; /* 0x124 */ + u32 unused_128[(0x200 - 0x128) / 4]; /* 0x128 */ + u32 mdio_address; /* 0x200 */ + u32 mdio_data; /* 0x204 */ + u32 unused_208[(0x300 - 0x208) / 4]; /* 0x208 */ + u32 macaddr0hi; /* 0x300 */ + u32 macaddr0lo; /* 0x304 */ +}; + +#define EQOS_MAC_CONFIGURATION_GPSLCE BIT(23) +#define EQOS_MAC_CONFIGURATION_CST BIT(21) +#define EQOS_MAC_CONFIGURATION_ACS BIT(20) +#define EQOS_MAC_CONFIGURATION_WD BIT(19) +#define EQOS_MAC_CONFIGURATION_JD BIT(17) +#define EQOS_MAC_CONFIGURATION_JE BIT(16) +#define EQOS_MAC_CONFIGURATION_PS BIT(15) +#define EQOS_MAC_CONFIGURATION_FES BIT(14) +#define EQOS_MAC_CONFIGURATION_DM BIT(13) +#define EQOS_MAC_CONFIGURATION_TE BIT(1) +#define EQOS_MAC_CONFIGURATION_RE BIT(0) + +#define EQOS_MAC_Q0_TX_FLOW_CTRL_PT_SHIFT 16 +#define EQOS_MAC_Q0_TX_FLOW_CTRL_PT_MASK 0xffff +#define EQOS_MAC_Q0_TX_FLOW_CTRL_TFE BIT(1) + +#define EQOS_MAC_RX_FLOW_CTRL_RFE BIT(0) + +#define EQOS_MAC_TXQ_PRTY_MAP0_PSTQ0_SHIFT 0 +#define EQOS_MAC_TXQ_PRTY_MAP0_PSTQ0_MASK 0xff + +#define EQOS_MAC_RXQ_CTRL2_PSRQ0_SHIFT 0 +#define EQOS_MAC_RXQ_CTRL2_PSRQ0_MASK 0xff + +#define EQOS_MAC_HW_FEATURE1_TXFIFOSIZE_SHIFT 6 +#define EQOS_MAC_HW_FEATURE1_TXFIFOSIZE_MASK 0x1f +#define EQOS_MAC_HW_FEATURE1_RXFIFOSIZE_SHIFT 0 +#define EQOS_MAC_HW_FEATURE1_RXFIFOSIZE_MASK 0x1f + +#define EQOS_MTL_REGS_BASE 0xd00 +struct eqos_mtl_regs { + u32 txq0_operation_mode; /* 0xd00 */ + u32 unused_d04; /* 0xd04 */ + u32 txq0_debug; /* 0xd08 */ + u32 unused_d0c[(0xd18 - 0xd0c) / 4]; /* 0xd0c */ + u32 txq0_quantum_weight; /* 0xd18 */ + u32 unused_d1c[(0xd30 - 0xd1c) / 4]; /* 0xd1c */ + u32 rxq0_operation_mode; /* 0xd30 */ + u32 unused_d34; /* 0xd34 */ + u32 rxq0_debug; /* 0xd38 */ +}; + +#define EQOS_MTL_TXQ0_OPERATION_MODE_TQS_SHIFT 16 +#define EQOS_MTL_TXQ0_OPERATION_MODE_TQS_MASK 0x1ff +#define EQOS_MTL_TXQ0_OPERATION_MODE_TXQEN_SHIFT 2 +#define EQOS_MTL_TXQ0_OPERATION_MODE_TXQEN_MASK 3 +#define EQOS_MTL_TXQ0_OPERATION_MODE_TXQEN_ENABLED 2 +#define EQOS_MTL_TXQ0_OPERATION_MODE_TSF BIT(1) +#define EQOS_MTL_TXQ0_OPERATION_MODE_FTQ BIT(0) + +#define EQOS_MTL_TXQ0_DEBUG_TXQSTS BIT(4) +#define EQOS_MTL_TXQ0_DEBUG_TRCSTS_SHIFT 1 +#define EQOS_MTL_TXQ0_DEBUG_TRCSTS_MASK 3 + +#define EQOS_MTL_RXQ0_OPERATION_MODE_RQS_SHIFT 20 +#define EQOS_MTL_RXQ0_OPERATION_MODE_RQS_MASK 0x3ff +#define EQOS_MTL_RXQ0_OPERATION_MODE_RFD_SHIFT 14 +#define EQOS_MTL_RXQ0_OPERATION_MODE_RFD_MASK 0x3f +#define EQOS_MTL_RXQ0_OPERATION_MODE_RFA_SHIFT 8 +#define EQOS_MTL_RXQ0_OPERATION_MODE_RFA_MASK 0x3f +#define EQOS_MTL_RXQ0_OPERATION_MODE_EHFC BIT(7) +#define EQOS_MTL_RXQ0_OPERATION_MODE_RSF BIT(5) + +#define EQOS_MTL_RXQ0_DEBUG_PRXQ_SHIFT 16 +#define EQOS_MTL_RXQ0_DEBUG_PRXQ_MASK 0x7fff +#define EQOS_MTL_RXQ0_DEBUG_RXQSTS_SHIFT 4 +#define EQOS_MTL_RXQ0_DEBUG_RXQSTS_MASK 3 + +#define EQOS_DMA_REGS_BASE 0x1000 +struct eqos_dma_regs { + u32 mode; /* 0x1000 */ + u32 sysbus_mode; /* 0x1004 */ + u32 unused_1008[(0x1100 - 0x1008) / 4]; /* 0x1008 */ + u32 ch0_control; /* 0x1100 */ + u32 ch0_tx_control; /* 0x1104 */ + u32 ch0_rx_control; /* 0x1108 */ + u32 unused_110c; /* 0x110c */ + u32 ch0_txdesc_list_haddress; /* 0x1110 */ + u32 ch0_txdesc_list_address; /* 0x1114 */ + u32 ch0_rxdesc_list_haddress; /* 0x1118 */ + u32 ch0_rxdesc_list_address; /* 0x111c */ + u32 ch0_txdesc_tail_pointer; /* 0x1120 */ + u32 unused_1124; /* 0x1124 */ + u32 ch0_rxdesc_tail_pointer; /* 0x1128 */ + u32 ch0_txdesc_ring_length; /* 0x112c */ + u32 ch0_rxdesc_ring_length; /* 0x1130 */ +}; + +#define EQOS_DMA_MODE_SWR BIT(0) + +#define EQOS_DMA_SYSBUS_MODE_RD_OSR_LMT_SHIFT 16 +#define EQOS_DMA_SYSBUS_MODE_RD_OSR_LMT_MASK 0xf +#define EQOS_DMA_SYSBUS_MODE_EAME BIT(11) +#define EQOS_DMA_SYSBUS_MODE_BLEN16 BIT(3) +#define EQOS_DMA_SYSBUS_MODE_BLEN8 BIT(2) +#define EQOS_DMA_SYSBUS_MODE_BLEN4 BIT(1) + +#define EQOS_DMA_CH0_CONTROL_PBLX8 BIT(16) + +#define EQOS_DMA_CH0_TX_CONTROL_TXPBL_SHIFT 16 +#define EQOS_DMA_CH0_TX_CONTROL_TXPBL_MASK 0x3f +#define EQOS_DMA_CH0_TX_CONTROL_OSP BIT(4) +#define EQOS_DMA_CH0_TX_CONTROL_ST BIT(0) + +#define EQOS_DMA_CH0_RX_CONTROL_RXPBL_SHIFT 16 +#define EQOS_DMA_CH0_RX_CONTROL_RXPBL_MASK 0x3f +#define EQOS_DMA_CH0_RX_CONTROL_RBSZ_SHIFT 1 +#define EQOS_DMA_CH0_RX_CONTROL_RBSZ_MASK 0x3fff +#define EQOS_DMA_CH0_RX_CONTROL_SR BIT(0) + +/* Descriptors */ + +#define EQOS_DESCRIPTOR_WORDS 4 +#define EQOS_DESCRIPTOR_SIZE (EQOS_DESCRIPTOR_WORDS * 4) +/* We assume ARCH_DMA_MINALIGN >= 16; 16 is the EQOS HW minimum */ +#define EQOS_DESCRIPTOR_ALIGN 64 +#define EQOS_DESCRIPTORS_TX 4 +#define EQOS_DESCRIPTORS_RX 4 +#define EQOS_DESCRIPTORS_NUM (EQOS_DESCRIPTORS_TX + EQOS_DESCRIPTORS_RX) +#define EQOS_DESCRIPTORS_SIZE ALIGN(EQOS_DESCRIPTORS_NUM * \ + EQOS_DESCRIPTOR_SIZE, EQOS_DESCRIPTOR_ALIGN) +#define EQOS_BUFFER_ALIGN EQOS_DESCRIPTOR_ALIGN +#define EQOS_MAX_PACKET_SIZE ALIGN(1568, EQOS_DESCRIPTOR_ALIGN) + +struct eqos_desc { + u32 des0; /* PA of buffer 1 or TSO header */ + u32 des1; /* PA of buffer 2 with descriptor rings */ + u32 des2; /* Length, VLAN, Timestamps, Interrupts */ + u32 des3; /* All other flags */ +}; + +#define EQOS_DESC3_OWN BIT(31) +#define EQOS_DESC3_FD BIT(29) +#define EQOS_DESC3_LD BIT(28) +#define EQOS_DESC3_BUF1V BIT(24) + +#define EQOS_MDIO_ADDR(reg) ((addr << 21) & GENMASK(25, 21)) +#define EQOS_MDIO_REG(reg) ((reg << 16) & GENMASK(20, 16)) +#define EQOS_MDIO_CLK_CSR(clk_csr) ((clk_csr << 8) & GENMASK(11, 8)) + +#define MII_BUSY (1 << 0) + +static int eqos_mdio_wait_idle(struct eqos *eqos) +{ + u32 idle; + return readl_poll_timeout(&eqos->mac_regs->mdio_address, idle, + !(idle & MII_BUSY), 10 * USEC_PER_MSEC); +} + +static int eqos_mdio_read(struct mii_bus *bus, int addr, int reg) +{ + struct eqos *eqos = bus->priv; + u32 miiaddr; + int ret; + + ret = eqos_mdio_wait_idle(eqos); + if (ret) { + eqos_err(eqos, "MDIO not idle at entry\n"); + return ret; + } + + miiaddr = readl(&eqos->mac_regs->mdio_address); + miiaddr &= EQOS_MDIO_ADDR_SKAP | EQOS_MDIO_ADDR_C45E; + miiaddr |= EQOS_MDIO_ADDR_GOC_READ << EQOS_MDIO_ADDR_GOC_SHIFT; + + miiaddr |= EQOS_MDIO_CLK_CSR(eqos->ops->clk_csr); + miiaddr |= EQOS_MDIO_ADDR(addr) | EQOS_MDIO_REG(reg); + miiaddr |= MII_BUSY; + + writel(miiaddr, &eqos->mac_regs->mdio_address); + + udelay(eqos->ops->mdio_wait_us); + + ret = eqos_mdio_wait_idle(eqos); + if (ret) { + eqos_err(eqos, "MDIO read didn't complete\n"); + return ret; + } + + return readl(&eqos->mac_regs->mdio_data) & 0xffff; +} + +static int eqos_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct eqos *eqos = bus->priv; + u32 miiaddr = 0; + int ret; + + ret = eqos_mdio_wait_idle(eqos); + if (ret) { + eqos_err(eqos, "MDIO not idle at entry\n"); + return ret; + } + + miiaddr = readl(&eqos->mac_regs->mdio_address); + miiaddr &= EQOS_MDIO_ADDR_SKAP | EQOS_MDIO_ADDR_C45E; + miiaddr |= EQOS_MDIO_ADDR_GOC_WRITE << EQOS_MDIO_ADDR_GOC_SHIFT; + + miiaddr |= EQOS_MDIO_CLK_CSR(eqos->ops->clk_csr); + miiaddr |= EQOS_MDIO_ADDR(addr) | EQOS_MDIO_REG(reg); + miiaddr |= MII_BUSY; + + writel(val, &eqos->mac_regs->mdio_data); + writel(addr, &eqos->mac_regs->mdio_address); + + udelay(eqos->ops->mdio_wait_us); + + ret = eqos_mdio_wait_idle(eqos); + if (ret) { + eqos_err(eqos, "MDIO read didn't complete\n"); + return ret; + } + + /* Needed as a fix for ST-Phy */ + eqos_mdio_read(bus, addr, reg); + return 0; +} + + +static inline void eqos_set_full_duplex(struct eqos *eqos) +{ + setbits_le32(&eqos->mac_regs->config, EQOS_MAC_CONFIGURATION_DM); +} + +static inline void eqos_set_half_duplex(struct eqos *eqos) +{ + clrbits_le32(&eqos->mac_regs->config, EQOS_MAC_CONFIGURATION_DM); + + /* WAR: Flush TX queue when switching to half-duplex */ + setbits_le32(&eqos->mtl_regs->txq0_operation_mode, + EQOS_MTL_TXQ0_OPERATION_MODE_FTQ); +} + +static inline void eqos_set_gmii_speed(struct eqos *eqos) +{ + clrbits_le32(&eqos->mac_regs->config, + EQOS_MAC_CONFIGURATION_PS | EQOS_MAC_CONFIGURATION_FES); +} + +static inline void eqos_set_mii_speed_100(struct eqos *eqos) +{ + setbits_le32(&eqos->mac_regs->config, + EQOS_MAC_CONFIGURATION_PS | EQOS_MAC_CONFIGURATION_FES); +} + +static inline void eqos_set_mii_speed_10(struct eqos *eqos) +{ + clrsetbits_le32(&eqos->mac_regs->config, + EQOS_MAC_CONFIGURATION_FES, EQOS_MAC_CONFIGURATION_PS); +} + +void eqos_adjust_link(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + unsigned speed = edev->phydev->speed; + + if (edev->phydev->duplex) + eqos_set_full_duplex(eqos); + else + eqos_set_half_duplex(eqos); + + switch (speed) { + case SPEED_1000: + eqos_set_gmii_speed(eqos); + break; + case SPEED_100: + eqos_set_mii_speed_100(eqos); + break; + case SPEED_10: + eqos_set_mii_speed_10(eqos); + break; + default: + eqos_warn(eqos, "invalid speed %d\n", speed); + return; + } +} + +int eqos_get_ethaddr(struct eth_device *edev, unsigned char *mac) +{ + return -EOPNOTSUPP; +} + +int eqos_set_ethaddr(struct eth_device *edev, const unsigned char *mac) +{ + struct eqos *eqos = edev->priv; + __le32 mac_hi, mac_lo; + + memcpy(eqos->macaddr, mac, ETH_ALEN); + + /* Update the MAC address */ + memcpy(&mac_hi, &mac[4], 2); + memcpy(&mac_lo, &mac[0], 4); + + __raw_writel(mac_hi, &eqos->mac_regs->macaddr0hi); + __raw_writel(mac_lo, &eqos->mac_regs->macaddr0lo); + + return 0; +} + +/* Get PHY out of power saving mode. If this is needed elsewhere then + * consider making it part of phy-core and adding a resume method to + * the phy device ops. */ +static int phy_resume(struct phy_device *phydev) +{ + int bmcr; + + bmcr = phy_read(phydev, MII_BMCR); + if (bmcr < 0) + return bmcr; + + if (bmcr & BMCR_PDOWN) { + bmcr &= ~BMCR_PDOWN; + return phy_write(phydev, MII_BMCR, bmcr); + } + + return 0; +} + +int eqos_start(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + u32 val, tx_fifo_sz, rx_fifo_sz, tqs, rqs, pbl; + unsigned long last_rx_desc; + unsigned long rate; + u32 mode_set; + int ret; + int i; + + setbits_le32(&eqos->dma_regs->mode, EQOS_DMA_MODE_SWR); + + ret = readl_poll_timeout(&eqos->dma_regs->mode, mode_set, + !(mode_set & EQOS_DMA_MODE_SWR), + 100 * USEC_PER_MSEC); + if (ret) { + eqos_err(eqos, "EQOS_DMA_MODE_SWR stuck: 0x%08x\n", mode_set); + return ret; + } + + /* Reset above clears MAC address */ + eqos_set_ethaddr(edev, eqos->macaddr); + + /* Required for accurate time keeping with EEE counters */ + rate = eqos->ops->get_csr_clk_rate(eqos); + + val = (rate / USEC_PER_SEC) - 1; /* -1 because the data sheet says so */ + writel(val, &eqos->mac_regs->us_tic_counter); + + ret = phy_device_connect(edev, &eqos->miibus, eqos->phy_addr, + eqos->ops->adjust_link, 0, eqos->interface); + if (ret) + return ret; + + /* Before we reset the mac, we must insure the PHY is not powered down + * as the dw controller needs all clock domains to be running, including + * the PHY clock, to come out of a mac reset. */ + ret = phy_resume(edev->phydev); + if (ret) + return ret; + + /* Configure MTL */ + + /* Enable Store and Forward mode for TX */ + /* Program Tx operating mode */ + setbits_le32(&eqos->mtl_regs->txq0_operation_mode, + EQOS_MTL_TXQ0_OPERATION_MODE_TSF | + (EQOS_MTL_TXQ0_OPERATION_MODE_TXQEN_ENABLED << + EQOS_MTL_TXQ0_OPERATION_MODE_TXQEN_SHIFT)); + + /* Transmit Queue weight */ + writel(0x10, &eqos->mtl_regs->txq0_quantum_weight); + + /* Enable Store and Forward mode for RX, since no jumbo frame */ + setbits_le32(&eqos->mtl_regs->rxq0_operation_mode, + EQOS_MTL_RXQ0_OPERATION_MODE_RSF); + + /* Transmit/Receive queue fifo size; use all RAM for 1 queue */ + val = readl(&eqos->mac_regs->hw_feature1); + tx_fifo_sz = (val >> EQOS_MAC_HW_FEATURE1_TXFIFOSIZE_SHIFT) & + EQOS_MAC_HW_FEATURE1_TXFIFOSIZE_MASK; + rx_fifo_sz = (val >> EQOS_MAC_HW_FEATURE1_RXFIFOSIZE_SHIFT) & + EQOS_MAC_HW_FEATURE1_RXFIFOSIZE_MASK; + + /* + * r/tx_fifo_sz is encoded as log2(n / 128). Undo that by shifting. + * r/tqs is encoded as (n / 256) - 1. + */ + tqs = (128 << tx_fifo_sz) / 256 - 1; + rqs = (128 << rx_fifo_sz) / 256 - 1; + + clrsetbits_le32(&eqos->mtl_regs->txq0_operation_mode, + EQOS_MTL_TXQ0_OPERATION_MODE_TQS_MASK << + EQOS_MTL_TXQ0_OPERATION_MODE_TQS_SHIFT, + tqs << EQOS_MTL_TXQ0_OPERATION_MODE_TQS_SHIFT); + + clrsetbits_le32(&eqos->mtl_regs->rxq0_operation_mode, + EQOS_MTL_RXQ0_OPERATION_MODE_RQS_MASK << + EQOS_MTL_RXQ0_OPERATION_MODE_RQS_SHIFT, + rqs << EQOS_MTL_RXQ0_OPERATION_MODE_RQS_SHIFT); + + /* Flow control used only if each channel gets 4KB or more FIFO */ + if (rqs >= ((SZ_4K / 256) - 1)) { + u32 rfd, rfa; + + setbits_le32(&eqos->mtl_regs->rxq0_operation_mode, + EQOS_MTL_RXQ0_OPERATION_MODE_EHFC); + + /* + * Set Threshold for Activating Flow Contol space for min 2 + * frames ie, (1500 * 1) = 1500 bytes. + * + * Set Threshold for Deactivating Flow Contol for space of + * min 1 frame (frame size 1500bytes) in receive fifo + */ + if (rqs == ((SZ_4K / 256) - 1)) { + /* + * This violates the above formula because of FIFO size + * limit therefore overflow may occur inspite of this. + */ + rfd = 0x3; /* Full-3K */ + rfa = 0x1; /* Full-1.5K */ + } else if (rqs == ((SZ_8K / 256) - 1)) { + rfd = 0x6; /* Full-4K */ + rfa = 0xa; /* Full-6K */ + } else if (rqs == ((16384 / 256) - 1)) { + rfd = 0x6; /* Full-4K */ + rfa = 0x12; /* Full-10K */ + } else { + rfd = 0x6; /* Full-4K */ + rfa = 0x1E; /* Full-16K */ + } + + clrsetbits_le32(&eqos->mtl_regs->rxq0_operation_mode, + (EQOS_MTL_RXQ0_OPERATION_MODE_RFD_MASK << + EQOS_MTL_RXQ0_OPERATION_MODE_RFD_SHIFT) | + (EQOS_MTL_RXQ0_OPERATION_MODE_RFA_MASK << + EQOS_MTL_RXQ0_OPERATION_MODE_RFA_SHIFT), + (rfd << + EQOS_MTL_RXQ0_OPERATION_MODE_RFD_SHIFT) | + (rfa << + EQOS_MTL_RXQ0_OPERATION_MODE_RFA_SHIFT)); + } + + /* Configure MAC */ + + clrsetbits_le32(&eqos->mac_regs->rxq_ctrl0, + EQOS_MAC_RXQ_CTRL0_RXQ0EN_MASK << + EQOS_MAC_RXQ_CTRL0_RXQ0EN_SHIFT, + eqos->ops->config_mac << + EQOS_MAC_RXQ_CTRL0_RXQ0EN_SHIFT); + + /* Set TX flow control parameters */ + /* Set Pause Time */ + setbits_le32(&eqos->mac_regs->q0_tx_flow_ctrl, + 0xffff << EQOS_MAC_Q0_TX_FLOW_CTRL_PT_SHIFT); + /* Assign priority for TX flow control */ + clrbits_le32(&eqos->mac_regs->txq_prty_map0, + EQOS_MAC_TXQ_PRTY_MAP0_PSTQ0_MASK << + EQOS_MAC_TXQ_PRTY_MAP0_PSTQ0_SHIFT); + /* Assign priority for RX flow control */ + clrbits_le32(&eqos->mac_regs->rxq_ctrl2, + EQOS_MAC_RXQ_CTRL2_PSRQ0_MASK << + EQOS_MAC_RXQ_CTRL2_PSRQ0_SHIFT); + /* Enable flow control */ + setbits_le32(&eqos->mac_regs->q0_tx_flow_ctrl, + EQOS_MAC_Q0_TX_FLOW_CTRL_TFE); + setbits_le32(&eqos->mac_regs->rx_flow_ctrl, + EQOS_MAC_RX_FLOW_CTRL_RFE); + + clrsetbits_le32(&eqos->mac_regs->config, + EQOS_MAC_CONFIGURATION_GPSLCE | + EQOS_MAC_CONFIGURATION_WD | + EQOS_MAC_CONFIGURATION_JD | + EQOS_MAC_CONFIGURATION_JE, + EQOS_MAC_CONFIGURATION_CST | + EQOS_MAC_CONFIGURATION_ACS); + + /* Configure DMA */ + + /* Enable OSP mode */ + setbits_le32(&eqos->dma_regs->ch0_tx_control, + EQOS_DMA_CH0_TX_CONTROL_OSP); + + /* RX buffer size. Must be a multiple of bus width */ + clrsetbits_le32(&eqos->dma_regs->ch0_rx_control, + EQOS_DMA_CH0_RX_CONTROL_RBSZ_MASK << + EQOS_DMA_CH0_RX_CONTROL_RBSZ_SHIFT, + EQOS_MAX_PACKET_SIZE << + EQOS_DMA_CH0_RX_CONTROL_RBSZ_SHIFT); + + setbits_le32(&eqos->dma_regs->ch0_control, + EQOS_DMA_CH0_CONTROL_PBLX8); + + /* + * Burst length must be < 1/2 FIFO size. + * FIFO size in tqs is encoded as (n / 256) - 1. + * Each burst is n * 8 (PBLX8) * 16 (AXI width) == 128 bytes. + * Half of n * 256 is n * 128, so pbl == tqs, modulo the -1. + */ + pbl = tqs + 1; + if (pbl > 32) + pbl = 32; + clrsetbits_le32(&eqos->dma_regs->ch0_tx_control, + EQOS_DMA_CH0_TX_CONTROL_TXPBL_MASK << + EQOS_DMA_CH0_TX_CONTROL_TXPBL_SHIFT, + pbl << EQOS_DMA_CH0_TX_CONTROL_TXPBL_SHIFT); + + clrsetbits_le32(&eqos->dma_regs->ch0_rx_control, + EQOS_DMA_CH0_RX_CONTROL_RXPBL_MASK << + EQOS_DMA_CH0_RX_CONTROL_RXPBL_SHIFT, + 8 << EQOS_DMA_CH0_RX_CONTROL_RXPBL_SHIFT); + + /* DMA performance configuration */ + val = (2 << EQOS_DMA_SYSBUS_MODE_RD_OSR_LMT_SHIFT) | + EQOS_DMA_SYSBUS_MODE_EAME | EQOS_DMA_SYSBUS_MODE_BLEN16 | + EQOS_DMA_SYSBUS_MODE_BLEN8 | EQOS_DMA_SYSBUS_MODE_BLEN4; + writel(val, &eqos->dma_regs->sysbus_mode); + + /* Set up descriptors */ + + eqos->tx_currdescnum = eqos->rx_currdescnum = 0; + + for (i = 0; i < EQOS_DESCRIPTORS_RX; i++) { + struct eqos_desc *rx_desc = &eqos->rx_descs[i]; + + writel(EQOS_DESC3_BUF1V | EQOS_DESC3_OWN, &rx_desc->des3); + } + + writel(0, &eqos->dma_regs->ch0_txdesc_list_haddress); + writel((ulong)eqos->tx_descs, &eqos->dma_regs->ch0_txdesc_list_address); + writel(EQOS_DESCRIPTORS_TX - 1, + &eqos->dma_regs->ch0_txdesc_ring_length); + + writel(0, &eqos->dma_regs->ch0_rxdesc_list_haddress); + writel((ulong)eqos->rx_descs, &eqos->dma_regs->ch0_rxdesc_list_address); + writel(EQOS_DESCRIPTORS_RX - 1, + &eqos->dma_regs->ch0_rxdesc_ring_length); + + /* Enable everything */ + + setbits_le32(&eqos->mac_regs->config, + EQOS_MAC_CONFIGURATION_TE | EQOS_MAC_CONFIGURATION_RE); + + setbits_le32(&eqos->dma_regs->ch0_tx_control, + EQOS_DMA_CH0_TX_CONTROL_ST); + setbits_le32(&eqos->dma_regs->ch0_rx_control, + EQOS_DMA_CH0_RX_CONTROL_SR); + + /* TX tail pointer not written until we need to TX a packet */ + /* + * Point RX tail pointer at last descriptor. Ideally, we'd point at the + * first descriptor, implying all descriptors were available. However, + * that's not distinguishable from none of the descriptors being + * available. + */ + last_rx_desc = (ulong)&eqos->rx_descs[(EQOS_DESCRIPTORS_RX - 1)]; + writel(last_rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer); + + barrier(); + + eqos->started = true; + + return 0; +} + +void eqos_stop(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + int i; + + if (!eqos->started) + return; + + eqos->started = false; + + barrier(); + + /* Disable TX DMA */ + clrbits_le32(&eqos->dma_regs->ch0_tx_control, + EQOS_DMA_CH0_TX_CONTROL_ST); + + /* Wait for TX all packets to drain out of MTL */ + for (i = 0; i < 1000000; i++) { + u32 val = readl(&eqos->mtl_regs->txq0_debug); + u32 trcsts = (val >> EQOS_MTL_TXQ0_DEBUG_TRCSTS_SHIFT) & + EQOS_MTL_TXQ0_DEBUG_TRCSTS_MASK; + u32 txqsts = val & EQOS_MTL_TXQ0_DEBUG_TXQSTS; + if ((trcsts != 1) && (!txqsts)) + break; + } + + /* Turn off MAC TX and RX */ + clrbits_le32(&eqos->mac_regs->config, + EQOS_MAC_CONFIGURATION_TE | EQOS_MAC_CONFIGURATION_RE); + + /* Wait for all RX packets to drain out of MTL */ + for (i = 0; i < 1000000; i++) { + u32 val = readl(&eqos->mtl_regs->rxq0_debug); + u32 prxq = (val >> EQOS_MTL_RXQ0_DEBUG_PRXQ_SHIFT) & + EQOS_MTL_RXQ0_DEBUG_PRXQ_MASK; + u32 rxqsts = (val >> EQOS_MTL_RXQ0_DEBUG_RXQSTS_SHIFT) & + EQOS_MTL_RXQ0_DEBUG_RXQSTS_MASK; + if ((!prxq) && (!rxqsts)) + break; + } + + /* Turn off RX DMA */ + clrbits_le32(&eqos->dma_regs->ch0_rx_control, + EQOS_DMA_CH0_RX_CONTROL_SR); +} + +static int eqos_send(struct eth_device *edev, void *packet, int length) +{ + struct eqos *eqos = edev->priv; + struct device_d *dev = &eqos->netdev.dev; + struct eqos_desc *tx_desc; + dma_addr_t dma; + u32 des3; + int ret; + + tx_desc = &eqos->tx_descs[eqos->tx_currdescnum]; + eqos->tx_currdescnum++; + eqos->tx_currdescnum %= EQOS_DESCRIPTORS_TX; + + dma = dma_map_single(dev, packet, length, DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma)) + return -EFAULT; + + tx_desc->des0 = (unsigned long)dma; + tx_desc->des1 = 0; + tx_desc->des2 = length; + /* + * Make sure the compiler doesn't reorder the _OWN write below, before + * the writes to the rest of the descriptor. + */ + barrier(); + + writel(EQOS_DESC3_OWN | EQOS_DESC3_FD | EQOS_DESC3_LD | length, &tx_desc->des3); + writel((ulong)(tx_desc + 1), &eqos->dma_regs->ch0_txdesc_tail_pointer); + + ret = readl_poll_timeout(&tx_desc->des3, des3, + !(des3 & EQOS_DESC3_OWN), + 100 * USEC_PER_MSEC); + + dma_unmap_single(dev, dma, length, DMA_TO_DEVICE); + + if (ret == -ETIMEDOUT) + eqos_dbg(eqos, "TX timeout\n"); + + return ret; +} + +static int eqos_recv(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + struct eqos_desc *rx_desc; + void *frame; + int length; + + rx_desc = &eqos->rx_descs[eqos->rx_currdescnum]; + if (readl(&rx_desc->des3) & EQOS_DESC3_OWN) + return 0; + + frame = phys_to_virt(rx_desc->des0); + length = rx_desc->des3 & 0x7fff; + + dma_sync_single_for_cpu((unsigned long)frame, length, DMA_FROM_DEVICE); + net_receive(edev, frame, length); + dma_sync_single_for_device((unsigned long)frame, length, DMA_FROM_DEVICE); + + rx_desc->des0 = (unsigned long)frame; + rx_desc->des1 = 0; + rx_desc->des2 = 0; + /* + * Make sure that if HW sees the _OWN write below, it will see all the + * writes to the rest of the descriptor too. + */ + rx_desc->des3 |= EQOS_DESC3_BUF1V; + rx_desc->des3 |= EQOS_DESC3_OWN; + barrier(); + + writel((ulong)rx_desc, &eqos->dma_regs->ch0_rxdesc_tail_pointer); + + eqos->rx_currdescnum++; + eqos->rx_currdescnum %= EQOS_DESCRIPTORS_RX; + + return 0; +} + +static int eqos_init_resources(struct eqos *eqos) +{ + struct device_d *dev = eqos->netdev.parent; + int ret = -ENOMEM; + void *descs; + void *p; + int i; + + descs = dma_alloc_coherent(EQOS_DESCRIPTORS_SIZE, DMA_ADDRESS_BROKEN); + if (!descs) + goto err; + + eqos->tx_descs = (struct eqos_desc *)descs; + eqos->rx_descs = (eqos->tx_descs + EQOS_DESCRIPTORS_TX); + + p = dma_alloc(EQOS_DESCRIPTORS_RX * EQOS_MAX_PACKET_SIZE); + if (!p) + goto err_free_desc; + + for (i = 0; i < EQOS_DESCRIPTORS_RX; i++) { + struct eqos_desc *rx_desc = &eqos->rx_descs[i]; + dma_addr_t dma; + + dma = dma_map_single(dev, p, EQOS_MAX_PACKET_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(dev, dma)) { + ret = -EFAULT; + goto err_free_rx_bufs; + } + + rx_desc->des0 = dma; + + p += EQOS_MAX_PACKET_SIZE; + } + + return 0; + +err_free_rx_bufs: + dma_free(phys_to_virt(eqos->rx_descs[0].des0)); +err_free_desc: + dma_free_coherent(descs, 0, EQOS_DESCRIPTORS_SIZE); +err: + + return ret; +} + +static int eqos_init(struct device_d *dev, struct eqos *eqos) +{ + int ret; + + ret = eqos_init_resources(eqos); + if (ret) { + dev_err(dev, "eqos_init_resources() failed: %s\n", strerror(-ret)); + return ret; + } + + if (eqos->ops->init) + ret = eqos->ops->init(dev, eqos); + + return ret; +} + +static void eqos_probe_dt(struct device_d *dev, struct eqos *eqos) +{ + struct device_node *child; + + eqos->interface = of_get_phy_mode(dev->device_node); + eqos->phy_addr = -1; + + /* Set MDIO bus device node, if present. */ + for_each_child_of_node(dev->device_node, child) { + if (of_device_is_compatible(child, "snps,dwmac-mdio") || + (child->name && !of_node_cmp(child->name, "mdio"))) { + eqos->miibus.dev.device_node = child; + break; + } + } +} + +int eqos_probe(struct device_d *dev, const struct eqos_ops *ops, void *priv) +{ + struct mii_bus *miibus; + struct resource *iores; + struct eqos *eqos; + struct eth_device *edev; + int ret; + + eqos = xzalloc(sizeof(*eqos)); + + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) + return PTR_ERR(iores); + eqos->regs = IOMEM(iores->start); + + eqos->mac_regs = eqos->regs + EQOS_MAC_REGS_BASE; + eqos->mtl_regs = eqos->regs + EQOS_MTL_REGS_BASE; + eqos->dma_regs = eqos->regs + EQOS_DMA_REGS_BASE; + eqos->ops = ops; + eqos->priv = priv; + + eqos_probe_dt(dev, eqos); + + edev = &eqos->netdev; + + dev->priv = edev->priv = eqos; + + edev->parent = dev; + edev->open = ops->start; + edev->send = eqos_send; + edev->recv = eqos_recv; + edev->halt = ops->stop; + edev->get_ethaddr = ops->get_ethaddr; + edev->set_ethaddr = ops->set_ethaddr; + + miibus = &eqos->miibus; + miibus->parent = edev->parent; + miibus->read = eqos_mdio_read; + miibus->write = eqos_mdio_write; + miibus->priv = eqos; + + ret = eqos_init(dev, eqos); + if (ret) + return ret; + + ret = mdiobus_register(miibus); + if (ret) + return ret; + + return eth_register(edev); +} + +void eqos_remove(struct device_d *dev) +{ + struct eqos *eqos = dev->priv; + + mdiobus_unregister(&eqos->miibus); + + dma_free(phys_to_virt(eqos->rx_descs[0].des0)); + dma_free_coherent(eqos->tx_descs, 0, EQOS_DESCRIPTORS_SIZE); +} diff --git a/drivers/net/designware_eqos.h b/drivers/net/designware_eqos.h new file mode 100644 index 0000000000..969a524c0a --- /dev/null +++ b/drivers/net/designware_eqos.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 Ahmad Fatoum, Pengutronix + */ + +#ifndef __EQOS_H_ +#define __EQOS_H_ + +struct eqos; +struct eth_device; + +struct eqos_ops { + int (*init)(struct device_d *dev, struct eqos *priv); + int (*start)(struct eth_device *edev); + void (*stop)(struct eth_device *edev); + int (*get_ethaddr)(struct eth_device *dev, unsigned char *mac); + int (*set_ethaddr)(struct eth_device *edev, const unsigned char *mac); + void (*adjust_link)(struct eth_device *edev); + unsigned long (*get_csr_clk_rate)(struct eqos *); + + bool enh_desc; + int mdio_wait_us; + +#define EQOS_MAC_RXQ_CTRL0_RXQ0EN_SHIFT 0 +#define EQOS_MAC_RXQ_CTRL0_RXQ0EN_MASK 3 +#define EQOS_MAC_RXQ_CTRL0_RXQ0EN_NOT_ENABLED 0 +#define EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_DCB 2 +#define EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_AV 1 + unsigned clk_csr; + +#define EQOS_MDIO_ADDR_CR_20_35 2 +#define EQOS_MDIO_ADDR_CR_250_300 5 +#define EQOS_MDIO_ADDR_SKAP BIT(4) +#define EQOS_MDIO_ADDR_GOC_SHIFT 2 +#define EQOS_MDIO_ADDR_GOC_READ 3 +#define EQOS_MDIO_ADDR_GOC_WRITE 1 +#define EQOS_MDIO_ADDR_C45E BIT(1) + unsigned config_mac; +}; + +struct eqos_desc; +struct eqos_dma_regs; +struct eqos_mac_regs; +struct eqos_mtl_regs; + +struct eqos { + struct eth_device netdev; + struct mii_bus miibus; + + u8 macaddr[6]; + + u32 tx_currdescnum, rx_currdescnum; + + struct eqos_desc *tx_descs, *rx_descs; + + void __iomem *regs; + struct eqos_mac_regs __iomem *mac_regs; + struct eqos_dma_regs __iomem *dma_regs; + struct eqos_mtl_regs __iomem *mtl_regs; + + int phy_addr; + phy_interface_t interface; + + const struct eqos_ops *ops; + void *priv; + bool started; +}; + +struct device_d; +int eqos_probe(struct device_d *dev, const struct eqos_ops *ops, void *priv); +void eqos_remove(struct device_d *dev); +int eqos_reset(struct eqos *priv); + +int eqos_get_ethaddr(struct eth_device *edev, unsigned char *mac); +int eqos_set_ethaddr(struct eth_device *edev, const unsigned char *mac); +int eqos_start(struct eth_device *edev); +void eqos_stop(struct eth_device *edev); +void eqos_adjust_link(struct eth_device *edev); + +#define eqos_dbg(eqos, ...) dev_dbg(&eqos->netdev.dev, __VA_ARGS__) +#define eqos_warn(eqos, ...) dev_warn(&eqos->netdev.dev, __VA_ARGS__) +#define eqos_err(eqos, ...) dev_err(&eqos->netdev.dev, __VA_ARGS__) + +#endif diff --git a/drivers/net/designware_stm32.c b/drivers/net/designware_stm32.c new file mode 100644 index 0000000000..5b087ad5a3 --- /dev/null +++ b/drivers/net/designware_stm32.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * Copyright (c) 2019, Ahmad Fatoum, Pengutronix + * + * Portions based on U-Boot's rtl8169.c and dwc_eth_qos. + */ + +#include +#include +#include +#include +#include + +#include "designware_eqos.h" + +#define SYSCFG_PMCR_ETH_CLK_SEL BIT(16) +#define SYSCFG_PMCR_ETH_REF_CLK_SEL BIT(17) + +/* Ethernet PHY interface selection in register SYSCFG Configuration + *------------------------------------------ + * src |BIT(23)| BIT(22)| BIT(21)|BIT(20)| + *------------------------------------------ + * MII | 0 | 0 | 0 | 1 | + *------------------------------------------ + * GMII | 0 | 0 | 0 | 0 | + *------------------------------------------ + * RGMII | 0 | 0 | 1 | n/a | + *------------------------------------------ + * RMII | 1 | 0 | 0 | n/a | + *------------------------------------------ + */ +#define SYSCFG_PMCR_ETH_SEL_MII BIT(20) +#define SYSCFG_PMCR_ETH_SEL_RGMII BIT(21) +#define SYSCFG_PMCR_ETH_SEL_RMII BIT(23) +#define SYSCFG_PMCR_ETH_SEL_GMII 0 +#define SYSCFG_MCU_ETH_SEL_MII 0 +#define SYSCFG_MCU_ETH_SEL_RMII 1 + +/* Descriptors */ + +#define SYSCFG_MCU_ETH_MASK BIT(23) +#define SYSCFG_MP1_ETH_MASK GENMASK(23, 16) +#define SYSCFG_PMCCLRR_OFFSET 0x40 + +struct eqos_stm32 { + struct clk_bulk_data *clks; + int num_clks; + struct regmap *regmap; + u32 mode_reg; + int eth_clk_sel_reg; + int eth_ref_clk_sel_reg; +}; + +static inline struct eqos_stm32 *to_stm32(struct eqos *eqos) +{ + return eqos->priv; +} + +enum { CLK_STMMACETH, CLK_MAX_RX, CLK_MAX_TX, CLK_SYSCFG, }; +static const struct clk_bulk_data stm32_clks[] = { + [CLK_STMMACETH] = { .id = "stmmaceth" }, + [CLK_MAX_RX] = { .id = "mac-clk-rx" }, + [CLK_MAX_TX] = { .id = "mac-clk-tx" }, + [CLK_SYSCFG] = { .id = "syscfg-clk" }, +}; + +static unsigned long eqos_get_csr_clk_rate_stm32(struct eqos *eqos) +{ + return clk_get_rate(to_stm32(eqos)->clks[CLK_STMMACETH].clk); +} + +static int eqos_set_mode_stm32(struct eqos_stm32 *priv, phy_interface_t interface) +{ + u32 val, reg = priv->mode_reg; + int ret; + + switch (interface) { + case PHY_INTERFACE_MODE_MII: + val = SYSCFG_PMCR_ETH_SEL_MII; + break; + case PHY_INTERFACE_MODE_GMII: + val = SYSCFG_PMCR_ETH_SEL_GMII; + if (priv->eth_clk_sel_reg) + val |= SYSCFG_PMCR_ETH_CLK_SEL; + break; + case PHY_INTERFACE_MODE_RMII: + val = SYSCFG_PMCR_ETH_SEL_RMII; + if (priv->eth_ref_clk_sel_reg) + val |= SYSCFG_PMCR_ETH_REF_CLK_SEL; + break; + case PHY_INTERFACE_MODE_RGMII: + case PHY_INTERFACE_MODE_RGMII_ID: + case PHY_INTERFACE_MODE_RGMII_RXID: + case PHY_INTERFACE_MODE_RGMII_TXID: + val = SYSCFG_PMCR_ETH_SEL_RGMII; + if (priv->eth_clk_sel_reg) + val |= SYSCFG_PMCR_ETH_CLK_SEL; + break; + default: + return -EINVAL; + } + + /* Need to update PMCCLRR (clear register) */ + ret = regmap_write(priv->regmap, reg + SYSCFG_PMCCLRR_OFFSET, + SYSCFG_MP1_ETH_MASK); + if (ret) + return -EIO; + + /* Update PMCSETR (set register) */ + regmap_update_bits(priv->regmap, reg, GENMASK(23, 16), val); + + return 0; +} + +static int eqos_init_stm32(struct device_d *dev, struct eqos *eqos) +{ + struct device_node *np = dev->device_node; + struct eqos_stm32 *priv = to_stm32(eqos); + struct clk_bulk_data *eth_ck; + int ret; + + /* Gigabit Ethernet 125MHz clock selection. */ + priv->eth_clk_sel_reg = of_property_read_bool(np, "st,eth-clk-sel"); + + /* Ethernet 50Mhz RMII clock selection */ + priv->eth_ref_clk_sel_reg = + of_property_read_bool(np, "st,eth-ref-clk-sel"); + + priv->regmap = syscon_regmap_lookup_by_phandle(dev->device_node, + "st,syscon"); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Could not get st,syscon node\n"); + return PTR_ERR(priv->regmap); + } + + ret = of_property_read_u32_index(dev->device_node, "st,syscon", + 1, &priv->mode_reg); + if (ret) { + dev_err(dev, "Can't get sysconfig mode offset (%s)\n", + strerror(-ret)); + return -EINVAL; + } + + ret = eqos_set_mode_stm32(priv, eqos->interface); + if (ret) + dev_warn(dev, "Configuring syscfg failed: %s\n", strerror(-ret)); + + priv->num_clks = ARRAY_SIZE(stm32_clks) + 1; + priv->clks = xmalloc(priv->num_clks * sizeof(*priv->clks)); + memcpy(priv->clks, stm32_clks, sizeof stm32_clks); + + ret = clk_bulk_get(dev, ARRAY_SIZE(stm32_clks), priv->clks); + if (ret) { + dev_err(dev, "Failed to get clks: %s\n", strerror(-ret)); + return ret; + } + + eth_ck = &priv->clks[ARRAY_SIZE(stm32_clks)]; + eth_ck->id = "eth-ck"; + eth_ck->clk = clk_get(dev, eth_ck->id); + if (IS_ERR(eth_ck->clk)) { + priv->num_clks--; + dev_dbg(dev, "No phy clock provided. Continuing without.\n"); + } + + return 0; + +} + +static int eqos_start_stm32(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + struct eqos_stm32 *priv = to_stm32(eqos); + int ret; + + ret = clk_bulk_enable(priv->num_clks, priv->clks); + if (ret < 0) { + eqos_err(eqos, "clk_bulk_enable() failed: %s\n", + strerror(-ret)); + return ret; + } + + udelay(10); + + ret = eqos_start(edev); + if (ret) + goto err_stop_clks; + + return 0; + +err_stop_clks: + clk_bulk_disable(priv->num_clks, priv->clks); + + return ret; +} + +static void eqos_stop_stm32(struct eth_device *edev) +{ + struct eqos_stm32 *priv = to_stm32(edev->priv); + + clk_bulk_disable(priv->num_clks, priv->clks); +} + +// todo split! +static struct eqos_ops stm32_ops = { + .init = eqos_init_stm32, + .get_ethaddr = eqos_get_ethaddr, + .set_ethaddr = eqos_set_ethaddr, + .start = eqos_start_stm32, + .stop = eqos_stop_stm32, + .adjust_link = eqos_adjust_link, + .get_csr_clk_rate = eqos_get_csr_clk_rate_stm32, + + .mdio_wait_us = 10 * USEC_PER_MSEC, + .clk_csr = EQOS_MDIO_ADDR_CR_250_300, + .config_mac = EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_AV, +}; + +static int eqos_probe_stm32(struct device_d *dev) +{ + return eqos_probe(dev, &stm32_ops, xzalloc(sizeof(struct eqos_stm32))); +} + +static void eqos_remove_stm32(struct device_d *dev) +{ + struct eqos_stm32 *priv = to_stm32(dev->priv); + + eqos_remove(dev); + + clk_bulk_put(priv->num_clks, priv->clks); +} + +static const struct of_device_id eqos_stm32_ids[] = { + { .compatible = "st,stm32mp1-dwmac" }, + { /* sentinel */ } +}; + +static struct driver_d eqos_stm32_driver = { + .name = "eqos-stm32", + .probe = eqos_probe_stm32, + .remove = eqos_remove_stm32, + .of_compatible = DRV_OF_COMPAT(eqos_stm32_ids), +}; +device_platform_driver(eqos_stm32_driver); -- cgit v1.2.3 From ec80320aee33ce47ea3b758ecc3623548e2e1aa1 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 22:39:41 +0100 Subject: net: eqos: extend Designware Ethernet QoS for Tegra 186 support The U-Boot dwc_eth_qos driver ported in the previous commit had support for both the Tegra 186/194 and STM32MP variants of the EQOS IP. The barebox Tegra supported doesn't include the 186, but as the driver was nevertheless ported along with the rest, lets include it in the source tree. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/net/Kconfig | 6 + drivers/net/Makefile | 1 + drivers/net/designware_tegra186.c | 347 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 drivers/net/designware_tegra186.c diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 5b66fe84eb..62e522a302 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -104,6 +104,12 @@ config DRIVER_NET_DESIGNWARE_STM32 help This option enables support for the ethernet MAC on the STM32MP platforms. +config DRIVER_NET_DESIGNWARE_TEGRA186 + bool "Designware Universal MAC ethernet driver for Tegra 186 platforms" + select RESET_CONTROLLER + help + This option enables support for the ethernet MAC on the Tegra186 & 194. + endif config DRIVER_NET_DM9K diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 7ecf4e2851..656d45a868 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_DRIVER_NET_DESIGNWARE_GENERIC) += designware_generic.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_SOCFPGA) += designware_socfpga.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_EQOS) += designware_eqos.o obj-$(CONFIG_DRIVER_NET_DESIGNWARE_STM32) += designware_stm32.o +obj-$(CONFIG_DRIVER_NET_DESIGNWARE_TEGRA186) += designware_tegra186.o obj-$(CONFIG_DRIVER_NET_DM9K) += dm9k.o obj-$(CONFIG_DRIVER_NET_E1000) += e1000/regio.o e1000/main.o e1000/eeprom.o obj-$(CONFIG_DRIVER_NET_ENC28J60) += enc28j60.o diff --git a/drivers/net/designware_tegra186.c b/drivers/net/designware_tegra186.c new file mode 100644 index 0000000000..58484d4095 --- /dev/null +++ b/drivers/net/designware_tegra186.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016, NVIDIA CORPORATION. + * Copyright (c) 2019, Ahmad Fatoum, Pengutronix + * + * Portions based on U-Boot's rtl8169.c and dwc_eth_qos. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "designware_eqos.h" + +/* These registers are Tegra186-specific */ +#define EQOS_TEGRA186_REGS_BASE 0x8800 +struct eqos_tegra186_regs { + uint32_t sdmemcomppadctrl; /* 0x8800 */ + uint32_t auto_cal_config; /* 0x8804 */ + uint32_t unused_8808; /* 0x8808 */ + uint32_t auto_cal_status; /* 0x880c */ +}; + +#define EQOS_SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD BIT(31) + +#define EQOS_AUTO_CAL_CONFIG_START BIT(31) +#define EQOS_AUTO_CAL_CONFIG_ENABLE BIT(29) + +#define EQOS_AUTO_CAL_STATUS_ACTIVE BIT(31) + +struct eqos_tegra186 { + struct clk_bulk_data *clks; + int num_clks; + struct reset_control *rst; + struct eqos_tegra186_regs __iomem *tegra186_regs; + int phy_reset_gpio; +}; + +static inline struct eqos_tegra186 *to_tegra186(struct eqos *eqos) +{ + return eqos->priv; +} + +enum { CLK_SLAVE_BUS, CLK_MASTER_BUS, CLK_RX, CLK_PTP_REF, CLK_TX }; +static const struct clk_bulk_data tegra186_clks[] = { + [CLK_SLAVE_BUS] = { .id = "slave_bus" }, + [CLK_MASTER_BUS] = { .id = "master_bus" }, + [CLK_RX] = { .id = "rx" }, + [CLK_PTP_REF] = { .id = "ptp_ref" }, + [CLK_TX] = { .id = "tx" }, +}; + +static int eqos_clks_set_rate_tegra186(struct eqos_tegra186 *priv) +{ + return clk_set_rate(priv->clks[CLK_PTP_REF].clk, 125 * 1000 * 1000); +} + +static int eqos_reset_tegra186(struct eqos_tegra186 *priv, bool reset) +{ + int ret; + + if (reset) { + reset_control_assert(priv->rst); + gpio_set_value(priv->phy_reset_gpio, 1); + return 0; + } + + gpio_set_value(priv->phy_reset_gpio, 1); + + udelay(2); + + gpio_set_value(priv->phy_reset_gpio, 0); + + ret = reset_control_assert(priv->rst); + if (ret < 0) + return ret; + + udelay(2); + + return reset_control_deassert(priv->rst); +} + +static int eqos_calibrate_pads_tegra186(struct eqos *eqos) +{ + struct eqos_tegra186 *priv = to_tegra186(eqos); + u32 active; + int ret; + + setbits_le32(&priv->tegra186_regs->sdmemcomppadctrl, + EQOS_SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD); + + udelay(1); + + setbits_le32(&priv->tegra186_regs->auto_cal_config, + EQOS_AUTO_CAL_CONFIG_START | EQOS_AUTO_CAL_CONFIG_ENABLE); + + ret = readl_poll_timeout(&priv->tegra186_regs->auto_cal_status, active, + active & EQOS_AUTO_CAL_STATUS_ACTIVE, + 10000); + if (ret) { + eqos_err(eqos, "calibrate didn't start\n"); + goto failed; + } + + ret = readl_poll_timeout(&priv->tegra186_regs->auto_cal_status, active, + !(active & EQOS_AUTO_CAL_STATUS_ACTIVE), + 10000); + if (ret) { + eqos_err(eqos, "calibrate didn't finish\n"); + goto failed; + } + +failed: + clrbits_le32(&priv->tegra186_regs->sdmemcomppadctrl, + EQOS_SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD); + + return ret; +} + +static int eqos_calibrate_link_tegra186(struct eqos *eqos, unsigned speed) +{ + struct eqos_tegra186 *priv = to_tegra186(eqos); + int ret = 0; + unsigned long rate; + bool calibrate; + + switch (speed) { + case SPEED_1000: + rate = 125 * 1000 * 1000; + calibrate = true; + break; + case SPEED_100: + rate = 25 * 1000 * 1000; + calibrate = true; + break; + case SPEED_10: + rate = 2.5 * 1000 * 1000; + calibrate = false; + break; + default: + return -EINVAL; + } + + if (calibrate) { + ret = eqos_calibrate_pads_tegra186(eqos); + if (ret) + return ret; + } else { + clrbits_le32(&priv->tegra186_regs->auto_cal_config, + EQOS_AUTO_CAL_CONFIG_ENABLE); + } + + ret = clk_set_rate(priv->clks[CLK_TX].clk, rate); + if (ret < 0) { + eqos_err(eqos, "clk_set_rate(tx_clk, %lu) failed: %d\n", rate, ret); + return ret; + } + + return 0; +} + +static unsigned long eqos_get_csr_clk_rate_tegra186(struct eqos *eqos) +{ + return clk_get_rate(to_tegra186(eqos)->clks[CLK_SLAVE_BUS].clk); +} + +static int eqos_set_ethaddr_tegra186(struct eth_device *edev, const unsigned char *mac) +{ + struct eqos *eqos = edev->priv; + + /* + * This function may be called before start() or after stop(). At that + * time, on at least some configurations of the EQoS HW, all clocks to + * the EQoS HW block will be stopped, and a reset signal applied. If + * any register access is attempted in this state, bus timeouts or CPU + * hangs may occur. This check prevents that. + * + * A simple solution to this problem would be to not implement + * write_hwaddr(), since start() always writes the MAC address into HW + * anyway. However, it is desirable to implement write_hwaddr() to + * support the case of SW that runs subsequent to U-Boot which expects + * the MAC address to already be programmed into the EQoS registers, + * which must happen irrespective of whether the U-Boot user (or + * scripts) actually made use of the EQoS device, and hence + * irrespective of whether start() was ever called. + * + * Note that this requirement by subsequent SW is not valid for + * Tegra186, and is likely not valid for any non-PCI instantiation of + * the EQoS HW block. This function is implemented solely as + * future-proofing with the expectation the driver will eventually be + * ported to some system where the expectation above is true. + */ + + if (!eqos->started) { + memcpy(eqos->macaddr, mac, 6); + return 0; + } + + return eqos_set_ethaddr(edev, mac); +} + +static int eqos_init_tegra186(struct device_d *dev, struct eqos *eqos) +{ + struct eqos_tegra186 *priv = to_tegra186(eqos); + int phy_reset; + int ret; + + priv->tegra186_regs = IOMEM(eqos->regs + EQOS_TEGRA186_REGS_BASE); + + priv->rst = reset_control_get(dev, "eqos"); + if (IS_ERR(priv->rst)) { + ret = PTR_ERR(priv->rst); + dev_err(dev, "reset_get_by_name(rst) failed: %s\n", strerror(-ret)); + return ret; + } + + phy_reset = of_get_named_gpio(dev->device_node, "phy-reset-gpios", 0); + if (gpio_is_valid(phy_reset)) { + ret = gpio_request(phy_reset, "phy-reset"); + if (ret) + goto release_res; + + priv->phy_reset_gpio = phy_reset; + } + + priv->clks = xmemdup(tegra186_clks, sizeof(tegra186_clks)); + priv->num_clks = ARRAY_SIZE(tegra186_clks); + + return 0; + +release_res: + reset_control_put(priv->rst); + return ret; +} + +static int eqos_start_tegra186(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + struct eqos_tegra186 *priv = to_tegra186(eqos); + int ret; + + ret = clk_bulk_enable(priv->num_clks, priv->clks); + if (ret < 0) { + eqos_err(eqos, "clk_bulk_enable() failed: %s\n", strerror(-ret)); + return ret; + } + + ret = eqos_clks_set_rate_tegra186(priv); + if (ret < 0) { + eqos_err(eqos, "clks_set_rate() failed: %s\n", strerror(-ret)); + goto err; + } + + eqos_reset_tegra186(priv, false); + if (ret < 0) { + eqos_err(eqos, "reset(0) failed: %s\n", strerror(-ret)); + goto err_stop_clks; + } + + udelay(10); + + ret = eqos_start(edev); + if (ret) + goto err_stop_resets; + + return 0; + +err_stop_resets: + eqos_reset_tegra186(priv, true); +err_stop_clks: + clk_bulk_disable(priv->num_clks, priv->clks); +err: + return ret; +} + + +static void eqos_stop_tegra186(struct eth_device *edev) +{ + struct eqos_tegra186 *priv = to_tegra186(edev->priv); + + eqos_reset_tegra186(priv, true); + + clk_bulk_disable(priv->num_clks, priv->clks); +} + +static void eqos_adjust_link_tegra186(struct eth_device *edev) +{ + struct eqos *eqos = edev->priv; + unsigned speed = edev->phydev->speed; + int ret; + + eqos_adjust_link(edev); + + ret = eqos_calibrate_link_tegra186(eqos, speed); + if (ret < 0) { + eqos_err(eqos, "eqos_calibrate_link_tegra186() failed: %d\n", ret); + return; + } +} + +static const struct eqos_ops tegra186_ops = { + .init = eqos_init_tegra186, + .get_ethaddr = eqos_get_ethaddr, + .set_ethaddr = eqos_set_ethaddr_tegra186, + .start = eqos_start_tegra186, + .stop = eqos_stop_tegra186, + .adjust_link = eqos_adjust_link_tegra186, + .get_csr_clk_rate = eqos_get_csr_clk_rate_tegra186, + + .mdio_wait_us = 10, + .clk_csr = EQOS_MDIO_ADDR_CR_20_35, + .config_mac = EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_DCB, +}; + +static int eqos_probe_tegra186(struct device_d *dev) +{ + return eqos_probe(dev, &tegra186_ops, xzalloc(sizeof(struct eqos_tegra186))); +} + +static void eqos_remove_tegra186(struct device_d *dev) +{ + struct eqos_tegra186 *priv = to_tegra186(dev->priv); + + eqos_remove(dev); + + clk_bulk_put(priv->num_clks, priv->clks); + + gpio_free(priv->phy_reset_gpio); + reset_control_put(priv->rst); +} + +static const struct of_device_id eqos_tegra186_ids[] = { + { .compatible = "nvidia,tegra186-eqos" }, + { /* sentinel */ } +}; + +static struct driver_d eqos_tegra186_driver = { + .name = "eqos-tegra186", + .probe = eqos_probe_tegra186, + .remove = eqos_remove_tegra186, + .of_compatible = DRV_OF_COMPAT(eqos_tegra186_ids), +}; +device_platform_driver(eqos_tegra186_driver); -- cgit v1.2.3 From b62c75831905d38c2dea3259d6b04068a80f140d Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 22:39:42 +0100 Subject: ARM: stm32mp: include new drivers in defconfig We've had some new drivers added for the STM32MP since the defconfig was first added. Include the newcomers. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/configs/stm32mp_defconfig | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/arch/arm/configs/stm32mp_defconfig b/arch/arm/configs/stm32mp_defconfig index 9f30bb1caa..292d7751e1 100644 --- a/arch/arm/configs/stm32mp_defconfig +++ b/arch/arm/configs/stm32mp_defconfig @@ -4,7 +4,6 @@ CONFIG_THUMB2_BAREBOX=y CONFIG_ARM_BOARD_APPEND_ATAG=y CONFIG_ARM_OPTIMZED_STRING_FUNCTIONS=y CONFIG_ARM_UNWIND=y -CONFIG_ARM_PSCI=y CONFIG_MMU=y CONFIG_MALLOC_SIZE=0x0 CONFIG_MALLOC_TLSF=y @@ -23,10 +22,12 @@ CONFIG_BLSPEC=y CONFIG_CONSOLE_ACTIVATE_NONE=y CONFIG_CONSOLE_ALLOW_COLOR=y CONFIG_PBL_CONSOLE=y -CONFIG_PARTITION=y +CONFIG_PARTITION_DISK_EFI=y +# CONFIG_PARTITION_DISK_EFI_GPT_NO_FORCE is not set +# CONFIG_PARTITION_DISK_EFI_GPT_COMPARE is not set CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW=y CONFIG_RESET_SOURCE=y -CONFIG_DEBUG_INITCALLS=y +CONFIG_MACHINE_ID=y CONFIG_CMD_DMESG=y CONFIG_LONGHELP=y CONFIG_CMD_IOMEM=y @@ -82,19 +83,31 @@ CONFIG_NET=y CONFIG_NET_NETCONSOLE=y CONFIG_OFDEVICE=y CONFIG_OF_BAREBOX_DRIVERS=y +CONFIG_DRIVER_SERIAL_STM32=y CONFIG_DRIVER_NET_DESIGNWARE=y CONFIG_DRIVER_NET_DESIGNWARE_GENERIC=y +CONFIG_DRIVER_NET_DESIGNWARE_EQOS=y +CONFIG_DRIVER_NET_DESIGNWARE_STM32=y CONFIG_AT803X_PHY=y CONFIG_MICREL_PHY=y +CONFIG_REALTEK_PHY=y # CONFIG_SPI is not set +CONFIG_I2C=y +CONFIG_I2C_STM32=y +CONFIG_MCI=y +CONFIG_MCI_STARTUP=y +CONFIG_MCI_STM32_SDMMC2=y CONFIG_LED=y CONFIG_LED_GPIO=y CONFIG_LED_GPIO_OF=y CONFIG_WATCHDOG=y CONFIG_WATCHDOG_POLLER=y CONFIG_STM32_IWDG_WATCHDOG=y +CONFIG_NVMEM=y +CONFIG_STM32_BSEC=y CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED=y +CONFIG_RESET_STM32=y CONFIG_FS_EXT4=y CONFIG_FS_TFTP=y CONFIG_FS_NFS=y @@ -103,3 +116,4 @@ CONFIG_FS_FAT_WRITE=y CONFIG_FS_FAT_LFN=y CONFIG_ZLIB=y CONFIG_CRC8=y +CONFIG_DIGEST_SHA1_ARM=y -- cgit v1.2.3 From cc5c3ad060a219cb8c2964946f1bae67f712e262 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Mon, 28 Oct 2019 22:39:43 +0100 Subject: Documentation: boards: stm32mp: remove done TODOs With SD/MMC controller and Ethernet MAC support merged, we now have usable STM32MP support. Remove the text that suggested otherwise. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- Documentation/boards/stm32mp.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Documentation/boards/stm32mp.rst b/Documentation/boards/stm32mp.rst index 774ede6e56..d4429aae27 100644 --- a/Documentation/boards/stm32mp.rst +++ b/Documentation/boards/stm32mp.rst @@ -1,11 +1,6 @@ STMicroelectronics STM32MP ========================== -.. note:: - - Support for the STM32MP architecure in barebox is still in progress. - Bootstrapping an OS from mainline barebox is not yet supported. - The STM32MP is a line of 32-bit ARM SoCs. They reuse peripherals of the STM32 line of microcontrollers and can have a STM32 MCU embedded as co-processor as well. @@ -67,9 +62,3 @@ An appropriate image for the boot media can be generated with following } Image can then be flashed on e.g. a SD-Card. - -TODO ----- - -* Extend barebox MMCI support to support the SDMMC2 -* Extend barebox DesignWare MAC support to support the stmmac -- cgit v1.2.3 From d510bd6e5746dcba23c11fd03494c2f9114e0ac7 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 30 Oct 2019 10:27:31 +0100 Subject: Documentation: boards: stm32mp: document boot mode strap pins The evaluation kits have 2P DIP-Switches for boot mode selection. Document them. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- Documentation/boards/stm32mp.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Documentation/boards/stm32mp.rst b/Documentation/boards/stm32mp.rst index d4429aae27..f93ec04eb0 100644 --- a/Documentation/boards/stm32mp.rst +++ b/Documentation/boards/stm32mp.rst @@ -62,3 +62,22 @@ An appropriate image for the boot media can be generated with following } Image can then be flashed on e.g. a SD-Card. + +Boot source selection +--------------------- + +The STM32MP BootROM samples three boot pins at reset. Usually BOOT1 is +pulled down and BOOT0 and BOOT2 are connected to a 2P DIP switch:: + + +-------+ + BOOT2 | O O-- | + BOOT0 | N O-- | <---- SDMMC + +-------+ + +-------+ + BOOT2 | O O-- | + BOOT0 | N --O | <---- QSPI-NOR Flash + +-------+ + +-------+ + BOOT2 | O --O | + BOOT0 | N --O | <---- DFU on UART and USB OTG + +-------+ -- cgit v1.2.3 From 764cc5cb7659c06f5818d95d2e20257006aefb0a Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:48 +0100 Subject: regulator: copy upstream struct regulator_desc documentation This imports the regulator_desc documentation for the fields barebox uses out of Linux v5.4-rc1. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- include/regulator.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/include/regulator.h b/include/regulator.h index cd1d3ccf55..28ae25652a 100644 --- a/include/regulator.h +++ b/include/regulator.h @@ -4,6 +4,34 @@ /* struct regulator is an opaque object for consumers */ struct regulator; +/** + * struct regulator_desc - Static regulator descriptor + * + * Each regulator registered with the core is described with a + * structure of this type and a struct regulator_config. This + * structure contains the non-varying parts of the regulator + * description. + * + * @n_voltages: Number of selectors available for ops.list_voltage(). + * @ops: Regulator operations table. + * + * @min_uV: Voltage given by the lowest selector (if linear mapping) + * @uV_step: Voltage increase with each selector (if linear mapping) + * @linear_min_sel: Minimal selector for starting linear mapping + * + * @vsel_reg: Register for selector when using regulator_regmap_X_voltage_ + * @vsel_mask: Mask for register bitfield used for selector + * @apply_reg: Register for initiate voltage change on the output when + * using regulator_set_voltage_sel_regmap + * @apply_bit: Register bitfield used for initiate voltage change on the + * output when using regulator_set_voltage_sel_regmap + * @enable_reg: Register for control when using regmap enable/disable ops + * @enable_mask: Mask for control when using regmap enable/disable ops + * @enable_val: Enabling value for control when using regmap enable/disable ops + * @disable_val: Disabling value for control when using regmap enable/disable ops + * @enable_is_inverted: A flag to indicate set enable_mask bits to disable + * when using regulator_enable_regmap and friends APIs. + */ struct regulator_desc { unsigned n_voltages; const struct regulator_ops *ops; -- cgit v1.2.3 From e0e52b83307ec037912c5c9e762ce6e1ea3b1740 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:49 +0100 Subject: regulator: port Linux of_regulator_match Linux regulator drivers like the pfuze and the incoming stpmic make use of of_regulator_match to parse regulator init data from the device tree. Port the function over from Linux v5.3, so drivers depending on it can follow. As we have no use in barebox for power saving and suspend states, these parts are omitted. Signed-off-by: Oleksij Rempel Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/regulator/Makefile | 1 + drivers/regulator/of_regulator.c | 229 +++++++++++++++++++++++++++++++++ include/linux/regulator/machine.h | 207 +++++++++++++++++++++++++++++ include/linux/regulator/of_regulator.h | 48 +++++++ 4 files changed, 485 insertions(+) create mode 100644 drivers/regulator/of_regulator.c create mode 100644 include/linux/regulator/machine.h create mode 100644 include/linux/regulator/of_regulator.h diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index b2fc5b79b6..f51e89cd3c 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_REGULATOR) += core.o helpers.o +obj-$(CONFIG_OFDEVICE) += of_regulator.o obj-$(CONFIG_REGULATOR_FIXED) += fixed.o obj-$(CONFIG_REGULATOR_BCM283X) += bcm2835.o obj-$(CONFIG_REGULATOR_PFUZE) += pfuze.o diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c new file mode 100644 index 0000000000..3e8caa8710 --- /dev/null +++ b/drivers/regulator/of_regulator.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OF helpers for regulator framework + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Rajendra Nayak + */ + +#include +#include +#include +#include + +static int of_get_regulation_constraints(struct device_d *dev, + struct device_node *np, + struct regulator_init_data **init_data, + const struct regulator_desc *desc) +{ + struct regulation_constraints *constraints = &(*init_data)->constraints; + int ret; + u32 pval; + + constraints->name = of_get_property(np, "regulator-name", NULL); + + if (!of_property_read_u32(np, "regulator-min-microvolt", &pval)) + constraints->min_uV = pval; + + if (!of_property_read_u32(np, "regulator-max-microvolt", &pval)) + constraints->max_uV = pval; + + /* Voltage change possible? */ + if (constraints->min_uV != constraints->max_uV) + constraints->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE; + + /* Do we have a voltage range, if so try to apply it? */ + if (constraints->min_uV && constraints->max_uV) + constraints->apply_uV = true; + + if (!of_property_read_u32(np, "regulator-microvolt-offset", &pval)) + constraints->uV_offset = pval; + if (!of_property_read_u32(np, "regulator-min-microamp", &pval)) + constraints->min_uA = pval; + if (!of_property_read_u32(np, "regulator-max-microamp", &pval)) + constraints->max_uA = pval; + + if (!of_property_read_u32(np, "regulator-input-current-limit-microamp", + &pval)) + constraints->ilim_uA = pval; + + /* Current change possible? */ + if (constraints->min_uA != constraints->max_uA) + constraints->valid_ops_mask |= REGULATOR_CHANGE_CURRENT; + + constraints->boot_on = of_property_read_bool(np, "regulator-boot-on"); + constraints->always_on = of_property_read_bool(np, "regulator-always-on"); + if (!constraints->always_on) /* status change should be possible. */ + constraints->valid_ops_mask |= REGULATOR_CHANGE_STATUS; + + constraints->pull_down = of_property_read_bool(np, "regulator-pull-down"); + + if (of_property_read_bool(np, "regulator-allow-bypass")) + constraints->valid_ops_mask |= REGULATOR_CHANGE_BYPASS; + + if (of_property_read_bool(np, "regulator-allow-set-load")) + constraints->valid_ops_mask |= REGULATOR_CHANGE_DRMS; + + ret = of_property_read_u32(np, "regulator-ramp-delay", &pval); + if (!ret) { + if (pval) + constraints->ramp_delay = pval; + else + constraints->ramp_disable = true; + } + + ret = of_property_read_u32(np, "regulator-settling-time-us", &pval); + if (!ret) + constraints->settling_time = pval; + + ret = of_property_read_u32(np, "regulator-settling-time-up-us", &pval); + if (!ret) + constraints->settling_time_up = pval; + if (constraints->settling_time_up && constraints->settling_time) { + pr_warn("%pOFn: ambiguous configuration for settling time, ignoring 'regulator-settling-time-up-us'\n", + np); + constraints->settling_time_up = 0; + } + + ret = of_property_read_u32(np, "regulator-settling-time-down-us", + &pval); + if (!ret) + constraints->settling_time_down = pval; + if (constraints->settling_time_down && constraints->settling_time) { + pr_warn("%pOFn: ambiguous configuration for settling time, ignoring 'regulator-settling-time-down-us'\n", + np); + constraints->settling_time_down = 0; + } + + ret = of_property_read_u32(np, "regulator-enable-ramp-delay", &pval); + if (!ret) + constraints->enable_time = pval; + + constraints->soft_start = of_property_read_bool(np, + "regulator-soft-start"); + ret = of_property_read_u32(np, "regulator-active-discharge", &pval); + if (!ret) { + constraints->active_discharge = + (pval) ? REGULATOR_ACTIVE_DISCHARGE_ENABLE : + REGULATOR_ACTIVE_DISCHARGE_DISABLE; + } + + if (!of_property_read_u32(np, "regulator-system-load", &pval)) + constraints->system_load = pval; + + if (!of_property_read_u32(np, "regulator-max-step-microvolt", + &pval)) + constraints->max_uV_step = pval; + + constraints->over_current_protection = of_property_read_bool(np, + "regulator-over-current-protection"); + + return 0; +} + +/** + * of_get_regulator_init_data - extract regulator_init_data structure info + * @dev: device requesting for regulator_init_data + * @node: regulator device node + * @desc: regulator description + * + * Populates regulator_init_data structure by extracting data from device + * tree node, returns a pointer to the populated structure or NULL if memory + * alloc fails. + */ +struct regulator_init_data *of_get_regulator_init_data(struct device_d *dev, + struct device_node *node, + const struct regulator_desc *desc) +{ + struct regulator_init_data *init_data; + + if (!node) + return NULL; + + init_data = xzalloc(sizeof(*init_data)); + + if (of_get_regulation_constraints(dev, node, &init_data, desc)) + return NULL; + + return init_data; +} +EXPORT_SYMBOL_GPL(of_get_regulator_init_data); + +struct devm_of_regulator_matches { + struct of_regulator_match *matches; + unsigned int num_matches; +}; + +/** + * of_regulator_match - extract multiple regulator init data from device tree. + * @dev: device requesting the data + * @node: parent device node of the regulators + * @matches: match table for the regulators + * @num_matches: number of entries in match table + * + * This function uses a match table specified by the regulator driver to + * parse regulator init data from the device tree. @node is expected to + * contain a set of child nodes, each providing the init data for one + * regulator. The data parsed from a child node will be matched to a regulator + * based on either the deprecated property regulator-compatible if present, + * or otherwise the child node's name. Note that the match table is modified + * in place and an additional of_node reference is taken for each matched + * regulator. + * + * Returns the number of matches found or a negative error code on failure. + */ +int of_regulator_match(struct device_d *dev, struct device_node *node, + struct of_regulator_match *matches, + unsigned int num_matches) +{ + unsigned int count = 0; + unsigned int i; + const char *name; + struct device_node *child; + struct devm_of_regulator_matches *devm_matches; + + if (!dev || !node) + return -EINVAL; + + devm_matches = xzalloc(sizeof(struct devm_of_regulator_matches)); + + devm_matches->matches = matches; + devm_matches->num_matches = num_matches; + + for (i = 0; i < num_matches; i++) { + struct of_regulator_match *match = &matches[i]; + match->init_data = NULL; + match->of_node = NULL; + } + + for_each_child_of_node(node, child) { + name = of_get_property(child, + "regulator-compatible", NULL); + if (!name) + name = child->name; + + for (i = 0; i < num_matches; i++) { + struct of_regulator_match *match = &matches[i]; + if (match->of_node) + continue; + + if (strcmp(match->name, name)) + continue; + + match->init_data = of_get_regulator_init_data(dev, child, + match->desc); + if (!match->init_data) { + dev_err(dev, + "failed to parse DT for regulator %pOFn\n", + child); + return -EINVAL; + } + match->of_node = child; + count++; + break; + } + } + + return count; +} +EXPORT_SYMBOL_GPL(of_regulator_match); diff --git a/include/linux/regulator/machine.h b/include/linux/regulator/machine.h new file mode 100644 index 0000000000..bb8bc7c4e0 --- /dev/null +++ b/include/linux/regulator/machine.h @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * machine.h -- SoC Regulator support, machine/board driver API. + * + * Copyright (C) 2007, 2008 Wolfson Microelectronics PLC. + * + * Author: Liam Girdwood + * + * Regulator Machine/Board Interface. + */ + +#ifndef __LINUX_REGULATOR_MACHINE_H_ +#define __LINUX_REGULATOR_MACHINE_H_ + +#include + +struct regulator; + +/* + * Regulator operation constraint flags. These flags are used to enable + * certain regulator operations and can be OR'ed together. + * + * VOLTAGE: Regulator output voltage can be changed by software on this + * board/machine. + * CURRENT: Regulator output current can be changed by software on this + * board/machine. + * MODE: Regulator operating mode can be changed by software on this + * board/machine. + * STATUS: Regulator can be enabled and disabled. + * DRMS: Dynamic Regulator Mode Switching is enabled for this regulator. + * BYPASS: Regulator can be put into bypass mode + */ + +#define REGULATOR_CHANGE_VOLTAGE 0x1 +#define REGULATOR_CHANGE_CURRENT 0x2 +#define REGULATOR_CHANGE_MODE 0x4 +#define REGULATOR_CHANGE_STATUS 0x8 +#define REGULATOR_CHANGE_DRMS 0x10 +#define REGULATOR_CHANGE_BYPASS 0x20 + +/* Regulator active discharge flags */ +enum regulator_active_discharge { + REGULATOR_ACTIVE_DISCHARGE_DEFAULT, + REGULATOR_ACTIVE_DISCHARGE_DISABLE, + REGULATOR_ACTIVE_DISCHARGE_ENABLE, +}; + +/** + * struct regulation_constraints - regulator operating constraints. + * + * This struct describes regulator and board/machine specific constraints. + * + * @name: Descriptive name for the constraints, used for display purposes. + * + * @min_uV: Smallest voltage consumers may set. + * @max_uV: Largest voltage consumers may set. + * @uV_offset: Offset applied to voltages from consumer to compensate for + * voltage drops. + * + * @min_uA: Smallest current consumers may set. + * @max_uA: Largest current consumers may set. + * @ilim_uA: Maximum input current. + * @system_load: Load that isn't captured by any consumer requests. + * + * @max_spread: Max possible spread between coupled regulators + * @valid_modes_mask: Mask of modes which may be configured by consumers. + * @valid_ops_mask: Operations which may be performed by consumers. + * + * @always_on: Set if the regulator should never be disabled. + * @boot_on: Set if the regulator is enabled when the system is initially + * started. If the regulator is not enabled by the hardware or + * bootloader then it will be enabled when the constraints are + * applied. + * @apply_uV: Apply the voltage constraint when initialising. + * @ramp_disable: Disable ramp delay when initialising or when setting voltage. + * @soft_start: Enable soft start so that voltage ramps slowly. + * @pull_down: Enable pull down when regulator is disabled. + * @over_current_protection: Auto disable on over current event. + * + * @input_uV: Input voltage for regulator when supplied by another regulator. + * + * @initial_mode: Mode to set at startup. + * @ramp_delay: Time to settle down after voltage change (unit: uV/us) + * @settling_time: Time to settle down after voltage change when voltage + * change is non-linear (unit: microseconds). + * @settling_time_up: Time to settle down after voltage increase when voltage + * change is non-linear (unit: microseconds). + * @settling_time_down : Time to settle down after voltage decrease when + * voltage change is non-linear (unit: microseconds). + * @active_discharge: Enable/disable active discharge. The enum + * regulator_active_discharge values are used for + * initialisation. + * @enable_time: Turn-on time of the rails (unit: microseconds) + */ +struct regulation_constraints { + + const char *name; + + /* voltage output range (inclusive) - for voltage control */ + int min_uV; + int max_uV; + + int uV_offset; + + /* current output range (inclusive) - for current control */ + int min_uA; + int max_uA; + int ilim_uA; + + int system_load; + + /* used for coupled regulators */ + u32 *max_spread; + + /* used for changing voltage in steps */ + int max_uV_step; + + /* valid regulator operating modes for this machine */ + unsigned int valid_modes_mask; + + /* valid operations for regulator on this machine */ + unsigned int valid_ops_mask; + + /* regulator input voltage - only if supply is another regulator */ + int input_uV; + + /* mode to set on startup */ + unsigned int initial_mode; + + unsigned int ramp_delay; + unsigned int settling_time; + unsigned int settling_time_up; + unsigned int settling_time_down; + unsigned int enable_time; + + unsigned int active_discharge; + + /* constraint flags */ + unsigned always_on:1; /* regulator never off when system is on */ + unsigned boot_on:1; /* bootloader/firmware enabled regulator */ + unsigned apply_uV:1; /* apply uV constraint if min == max */ + unsigned ramp_disable:1; /* disable ramp delay */ + unsigned soft_start:1; /* ramp voltage slowly */ + unsigned pull_down:1; /* pull down resistor when regulator off */ + unsigned over_current_protection:1; /* auto disable on over current */ +}; + +/** + * struct regulator_consumer_supply - supply -> device mapping + * + * This maps a supply name to a device. Use of dev_name allows support for + * buses which make struct device available late such as I2C. + * + * @dev_name: Result of dev_name() for the consumer. + * @supply: Name for the supply. + */ +struct regulator_consumer_supply { + const char *dev_name; /* dev_name() for consumer */ + const char *supply; /* consumer supply - e.g. "vcc" */ +}; + +/* Initialize struct regulator_consumer_supply */ +#define REGULATOR_SUPPLY(_name, _dev_name) \ +{ \ + .supply = _name, \ + .dev_name = _dev_name, \ +} + +/** + * struct regulator_init_data - regulator platform initialisation data. + * + * Initialisation constraints, our supply and consumers supplies. + * + * @supply_regulator: Parent regulator. Specified using the regulator name + * as it appears in the name field in sysfs, which can + * be explicitly set using the constraints field 'name'. + * + * @constraints: Constraints. These must be specified for the regulator to + * be usable. + * @num_consumer_supplies: Number of consumer device supplies. + * @consumer_supplies: Consumer device supply configuration. + * + * @regulator_init: Callback invoked when the regulator has been registered. + * @driver_data: Data passed to regulator_init. + */ +struct regulator_init_data { + const char *supply_regulator; /* or NULL for system supply */ + + struct regulation_constraints constraints; + + int num_consumer_supplies; + struct regulator_consumer_supply *consumer_supplies; + + /* optional regulator machine specific init */ + int (*regulator_init)(void *driver_data); + void *driver_data; /* core does not touch this */ +}; + +#ifdef CONFIG_REGULATOR +void regulator_has_full_constraints(void); +#else +static inline void regulator_has_full_constraints(void) +{ +} +#endif + +#endif diff --git a/include/linux/regulator/of_regulator.h b/include/linux/regulator/of_regulator.h new file mode 100644 index 0000000000..265b98d1ee --- /dev/null +++ b/include/linux/regulator/of_regulator.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * OpenFirmware regulator support routines + */ + +#ifndef __LINUX_OF_REG_H +#define __LINUX_OF_REG_H + +#include + +struct device_d; +struct regulator_desc; + +struct of_regulator_match { + const char *name; + void *driver_data; + struct regulator_init_data *init_data; + struct device_node *of_node; + const struct regulator_desc *desc; +}; + +#if defined(CONFIG_OFDEVICE) +extern struct regulator_init_data + *of_get_regulator_init_data(struct device_d *dev, + struct device_node *node, + const struct regulator_desc *desc); +extern int of_regulator_match(struct device_d *dev, struct device_node *node, + struct of_regulator_match *matches, + unsigned int num_matches); +#else +static inline struct regulator_init_data + *of_get_regulator_init_data(struct device_d *dev, + struct device_node *node, + const struct regulator_desc *desc) +{ + return NULL; +} + +static inline int of_regulator_match(struct device_d *dev, + struct device_node *node, + struct of_regulator_match *matches, + unsigned int num_matches) +{ + return 0; +} +#endif /* CONFIG_OF */ + +#endif /* __LINUX_OF_REG_H */ -- cgit v1.2.3 From 3e7e76a81aef908f2e1c11e72e3bb8de8bee8f46 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:50 +0100 Subject: regulator: import linear voltage range helpers The incoming stpmic1 regulator driver makes use of these helpers internally. Thus port them out of Linux v5.3. Signed-off-by: Oleksij Rempel Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/regulator/helpers.c | 186 ++++++++++++++++++++++++++++++++++++++++++++ include/regulator.h | 46 +++++++++++ 2 files changed, 232 insertions(+) diff --git a/drivers/regulator/helpers.c b/drivers/regulator/helpers.c index f22d21b35d..c4877cecf7 100644 --- a/drivers/regulator/helpers.c +++ b/drivers/regulator/helpers.c @@ -184,3 +184,189 @@ int regulator_list_voltage_linear(struct regulator_dev *rdev, return rdev->desc->min_uV + (rdev->desc->uV_step * selector); } EXPORT_SYMBOL_GPL(regulator_list_voltage_linear); + +/** + * regulator_desc_list_voltage_linear_range - List voltages for linear ranges + * + * @desc: Regulator desc for regulator which volatges are to be listed + * @selector: Selector to convert into a voltage + * + * Regulators with a series of simple linear mappings between voltages + * and selectors who have set linear_ranges in the regulator descriptor + * can use this function prior regulator registration to list voltages. + * This is useful when voltages need to be listed during device-tree + * parsing. + */ +int regulator_desc_list_voltage_linear_range(const struct regulator_desc *desc, + unsigned int selector) +{ + const struct regulator_linear_range *range; + int i; + + if (!desc->n_linear_ranges) { + BUG_ON(!desc->n_linear_ranges); + return -EINVAL; + } + + for (i = 0; i < desc->n_linear_ranges; i++) { + range = &desc->linear_ranges[i]; + + if (!(selector >= range->min_sel && + selector <= range->max_sel)) + continue; + + selector -= range->min_sel; + + return range->min_uV + (range->uV_step * selector); + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regulator_desc_list_voltage_linear_range); + +/** + * regulator_list_voltage_linear_range - List voltages for linear ranges + * + * @rdev: Regulator device + * @selector: Selector to convert into a voltage + * + * Regulators with a series of simple linear mappings between voltages + * and selectors can set linear_ranges in the regulator descriptor and + * then use this function as their list_voltage() operation, + */ +int regulator_list_voltage_linear_range(struct regulator_dev *rdev, + unsigned int selector) +{ + return regulator_desc_list_voltage_linear_range(rdev->desc, selector); +} +EXPORT_SYMBOL_GPL(regulator_list_voltage_linear_range); + +/** + * regulator_map_voltage_linear_range - map_voltage() for multiple linear ranges + * + * @rdev: Regulator to operate on + * @min_uV: Lower bound for voltage + * @max_uV: Upper bound for voltage + * + * Drivers providing linear_ranges in their descriptor can use this as + * their map_voltage() callback. + */ +int regulator_map_voltage_linear_range(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + const struct regulator_linear_range *range; + int ret = -EINVAL; + int voltage, i; + + if (!rdev->desc->n_linear_ranges) { + BUG_ON(!rdev->desc->n_linear_ranges); + return -EINVAL; + } + + for (i = 0; i < rdev->desc->n_linear_ranges; i++) { + int linear_max_uV; + + range = &rdev->desc->linear_ranges[i]; + linear_max_uV = range->min_uV + + (range->max_sel - range->min_sel) * range->uV_step; + + if (!(min_uV <= linear_max_uV && max_uV >= range->min_uV)) + continue; + + if (min_uV <= range->min_uV) + min_uV = range->min_uV; + + /* range->uV_step == 0 means fixed voltage range */ + if (range->uV_step == 0) { + ret = 0; + } else { + ret = DIV_ROUND_UP(min_uV - range->min_uV, + range->uV_step); + if (ret < 0) + return ret; + } + + ret += range->min_sel; + + /* + * Map back into a voltage to verify we're still in bounds. + * If we are not, then continue checking rest of the ranges. + */ + voltage = rdev->desc->ops->list_voltage(rdev, ret); + if (voltage >= min_uV && voltage <= max_uV) + break; + } + + if (i == rdev->desc->n_linear_ranges) + return -EINVAL; + + return ret; +} +EXPORT_SYMBOL_GPL(regulator_map_voltage_linear_range); + +/** + * regulator_get_voltage_sel_regmap - standard get_voltage_sel for regmap users + * + * @rdev: regulator to operate on + * + * Regulators that use regmap for their register I/O can set the + * vsel_reg and vsel_mask fields in their descriptor and then use this + * as their get_voltage_vsel operation, saving some code. + */ +int regulator_get_voltage_sel_regmap(struct regulator_dev *rdev) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->vsel_reg, &val); + if (ret != 0) + return ret; + + val &= rdev->desc->vsel_mask; + val >>= ffs(rdev->desc->vsel_mask) - 1; + + return val; +} +EXPORT_SYMBOL_GPL(regulator_get_voltage_sel_regmap); + +/** + * regulator_map_voltage_iterate - map_voltage() based on list_voltage() + * + * @rdev: Regulator to operate on + * @min_uV: Lower bound for voltage + * @max_uV: Upper bound for voltage + * + * Drivers implementing set_voltage_sel() and list_voltage() can use + * this as their map_voltage() operation. It will find a suitable + * voltage by calling list_voltage() until it gets something in bounds + * for the requested voltages. + */ +int regulator_map_voltage_iterate(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + int best_val = INT_MAX; + int selector = 0; + int i, ret; + + /* Find the smallest voltage that falls within the specified + * range. + */ + for (i = 0; i < rdev->desc->n_voltages; i++) { + ret = rdev->desc->ops->list_voltage(rdev, i); + if (ret < 0) + continue; + + if (ret < best_val && ret >= min_uV && ret <= max_uV) { + best_val = ret; + selector = i; + } + } + + if (best_val != INT_MAX) + return selector; + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regulator_map_voltage_iterate); + + diff --git a/include/regulator.h b/include/regulator.h index 28ae25652a..156acb82f8 100644 --- a/include/regulator.h +++ b/include/regulator.h @@ -32,6 +32,7 @@ struct regulator; * @enable_is_inverted: A flag to indicate set enable_mask bits to disable * when using regulator_enable_regmap and friends APIs. */ + struct regulator_desc { unsigned n_voltages; const struct regulator_ops *ops; @@ -49,6 +50,9 @@ struct regulator_desc { unsigned int enable_val; unsigned int disable_val; bool enable_is_inverted; + + const struct regulator_linear_range *linear_ranges; + int n_linear_ranges; }; struct regulator_dev { @@ -65,8 +69,36 @@ struct regulator_ops { int (*list_voltage) (struct regulator_dev *, unsigned int); int (*set_voltage_sel) (struct regulator_dev *, unsigned int); + int (*map_voltage)(struct regulator_dev *, int min_uV, int max_uV); +}; + +/* + * struct regulator_linear_range - specify linear voltage ranges + * + * Specify a range of voltages for regulator_map_linear_range() and + * regulator_list_linear_range(). + * + * @min_uV: Lowest voltage in range + * @min_sel: Lowest selector for range + * @max_sel: Highest selector for range + * @uV_step: Step size + */ +struct regulator_linear_range { + unsigned int min_uV; + unsigned int min_sel; + unsigned int max_sel; + unsigned int uV_step; }; +/* Initialize struct regulator_linear_range */ +#define REGULATOR_LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV) \ +{ \ + .min_uV = _min_uV, \ + .min_sel = _min_sel, \ + .max_sel = _max_sel, \ + .uV_step = _step_uV, \ +} + #ifdef CONFIG_OFDEVICE int of_regulator_register(struct regulator_dev *rd, struct device_node *node); #else @@ -93,8 +125,22 @@ int regulator_set_voltage_sel_regmap(struct regulator_dev *, unsigned); int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV); int regulator_map_voltage_linear(struct regulator_dev *rdev, int min_uV, int max_uV); +int regulator_map_voltage_linear_range(struct regulator_dev *rdev, + int min_uV, int max_uV); int regulator_list_voltage_linear(struct regulator_dev *rdev, unsigned int selector); +int regulator_list_voltage_linear_range(struct regulator_dev *rdev, + unsigned int selector); +int regulator_get_voltage_sel_regmap(struct regulator_dev *rdev); +int regulator_map_voltage_iterate(struct regulator_dev *rdev, + int min_uV, int max_uV); + +/* + * Helper functions intended to be used by regulator drivers prior registering + * their regulators. + */ +int regulator_desc_list_voltage_linear_range(const struct regulator_desc *desc, + unsigned int selector); #else static inline struct regulator *regulator_get(struct device_d *dev, const char *id) -- cgit v1.2.3 From bf58ad743e2bc0bf5d439bb05651a65ff52b4d10 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:51 +0100 Subject: mfd: stpmic1: use dev_get_regmap instead of priv member When we call regmap_init, the regmap is associated with the device supplied and can be queried with dev_get_regmap. This is more natural than expecting cell drivers to cast dev->parent->priv. Do it. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/mfd/stpmic1.c | 1 - drivers/watchdog/stpmic1_wdt.c | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/stpmic1.c b/drivers/mfd/stpmic1.c index 88c7921990..eae6fe3a4e 100644 --- a/drivers/mfd/stpmic1.c +++ b/drivers/mfd/stpmic1.c @@ -69,7 +69,6 @@ static int __init stpmic1_probe(struct device_d *dev) stpmic1->client = to_i2c_client(dev); regmap = regmap_init(dev, ®map_stpmic1_i2c_bus, stpmic1, &stpmic1_regmap_i2c_config); - dev->priv = regmap; ret = regmap_register_cdev(regmap, NULL); if (ret) diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c index eb8c43f716..5d9720c230 100644 --- a/drivers/watchdog/stpmic1_wdt.c +++ b/drivers/watchdog/stpmic1_wdt.c @@ -169,7 +169,10 @@ static int stpmic1_wdt_probe(struct device_d *dev) int ret; wdt = xzalloc(sizeof(*wdt)); - wdt->regmap = dev->parent->priv; + + wdt->regmap = dev_get_regmap(dev->parent, NULL); + if (IS_ERR(wdt->regmap)) + return PTR_ERR(wdt->regmap); wdd = &wdt->wdd; wdd->hwdev = dev; -- cgit v1.2.3 From 4e0c57fab935d99934165af7c4e8c6bb46bc74a5 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:52 +0100 Subject: regulator: add driver for stpmic1-regulators This commit imports the Linux v5.3 state of the driver. This allows us to regulate at least the v3v3 voltage rail used by the DK1 and DK2 Ethernet PHY and SD-Card. Eventually, this driver should be used by the USB host driver to supply the vbus voltage as well. Signed-off-by: Oleksij Rempel Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 3 +- drivers/regulator/stpmic1_regulator.c | 436 ++++++++++++++++++++++++++++++++++ include/linux/mfd/stpmic1.h | 199 ++++++++++++++++ 4 files changed, 646 insertions(+), 1 deletion(-) create mode 100644 drivers/regulator/stpmic1_regulator.c create mode 100644 include/linux/mfd/stpmic1.h diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c734ef5ef9..28bd69a2a5 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -21,6 +21,15 @@ config REGULATOR_PFUZE depends on I2C depends on ARCH_IMX6 +config REGULATOR_STPMIC1 + tristate "STMicroelectronics STPMIC1 PMIC Regulators" + depends on MFD_STPMIC1 + help + This driver supports STMicroelectronics STPMIC1 PMIC voltage + regulators and switches. The STPMIC1 regulators supply power to + an application processor as well as to external system + peripherals such as DDR, Flash memories and system devices. + config REGULATOR_ANATOP tristate "Freescale i.MX on-chip ANATOP LDO regulators" depends on MFD_SYSCON diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index f51e89cd3c..e27e155cf6 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_OFDEVICE) += of_regulator.o obj-$(CONFIG_REGULATOR_FIXED) += fixed.o obj-$(CONFIG_REGULATOR_BCM283X) += bcm2835.o obj-$(CONFIG_REGULATOR_PFUZE) += pfuze.o -obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o \ No newline at end of file +obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o +obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o diff --git a/drivers/regulator/stpmic1_regulator.c b/drivers/regulator/stpmic1_regulator.c new file mode 100644 index 0000000000..aaaba092c1 --- /dev/null +++ b/drivers/regulator/stpmic1_regulator.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) STMicroelectronics 2018 +// Author: Pascal Paillet for STMicroelectronics. + +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * stpmic1 regulator description: this structure is used as driver data + * @desc: regulator framework description + * @mask_reset_reg: mask reset register address + * @mask_reset_mask: mask rank and mask reset register mask + * @icc_reg: icc register address + * @icc_mask: icc register mask + */ +struct stpmic1_regulator_cfg { + struct device_d *dev; + struct regulator_dev rdev; + struct regulator_desc desc; + u8 mask_reset_reg; + u8 mask_reset_mask; + u8 icc_reg; + u8 icc_mask; +}; + +enum { + STPMIC1_BUCK1 = 0, + STPMIC1_BUCK2 = 1, + STPMIC1_BUCK3 = 2, + STPMIC1_BUCK4 = 3, + STPMIC1_LDO1 = 4, + STPMIC1_LDO2 = 5, + STPMIC1_LDO3 = 6, + STPMIC1_LDO4 = 7, + STPMIC1_LDO5 = 8, + STPMIC1_LDO6 = 9, + STPMIC1_VREF_DDR = 10, + STPMIC1_BOOST = 11, + STPMIC1_VBUS_OTG = 12, + STPMIC1_SW_OUT = 13, +}; + +/* Enable time worst case is 5000mV/(2250uV/uS) */ +#define PMIC_ENABLE_TIME_US 2200 + +static const struct regulator_linear_range buck1_ranges[] = { + REGULATOR_LINEAR_RANGE(725000, 0, 4, 0), + REGULATOR_LINEAR_RANGE(725000, 5, 36, 25000), + REGULATOR_LINEAR_RANGE(1500000, 37, 63, 0), +}; + +static const struct regulator_linear_range buck2_ranges[] = { + REGULATOR_LINEAR_RANGE(1000000, 0, 17, 0), + REGULATOR_LINEAR_RANGE(1050000, 18, 19, 0), + REGULATOR_LINEAR_RANGE(1100000, 20, 21, 0), + REGULATOR_LINEAR_RANGE(1150000, 22, 23, 0), + REGULATOR_LINEAR_RANGE(1200000, 24, 25, 0), + REGULATOR_LINEAR_RANGE(1250000, 26, 27, 0), + REGULATOR_LINEAR_RANGE(1300000, 28, 29, 0), + REGULATOR_LINEAR_RANGE(1350000, 30, 31, 0), + REGULATOR_LINEAR_RANGE(1400000, 32, 33, 0), + REGULATOR_LINEAR_RANGE(1450000, 34, 35, 0), + REGULATOR_LINEAR_RANGE(1500000, 36, 63, 0), +}; + +static const struct regulator_linear_range buck3_ranges[] = { + REGULATOR_LINEAR_RANGE(1000000, 0, 19, 0), + REGULATOR_LINEAR_RANGE(1100000, 20, 23, 0), + REGULATOR_LINEAR_RANGE(1200000, 24, 27, 0), + REGULATOR_LINEAR_RANGE(1300000, 28, 31, 0), + REGULATOR_LINEAR_RANGE(1400000, 32, 35, 0), + REGULATOR_LINEAR_RANGE(1500000, 36, 55, 100000), + REGULATOR_LINEAR_RANGE(3400000, 56, 63, 0), +}; + +static const struct regulator_linear_range buck4_ranges[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 27, 25000), + REGULATOR_LINEAR_RANGE(1300000, 28, 29, 0), + REGULATOR_LINEAR_RANGE(1350000, 30, 31, 0), + REGULATOR_LINEAR_RANGE(1400000, 32, 33, 0), + REGULATOR_LINEAR_RANGE(1450000, 34, 35, 0), + REGULATOR_LINEAR_RANGE(1500000, 36, 60, 100000), + REGULATOR_LINEAR_RANGE(3900000, 61, 63, 0), +}; + +static const struct regulator_linear_range ldo1_ranges[] = { + REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0), + REGULATOR_LINEAR_RANGE(1700000, 8, 24, 100000), + REGULATOR_LINEAR_RANGE(3300000, 25, 31, 0), +}; + +static const struct regulator_linear_range ldo2_ranges[] = { + REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0), + REGULATOR_LINEAR_RANGE(1700000, 8, 24, 100000), + REGULATOR_LINEAR_RANGE(3300000, 25, 30, 0), +}; + +static const struct regulator_linear_range ldo3_ranges[] = { + REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0), + REGULATOR_LINEAR_RANGE(1700000, 8, 24, 100000), + REGULATOR_LINEAR_RANGE(3300000, 25, 30, 0), + /* with index 31 LDO3 is in DDR mode */ + REGULATOR_LINEAR_RANGE(500000, 31, 31, 0), +}; + +static const struct regulator_linear_range ldo5_ranges[] = { + REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0), + REGULATOR_LINEAR_RANGE(1700000, 8, 30, 100000), + REGULATOR_LINEAR_RANGE(3900000, 31, 31, 0), +}; + +static const struct regulator_linear_range ldo6_ranges[] = { + REGULATOR_LINEAR_RANGE(900000, 0, 24, 100000), + REGULATOR_LINEAR_RANGE(3300000, 25, 31, 0), +}; + +static const struct regulator_ops stpmic1_ldo_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops stpmic1_ldo3_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_iterate, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops stpmic1_ldo4_fixed_regul_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static const struct regulator_ops stpmic1_buck_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, +}; + +static const struct regulator_ops stpmic1_vref_ddr_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static const struct regulator_ops stpmic1_boost_regul_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +static const struct regulator_ops stpmic1_switch_regul_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +}; + +#define REG_LDO(ids, base) { \ + .n_voltages = 32, \ + .ops = &stpmic1_ldo_ops, \ + .linear_ranges = base ## _ranges, \ + .n_linear_ranges = ARRAY_SIZE(base ## _ranges), \ + .vsel_reg = ids##_ACTIVE_CR, \ + .vsel_mask = LDO_VOLTAGE_MASK, \ + .enable_reg = ids##_ACTIVE_CR, \ + .enable_mask = LDO_ENABLE_MASK, \ + .enable_val = 1, \ + .disable_val = 0, \ +} + +#define REG_LDO3(ids, base) { \ + .n_voltages = 32, \ + .ops = &stpmic1_ldo3_ops, \ + .linear_ranges = ldo3_ranges, \ + .n_linear_ranges = ARRAY_SIZE(ldo3_ranges), \ + .vsel_reg = LDO3_ACTIVE_CR, \ + .vsel_mask = LDO_VOLTAGE_MASK, \ + .enable_reg = LDO3_ACTIVE_CR, \ + .enable_mask = LDO_ENABLE_MASK, \ + .enable_val = 1, \ + .disable_val = 0, \ +} + +#define REG_LDO4(ids, base) { \ + .n_voltages = 1, \ + .ops = &stpmic1_ldo4_fixed_regul_ops, \ + .min_uV = 3300000, \ + .enable_reg = LDO4_ACTIVE_CR, \ + .enable_mask = LDO_ENABLE_MASK, \ + .enable_val = 1, \ + .disable_val = 0, \ +} + +#define REG_BUCK(ids, base) { \ + .ops = &stpmic1_buck_ops, \ + .n_voltages = 64, \ + .linear_ranges = base ## _ranges, \ + .n_linear_ranges = ARRAY_SIZE(base ## _ranges), \ + .vsel_reg = ids##_ACTIVE_CR, \ + .vsel_mask = BUCK_VOLTAGE_MASK, \ + .enable_reg = ids##_ACTIVE_CR, \ + .enable_mask = BUCK_ENABLE_MASK, \ + .enable_val = 1, \ + .disable_val = 0, \ +} + +#define REG_VREF_DDR(ids, base) { \ + .n_voltages = 1, \ + .ops = &stpmic1_vref_ddr_ops, \ + .min_uV = 500000, \ + .enable_reg = VREF_DDR_ACTIVE_CR, \ + .enable_mask = BUCK_ENABLE_MASK, \ + .enable_val = 1, \ + .disable_val = 0, \ +} + +#define REG_BOOST(ids, base) { \ + .n_voltages = 1, \ + .ops = &stpmic1_boost_regul_ops, \ + .min_uV = 0, \ + .enable_reg = BST_SW_CR, \ + .enable_mask = BOOST_ENABLED, \ + .enable_val = BOOST_ENABLED, \ + .disable_val = 0, \ +} + +#define REG_VBUS_OTG(ids, base) { \ + .n_voltages = 1, \ + .ops = &stpmic1_switch_regul_ops, \ + .min_uV = 0, \ + .enable_reg = BST_SW_CR, \ + .enable_mask = USBSW_OTG_SWITCH_ENABLED, \ + .enable_val = USBSW_OTG_SWITCH_ENABLED, \ + .disable_val = 0, \ +} + +#define REG_SW_OUT(ids, base) { \ + .n_voltages = 1, \ + .ops = &stpmic1_switch_regul_ops, \ + .min_uV = 0, \ + .enable_reg = BST_SW_CR, \ + .enable_mask = SWIN_SWOUT_ENABLED, \ + .enable_val = SWIN_SWOUT_ENABLED, \ + .disable_val = 0, \ +} + +static struct stpmic1_regulator_cfg stpmic1_regulator_cfgs[] = { + [STPMIC1_BUCK1] = { + .desc = REG_BUCK(BUCK1, buck1), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(0), + .mask_reset_reg = BUCKS_MASK_RESET_CR, + .mask_reset_mask = BIT(0), + }, + [STPMIC1_BUCK2] = { + .desc = REG_BUCK(BUCK2, buck2), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(1), + .mask_reset_reg = BUCKS_MASK_RESET_CR, + .mask_reset_mask = BIT(1), + }, + [STPMIC1_BUCK3] = { + .desc = REG_BUCK(BUCK3, buck3), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(2), + .mask_reset_reg = BUCKS_MASK_RESET_CR, + .mask_reset_mask = BIT(2), + }, + [STPMIC1_BUCK4] = { + .desc = REG_BUCK(BUCK4, buck4), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(3), + .mask_reset_reg = BUCKS_MASK_RESET_CR, + .mask_reset_mask = BIT(3), + }, + [STPMIC1_LDO1] = { + .desc = REG_LDO(LDO1, ldo1), + .icc_reg = LDOS_ICCTO_CR, + .icc_mask = BIT(0), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(0), + }, + [STPMIC1_LDO2] = { + .desc = REG_LDO(LDO2, ldo2), + .icc_reg = LDOS_ICCTO_CR, + .icc_mask = BIT(1), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(1), + }, + [STPMIC1_LDO3] = { + .desc = REG_LDO3(LDO3, ldo3), + .icc_reg = LDOS_ICCTO_CR, + .icc_mask = BIT(2), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(2), + }, + [STPMIC1_LDO4] = { + .desc = REG_LDO4(LDO4, ldo4), + .icc_reg = LDOS_ICCTO_CR, + .icc_mask = BIT(3), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(3), + }, + [STPMIC1_LDO5] = { + .desc = REG_LDO(LDO5, ldo5), + .icc_reg = LDOS_ICCTO_CR, + .icc_mask = BIT(4), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(4), + }, + [STPMIC1_LDO6] = { + .desc = REG_LDO(LDO6, ldo6), + .icc_reg = LDOS_ICCTO_CR, + .icc_mask = BIT(5), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(5), + }, + [STPMIC1_VREF_DDR] = { + .desc = REG_VREF_DDR(VREF_DDR, vref_ddr), + .mask_reset_reg = LDOS_MASK_RESET_CR, + .mask_reset_mask = BIT(6), + }, + [STPMIC1_BOOST] = { + .desc = REG_BOOST(BOOST, boost), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(6), + }, + [STPMIC1_VBUS_OTG] = { + .desc = REG_VBUS_OTG(VBUS_OTG, pwr_sw1), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(4), + }, + [STPMIC1_SW_OUT] = { + .desc = REG_SW_OUT(SW_OUT, pwr_sw2), + .icc_reg = BUCKS_ICCTO_CR, + .icc_mask = BIT(5), + }, +}; + +#define MATCH(_name, _id) \ + [STPMIC1_##_id] = { \ + .name = #_name, \ + .desc = &stpmic1_regulator_cfgs[STPMIC1_##_id].desc, \ + } + +static struct of_regulator_match stpmic1_matches[] = { + MATCH(buck1, BUCK1), + MATCH(buck2, BUCK2), + MATCH(buck3, BUCK3), + MATCH(buck4, BUCK4), + MATCH(ldo1, LDO1), + MATCH(ldo2, LDO2), + MATCH(ldo3, LDO3), + MATCH(ldo4, LDO4), + MATCH(ldo5, LDO5), + MATCH(ldo6, LDO6), + MATCH(vref_ddr, VREF_DDR), + MATCH(boost, BOOST), + MATCH(pwr_sw1, VBUS_OTG), + MATCH(pwr_sw2, SW_OUT), +}; + +static int stpmic1_regulator_register(struct device_d *dev, int id, + struct of_regulator_match *match, + struct stpmic1_regulator_cfg *cfg) +{ + int ret; + + cfg->dev = dev; + cfg->rdev.desc = &cfg->desc; + cfg->rdev.regmap = dev_get_regmap(dev->parent, NULL); + if (IS_ERR(cfg->rdev.regmap)) + return PTR_ERR(cfg->rdev.regmap); + + ret = of_regulator_register(&cfg->rdev, match->of_node); + if (ret) { + dev_err(dev, "failed to register %s regulator\n", match->name); + return ret; + } + + dev_dbg(dev, "registered %s\n", match->name); + + return 0; +} + +static int stpmic1_regulator_probe(struct device_d *dev) +{ + int i, ret; + + ret = of_regulator_match(dev, dev->device_node, stpmic1_matches, + ARRAY_SIZE(stpmic1_matches)); + if (ret < 0) { + dev_err(dev, "Error in PMIC regulator device tree node"); + return ret; + } + + for (i = 0; i < ARRAY_SIZE(stpmic1_regulator_cfgs); i++) { + ret = stpmic1_regulator_register(dev, i, &stpmic1_matches[i], + &stpmic1_regulator_cfgs[i]); + if (ret < 0) + return ret; + } + + dev_dbg(dev, "probed\n"); + + return 0; +} + +static __maybe_unused const struct of_device_id stpmic1_regulator_of_match[] = { + { .compatible = "st,stpmic1-regulators" }, + { /* sentinel */ }, +}; + +static struct driver_d stpmic1_regulator_driver = { + .name = "stpmic1-regulator", + .probe = stpmic1_regulator_probe, + .of_compatible = DRV_OF_COMPAT(stpmic1_regulator_of_match), +}; +device_platform_driver(stpmic1_regulator_driver); diff --git a/include/linux/mfd/stpmic1.h b/include/linux/mfd/stpmic1.h new file mode 100644 index 0000000000..3a8d6a7dcf --- /dev/null +++ b/include/linux/mfd/stpmic1.h @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved + * Author: Philippe Peurichard , + * Pascal Paillet for STMicroelectronics. + */ + +#ifndef __LINUX_MFD_STPMIC1_H +#define __LINUX_MFD_STPMIC1_H + +#define TURN_ON_SR 0x1 +#define TURN_OFF_SR 0x2 +#define ICC_LDO_TURN_OFF_SR 0x3 +#define ICC_BUCK_TURN_OFF_SR 0x4 +#define RREQ_STATE_SR 0x5 +#define VERSION_SR 0x6 + +#define SWOFF_PWRCTRL_CR 0x10 +#define PADS_PULL_CR 0x11 +#define BUCKS_PD_CR 0x12 +#define LDO14_PD_CR 0x13 +#define LDO56_VREF_PD_CR 0x14 +#define VBUS_DET_VIN_CR 0x15 +#define PKEY_TURNOFF_CR 0x16 +#define BUCKS_MASK_RANK_CR 0x17 +#define BUCKS_MASK_RESET_CR 0x18 +#define LDOS_MASK_RANK_CR 0x19 +#define LDOS_MASK_RESET_CR 0x1A +#define WCHDG_CR 0x1B +#define WCHDG_TIMER_CR 0x1C +#define BUCKS_ICCTO_CR 0x1D +#define LDOS_ICCTO_CR 0x1E + +#define BUCK1_ACTIVE_CR 0x20 +#define BUCK2_ACTIVE_CR 0x21 +#define BUCK3_ACTIVE_CR 0x22 +#define BUCK4_ACTIVE_CR 0x23 +#define VREF_DDR_ACTIVE_CR 0x24 +#define LDO1_ACTIVE_CR 0x25 +#define LDO2_ACTIVE_CR 0x26 +#define LDO3_ACTIVE_CR 0x27 +#define LDO4_ACTIVE_CR 0x28 +#define LDO5_ACTIVE_CR 0x29 +#define LDO6_ACTIVE_CR 0x2A + +#define BUCK1_STDBY_CR 0x30 +#define BUCK2_STDBY_CR 0x31 +#define BUCK3_STDBY_CR 0x32 +#define BUCK4_STDBY_CR 0x33 +#define VREF_DDR_STDBY_CR 0x34 +#define LDO1_STDBY_CR 0x35 +#define LDO2_STDBY_CR 0x36 +#define LDO3_STDBY_CR 0x37 +#define LDO4_STDBY_CR 0x38 +#define LDO5_STDBY_CR 0x39 +#define LDO6_STDBY_CR 0x3A + +#define BST_SW_CR 0x40 + +#define INT_PENDING_R1 0x50 +#define INT_PENDING_R2 0x51 +#define INT_PENDING_R3 0x52 +#define INT_PENDING_R4 0x53 + +#define INT_DBG_LATCH_R1 0x60 +#define INT_DBG_LATCH_R2 0x61 +#define INT_DBG_LATCH_R3 0x62 +#define INT_DBG_LATCH_R4 0x63 + +#define INT_CLEAR_R1 0x70 +#define INT_CLEAR_R2 0x71 +#define INT_CLEAR_R3 0x72 +#define INT_CLEAR_R4 0x73 + +#define INT_MASK_R1 0x80 +#define INT_MASK_R2 0x81 +#define INT_MASK_R3 0x82 +#define INT_MASK_R4 0x83 + +#define INT_SET_MASK_R1 0x90 +#define INT_SET_MASK_R2 0x91 +#define INT_SET_MASK_R3 0x92 +#define INT_SET_MASK_R4 0x93 + +#define INT_CLEAR_MASK_R1 0xA0 +#define INT_CLEAR_MASK_R2 0xA1 +#define INT_CLEAR_MASK_R3 0xA2 +#define INT_CLEAR_MASK_R4 0xA3 + +#define INT_SRC_R1 0xB0 +#define INT_SRC_R2 0xB1 +#define INT_SRC_R3 0xB2 +#define INT_SRC_R4 0xB3 + +#define PMIC_MAX_REGISTER_ADDRESS INT_SRC_R4 + +#define STPMIC1_PMIC_NUM_IRQ_REGS 4 + +#define TURN_OFF_SR_ICC_EVENT 0x08 + +#define LDO_VOLTAGE_MASK GENMASK(6, 2) +#define BUCK_VOLTAGE_MASK GENMASK(7, 2) +#define LDO_BUCK_VOLTAGE_SHIFT 2 + +#define LDO_ENABLE_MASK BIT(0) +#define BUCK_ENABLE_MASK BIT(0) + +#define BUCK_HPLP_ENABLE_MASK BIT(1) +#define BUCK_HPLP_SHIFT 1 + +#define STDBY_ENABLE_MASK BIT(0) + +#define BUCKS_PD_CR_REG_MASK GENMASK(7, 0) +#define BUCK_MASK_RANK_REGISTER_MASK GENMASK(3, 0) +#define BUCK_MASK_RESET_REGISTER_MASK GENMASK(3, 0) +#define LDO1234_PULL_DOWN_REGISTER_MASK GENMASK(7, 0) +#define LDO56_VREF_PD_CR_REG_MASK GENMASK(5, 0) +#define LDO_MASK_RANK_REGISTER_MASK GENMASK(5, 0) +#define LDO_MASK_RESET_REGISTER_MASK GENMASK(5, 0) + +#define BUCK1_PULL_DOWN_REG BUCKS_PD_CR +#define BUCK1_PULL_DOWN_MASK BIT(0) +#define BUCK2_PULL_DOWN_REG BUCKS_PD_CR +#define BUCK2_PULL_DOWN_MASK BIT(2) +#define BUCK3_PULL_DOWN_REG BUCKS_PD_CR +#define BUCK3_PULL_DOWN_MASK BIT(4) +#define BUCK4_PULL_DOWN_REG BUCKS_PD_CR +#define BUCK4_PULL_DOWN_MASK BIT(6) + +#define LDO1_PULL_DOWN_REG LDO14_PD_CR +#define LDO1_PULL_DOWN_MASK BIT(0) +#define LDO2_PULL_DOWN_REG LDO14_PD_CR +#define LDO2_PULL_DOWN_MASK BIT(2) +#define LDO3_PULL_DOWN_REG LDO14_PD_CR +#define LDO3_PULL_DOWN_MASK BIT(4) +#define LDO4_PULL_DOWN_REG LDO14_PD_CR +#define LDO4_PULL_DOWN_MASK BIT(6) +#define LDO5_PULL_DOWN_REG LDO56_VREF_PD_CR +#define LDO5_PULL_DOWN_MASK BIT(0) +#define LDO6_PULL_DOWN_REG LDO56_VREF_PD_CR +#define LDO6_PULL_DOWN_MASK BIT(2) +#define VREF_DDR_PULL_DOWN_REG LDO56_VREF_PD_CR +#define VREF_DDR_PULL_DOWN_MASK BIT(4) + +#define BUCKS_ICCTO_CR_REG_MASK GENMASK(6, 0) +#define LDOS_ICCTO_CR_REG_MASK GENMASK(5, 0) + +#define LDO_BYPASS_MASK BIT(7) + +/* Main PMIC Control Register + * SWOFF_PWRCTRL_CR + * Address : 0x10 + */ +#define ICC_EVENT_ENABLED BIT(4) +#define PWRCTRL_POLARITY_HIGH BIT(3) +#define PWRCTRL_PIN_VALID BIT(2) +#define RESTART_REQUEST_ENABLED BIT(1) +#define SOFTWARE_SWITCH_OFF_ENABLED BIT(0) + +/* Main PMIC PADS Control Register + * PADS_PULL_CR + * Address : 0x11 + */ +#define WAKEUP_DETECTOR_DISABLED BIT(4) +#define PWRCTRL_PD_ACTIVE BIT(3) +#define PWRCTRL_PU_ACTIVE BIT(2) +#define WAKEUP_PD_ACTIVE BIT(1) +#define PONKEY_PU_INACTIVE BIT(0) + +/* Main PMIC VINLOW Control Register + * VBUS_DET_VIN_CRC DMSC + * Address : 0x15 + */ +#define SWIN_DETECTOR_ENABLED BIT(7) +#define SWOUT_DETECTOR_ENABLED BIT(6) +#define VINLOW_ENABLED BIT(0) +#define VINLOW_CTRL_REG_MASK GENMASK(7, 0) + +/* USB Control Register + * Address : 0x40 + */ +#define BOOST_OVP_DISABLED BIT(7) +#define VBUS_OTG_DETECTION_DISABLED BIT(6) +#define SW_OUT_DISCHARGE BIT(5) +#define VBUS_OTG_DISCHARGE BIT(4) +#define OCP_LIMIT_HIGH BIT(3) +#define SWIN_SWOUT_ENABLED BIT(2) +#define USBSW_OTG_SWITCH_ENABLED BIT(1) +#define BOOST_ENABLED BIT(0) + +/* PKEY_TURNOFF_CR + * Address : 0x16 + */ +#define PONKEY_PWR_OFF BIT(7) +#define PONKEY_CC_FLAG_CLEAR BIT(6) +#define PONKEY_TURNOFF_TIMER_MASK GENMASK(3, 0) +#define PONKEY_TURNOFF_MASK GENMASK(7, 0) + +#endif /* __LINUX_MFD_STPMIC1_H */ -- cgit v1.2.3 From e71a1a2899a2001ccf93632557af5c5793907ab9 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:53 +0100 Subject: ARM: dts: stm32mp157a-dk1: remove unnecessary sram node This node has so far been unused and is a remnant from when the stm32mp device drivers weren't completely upstream yet. Drop the node. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/dts/stm32mp157a-dk1.dtsi | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/arch/arm/dts/stm32mp157a-dk1.dtsi b/arch/arm/dts/stm32mp157a-dk1.dtsi index fc946ed9f4..e9e386a664 100644 --- a/arch/arm/dts/stm32mp157a-dk1.dtsi +++ b/arch/arm/dts/stm32mp157a-dk1.dtsi @@ -31,17 +31,4 @@ default-state = "on"; }; }; - - sram: sram@10050000 { - compatible = "mmio-sram"; - reg = <0x10050000 0x10000>; - #address-cells = <1>; - #size-cells = <1>; - ranges = <0 0x10050000 0x10000>; - - dma_pool: dma_pool@0 { - reg = <0x0 0x10000>; - pool; - }; - }; }; -- cgit v1.2.3 From cfb5d8dfe76357a4a03c2926a84f3ae5480f6a42 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 08:11:54 +0100 Subject: ARM: stm32mp: enable STPMIC1 MFD and cell drivers The STPMIC1 is the ST Microelectronics PMIC designed for use with STM32MP. Enable it and its cell drivers by default in the defconfig. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/configs/stm32mp_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/configs/stm32mp_defconfig b/arch/arm/configs/stm32mp_defconfig index 292d7751e1..f69f9f966a 100644 --- a/arch/arm/configs/stm32mp_defconfig +++ b/arch/arm/configs/stm32mp_defconfig @@ -97,16 +97,19 @@ CONFIG_I2C_STM32=y CONFIG_MCI=y CONFIG_MCI_STARTUP=y CONFIG_MCI_STM32_SDMMC2=y +CONFIG_MFD_STPMIC1=y CONFIG_LED=y CONFIG_LED_GPIO=y CONFIG_LED_GPIO_OF=y CONFIG_WATCHDOG=y CONFIG_WATCHDOG_POLLER=y CONFIG_STM32_IWDG_WATCHDOG=y +CONFIG_STPMIC1_WATCHDOG=y CONFIG_NVMEM=y CONFIG_STM32_BSEC=y CONFIG_REGULATOR=y CONFIG_REGULATOR_FIXED=y +CONFIG_REGULATOR_STPMIC1=y CONFIG_RESET_STM32=y CONFIG_FS_EXT4=y CONFIG_FS_TFTP=y -- cgit v1.2.3 From 95bf11436890136534868894f96181a79cbf2662 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 11:21:44 +0100 Subject: ARM: psci: translate PSCI error codes in smc command For more usability, translate CPU_ON error codes into the error descriptions found in the PSCI Platform Design Document[1]. [1]: http://infocenter.arm.com/help/topic/com.arm.doc.den0022d/Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/cpu/psci.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/arch/arm/cpu/psci.c b/arch/arm/cpu/psci.c index a976ddbb5c..b02e83986a 100644 --- a/arch/arm/cpu/psci.c +++ b/arch/arm/cpu/psci.c @@ -248,8 +248,41 @@ void second_entry(void) while (1); } +static const char *psci_xlate_str(long err) +{ + static char errno_string[sizeof "error 0x123456789ABCDEF0"]; + + switch(err) + { + case ARM_PSCI_RET_SUCCESS: + return "Success"; + case ARM_PSCI_RET_NOT_SUPPORTED: + return "Operation not supported"; + case ARM_PSCI_RET_INVAL: + return "Invalid argument"; + case ARM_PSCI_RET_DENIED: + return "Operation not permitted"; + case ARM_PSCI_RET_ALREADY_ON: + return "CPU already on"; + case ARM_PSCI_RET_ON_PENDING: + return "CPU_ON in progress"; + case ARM_PSCI_RET_INTERNAL_FAILURE: + return "Internal failure"; + case ARM_PSCI_RET_NOT_PRESENT: + return "Trusted OS not present on core"; + case ARM_PSCI_RET_DISABLED: + return "CPU is disabled"; + case ARM_PSCI_RET_INVALID_ADDRESS: + return "Bad address"; + } + + sprintf(errno_string, "error 0x%lx", err); + return errno_string; +} + static int do_smc(int argc, char *argv[]) { + long ret; int opt; struct arm_smccc_res res = { .a0 = 0xdeadbee0, @@ -271,7 +304,10 @@ static int do_smc(int argc, char *argv[]) case 'c': arm_smccc_smc(ARM_PSCI_0_2_FN_CPU_ON, 1, (unsigned long)second_entry, 0, 0, 0, 0, 0, &res); - break; + ret = (long)res.a0; + printf("CPU_ON returns with: %s\n", psci_xlate_str(ret)); + if (ret) + return COMMAND_ERROR; } } -- cgit v1.2.3 From b64f035f7caaa90bf716bd3ccd95a67fd2085daf Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 11:21:45 +0100 Subject: ARM: psci: use CONFIG_ARM_PSCI_DEBUG for smc command There's already an option to use when debugging PSCI. Instead of requiring users to #define DEBUG 1 as well, have the smc command be usable when CONFIG_ARM_PSCI_DEBUG, not DEBUG is defined. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/cpu/psci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/cpu/psci.c b/arch/arm/cpu/psci.c index b02e83986a..8c5043d83c 100644 --- a/arch/arm/cpu/psci.c +++ b/arch/arm/cpu/psci.c @@ -228,7 +228,7 @@ static int armv7_psci_init(void) } device_initcall(armv7_psci_init); -#ifdef DEBUG +#ifdef CONFIG_ARM_PSCI_DEBUG #include #include -- cgit v1.2.3 From 9efa1f8bdcc220be11bb94491b9fc706831a23a7 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 11:21:46 +0100 Subject: ARM: psci: wire in smc command help The smc command has a help defined, but unused. Wire it in, so help smc and smc -invalidoption work as expected. While at it, remove the unimplemented -z option. It's unneeded, because -c turns off the CPU after starting it again already. Also it seems it's not implementable without interprocessor communication, which is probably overkill here. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/cpu/psci.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arch/arm/cpu/psci.c b/arch/arm/cpu/psci.c index 8c5043d83c..22ce1dfd0e 100644 --- a/arch/arm/cpu/psci.c +++ b/arch/arm/cpu/psci.c @@ -291,7 +291,10 @@ static int do_smc(int argc, char *argv[]) .a3 = 0xdeadbee3, }; - while ((opt = getopt(argc, argv, "nicz")) > 0) { + if (argc < 2) + return COMMAND_ERROR_USAGE; + + while ((opt = getopt(argc, argv, "nic")) > 0) { switch (opt) { case 'n': armv7_secure_monitor_install(); @@ -321,7 +324,6 @@ BAREBOX_CMD_HELP_TEXT("Options:") BAREBOX_CMD_HELP_OPT ("-n", "Install secure monitor and switch to nonsecure mode") BAREBOX_CMD_HELP_OPT ("-i", "Show information about installed PSCI version") BAREBOX_CMD_HELP_OPT ("-c", "Start secondary CPU core") -BAREBOX_CMD_HELP_OPT ("-z", "Turn off secondary CPU core") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(smc) -- cgit v1.2.3 From 80bd1782c6a3aa8bf6e7ecf7a1153113e6a5b1ef Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 11:21:47 +0100 Subject: ARM: psci: implement PSCI client driver System reset on the STM32MP may be done via PSCI when running TF-A as first-stage boot loader. Provide a PSCI driver to simplify using it: - A psci_invoke function is exported, so other code can use it - A fixup for the PSCI device tree node is registered - A reset and poweroff handler via PSCI is registered for PSCI >= v0.2 Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 9 +++ arch/arm/cpu/Makefile | 1 + arch/arm/cpu/psci-client.c | 190 ++++++++++++++++++++++++++++++++++++++++++++ arch/arm/include/asm/psci.h | 23 +++++- 4 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 arch/arm/cpu/psci-client.c diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index f82844a83a..1346f70f4f 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -449,6 +449,15 @@ config ARM_PSCI PSCI is used for controlling secondary CPU cores on some systems. Say yes here if you want barebox to service PSCI calls on such systems. +config ARM_PSCI_CLIENT + bool "Enable barebox PSCI client support" + select ARM_SMCCC + select ARM_PSCI_OF + help + Say yes here if you want barebox to communicate with a secure monitor + for resetting/powering off the system over PSCI. barebox' PSCI version + information will also be shared with Linux via device tree fixups. + config ARM_PSCI_DEBUG bool "Enable PSCI debugging" depends on ARM_PSCI diff --git a/arch/arm/cpu/Makefile b/arch/arm/cpu/Makefile index e0b16747ad..09b3bc2eea 100644 --- a/arch/arm/cpu/Makefile +++ b/arch/arm/cpu/Makefile @@ -16,6 +16,7 @@ pbl-$(CONFIG_BOARD_ARM_GENERIC_DT_AARCH64) += board-dt-2nd-aarch64.o obj-pbl-y += setupc$(S64).o cache$(S64).o obj-$(CONFIG_BOOTM_OPTEE) += start-kernel-optee.o +obj-$(CONFIG_ARM_PSCI_CLIENT) += psci-client.o # # Any variants can be called as start-armxyz.S diff --git a/arch/arm/cpu/psci-client.c b/arch/arm/cpu/psci-client.c new file mode 100644 index 0000000000..b5d0d37497 --- /dev/null +++ b/arch/arm/cpu/psci-client.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017 Masahiro Yamada + * Copyright (C) 2019 Ahmad Fatoum, Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct restart_handler restart; + +static void __noreturn psci_invoke_noreturn(int function) +{ + int ret; + + ret = psci_invoke(function, 0, 0, 0, NULL); + + pr_err("psci command failed: %s\n", strerror(-ret)); + hang(); +} + +static void __noreturn psci_poweroff(struct poweroff_handler *handler) +{ + psci_invoke_noreturn(ARM_PSCI_0_2_FN_SYSTEM_OFF); +} + +static void __noreturn psci_restart(struct restart_handler *rst) +{ + psci_invoke_noreturn(ARM_PSCI_0_2_FN_SYSTEM_RESET); +} + +static u32 version; +int psci_get_version(void) +{ + if (!version) + return -EPROBE_DEFER; + + return version; +} + +static u32 (*psci_invoke_fn)(ulong, ulong, ulong, ulong); + +static int psci_xlate_error(s32 errnum) +{ + switch (errnum) { + case ARM_PSCI_RET_NOT_SUPPORTED: + return -ENOTSUPP; // Operation not supported + case ARM_PSCI_RET_INVAL: + return -EINVAL; // Invalid argument + case ARM_PSCI_RET_DENIED: + return -EPERM; // Operation not permitted + case ARM_PSCI_RET_ALREADY_ON: + return -EBUSY; // CPU already on + case ARM_PSCI_RET_ON_PENDING: + return -EALREADY; // CPU_ON in progress + case ARM_PSCI_RET_INTERNAL_FAILURE: + return -EIO; // Internal failure + case ARM_PSCI_RET_NOT_PRESENT: + return -ESRCH; // Trusted OS not present on core + case ARM_PSCI_RET_DISABLED: + return -ENODEV; // CPU is disabled + case ARM_PSCI_RET_INVALID_ADDRESS: + return -EACCES; // Bad address + default: + return errnum; + }; +} + +int psci_invoke(ulong function, ulong arg0, ulong arg1, ulong arg2, + ulong *result) +{ + ulong ret; + if (!psci_invoke_fn) + return -EPROBE_DEFER; + + ret = psci_invoke_fn(function, arg0, arg1, arg2); + if (result) + *result = ret; + + switch (function) { + case ARM_PSCI_0_2_FN_PSCI_VERSION: + case ARM_PSCI_1_0_FN64_STAT_RESIDENCY: + case ARM_PSCI_1_0_FN64_STAT_COUNT: + /* These don't return an error code */ + return 0; + } + + return psci_xlate_error(ret); +} + +static u32 invoke_psci_fn_hvc(ulong function, ulong arg0, ulong arg1, ulong arg2) +{ + struct arm_smccc_res res; + arm_smccc_hvc(function, arg0, arg1, arg2, 0, 0, 0, 0, &res); + return res.a0; +} + +static u32 invoke_psci_fn_smc(ulong function, ulong arg0, ulong arg1, ulong arg2) +{ + struct arm_smccc_res res; + arm_smccc_smc(function, arg0, arg1, arg2, 0, 0, 0, 0, &res); + return res.a0; +} + +static int of_psci_do_fixup(struct device_node *root, void *context) +{ + return of_psci_fixup(root, *(u32 *)context); +} + +static int __init psci_probe(struct device_d *dev) +{ + const char *method; + ulong of_version, actual_version; + int ret; + + ret = dev_get_drvdata(dev, (const void **)&of_version); + if (ret) + return -ENODEV; + + ret = of_property_read_string(dev->device_node, "method", &method); + if (ret) { + dev_warn(dev, "missing \"method\" property\n"); + return -ENXIO; + } + + if (!strcmp(method, "hvc")) { + psci_invoke_fn = invoke_psci_fn_hvc; + } else if (!strcmp(method, "smc")) { + psci_invoke_fn = invoke_psci_fn_smc; + } else { + pr_warn("invalid \"method\" property: %s\n", method); + return -EINVAL; + } + + + if (of_version < ARM_PSCI_VER(0,2)) { + version = of_version; + + dev_info(dev, "assuming version %u.%u\n", + version >> 16, version & 0xffff); + dev_dbg(dev, "Not registering reset handler due to PSCI version\n"); + + return 0; + } + + psci_invoke(ARM_PSCI_0_2_FN_PSCI_VERSION, 0, 0, 0, &actual_version); + version = actual_version; + + dev_info(dev, "detected version %u.%u\n", + version >> 16, version & 0xffff); + + if (actual_version != of_version) + of_register_fixup(of_psci_do_fixup, &version); + + ret = poweroff_handler_register_fn(psci_poweroff); + if (ret) + dev_warn(dev, "error registering poweroff handler: %s\n", + strerror(-ret)); + + restart.name = "psci"; + restart.restart = psci_restart; + restart.priority = 400; + + ret = restart_handler_register(&restart); + if (ret) + dev_warn(dev, "error registering restart handler: %s\n", + strerror(-ret)); + + return ret; +} + +static __maybe_unused struct of_device_id psci_dt_ids[] = { + { .compatible = "arm,psci", .data = (void*)ARM_PSCI_VER(0,1) }, + { .compatible = "arm,psci-0.2", .data = (void*)ARM_PSCI_VER(0,2) }, + { .compatible = "arm,psci-1.0", .data = (void*)ARM_PSCI_VER(1,0) }, + { /* sentinel */ }, +}; + +static struct driver_d psci_driver = { + .name = "psci", + .probe = psci_probe, + .of_compatible = DRV_OF_COMPAT(psci_dt_ids), +}; +coredevice_platform_driver(psci_driver); diff --git a/arch/arm/include/asm/psci.h b/arch/arm/include/asm/psci.h index f2db967f3a..b616e4b20e 100644 --- a/arch/arm/include/asm/psci.h +++ b/arch/arm/include/asm/psci.h @@ -18,8 +18,9 @@ #ifndef __ARM_PSCI_H__ #define __ARM_PSCI_H__ -#define ARM_PSCI_VER_1_0 (0x00010000) -#define ARM_PSCI_VER_0_2 (0x00000002) +#define ARM_PSCI_VER(major, minor) (((major) << 16) | (minor)) +#define ARM_PSCI_VER_1_0 ARM_PSCI_VER(1,0) +#define ARM_PSCI_VER_0_2 ARM_PSCI_VER(0,2) /* PSCI 0.1 interface */ #define ARM_PSCI_FN_BASE 0x95c1ba5e @@ -106,6 +107,24 @@ static inline void psci_set_ops(struct psci_ops *ops) } #endif +#ifdef CONFIG_ARM_PSCI_CLIENT +int psci_invoke(ulong function, ulong arg0, ulong arg1, ulong arg2, + ulong *result); + +int psci_get_version(void); +#else +static inline int psci_invoke(ulong function, ulong arg0, ulong arg1, ulong arg2, + ulong *result) +{ + return -ENOSYS; +} + +static inline int psci_get_version(void) +{ + return -ENOSYS; +} +#endif + void psci_cpu_entry(void); #ifdef CONFIG_ARM_PSCI_DEBUG -- cgit v1.2.3 From 705d494b2cf3196d3d5b854c97fbdef8a6c52810 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 11:21:48 +0100 Subject: ARM: stm32mp: select ARM_USE_COMPRESSED_DTB for the whole arch We'll probably be using compressed DTBs for all new boards as well, thus move the ARM_USE_COMPRESSED_DTB, so it's always selected for STM32MP. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/Kconfig | 1 + arch/arm/mach-stm32mp/Kconfig | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 1346f70f4f..9589a6a511 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -202,6 +202,7 @@ config ARCH_STM32MP select ARCH_HAS_RESET_CONTROLLER select ARM_AMBA select ARM_SMCCC + select ARM_USE_COMPRESSED_DTB config ARCH_VERSATILE bool "ARM Versatile boards (ARM926EJ-S)" diff --git a/arch/arm/mach-stm32mp/Kconfig b/arch/arm/mach-stm32mp/Kconfig index 6bf950b23f..b30439735e 100644 --- a/arch/arm/mach-stm32mp/Kconfig +++ b/arch/arm/mach-stm32mp/Kconfig @@ -9,7 +9,6 @@ config ARCH_STM32MP157 config MACH_STM32MP157C_DK2 select ARCH_STM32MP157 - select ARM_USE_COMPRESSED_DTB bool "STM32MP157C-DK2 board" endif -- cgit v1.2.3 From 52cc061f50aa12adcef0a00c9a56eb7c60865a1c Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Wed, 6 Nov 2019 11:21:49 +0100 Subject: ARM: dts: stm32mp: report psci v0.2 at least ARM TF-A reports compatibility with PSCI v1.1 since v1.5. Upstream ARM TF-A support for STM32MP was introduced with v1.6. It's thus safe to assume that the STM32MP barebox will never have to interact with a secure monitor implementing PSCI v0.1. Overwrite the psci device tree compatible to specify v0.2. This is the first version that implements PSCI_VERSION, which allows the barebox psci client driver selected in this commit to query the actual PSCI version and fix it up into the device tree. This fixes an issue where resetting via PSCI fails in Linux because the upstream device tree compatible: reboot: Restarting system Reboot failed -- System halted Reported-by: Michael Olbrich Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- arch/arm/dts/stm32mp157c.dtsi | 4 ++++ arch/arm/mach-stm32mp/Kconfig | 1 + 2 files changed, 5 insertions(+) diff --git a/arch/arm/dts/stm32mp157c.dtsi b/arch/arm/dts/stm32mp157c.dtsi index 771139c28a..97c075a020 100644 --- a/arch/arm/dts/stm32mp157c.dtsi +++ b/arch/arm/dts/stm32mp157c.dtsi @@ -19,6 +19,10 @@ gpio10 = &gpiok; gpio25 = &gpioz; }; + + psci { + compatible = "arm,psci-0.2"; + }; }; &bsec { diff --git a/arch/arm/mach-stm32mp/Kconfig b/arch/arm/mach-stm32mp/Kconfig index b30439735e..9b55a3d218 100644 --- a/arch/arm/mach-stm32mp/Kconfig +++ b/arch/arm/mach-stm32mp/Kconfig @@ -5,6 +5,7 @@ config ARCH_NR_GPIO default 416 config ARCH_STM32MP157 + select ARM_PSCI_CLIENT bool config MACH_STM32MP157C_DK2 -- cgit v1.2.3