diff options
-rw-r--r-- | arch/arm/dts/qcom-ipq4019-u-boot.dtsi | 104 | ||||
-rw-r--r-- | configs/qcom_defconfig | 5 | ||||
-rw-r--r-- | drivers/clk/qcom/clock-ipq4019.c | 9 | ||||
-rw-r--r-- | drivers/core/root.c | 3 | ||||
-rw-r--r-- | drivers/net/Kconfig | 8 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/essedma.c | 1192 | ||||
-rw-r--r-- | drivers/net/essedma.h | 198 | ||||
-rw-r--r-- | drivers/power/regulator/Kconfig | 8 | ||||
-rw-r--r-- | drivers/power/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/regulator/qcom-rpmh-regulator.c | 544 | ||||
-rw-r--r-- | drivers/soc/Kconfig | 1 | ||||
-rw-r--r-- | drivers/soc/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/Kconfig | 27 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 4 | ||||
-rw-r--r-- | drivers/soc/qcom/cmd-db.c | 225 | ||||
-rw-r--r-- | drivers/soc/qcom/rpmh-internal.h | 138 | ||||
-rw-r--r-- | drivers/soc/qcom/rpmh-rsc.c | 505 | ||||
-rw-r--r-- | drivers/soc/qcom/rpmh.c | 110 | ||||
-rw-r--r-- | include/dt-bindings/clock/qcom,gcc-ipq4019.h | 169 | ||||
-rw-r--r-- | include/linux/bitmap.h | 8 | ||||
-rw-r--r-- | include/soc/qcom/cmd-db.h | 30 | ||||
-rw-r--r-- | include/soc/qcom/rpmh.h | 28 | ||||
-rw-r--r-- | include/soc/qcom/tcs.h | 81 |
24 files changed, 3230 insertions, 170 deletions
diff --git a/arch/arm/dts/qcom-ipq4019-u-boot.dtsi b/arch/arm/dts/qcom-ipq4019-u-boot.dtsi new file mode 100644 index 00000000000..07aaaa9f417 --- /dev/null +++ b/arch/arm/dts/qcom-ipq4019-u-boot.dtsi @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT +/ { + soc { + switch: switch@c000000 { + compatible = "qcom,ipq4019-ess"; + reg = <0xc000000 0x80000>, <0x98000 0x800>, <0xc080000 0x8000>; + reg-names = "base", "psgmii_phy", "edma"; + resets = <&gcc ESS_PSGMII_ARES>, <&gcc ESS_RESET>; + reset-names = "psgmii", "ess"; + clocks = <&gcc GCC_ESS_CLK>; + clock-names = "ess"; + mdio = <&mdio>; + interrupts = <GIC_SPI 65 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 66 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 67 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 68 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 69 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 70 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 71 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 72 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 73 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 75 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 76 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 77 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 78 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 79 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 80 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 241 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 242 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 243 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 254 IRQ_TYPE_EDGE_RISING>, + <GIC_SPI 255 IRQ_TYPE_EDGE_RISING>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + swport1: port@1 { /* MAC1 */ + reg = <1>; + label = "lan1"; + phy-handle = <ðphy0>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport2: port@2 { /* MAC2 */ + reg = <2>; + label = "lan2"; + phy-handle = <ðphy1>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport3: port@3 { /* MAC3 */ + reg = <3>; + label = "lan3"; + phy-handle = <ðphy2>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport4: port@4 { /* MAC4 */ + reg = <4>; + label = "lan4"; + phy-handle = <ðphy3>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + + swport5: port@5 { /* MAC5 */ + reg = <5>; + label = "wan"; + phy-handle = <ðphy4>; + phy-mode = "psgmii"; + + status = "disabled"; + }; + }; + }; + }; +}; + +&mdio { + psgmiiphy: psgmii-phy@5 { + reg = <5>; + }; +}; diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig index 49422374026..419e969e5d1 100644 --- a/configs/qcom_defconfig +++ b/configs/qcom_defconfig @@ -33,6 +33,7 @@ CONFIG_CMD_UFS=y CONFIG_CMD_USB=y CONFIG_CMD_CAT=y CONFIG_CMD_BMP=y +CONFIG_CMD_REGULATOR=y CONFIG_CMD_LOG=y CONFIG_OF_LIVE=y CONFIG_BUTTON_QCOM_PMIC=y @@ -90,9 +91,13 @@ CONFIG_DM_PMIC=y CONFIG_PMIC_QCOM=y CONFIG_DM_REGULATOR=y CONFIG_DM_REGULATOR_FIXED=y +CONFIG_DM_REGULATOR_QCOM_RPMH=y CONFIG_SCSI=y CONFIG_MSM_SERIAL=y CONFIG_MSM_GENI_SERIAL=y +CONFIG_SOC_QCOM=y +CONFIG_QCOM_COMMAND_DB=y +CONFIG_QCOM_RPMH=y CONFIG_SPMI_MSM=y CONFIG_SYSINFO=y CONFIG_SYSINFO_SMBIOS=y diff --git a/drivers/clk/qcom/clock-ipq4019.c b/drivers/clk/qcom/clock-ipq4019.c index 9352ff46818..f6760c6fb3d 100644 --- a/drivers/clk/qcom/clock-ipq4019.c +++ b/drivers/clk/qcom/clock-ipq4019.c @@ -64,6 +64,9 @@ static int ipq4019_clk_enable(struct clk *clk) case GCC_USB2_MOCK_UTMI_CLK: /* These clocks is already initialized by SBL1 */ return 0; + case GCC_ESS_CLK: + /* This clock is already initialized by SBL1 */ + return 0; default: return -EINVAL; } @@ -141,6 +144,12 @@ static const struct qcom_reset_map gcc_ipq4019_resets[] = { [GCC_TCSR_BCR] = {0x22000, 0}, [GCC_MPM_BCR] = {0x24000, 0}, [GCC_SPDM_BCR] = {0x25000, 0}, + [ESS_MAC1_ARES] = {0x1200C, 0}, + [ESS_MAC2_ARES] = {0x1200C, 1}, + [ESS_MAC3_ARES] = {0x1200C, 2}, + [ESS_MAC4_ARES] = {0x1200C, 3}, + [ESS_MAC5_ARES] = {0x1200C, 4}, + [ESS_PSGMII_ARES] = {0x1200C, 5}, }; static struct msm_clk_data ipq4019_clk_data = { diff --git a/drivers/core/root.c b/drivers/core/root.c index 7cf6607a9b7..7a714f5478a 100644 --- a/drivers/core/root.c +++ b/drivers/core/root.c @@ -243,7 +243,8 @@ int dm_extended_scan(bool pre_reloc_only) const char * const nodes[] = { "/chosen", "/clocks", - "/firmware" + "/firmware", + "/reserved-memory", }; ret = dm_scan_fdt(pre_reloc_only); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index b4ff033afa9..69ae7c07508 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -325,6 +325,14 @@ config EEPRO100 This driver supports Intel(R) PRO/100 82557/82559/82559ER fast ethernet family of adapters. +config ESSEDMA + bool "Qualcomm ESS Edma support" + depends on DM_ETH && ARCH_IPQ40XX + select PHYLIB + help + This driver supports ethernet DMA adapter found in + Qualcomm IPQ40xx series SoC-s. + config ETH_SANDBOX depends on SANDBOX default y diff --git a/drivers/net/Makefile b/drivers/net/Makefile index dce71685c3d..425dd721f9d 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_DWC_ETH_QOS_STM32) += dwc_eth_qos_stm32.o obj-$(CONFIG_E1000) += e1000.o obj-$(CONFIG_E1000_SPI) += e1000_spi.o obj-$(CONFIG_EEPRO100) += eepro100.o +obj-$(CONFIG_ESSEDMA) += essedma.o obj-$(CONFIG_ETHOC) += ethoc.o obj-$(CONFIG_ETH_DESIGNWARE) += designware.o obj-$(CONFIG_ETH_DESIGNWARE_MESON8B) += dwmac_meson8b.o diff --git a/drivers/net/essedma.c b/drivers/net/essedma.c new file mode 100644 index 00000000000..fccc5f5a440 --- /dev/null +++ b/drivers/net/essedma.c @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Sartura Ltd. + * + * Author: Robert Marko <robert.marko@sartura.hr> + * + * Copyright (c) 2021 Toco Technologies FZE <contact@toco.ae> + * Copyright (c) 2021 Gabor Juhos <j4g8y7@gmail.com> + * + * Qualcomm ESS EDMA ethernet driver + */ + +#include <asm/io.h> +#include <clk.h> +#include <cpu_func.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <errno.h> +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <log.h> +#include <miiphy.h> +#include <net.h> +#include <reset.h> + +#include "essedma.h" + +#define EDMA_MAX_PKT_SIZE (PKTSIZE_ALIGN + PKTALIGN) + +#define EDMA_RXQ_ID 0 +#define EDMA_TXQ_ID 0 + +/* descriptor ring */ +struct edma_ring { + u16 count; /* number of descriptors in the ring */ + void *hw_desc; /* descriptor ring virtual address */ + unsigned int hw_size; /* hw descriptor ring length in bytes */ + dma_addr_t dma; /* descriptor ring physical address */ + u16 head; /* next Tx descriptor to fill */ + u16 tail; /* next Tx descriptor to clean */ +}; + +struct ess_switch { + phys_addr_t base; + struct phy_device *phydev[ESS_PORTS_NUM]; + u32 phy_mask; + ofnode ports_node; + phy_interface_t port_wrapper_mode; + int num_phy; +}; + +struct essedma_priv { + phys_addr_t base; + struct udevice *dev; + struct clk ess_clk; + struct reset_ctl ess_rst; + struct udevice *mdio_dev; + struct ess_switch esw; + phys_addr_t psgmii_base; + struct edma_ring tpd_ring; + struct edma_ring rfd_ring; +}; + +static void esw_port_loopback_set(struct ess_switch *esw, int port, + bool enable) +{ + u32 t; + + t = readl(esw->base + ESS_PORT_LOOKUP_CTRL(port)); + if (enable) + t |= ESS_PORT_LOOP_BACK_EN; + else + t &= ~ESS_PORT_LOOP_BACK_EN; + writel(t, esw->base + ESS_PORT_LOOKUP_CTRL(port)); +} + +static void esw_port_loopback_set_all(struct ess_switch *esw, bool enable) +{ + int i; + + for (i = 1; i < ESS_PORTS_NUM; i++) + esw_port_loopback_set(esw, i, enable); +} + +static void ess_reset(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + reset_assert(&priv->ess_rst); + mdelay(10); + + reset_deassert(&priv->ess_rst); + mdelay(10); +} + +void qca8075_ess_reset(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct phy_device *psgmii_phy; + int i, val; + + /* Find the PSGMII PHY */ + psgmii_phy = priv->esw.phydev[priv->esw.num_phy - 1]; + + /* Fix phy psgmii RX 20bit */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x005b); + + /* Reset phy psgmii */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x001b); + + /* Release reset phy psgmii */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x005b); + for (i = 0; i < 100; i++) { + val = phy_read_mmd(psgmii_phy, MDIO_MMD_PMAPMD, 0x28); + if (val & 0x1) + break; + mdelay(1); + } + if (i >= 100) + printf("QCA807x PSGMII PLL_VCO_CALIB Not Ready\n"); + + /* + * Check qca8075 psgmii calibration done end. + * Freeze phy psgmii RX CDR + */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, 0x1a, 0x2230); + + ess_reset(dev); + + /* Check ipq psgmii calibration done start */ + for (i = 0; i < 100; i++) { + val = readl(priv->psgmii_base + PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_2); + if (val & 0x1) + break; + mdelay(1); + } + if (i >= 100) + printf("PSGMII PLL_VCO_CALIB Not Ready\n"); + + /* + * Check ipq psgmii calibration done end. + * Relesae phy psgmii RX CDR + */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, 0x1a, 0x3230); + + /* Release phy psgmii RX 20bit */ + phy_write(psgmii_phy, MDIO_DEVAD_NONE, MII_BMCR, 0x005f); +} + +#define PSGMII_ST_NUM_RETRIES 20 +#define PSGMII_ST_PKT_COUNT (4 * 1024) +#define PSGMII_ST_PKT_SIZE 1504 + +/* + * Transmitting one byte over a 1000Mbps link requires 8 ns. + * Additionally, use + 1 ns for safety to compensate latencies + * and such. + */ +#define PSGMII_ST_TRAFFIC_TIMEOUT_NS \ + (PSGMII_ST_PKT_COUNT * PSGMII_ST_PKT_SIZE * (8 + 1)) + +#define PSGMII_ST_TRAFFIC_TIMEOUT \ + DIV_ROUND_UP(PSGMII_ST_TRAFFIC_TIMEOUT_NS, 1000000) + +static bool psgmii_self_test_repeat; + +static void psgmii_st_phy_power_down(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR); + val |= QCA807X_POWER_DOWN; + phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, val); +} + +static void psgmii_st_phy_prepare(struct phy_device *phydev) +{ + int val; + + /* check phydev combo port */ + val = phy_read(phydev, MDIO_DEVAD_NONE, + QCA807X_CHIP_CONFIGURATION); + if (val) { + /* Select copper page */ + val |= QCA807X_MEDIA_PAGE_SELECT; + phy_write(phydev, MDIO_DEVAD_NONE, + QCA807X_CHIP_CONFIGURATION, val); + } + + /* Force no link by power down */ + psgmii_st_phy_power_down(phydev); + + /* Packet number (Non documented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8021, PSGMII_ST_PKT_COUNT); + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8062, PSGMII_ST_PKT_SIZE); + + /* Fix MDI status */ + val = phy_read(phydev, MDIO_DEVAD_NONE, QCA807X_FUNCTION_CONTROL); + val &= ~QCA807X_MDI_CROSSOVER_MODE_MASK; + val |= FIELD_PREP(QCA807X_MDI_CROSSOVER_MODE_MASK, + QCA807X_MDI_CROSSOVER_MODE_MANUAL_MDI); + val &= ~QCA807X_POLARITY_REVERSAL; + phy_write(phydev, MDIO_DEVAD_NONE, QCA807X_FUNCTION_CONTROL, val); +} + +static void psgmii_st_phy_recover(struct phy_device *phydev) +{ + int val; + + /* Packet number (Non documented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8021, 0x0); + + /* Disable CRC checker and packet counter */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER); + val &= ~QCA807X_MMD7_PACKET_COUNTER_SELFCLR; + val &= ~QCA807X_MMD7_CRC_PACKET_COUNTER_EN; + phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER, val); + + /* Disable traffic (Undocumented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8020, 0x0); +} + +static void psgmii_st_phy_start_traffic(struct phy_device *phydev) +{ + int val; + + /* Enable CRC checker and packet counter */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER); + val |= QCA807X_MMD7_CRC_PACKET_COUNTER_EN; + phy_write_mmd(phydev, MDIO_MMD_AN, QCA807X_MMD7_CRC_PACKET_COUNTER, val); + + /* Start traffic (Undocumented) */ + phy_write_mmd(phydev, MDIO_MMD_AN, 0x8020, 0xa000); +} + +static bool psgmii_st_phy_check_counters(struct phy_device *phydev) +{ + u32 tx_ok; + + /* + * The number of test packets is limited to 65535 so + * only read the lower 16 bits of the counter. + */ + tx_ok = phy_read_mmd(phydev, MDIO_MMD_AN, + QCA807X_MMD7_VALID_EGRESS_COUNTER_2); + + return (tx_ok == PSGMII_ST_PKT_COUNT); +} + +static void psgmii_st_phy_reset_loopback(struct phy_device *phydev) +{ + /* reset the PHY */ + phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, 0x9000); + + /* enable loopback mode */ + phy_write(phydev, MDIO_DEVAD_NONE, MII_BMCR, 0x4140); +} + +static inline bool psgmii_st_phy_link_is_up(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MDIO_DEVAD_NONE, QCA807X_PHY_SPECIFIC); + return !!(val & QCA807X_PHY_SPECIFIC_LINK); +} + +static bool psgmii_st_phy_wait(struct ess_switch *esw, u32 mask, + int retries, int delay, + bool (*check)(struct phy_device *)) +{ + int i; + + for (i = 0; i < retries; i++) { + int phy; + + for (phy = 0; phy < esw->num_phy - 1; phy++) { + u32 phybit = BIT(phy); + + if (!(mask & phybit)) + continue; + + if (check(esw->phydev[phy])) + mask &= ~phybit; + } + + if (!mask) + break; + + mdelay(delay); + } + + return (!mask); +} + +static bool psgmii_st_phy_wait_link(struct ess_switch *esw, u32 mask) +{ + return psgmii_st_phy_wait(esw, mask, 100, 10, + psgmii_st_phy_link_is_up); +} + +static bool psgmii_st_phy_wait_tx_complete(struct ess_switch *esw, u32 mask) +{ + return psgmii_st_phy_wait(esw, mask, PSGMII_ST_TRAFFIC_TIMEOUT, 1, + psgmii_st_phy_check_counters); +} + +static bool psgmii_st_run_test_serial(struct ess_switch *esw) +{ + bool result = true; + int i; + + for (i = 0; i < esw->num_phy - 1; i++) { + struct phy_device *phydev = esw->phydev[i]; + + psgmii_st_phy_reset_loopback(phydev); + + psgmii_st_phy_wait_link(esw, BIT(i)); + + psgmii_st_phy_start_traffic(phydev); + + /* wait for the traffic to complete */ + result &= psgmii_st_phy_wait_tx_complete(esw, BIT(i)); + + /* Power down */ + psgmii_st_phy_power_down(phydev); + + if (!result) + break; + } + + return result; +} + +static bool psgmii_st_run_test_parallel(struct ess_switch *esw) +{ + bool result; + int i; + + /* enable loopback mode on all PHYs */ + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_reset_loopback(esw->phydev[i]); + + psgmii_st_phy_wait_link(esw, esw->phy_mask); + + /* start traffic on all PHYs parallely */ + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_start_traffic(esw->phydev[i]); + + /* wait for the traffic to complete on all PHYs */ + result = psgmii_st_phy_wait_tx_complete(esw, esw->phy_mask); + + /* Power down all PHYs */ + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_power_down(esw->phydev[i]); + + return result; +} + +struct psgmii_st_stats { + int succeed; + int failed; + int failed_max; + int failed_cont; +}; + +static void psgmii_st_update_stats(struct psgmii_st_stats *stats, + bool success) +{ + if (success) { + stats->succeed++; + stats->failed_cont = 0; + return; + } + + stats->failed++; + stats->failed_cont++; + if (stats->failed_max < stats->failed_cont) + stats->failed_max = stats->failed_cont; +} + +static void psgmii_self_test(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct ess_switch *esw = &priv->esw; + struct psgmii_st_stats stats; + bool result = false; + unsigned long tm; + int i; + + memset(&stats, 0, sizeof(stats)); + + tm = get_timer(0); + + for (i = 0; i < esw->num_phy - 1; i++) + psgmii_st_phy_prepare(esw->phydev[i]); + + for (i = 0; i < PSGMII_ST_NUM_RETRIES; i++) { + qca8075_ess_reset(dev); + + /* enable loopback mode on the switch's ports */ + esw_port_loopback_set_all(esw, true); + + /* run test on each PHYs individually after each other */ + result = psgmii_st_run_test_serial(esw); + + if (result) { + /* run test on each PHYs parallely */ + result = psgmii_st_run_test_parallel(esw); + } + + psgmii_st_update_stats(&stats, result); + + if (psgmii_self_test_repeat) + continue; + + if (result) + break; + } + + for (i = 0; i < esw->num_phy - 1; i++) { + /* Configuration recover */ + psgmii_st_phy_recover(esw->phydev[i]); + + /* Disable loopback */ + phy_write(esw->phydev[i], MDIO_DEVAD_NONE, + QCA807X_FUNCTION_CONTROL, 0x6860); + phy_write(esw->phydev[i], MDIO_DEVAD_NONE, MII_BMCR, 0x9040); + } + + /* disable loopback mode on the switch's ports */ + esw_port_loopback_set_all(esw, false); + + tm = get_timer(tm); + dev_dbg(priv->dev, "\nPSGMII self-test: succeed %d, failed %d (max %d), duration %lu.%03lu secs\n", + stats.succeed, stats.failed, stats.failed_max, + tm / 1000, tm % 1000); +} + +static int ess_switch_disable_lookup(struct ess_switch *esw) +{ + int val; + int i; + + /* Disable port lookup for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + int ess_port_vid; + + val = readl(esw->base + ESS_PORT_LOOKUP_CTRL(i)); + val &= ~ESS_PORT_VID_MEM_MASK; + + switch (i) { + case 0: + fallthrough; + case 5: + /* CPU,WAN port -> nothing */ + ess_port_vid = 0; + break; + case 1 ... 4: + /* LAN ports -> all other LAN ports */ + ess_port_vid = GENMASK(4, 1); + ess_port_vid &= ~BIT(i); + break; + default: + return -EINVAL; + } + + val |= FIELD_PREP(ESS_PORT_VID_MEM_MASK, ess_port_vid); + + writel(val, esw->base + ESS_PORT_LOOKUP_CTRL(i)); + } + + /* Set magic value for the global forwarding register 1 */ + writel(0x3e3e3e, esw->base + ESS_GLOBAL_FW_CTRL1); + + return 0; +} + +static int ess_switch_enable_lookup(struct ess_switch *esw) +{ + int val; + int i; + + /* Enable port lookup for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + int ess_port_vid; + + val = readl(esw->base + ESS_PORT_LOOKUP_CTRL(i)); + val &= ~ESS_PORT_VID_MEM_MASK; + + switch (i) { + case 0: + /* CPU port -> all other ports */ + ess_port_vid = GENMASK(5, 1); + break; + case 1 ... 4: + /* LAN ports -> CPU and all other LAN ports */ + ess_port_vid = GENMASK(4, 0); + ess_port_vid &= ~BIT(i); + break; + case 5: + /* WAN port -> CPU port only */ + ess_port_vid = BIT(0); + break; + default: + return -EINVAL; + } + + val |= FIELD_PREP(ESS_PORT_VID_MEM_MASK, ess_port_vid); + + writel(val, esw->base + ESS_PORT_LOOKUP_CTRL(i)); + } + + /* Set magic value for the global forwarding register 1 */ + writel(0x3f3f3f, esw->base + ESS_GLOBAL_FW_CTRL1); + + return 0; +} + +static void ess_switch_init(struct ess_switch *esw) +{ + int val = 0; + int i; + + /* Set magic value for the global forwarding register 1 */ + writel(0x3e3e3e, esw->base + ESS_GLOBAL_FW_CTRL1); + + /* Set 1000M speed, full duplex and RX/TX flow control for the CPU port*/ + val &= ~ESS_PORT_SPEED_MASK; + val |= FIELD_PREP(ESS_PORT_SPEED_MASK, ESS_PORT_SPEED_1000); + val |= ESS_PORT_DUPLEX_MODE; + val |= ESS_PORT_TX_FLOW_EN; + val |= ESS_PORT_RX_FLOW_EN; + + writel(val, esw->base + ESS_PORT0_STATUS); + + /* Disable port lookup for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + val = readl(esw->base + ESS_PORT_LOOKUP_CTRL(i)); + val &= ~ESS_PORT_VID_MEM_MASK; + + writel(val, esw->base + ESS_PORT_LOOKUP_CTRL(i)); + } + + /* Set HOL settings for all ports*/ + for (i = 0; i < ESS_PORTS_NUM; i++) { + val = 0; + + val |= FIELD_PREP(EG_PORT_QUEUE_NUM_MASK, 30); + if (i == 0 || i == 5) { + val |= FIELD_PREP(EG_PRI5_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI4_QUEUE_NUM_MASK, 4); + } + val |= FIELD_PREP(EG_PRI3_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI2_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI1_QUEUE_NUM_MASK, 4); + val |= FIELD_PREP(EG_PRI0_QUEUE_NUM_MASK, 4); + + writel(val, esw->base + ESS_PORT_HOL_CTRL0(i)); + + val = readl(esw->base + ESS_PORT_HOL_CTRL1(i)); + val &= ~ESS_ING_BUF_NUM_0_MASK; + val |= FIELD_PREP(ESS_ING_BUF_NUM_0_MASK, 6); + + writel(val, esw->base + ESS_PORT_HOL_CTRL1(i)); + } + + /* Give switch some time */ + mdelay(1); + + /* Enable RX and TX MAC-s */ + val = readl(esw->base + ESS_PORT0_STATUS); + val |= ESS_PORT_TXMAC_EN; + val |= ESS_PORT_RXMAC_EN; + + writel(val, esw->base + ESS_PORT0_STATUS); + + /* Set magic value for the global forwarding register 1 */ + writel(0x7f7f7f, esw->base + ESS_GLOBAL_FW_CTRL1); +} + +static int essedma_of_phy(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct ess_switch *esw = &priv->esw; + int num_phy = 0, ret = 0; + ofnode node; + int i; + + ofnode_for_each_subnode(node, esw->ports_node) { + struct ofnode_phandle_args phandle_args; + struct phy_device *phydev; + u32 phy_addr; + + if (ofnode_is_enabled(node)) { + if (ofnode_parse_phandle_with_args(node, "phy-handle", NULL, 0, 0, + &phandle_args)) { + dev_dbg(priv->dev, "Failed to find phy-handle\n"); + return -ENODEV; + } + + ret = ofnode_read_u32(phandle_args.node, "reg", &phy_addr); + if (ret) { + dev_dbg(priv->dev, "Missing reg property in PHY node %s\n", + ofnode_get_name(phandle_args.node)); + return ret; + } + + phydev = dm_mdio_phy_connect(priv->mdio_dev, phy_addr, + dev, priv->esw.port_wrapper_mode); + if (!phydev) { + dev_dbg(priv->dev, "Failed to find phy on addr %d\n", phy_addr); + return -ENODEV; + } + + phydev->node = phandle_args.node; + ret = phy_config(phydev); + + esw->phydev[num_phy] = phydev; + + num_phy++; + } + } + + esw->num_phy = num_phy; + + for (i = 0; i < esw->num_phy - 1; i++) + esw->phy_mask |= BIT(i); + + return ret; +} + +static int essedma_of_switch(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + int port_wrapper_mode = -1; + + priv->esw.ports_node = ofnode_find_subnode(dev_ofnode(dev), "ports"); + if (!ofnode_valid(priv->esw.ports_node)) { + printf("Failed to find ports node\n"); + return -EINVAL; + } + + port_wrapper_mode = ofnode_read_phy_mode(priv->esw.ports_node); + if (port_wrapper_mode == -1) + return -EINVAL; + + priv->esw.port_wrapper_mode = port_wrapper_mode; + + return essedma_of_phy(dev); +} + +static void ipq40xx_edma_start_rx_tx(struct essedma_priv *priv) +{ + volatile u32 data; + + /* enable RX queues */ + data = readl(priv->base + EDMA_REG_RXQ_CTRL); + data |= EDMA_RXQ_CTRL_EN; + writel(data, priv->base + EDMA_REG_RXQ_CTRL); + + /* enable TX queues */ + data = readl(priv->base + EDMA_REG_TXQ_CTRL); + data |= EDMA_TXQ_CTRL_TXQ_EN; + writel(data, priv->base + EDMA_REG_TXQ_CTRL); +} + +/* + * ipq40xx_edma_init_desc() + * Update descriptor ring size, + * Update buffer and producer/consumer index + */ +static void ipq40xx_edma_init_desc(struct essedma_priv *priv) +{ + struct edma_ring *rfd_ring; + struct edma_ring *etdr; + volatile u32 data = 0; + u16 hw_cons_idx = 0; + + /* Set the base address of every TPD ring. */ + etdr = &priv->tpd_ring; + + /* Update TX descriptor ring base address. */ + writel((u32)(etdr->dma & 0xffffffff), + priv->base + EDMA_REG_TPD_BASE_ADDR_Q(EDMA_TXQ_ID)); + data = readl(priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + + /* Calculate hardware consumer index for Tx. */ + hw_cons_idx = FIELD_GET(EDMA_TPD_CONS_IDX_MASK, data); + etdr->head = hw_cons_idx; + etdr->tail = hw_cons_idx; + data &= ~EDMA_TPD_PROD_IDX_MASK; + data |= hw_cons_idx; + + /* Update producer index for Tx. */ + writel(data, priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + + /* Update SW consumer index register for Tx. */ + writel(hw_cons_idx, + priv->base + EDMA_REG_TX_SW_CONS_IDX_Q(EDMA_TXQ_ID)); + + /* Set TPD ring size. */ + writel((u32)(etdr->count & EDMA_TPD_RING_SIZE_MASK), + priv->base + EDMA_REG_TPD_RING_SIZE); + + /* Configure Rx ring. */ + rfd_ring = &priv->rfd_ring; + + /* Update Receive Free descriptor ring base address. */ + writel((u32)(rfd_ring->dma & 0xffffffff), + priv->base + EDMA_REG_RFD_BASE_ADDR_Q(EDMA_RXQ_ID)); + data = readl(priv->base + EDMA_REG_RFD_BASE_ADDR_Q(EDMA_RXQ_ID)); + + /* Update RFD ring size and RX buffer size. */ + data = (rfd_ring->count & EDMA_RFD_RING_SIZE_MASK) + << EDMA_RFD_RING_SIZE_SHIFT; + data |= (EDMA_MAX_PKT_SIZE & EDMA_RX_BUF_SIZE_MASK) + << EDMA_RX_BUF_SIZE_SHIFT; + writel(data, priv->base + EDMA_REG_RX_DESC0); + + /* Disable TX FIFO low watermark and high watermark */ + writel(0, priv->base + EDMA_REG_TXF_WATER_MARK); + + /* Load all of base address above */ + data = readl(priv->base + EDMA_REG_TX_SRAM_PART); + data |= 1 << EDMA_LOAD_PTR_SHIFT; + writel(data, priv->base + EDMA_REG_TX_SRAM_PART); +} + +static void ipq40xx_edma_init_rfd_ring(struct essedma_priv *priv) +{ + struct edma_ring *erdr = &priv->rfd_ring; + struct edma_rfd *rfds = erdr->hw_desc; + int i; + + for (i = 0; i < erdr->count; i++) + rfds[i].buffer_addr = virt_to_phys(net_rx_packets[i]); + + flush_dcache_range(erdr->dma, erdr->dma + erdr->hw_size); + + /* setup producer index */ + erdr->head = erdr->count - 1; + writel(erdr->head, priv->base + EDMA_REG_RFD_IDX_Q(EDMA_RXQ_ID)); +} + +static void ipq40xx_edma_configure(struct essedma_priv *priv) +{ + u32 tmp; + int i; + + /* Set RSS type */ + writel(IPQ40XX_EDMA_RSS_TYPE_NONE, priv->base + EDMA_REG_RSS_TYPE); + + /* Configure RSS indirection table. + * 128 hash will be configured in the following + * pattern: hash{0,1,2,3} = {Q0,Q2,Q4,Q6} respectively + * and so on + */ + for (i = 0; i < EDMA_NUM_IDT; i++) + writel(EDMA_RSS_IDT_VALUE, priv->base + EDMA_REG_RSS_IDT(i)); + + /* Set RFD burst number */ + tmp = (EDMA_RFD_BURST << EDMA_RXQ_RFD_BURST_NUM_SHIFT); + + /* Set RFD prefetch threshold */ + tmp |= (EDMA_RFD_THR << EDMA_RXQ_RFD_PF_THRESH_SHIFT); + + /* Set RFD in host ring low threshold to generte interrupt */ + tmp |= (EDMA_RFD_LTHR << EDMA_RXQ_RFD_LOW_THRESH_SHIFT); + writel(tmp, priv->base + EDMA_REG_RX_DESC1); + + /* configure reception control data. */ + + /* Set Rx FIFO threshold to start to DMA data to host */ + tmp = EDMA_FIFO_THRESH_128_BYTE; + + /* Set RX remove vlan bit */ + tmp |= EDMA_RXQ_CTRL_RMV_VLAN; + writel(tmp, priv->base + EDMA_REG_RXQ_CTRL); + + /* Configure transmission control data */ + tmp = (EDMA_TPD_BURST << EDMA_TXQ_NUM_TPD_BURST_SHIFT); + tmp |= EDMA_TXQ_CTRL_TPD_BURST_EN; + tmp |= (EDMA_TXF_BURST << EDMA_TXQ_TXF_BURST_NUM_SHIFT); + writel(tmp, priv->base + EDMA_REG_TXQ_CTRL); +} + +static void ipq40xx_edma_stop_rx_tx(struct essedma_priv *priv) +{ + volatile u32 data; + + data = readl(priv->base + EDMA_REG_RXQ_CTRL); + data &= ~EDMA_RXQ_CTRL_EN; + writel(data, priv->base + EDMA_REG_RXQ_CTRL); + data = readl(priv->base + EDMA_REG_TXQ_CTRL); + data &= ~EDMA_TXQ_CTRL_TXQ_EN; + writel(data, priv->base + EDMA_REG_TXQ_CTRL); +} + +static int ipq40xx_eth_recv(struct udevice *dev, int flags, uchar **packetp) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct edma_ring *erdr = &priv->rfd_ring; + struct edma_rrd *rrd; + u32 hw_tail; + u8 *rx_pkt; + + hw_tail = readl(priv->base + EDMA_REG_RFD_IDX_Q(EDMA_RXQ_ID)); + hw_tail = FIELD_GET(EDMA_RFD_CONS_IDX_MASK, hw_tail); + + if (hw_tail == erdr->tail) + return -EAGAIN; + + rx_pkt = net_rx_packets[erdr->tail]; + invalidate_dcache_range((unsigned long)rx_pkt, + (unsigned long)(rx_pkt + EDMA_MAX_PKT_SIZE)); + + rrd = (struct edma_rrd *)rx_pkt; + + /* Check if RRD is valid */ + if (!(rrd->rrd7 & EDMA_RRD7_DESC_VALID)) + return 0; + + *packetp = rx_pkt + EDMA_RRD_SIZE; + + /* get the packet size */ + return rrd->rrd6; +} + +static int ipq40xx_eth_free_pkt(struct udevice *dev, uchar *packet, + int length) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct edma_ring *erdr; + + erdr = &priv->rfd_ring; + + /* Update the producer index */ + writel(erdr->head, priv->base + EDMA_REG_RFD_IDX_Q(EDMA_RXQ_ID)); + + erdr->head++; + if (erdr->head == erdr->count) + erdr->head = 0; + + /* Update the consumer index */ + erdr->tail++; + if (erdr->tail == erdr->count) + erdr->tail = 0; + + writel(erdr->tail, + priv->base + EDMA_REG_RX_SW_CONS_IDX_Q(EDMA_RXQ_ID)); + + return 0; +} + +static int ipq40xx_eth_start(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + ipq40xx_edma_init_rfd_ring(priv); + + ipq40xx_edma_start_rx_tx(priv); + ess_switch_enable_lookup(&priv->esw); + + return 0; +} + +/* + * One TPD would be enough for sending a packet, however because the + * minimal cache line size is larger than the size of a TPD it is not + * possible to flush only one at once. To overcome this limitation + * multiple TPDs are used for sending a single packet. + */ +#define EDMA_TPDS_PER_PACKET 4 +#define EDMA_TPD_MIN_BYTES 4 +#define EDMA_MIN_PKT_SIZE (EDMA_TPDS_PER_PACKET * EDMA_TPD_MIN_BYTES) + +#define EDMA_TX_COMPLETE_TIMEOUT 1000000 + +static int ipq40xx_eth_send(struct udevice *dev, void *packet, int length) +{ + struct essedma_priv *priv = dev_get_priv(dev); + struct edma_tpd *first_tpd; + struct edma_tpd *tpds; + int i; + + if (length < EDMA_MIN_PKT_SIZE) + return 0; + + flush_dcache_range((unsigned long)(packet), + (unsigned long)(packet) + + roundup(length, ARCH_DMA_MINALIGN)); + + tpds = priv->tpd_ring.hw_desc; + for (i = 0; i < EDMA_TPDS_PER_PACKET; i++) { + struct edma_tpd *tpd; + void *frag; + + frag = packet + (i * EDMA_TPD_MIN_BYTES); + + /* get the next TPD */ + tpd = &tpds[priv->tpd_ring.head]; + if (i == 0) + first_tpd = tpd; + + /* update the software index */ + priv->tpd_ring.head++; + if (priv->tpd_ring.head == priv->tpd_ring.count) + priv->tpd_ring.head = 0; + + tpd->svlan_tag = 0; + tpd->addr = virt_to_phys(frag); + tpd->word3 = EDMA_PORT_ENABLE_ALL << EDMA_TPD_PORT_BITMAP_SHIFT; + + if (i < (EDMA_TPDS_PER_PACKET - 1)) { + tpd->len = EDMA_TPD_MIN_BYTES; + tpd->word1 = 0; + } else { + tpd->len = length; + tpd->word1 = 1 << EDMA_TPD_EOP_SHIFT; + } + + length -= EDMA_TPD_MIN_BYTES; + } + + /* make sure that memory writing completes */ + wmb(); + + flush_dcache_range((unsigned long)first_tpd, + (unsigned long)first_tpd + + EDMA_TPDS_PER_PACKET * sizeof(struct edma_tpd)); + + /* update the TX producer index */ + writel(priv->tpd_ring.head, + priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + + /* Wait for TX DMA completion */ + for (i = 0; i < EDMA_TX_COMPLETE_TIMEOUT; i++) { + u32 r, prod, cons; + + r = readl(priv->base + EDMA_REG_TPD_IDX_Q(EDMA_TXQ_ID)); + prod = FIELD_GET(EDMA_TPD_PROD_IDX_MASK, r); + cons = FIELD_GET(EDMA_TPD_CONS_IDX_MASK, r); + + if (cons == prod) + break; + + udelay(1); + } + + if (i == EDMA_TX_COMPLETE_TIMEOUT) + printf("TX timeout: packet not sent!\n"); + + /* update the software TX consumer index register */ + writel(priv->tpd_ring.head, + priv->base + EDMA_REG_TX_SW_CONS_IDX_Q(EDMA_TXQ_ID)); + + return 0; +} + +static void ipq40xx_eth_stop(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + ess_switch_disable_lookup(&priv->esw); + ipq40xx_edma_stop_rx_tx(priv); +} + +static void ipq40xx_edma_free_ring(struct edma_ring *ring) +{ + free(ring->hw_desc); +} + +/* + * Free Tx and Rx rings + */ +static void ipq40xx_edma_free_rings(struct essedma_priv *priv) +{ + ipq40xx_edma_free_ring(&priv->tpd_ring); + ipq40xx_edma_free_ring(&priv->rfd_ring); +} + +/* + * ipq40xx_edma_alloc_ring() + * allocate edma ring descriptor. + */ +static int ipq40xx_edma_alloc_ring(struct edma_ring *erd, + unsigned int desc_size) +{ + erd->head = 0; + erd->tail = 0; + + /* Alloc HW descriptors */ + erd->hw_size = roundup(desc_size * erd->count, + ARCH_DMA_MINALIGN); + + erd->hw_desc = memalign(CONFIG_SYS_CACHELINE_SIZE, erd->hw_size); + if (!erd->hw_desc) + return -ENOMEM; + + memset(erd->hw_desc, 0, erd->hw_size); + erd->dma = virt_to_phys(erd->hw_desc); + + return 0; + +} + +/* + * ipq40xx_allocate_tx_rx_rings() + */ +static int ipq40xx_edma_alloc_tx_rx_rings(struct essedma_priv *priv) +{ + int ret; + + ret = ipq40xx_edma_alloc_ring(&priv->tpd_ring, + sizeof(struct edma_tpd)); + if (ret) + return ret; + + ret = ipq40xx_edma_alloc_ring(&priv->rfd_ring, + sizeof(struct edma_rfd)); + if (ret) + goto err_free_tpd; + + return 0; + +err_free_tpd: + ipq40xx_edma_free_ring(&priv->tpd_ring); + return ret; +} + +static int ipq40xx_eth_write_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_plat(dev); + struct essedma_priv *priv = dev_get_priv(dev); + unsigned char *mac = pdata->enetaddr; + u32 mac_lo, mac_hi; + + mac_hi = ((u32)mac[0]) << 8 | (u32)mac[1]; + mac_lo = ((u32)mac[2]) << 24 | ((u32)mac[3]) << 16 | + ((u32)mac[4]) << 8 | (u32)mac[5]; + + writel(mac_lo, priv->base + REG_MAC_CTRL0); + writel(mac_hi, priv->base + REG_MAC_CTRL1); + + return 0; +} + +static int edma_init(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + int ret; + + priv->tpd_ring.count = IPQ40XX_EDMA_TX_RING_SIZE; + priv->rfd_ring.count = PKTBUFSRX; + + ret = ipq40xx_edma_alloc_tx_rx_rings(priv); + if (ret) + return -ENOMEM; + + ipq40xx_edma_stop_rx_tx(priv); + + /* Configure EDMA. */ + ipq40xx_edma_configure(priv); + + /* Configure descriptor Ring */ + ipq40xx_edma_init_desc(priv); + + ess_switch_disable_lookup(&priv->esw); + + return 0; +} + +static int essedma_probe(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + int ret; + + priv->dev = dev; + + priv->base = dev_read_addr_name(dev, "edma"); + if (priv->base == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->psgmii_base = dev_read_addr_name(dev, "psgmii_phy"); + if (priv->psgmii_base == FDT_ADDR_T_NONE) + return -EINVAL; + + priv->esw.base = dev_read_addr_name(dev, "base"); + if (priv->esw.base == FDT_ADDR_T_NONE) + return -EINVAL; + + ret = clk_get_by_name(dev, "ess", &priv->ess_clk); + if (ret) + return ret; + + ret = reset_get_by_name(dev, "ess", &priv->ess_rst); + if (ret) + return ret; + + ret = clk_enable(&priv->ess_clk); + if (ret) + return ret; + + ess_reset(dev); + + ret = uclass_get_device_by_driver(UCLASS_MDIO, + DM_DRIVER_GET(ipq4019_mdio), + &priv->mdio_dev); + if (ret) { + dev_dbg(dev, "Cant find IPQ4019 MDIO: %d\n", ret); + goto err; + } + + /* OF switch and PHY parsing and configuration */ + ret = essedma_of_switch(dev); + if (ret) + goto err; + + switch (priv->esw.port_wrapper_mode) { + case PHY_INTERFACE_MODE_PSGMII: + writel(PSGMIIPHY_PLL_VCO_VAL, + priv->psgmii_base + PSGMIIPHY_PLL_VCO_RELATED_CTRL); + writel(PSGMIIPHY_VCO_VAL, priv->psgmii_base + + PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_1); + /* wait for 10ms */ + mdelay(10); + writel(PSGMIIPHY_VCO_RST_VAL, priv->psgmii_base + + PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_1); + break; + case PHY_INTERFACE_MODE_RGMII: + writel(0x1, RGMII_TCSR_ESS_CFG); + writel(0x400, priv->esw.base + ESS_RGMII_CTRL); + break; + default: + printf("Unknown MII interface\n"); + } + + if (priv->esw.port_wrapper_mode == PHY_INTERFACE_MODE_PSGMII) + psgmii_self_test(dev); + + ess_switch_init(&priv->esw); + + ret = edma_init(dev); + if (ret) + goto err; + + return 0; + +err: + reset_assert(&priv->ess_rst); + clk_disable(&priv->ess_clk); + return ret; +} + +static int essedma_remove(struct udevice *dev) +{ + struct essedma_priv *priv = dev_get_priv(dev); + + ipq40xx_edma_free_rings(priv); + + clk_disable(&priv->ess_clk); + reset_assert(&priv->ess_rst); + + return 0; +} + +static const struct eth_ops essedma_eth_ops = { + .start = ipq40xx_eth_start, + .send = ipq40xx_eth_send, + .recv = ipq40xx_eth_recv, + .free_pkt = ipq40xx_eth_free_pkt, + .stop = ipq40xx_eth_stop, + .write_hwaddr = ipq40xx_eth_write_hwaddr, +}; + +static const struct udevice_id essedma_ids[] = { + { .compatible = "qcom,ipq4019-ess", }, + { } +}; + +U_BOOT_DRIVER(essedma) = { + .name = "essedma", + .id = UCLASS_ETH, + .of_match = essedma_ids, + .probe = essedma_probe, + .remove = essedma_remove, + .priv_auto = sizeof(struct essedma_priv), + .plat_auto = sizeof(struct eth_pdata), + .ops = &essedma_eth_ops, + .flags = DM_FLAG_ALLOC_PRIV_DMA, +}; diff --git a/drivers/net/essedma.h b/drivers/net/essedma.h new file mode 100644 index 00000000000..067cb442fb4 --- /dev/null +++ b/drivers/net/essedma.h @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2020 Sartura Ltd. + * + * Author: Robert Marko <robert.marko@sartura.hr> + * + * Copyright (c) 2021 Toco Technologies FZE <contact@toco.ae> + * Copyright (c) 2021 Gabor Juhos <j4g8y7@gmail.com> + * + * Qualcomm ESS EDMA ethernet driver + */ + +#ifndef _ESSEDMA_ETH_H +#define _ESSEDMA_ETH_H + +#define ESS_PORTS_NUM 6 + +#define ESS_RGMII_CTRL 0x4 + +#define ESS_GLOBAL_FW_CTRL1 0x624 + +#define ESS_PORT0_STATUS 0x7c +#define ESS_PORT_SPEED_MASK GENMASK(1, 0) +#define ESS_PORT_SPEED_1000 3 +#define ESS_PORT_SPEED_100 2 +#define ESS_PORT_SPEED_10 1 +#define ESS_PORT_TXMAC_EN BIT(2) +#define ESS_PORT_RXMAC_EN BIT(3) +#define ESS_PORT_TX_FLOW_EN BIT(4) +#define ESS_PORT_RX_FLOW_EN BIT(5) +#define ESS_PORT_DUPLEX_MODE BIT(6) + +#define ESS_PORT_LOOKUP_CTRL(_p) (0x660 + (_p) * 12) +#define ESS_PORT_LOOP_BACK_EN BIT(21) +#define ESS_PORT_VID_MEM_MASK GENMASK(6, 0) + +#define ESS_PORT_HOL_CTRL0(_p) (0x970 + (_p) * 8) +#define EG_PORT_QUEUE_NUM_MASK GENMASK(29, 24) + +/* Ports 0 and 5 have queues 0-5 + * Ports 1 to 4 have queues 0-3 + */ +#define EG_PRI5_QUEUE_NUM_MASK GENMASK(23, 20) +#define EG_PRI4_QUEUE_NUM_MASK GENMASK(19, 16) +#define EG_PRI3_QUEUE_NUM_MASK GENMASK(15, 12) +#define EG_PRI2_QUEUE_NUM_MASK GENMASK(11, 8) +#define EG_PRI1_QUEUE_NUM_MASK GENMASK(7, 4) +#define EG_PRI0_QUEUE_NUM_MASK GENMASK(3, 0) + +#define ESS_PORT_HOL_CTRL1(_p) (0x974 + (_p) * 8) +#define ESS_ING_BUF_NUM_0_MASK GENMASK(3, 0) + +/* QCA807x PHY registers */ +#define QCA807X_CHIP_CONFIGURATION 0x1f +#define QCA807X_MEDIA_PAGE_SELECT BIT(15) + +#define QCA807X_POWER_DOWN BIT(11) + +#define QCA807X_FUNCTION_CONTROL 0x10 +#define QCA807X_MDI_CROSSOVER_MODE_MASK GENMASK(6, 5) +#define QCA807X_MDI_CROSSOVER_MODE_MANUAL_MDI 0 +#define QCA807X_POLARITY_REVERSAL BIT(1) + +#define QCA807X_PHY_SPECIFIC 0x11 +#define QCA807X_PHY_SPECIFIC_LINK BIT(10) + +#define QCA807X_MMD7_CRC_PACKET_COUNTER 0x8029 +#define QCA807X_MMD7_PACKET_COUNTER_SELFCLR BIT(1) +#define QCA807X_MMD7_CRC_PACKET_COUNTER_EN BIT(0) +#define QCA807X_MMD7_VALID_EGRESS_COUNTER_2 0x802e + +/* PSGMII specific registers */ +#define PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_1 0x9c +#define PSGMIIPHY_VCO_VAL 0x4ada +#define PSGMIIPHY_VCO_RST_VAL 0xada +#define PSGMIIPHY_VCO_CALIBRATION_CTRL_REGISTER_2 0xa0 + +#define PSGMIIPHY_PLL_VCO_RELATED_CTRL 0x78c +#define PSGMIIPHY_PLL_VCO_VAL 0x2803 + +#define RGMII_TCSR_ESS_CFG 0x01953000 + +/* EDMA registers */ +#define IPQ40XX_EDMA_TX_RING_SIZE 8 +#define IPQ40XX_EDMA_RSS_TYPE_NONE 0x1 + +#define EDMA_RSS_TYPE 0 +#define EDMA_TPD_EOP_SHIFT 31 + +/* tpd word 3 bit 18-28 */ +#define EDMA_TPD_PORT_BITMAP_SHIFT 18 + +/* Enable Tx for all ports */ +#define EDMA_PORT_ENABLE_ALL 0x3E + +/* Edma receive consumer index */ +/* x = queue id */ +#define EDMA_REG_RX_SW_CONS_IDX_Q(x) (0x220 + ((x) << 2)) +/* Edma transmit consumer index */ +#define EDMA_REG_TX_SW_CONS_IDX_Q(x) (0x240 + ((x) << 2)) +/* TPD Index Register */ +#define EDMA_REG_TPD_IDX_Q(x) (0x460 + ((x) << 2)) +/* Tx Descriptor Control Register */ +#define EDMA_REG_TPD_RING_SIZE 0x41C +#define EDMA_TPD_RING_SIZE_MASK 0xFFFF + +/* Transmit descriptor base address */ + /* x = queue id */ +#define EDMA_REG_TPD_BASE_ADDR_Q(x) (0x420 + ((x) << 2)) +#define EDMA_TPD_PROD_IDX_MASK GENMASK(15, 0) +#define EDMA_TPD_CONS_IDX_MASK GENMASK(31, 16) + +#define EDMA_REG_TX_SRAM_PART 0x400 +#define EDMA_LOAD_PTR_SHIFT 16 + +/* TXQ Control Register */ +#define EDMA_REG_TXQ_CTRL 0x404 +#define EDMA_TXQ_CTRL_TXQ_EN 0x20 +#define EDMA_TXQ_CTRL_TPD_BURST_EN 0x100 +#define EDMA_TXQ_NUM_TPD_BURST_SHIFT 0 +#define EDMA_TXQ_TXF_BURST_NUM_SHIFT 16 +#define EDMA_TXF_BURST 0x100 +#define EDMA_TPD_BURST 5 + +#define EDMA_REG_TXF_WATER_MARK 0x408 + +/* RSS Indirection Register */ +/* x = No. of indirection table */ +#define EDMA_REG_RSS_IDT(x) (0x840 + ((x) << 2)) +#define EDMA_NUM_IDT 16 +#define EDMA_RSS_IDT_VALUE 0x64206420 + +/* RSS Hash Function Type Register */ +#define EDMA_REG_RSS_TYPE 0x894 + +/* x = queue id */ +#define EDMA_REG_RFD_BASE_ADDR_Q(x) (0x950 + ((x) << 2)) +/* RFD Index Register */ +#define EDMA_RFD_BURST 8 +#define EDMA_RFD_THR 16 +#define EDMA_RFD_LTHR 0 +#define EDMA_REG_RFD_IDX_Q(x) (0x9B0 + ((x) << 2)) + +#define EDMA_RFD_CONS_IDX_MASK GENMASK(27, 16) + +/* Rx Descriptor Control Register */ +#define EDMA_REG_RX_DESC0 0xA10 +#define EDMA_RFD_RING_SIZE_MASK 0xFFF +#define EDMA_RX_BUF_SIZE_MASK 0xFFFF +#define EDMA_RFD_RING_SIZE_SHIFT 0 +#define EDMA_RX_BUF_SIZE_SHIFT 16 + +#define EDMA_REG_RX_DESC1 0xA14 +#define EDMA_RXQ_RFD_BURST_NUM_SHIFT 0 +#define EDMA_RXQ_RFD_PF_THRESH_SHIFT 8 +#define EDMA_RXQ_RFD_LOW_THRESH_SHIFT 16 + +/* RXQ Control Register */ +#define EDMA_REG_RXQ_CTRL 0xA18 +#define EDMA_FIFO_THRESH_128_BYTE 0x0 +#define EDMA_RXQ_CTRL_RMV_VLAN 0x00000002 +#define EDMA_RXQ_CTRL_EN 0x0000FF00 + +/* MAC Control Register */ +#define REG_MAC_CTRL0 0xC20 +#define REG_MAC_CTRL1 0xC24 + +/* Transmit Packet Descriptor */ +struct edma_tpd { + u16 len; /* full packet including CRC */ + u16 svlan_tag; /* vlan tag */ + u32 word1; /* byte 4-7 */ + u32 addr; /* address of buffer */ + u32 word3; /* byte 12 */ +}; + +/* Receive Return Descriptor */ +struct edma_rrd { + u16 rrd0; + u16 rrd1; + u16 rrd2; + u16 rrd3; + u16 rrd4; + u16 rrd5; + u16 rrd6; + u16 rrd7; +} __packed; + +#define EDMA_RRD_SIZE sizeof(struct edma_rrd) + +#define EDMA_RRD7_DESC_VALID BIT(15) + +/* Receive Free Descriptor */ +struct edma_rfd { + u32 buffer_addr; /* buffer address */ +}; + +#endif /* _ESSEDMA_ETH_H */ diff --git a/drivers/power/regulator/Kconfig b/drivers/power/regulator/Kconfig index 102ec7bc5f8..bc061c20d75 100644 --- a/drivers/power/regulator/Kconfig +++ b/drivers/power/regulator/Kconfig @@ -216,6 +216,14 @@ config DM_REGULATOR_GPIO features for gpio regulators. The driver implements get/set for voltage value. +config DM_REGULATOR_QCOM_RPMH + bool "Enable driver model for Qualcomm RPMh regulator" + depends on DM_REGULATOR && QCOM_RPMH + ---help--- + Enable support for the Qualcomm RPMh regulator. The driver + implements get/set api for a limited set of regulators used + by u-boot. + config SPL_DM_REGULATOR_GPIO bool "Enable Driver Model for GPIO REGULATOR in SPL" depends on DM_REGULATOR_GPIO && SPL_GPIO diff --git a/drivers/power/regulator/Makefile b/drivers/power/regulator/Makefile index f79932d8330..56a527612b7 100644 --- a/drivers/power/regulator/Makefile +++ b/drivers/power/regulator/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_$(SPL_)DM_REGULATOR_FAN53555) += fan53555.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_COMMON) += regulator_common.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_FIXED) += fixed.o obj-$(CONFIG_$(SPL_)DM_REGULATOR_GPIO) += gpio-regulator.o +obj-$(CONFIG_DM_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o obj-$(CONFIG_$(SPL_TPL_)REGULATOR_RK8XX) += rk8xx.o obj-$(CONFIG_DM_REGULATOR_S2MPS11) += s2mps11_regulator.o obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o diff --git a/drivers/power/regulator/qcom-rpmh-regulator.c b/drivers/power/regulator/qcom-rpmh-regulator.c new file mode 100644 index 00000000000..06fd3f31956 --- /dev/null +++ b/drivers/power/regulator/qcom-rpmh-regulator.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. +// Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/err.h> +#include <dm/device_compat.h> +#include <dm/device.h> +#include <dm/devres.h> +#include <dm/lists.h> +#include <power/regulator.h> +#include <log.h> + +#include <soc/qcom/cmd-db.h> +#include <soc/qcom/rpmh.h> + +#include <dt-bindings/regulator/qcom,rpmh-regulator.h> + +/** + * enum rpmh_regulator_type - supported RPMh accelerator types + * @VRM: RPMh VRM accelerator which supports voting on enable, voltage, + * and mode of LDO, SMPS, and BOB type PMIC regulators. + * @XOB: RPMh XOB accelerator which supports voting on the enable state + * of PMIC regulators. + */ +enum rpmh_regulator_type { + VRM, + XOB, +}; + +enum rpmh_regulator_mode { + REGULATOR_MODE_RETENTION, + REGULATOR_MODE_LPM, + REGULATOR_MODE_AUTO, + REGULATOR_MODE_HPM, +}; + +#define RPMH_REGULATOR_REG_VRM_VOLTAGE 0x0 +#define RPMH_REGULATOR_REG_ENABLE 0x4 +#define RPMH_REGULATOR_REG_VRM_MODE 0x8 + +#define PMIC4_LDO_MODE_RETENTION 4 +#define PMIC4_LDO_MODE_LPM 5 +#define PMIC4_LDO_MODE_HPM 7 + +#define PMIC4_SMPS_MODE_RETENTION 4 +#define PMIC4_SMPS_MODE_PFM 5 +#define PMIC4_SMPS_MODE_AUTO 6 +#define PMIC4_SMPS_MODE_PWM 7 + +#define PMIC4_BOB_MODE_PASS 0 +#define PMIC4_BOB_MODE_PFM 1 +#define PMIC4_BOB_MODE_AUTO 2 +#define PMIC4_BOB_MODE_PWM 3 + +#define PMIC5_LDO_MODE_RETENTION 3 +#define PMIC5_LDO_MODE_LPM 4 +#define PMIC5_LDO_MODE_HPM 7 + +#define PMIC5_SMPS_MODE_RETENTION 3 +#define PMIC5_SMPS_MODE_PFM 4 +#define PMIC5_SMPS_MODE_AUTO 6 +#define PMIC5_SMPS_MODE_PWM 7 + +#define PMIC5_BOB_MODE_PASS 2 +#define PMIC5_BOB_MODE_PFM 4 +#define PMIC5_BOB_MODE_AUTO 6 +#define PMIC5_BOB_MODE_PWM 7 + + +/** + * struct linear_range - table of selector - value pairs + * + * Define a lookup-table for range of values. Intended to help when looking + * for a register value matching certaing physical measure (like voltage). + * Usable when increment of one in register always results a constant increment + * of the physical measure (like voltage). + * + * @min: Lowest value in range + * @min_sel: Lowest selector for range + * @max_sel: Highest selector for range + * @step: Value step size + */ +struct linear_range { + unsigned int min; + unsigned int min_sel; + unsigned int max_sel; + unsigned int step; +}; + +/* Initialize struct linear_range for regulators */ +#define REGULATOR_LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV) \ +{ \ + .min = _min_uV, \ + .min_sel = _min_sel, \ + .max_sel = _max_sel, \ + .step = _step_uV, \ +} + +/** + * struct rpmh_vreg_hw_data - RPMh regulator hardware configurations + * @regulator_type: RPMh accelerator type used to manage this + * regulator + * @ops: Pointer to regulator ops callback structure + * @voltage_range: The single range of voltages supported by this + * PMIC regulator type + * @n_voltages: The number of unique voltage set points defined + * by voltage_range + * @hpm_min_load_uA: Minimum load current in microamps that requires + * high power mode (HPM) operation. This is used + * for LDO hardware type regulators only. + * @pmic_mode_map: Array indexed by regulator framework mode + * containing PMIC hardware modes. Must be large + * enough to index all framework modes supported + * by this regulator hardware type. + * @of_map_mode: Maps an RPMH_REGULATOR_MODE_* mode value defined + * in device tree to a regulator framework mode + */ +struct rpmh_vreg_hw_data { + enum rpmh_regulator_type regulator_type; + const struct dm_regulator_ops *ops; + struct linear_range voltage_range; + int n_voltages; + int hpm_min_load_uA; + struct dm_regulator_mode *pmic_mode_map; + int n_modes; + unsigned int (*of_map_mode)(unsigned int mode); +}; + +/** + * struct rpmh_vreg - individual RPMh regulator data structure encapsulating a + * single regulator device + * @dev: Device pointer for the top-level PMIC RPMh + * regulator parent device. This is used as a + * handle in RPMh write requests. + * @addr: Base address of the regulator resource within + * an RPMh accelerator + * @rdesc: Regulator descriptor + * @hw_data: PMIC regulator configuration data for this RPMh + * regulator + * @always_wait_for_ack: Boolean flag indicating if a request must always + * wait for an ACK from RPMh before continuing even + * if it corresponds to a strictly lower power + * state (e.g. enabled --> disabled). + * @enabled: Flag indicating if the regulator is enabled or + * not + * @bypassed: Boolean indicating if the regulator is in + * bypass (pass-through) mode or not. This is + * only used by BOB rpmh-regulator resources. + * @uv: Selector used for get_voltage_sel() and + * set_value() callbacks + * @mode: RPMh VRM regulator current framework mode + */ +struct rpmh_vreg { + struct udevice *dev; + u32 addr; + const struct rpmh_vreg_hw_data *hw_data; + bool always_wait_for_ack; + + int enabled; + bool bypassed; + int uv; + int mode; +}; + +/** + * struct rpmh_vreg_init_data - initialization data for an RPMh regulator + * @name: Name for the regulator which also corresponds + * to the device tree subnode name of the regulator + * @resource_name: RPMh regulator resource name format string. + * This must include exactly one field: '%s' which + * is filled at run-time with the PMIC ID provided + * by device tree property qcom,pmic-id. Example: + * "ldo%s1" for RPMh resource "ldoa1". + * @supply_name: Parent supply regulator name + * @hw_data: Configuration data for this PMIC regulator type + */ +struct rpmh_vreg_init_data { + const char *name; + const char *resource_name; + const char *supply_name; + const struct rpmh_vreg_hw_data *hw_data; +}; + +/** + * rpmh_regulator_send_request() - send the request to RPMh + * @vreg: Pointer to the RPMh regulator + * @cmd: Pointer to the RPMh command to send + * @wait_for_ack: Boolean indicating if execution must wait until the + * request has been acknowledged as complete + * + * Return: 0 on success, errno on failure + */ +static int rpmh_regulator_send_request(struct rpmh_vreg *vreg, + const struct tcs_cmd *cmd, bool wait_for_ack) +{ + int ret; + + if (wait_for_ack || vreg->always_wait_for_ack) + ret = rpmh_write(vreg->dev->parent, RPMH_ACTIVE_ONLY_STATE, cmd, 1); + else + ret = rpmh_write_async(vreg->dev->parent, RPMH_ACTIVE_ONLY_STATE, cmd, 1); + + return ret; +} + +static int _rpmh_regulator_vrm_set_value(struct udevice *rdev, + int uv, bool wait_for_ack) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_VOLTAGE, + }; + int ret; + unsigned int selector; + + selector = (uv - vreg->hw_data->voltage_range.min) / vreg->hw_data->voltage_range.step; + cmd.data = DIV_ROUND_UP(vreg->hw_data->voltage_range.min + + selector * vreg->hw_data->voltage_range.step, 1000); + + ret = rpmh_regulator_send_request(vreg, &cmd, wait_for_ack); + if (!ret) + vreg->uv = cmd.data * 1000; + + return ret; +} + +static int rpmh_regulator_vrm_set_value(struct udevice *rdev, + int uv) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: set_value %d (current %d)\n", rdev->name, uv, vreg->uv); + + if (vreg->enabled == -EINVAL) { + /* + * Cache the voltage and send it later when the regulator is + * enabled or disabled. + */ + vreg->uv = uv; + return 0; + } + + return _rpmh_regulator_vrm_set_value(rdev, uv, + uv > vreg->uv); +} + +static int rpmh_regulator_vrm_get_value(struct udevice *rdev) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: get_value %d\n", rdev->name, vreg->uv); + + return vreg->uv; +} + +static int rpmh_regulator_is_enabled(struct udevice *rdev) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: is_enabled %d\n", rdev->name, vreg->enabled); + + return vreg->enabled > 0; +} + +static int rpmh_regulator_set_enable_state(struct udevice *rdev, + bool enable) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_ENABLE, + .data = enable, + }; + int ret; + + debug("%s: set_enable %d (current %d)\n", rdev->name, enable, + vreg->enabled); + + if (vreg->enabled == -EINVAL && + vreg->uv != -ENOTRECOVERABLE) { + ret = _rpmh_regulator_vrm_set_value(rdev, + vreg->uv, true); + if (ret < 0) + return ret; + } + + ret = rpmh_regulator_send_request(vreg, &cmd, enable); + if (!ret) + vreg->enabled = enable; + + return ret; +} + +static int rpmh_regulator_vrm_set_mode_bypass(struct rpmh_vreg *vreg, + unsigned int mode, bool bypassed) +{ + struct tcs_cmd cmd = { + .addr = vreg->addr + RPMH_REGULATOR_REG_VRM_MODE, + }; + struct dm_regulator_mode *pmic_mode; + int i; + + if (mode > REGULATOR_MODE_HPM) + return -EINVAL; + + for (i = 0; i < vreg->hw_data->n_modes; i++) { + pmic_mode = &vreg->hw_data->pmic_mode_map[i]; + if (pmic_mode->id == mode) + break; + } + if (pmic_mode->id != mode) { + printf("Invalid mode %d\n", mode); + return -EINVAL; + } + + if (bypassed) + cmd.data = PMIC4_BOB_MODE_PASS; + else + cmd.data = pmic_mode->id; + + return rpmh_regulator_send_request(vreg, &cmd, true); +} + +static int rpmh_regulator_vrm_set_mode(struct udevice *rdev, + int mode) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + int ret; + + debug("%s: set_mode %d (current %d)\n", rdev->name, mode, vreg->mode); + + if (mode == vreg->mode) + return 0; + + ret = rpmh_regulator_vrm_set_mode_bypass(vreg, mode, vreg->bypassed); + if (!ret) + vreg->mode = mode; + + return ret; +} + +static int rpmh_regulator_vrm_get_mode(struct udevice *rdev) +{ + struct rpmh_vreg *vreg = dev_get_priv(rdev); + + debug("%s: get_mode %d\n", rdev->name, vreg->mode); + + return vreg->mode; +} +static const struct dm_regulator_ops rpmh_regulator_vrm_drms_ops = { + .get_value = rpmh_regulator_vrm_get_value, + .set_value = rpmh_regulator_vrm_set_value, + .set_enable = rpmh_regulator_set_enable_state, + .get_enable = rpmh_regulator_is_enabled, + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, +}; + +static struct dm_regulator_mode pmic_mode_map_pmic5_ldo[] = { + { + .id = REGULATOR_MODE_RETENTION, + .register_value = PMIC5_LDO_MODE_RETENTION, + .name = "PMIC5_LDO_MODE_RETENTION" + }, { + .id = REGULATOR_MODE_LPM, + .register_value = PMIC5_LDO_MODE_LPM, + .name = "PMIC5_LDO_MODE_LPM" + }, { + .id = REGULATOR_MODE_HPM, + .register_value = PMIC5_LDO_MODE_HPM, + .name = "PMIC5_LDO_MODE_HPM" + }, +}; + +static const struct rpmh_vreg_hw_data pmic5_pldo = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1504000, 0, 255, 8000), + .n_voltages = 256, + .hpm_min_load_uA = 10000, + .pmic_mode_map = pmic_mode_map_pmic5_ldo, + .n_modes = ARRAY_SIZE(pmic_mode_map_pmic5_ldo), +}; + +static const struct rpmh_vreg_hw_data pmic5_pldo_lv = { + .regulator_type = VRM, + .ops = &rpmh_regulator_vrm_drms_ops, + .voltage_range = REGULATOR_LINEAR_RANGE(1504000, 0, 62, 8000), + .n_voltages = 63, + .hpm_min_load_uA = 10000, + .pmic_mode_map = pmic_mode_map_pmic5_ldo, + .n_modes = ARRAY_SIZE(pmic_mode_map_pmic5_ldo), +}; + +#define RPMH_VREG(_name, _resource_name, _hw_data, _supply_name) \ +{ \ + .name = _name, \ + .resource_name = _resource_name, \ + .hw_data = _hw_data, \ + .supply_name = _supply_name, \ +} + +static const struct rpmh_vreg_init_data pm8150_vreg_data[] = { + RPMH_VREG("ldo13", "ldo%s13", &pmic5_pldo, "vdd-l13-l16-l17"), + {} +}; + +static const struct rpmh_vreg_init_data pm8150l_vreg_data[] = { + RPMH_VREG("ldo1", "ldo%s1", &pmic5_pldo_lv, "vdd-l1-l8"), + RPMH_VREG("ldo11", "ldo%s11", &pmic5_pldo, "vdd-l7-l11"), + {} +}; + +/* probe an individual regulator */ +static int rpmh_regulator_probe(struct udevice *dev) +{ + const struct rpmh_vreg_init_data *init_data; + struct rpmh_vreg *priv; + struct dm_regulator_uclass_plat *plat_data; + + init_data = (const struct rpmh_vreg_init_data *)dev_get_driver_data(dev); + priv = dev_get_priv(dev); + plat_data = dev_get_uclass_plat(dev); + + priv->dev = dev; + priv->addr = cmd_db_read_addr(dev->name); + if (!priv->addr) { + dev_err(dev, "Failed to read RPMh address for %s\n", dev->name); + return -ENODEV; + } + + priv->hw_data = init_data->hw_data; + priv->enabled = -EINVAL; + priv->uv = -ENOTRECOVERABLE; + if (ofnode_read_u32(dev_ofnode(dev), "regulator-initial-mode", &priv->mode)) + priv->mode = -EINVAL; + + plat_data->mode = priv->hw_data->pmic_mode_map; + plat_data->mode_count = priv->hw_data->n_modes; + + return 0; +} + +/* for non-drm, xob, or bypass regulators add additional driver definitions */ +U_BOOT_DRIVER(rpmh_regulator_drm) = { + .name = "rpmh_regulator_drm", + .id = UCLASS_REGULATOR, + .probe = rpmh_regulator_probe, + .priv_auto = sizeof(struct rpmh_vreg), + .ops = &rpmh_regulator_vrm_drms_ops, +}; + +/* This driver intentionally only supports a subset of the available regulators. + * This function checks to see if a given regulator node in DT matches a regulator + * defined in the driver. + */ +static const struct rpmh_vreg_init_data * +vreg_get_init_data(const struct rpmh_vreg_init_data *init_data, ofnode node) +{ + const struct rpmh_vreg_init_data *data; + + for (data = init_data; data->name; data++) { + if (!strcmp(data->name, ofnode_get_name(node))) + return data; + } + + return NULL; +} + +static int rpmh_regulators_bind(struct udevice *dev) +{ + const struct rpmh_vreg_init_data *init_data, *data; + const char *pmic_id; + char *name; + struct driver *drv; + ofnode node; + int ret; + size_t namelen; + + init_data = (const struct rpmh_vreg_init_data *)dev_get_driver_data(dev); + if (!init_data) { + dev_err(dev, "No RPMh regulator init data\n"); + return -ENODEV; + } + + pmic_id = ofnode_read_string(dev_ofnode(dev), "qcom,pmic-id"); + if (!pmic_id) { + dev_err(dev, "No PMIC ID\n"); + return -ENODEV; + } + + drv = lists_driver_lookup_name("rpmh_regulator_drm"); + + ofnode_for_each_subnode(node, dev_ofnode(dev)) { + data = vreg_get_init_data(init_data, node); + if (!data) + continue; + + /* %s is replaced with pmic_id, so subtract 2, then add 1 for the null terminator */ + namelen = strlen(data->resource_name) + strlen(pmic_id) - 1; + name = devm_kzalloc(dev, namelen, GFP_KERNEL); + ret = snprintf(name, namelen, data->resource_name, pmic_id); + if (ret < 0 || ret >= namelen) { + dev_err(dev, "Failed to create RPMh regulator name\n"); + return -ENOMEM; + } + + ret = device_bind_with_driver_data(dev, drv, name, (ulong)data, + node, NULL); + if (ret < 0) { + dev_err(dev, "Failed to bind RPMh regulator %s: %d\n", name, ret); + return ret; + } + } + + return 0; +} + +static const struct udevice_id rpmh_regulator_ids[] = { + { + .compatible = "qcom,pm8150-rpmh-regulators", + .data = (ulong)pm8150_vreg_data, + }, + { + .compatible = "qcom,pm8150l-rpmh-regulators", + .data = (ulong)pm8150l_vreg_data, + }, + { /* sentinal */ }, +}; + +/* Driver for a 'bank' of regulators. This creates devices for each + * individual regulator + */ +U_BOOT_DRIVER(rpmh_regulators) = { + .name = "rpmh_regulators", + .id = UCLASS_MISC, + .bind = rpmh_regulators_bind, + .of_match = rpmh_regulator_ids, + .ops = &rpmh_regulator_vrm_drms_ops, +}; + +MODULE_DESCRIPTION("Qualcomm RPMh regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig index cee506fe474..8ef408d9ba1 100644 --- a/drivers/soc/Kconfig +++ b/drivers/soc/Kconfig @@ -48,6 +48,7 @@ config SOC_XILINX_VERSAL_NET This allows other drivers to verify the SoC familiy & revision using matching SoC attributes. +source "drivers/soc/qcom/Kconfig" source "drivers/soc/samsung/Kconfig" source "drivers/soc/ti/Kconfig" diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 5ec89a05316..00e6a5ac8e2 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -3,6 +3,7 @@ # Makefile for the U-Boot SOC specific device drivers. obj-$(CONFIG_SOC_AMD_VERSAL2) += soc_amd_versal2.o +obj-$(CONFIG_SOC_QCOM) += qcom/ obj-$(CONFIG_SOC_SAMSUNG) += samsung/ obj-$(CONFIG_SOC_TI) += ti/ obj-$(CONFIG_SOC_DEVICE) += soc-uclass.o diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig new file mode 100644 index 00000000000..4aa7833930c --- /dev/null +++ b/drivers/soc/qcom/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# QCOM Soc drivers +# +menuconfig SOC_QCOM + bool "Qualcomm SOC drivers support" + help + Say Y here if you want to enable Qualcomm SOC drivers support. + +if SOC_QCOM + +config QCOM_COMMAND_DB + bool "Qualcomm Command DB" + help + Command DB queries shared memory by key string for shared system + resources. Platform drivers that require to set state of a shared + resource on a RPM-hardened platform must use this database to get + SoC specific identifier and information for the shared resources. + +config QCOM_RPMH + bool "Qualcomm RPMh support" + depends on QCOM_COMMAND_DB + help + Say y here to support the Qualcomm RPMh (resource peripheral manager) + if you need to control regulators on Qualcomm platforms, say y here. + +endif # SOC_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile new file mode 100644 index 00000000000..78fae8bbfa1 --- /dev/null +++ b/drivers/soc/qcom/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_QCOM_COMMAND_DB) += cmd-db.o +obj-$(CONFIG_QCOM_RPMH) += rpmh-rsc.o rpmh.o diff --git a/drivers/soc/qcom/cmd-db.c b/drivers/soc/qcom/cmd-db.c new file mode 100644 index 00000000000..08736ea936a --- /dev/null +++ b/drivers/soc/qcom/cmd-db.c @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "cmd-db: " fmt + +#include <dm.h> +#include <dm/ofnode.h> +#include <dm/device_compat.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/ioport.h> +#include <linux/byteorder/generic.h> + +#include <soc/qcom/cmd-db.h> + +#define NUM_PRIORITY 2 +#define MAX_SLV_ID 8 +#define SLAVE_ID_MASK 0x7 +#define SLAVE_ID_SHIFT 16 +#define SLAVE_ID(addr) FIELD_GET(GENMASK(19, 16), addr) +#define VRM_ADDR(addr) FIELD_GET(GENMASK(19, 4), addr) + +/** + * struct entry_header: header for each entry in cmddb + * + * @id: resource's identifier + * @priority: unused + * @addr: the address of the resource + * @len: length of the data + * @offset: offset from :@data_offset, start of the data + */ +struct entry_header { + u8 id[8]; + __le32 priority[NUM_PRIORITY]; + __le32 addr; + __le16 len; + __le16 offset; +}; + +/** + * struct rsc_hdr: resource header information + * + * @slv_id: id for the resource + * @header_offset: entry's header at offset from the end of the cmd_db_header + * @data_offset: entry's data at offset from the end of the cmd_db_header + * @cnt: number of entries for HW type + * @version: MSB is major, LSB is minor + * @reserved: reserved for future use. + */ +struct rsc_hdr { + __le16 slv_id; + __le16 header_offset; + __le16 data_offset; + __le16 cnt; + __le16 version; + __le16 reserved[3]; +}; + +/** + * struct cmd_db_header: The DB header information + * + * @version: The cmd db version + * @magic: constant expected in the database + * @header: array of resources + * @checksum: checksum for the header. Unused. + * @reserved: reserved memory + * @data: driver specific data + */ +struct cmd_db_header { + __le32 version; + u8 magic[4]; + struct rsc_hdr header[MAX_SLV_ID]; + __le32 checksum; + __le32 reserved; + u8 data[]; +}; + +/** + * DOC: Description of the Command DB database. + * + * At the start of the command DB memory is the cmd_db_header structure. + * The cmd_db_header holds the version, checksum, magic key as well as an + * array for header for each slave (depicted by the rsc_header). Each h/w + * based accelerator is a 'slave' (shared resource) and has slave id indicating + * the type of accelerator. The rsc_header is the header for such individual + * slaves of a given type. The entries for each of these slaves begin at the + * rsc_hdr.header_offset. In addition each slave could have auxiliary data + * that may be needed by the driver. The data for the slave starts at the + * entry_header.offset to the location pointed to by the rsc_hdr.data_offset. + * + * Drivers have a stringified key to a slave/resource. They can query the slave + * information and get the slave id and the auxiliary data and the length of the + * data. Using this information, they can format the request to be sent to the + * h/w accelerator and request a resource state. + */ + +static const u8 CMD_DB_MAGIC[] = { 0xdb, 0x30, 0x03, 0x0c }; + +static bool cmd_db_magic_matches(const struct cmd_db_header *header) +{ + const u8 *magic = header->magic; + + return memcmp(magic, CMD_DB_MAGIC, ARRAY_SIZE(CMD_DB_MAGIC)) == 0; +} + +static struct cmd_db_header *cmd_db_header __section(".data") = NULL; + +static inline const void *rsc_to_entry_header(const struct rsc_hdr *hdr) +{ + u16 offset = le16_to_cpu(hdr->header_offset); + + return cmd_db_header->data + offset; +} + +static inline void * +rsc_offset(const struct rsc_hdr *hdr, const struct entry_header *ent) +{ + u16 offset = le16_to_cpu(hdr->data_offset); + u16 loffset = le16_to_cpu(ent->offset); + + return cmd_db_header->data + offset + loffset; +} + +static int cmd_db_get_header(const char *id, const struct entry_header **eh, + const struct rsc_hdr **rh) +{ + const struct rsc_hdr *rsc_hdr; + const struct entry_header *ent; + int i, j; + u8 query[sizeof(ent->id)] __nonstring; + + strncpy(query, id, sizeof(query)); + + for (i = 0; i < MAX_SLV_ID; i++) { + rsc_hdr = &cmd_db_header->header[i]; + if (!rsc_hdr->slv_id) + break; + + ent = rsc_to_entry_header(rsc_hdr); + for (j = 0; j < le16_to_cpu(rsc_hdr->cnt); j++, ent++) { + if (memcmp(ent->id, query, sizeof(ent->id)) == 0) { + if (eh) + *eh = ent; + if (rh) + *rh = rsc_hdr; + return 0; + } + } + } + + return -ENODEV; +} + +/** + * cmd_db_read_addr() - Query command db for resource id address. + * + * @id: resource id to query for address + * + * Return: resource address on success, 0 on error + * + * This is used to retrieve resource address based on resource + * id. + */ +u32 cmd_db_read_addr(const char *id) +{ + int ret; + const struct entry_header *ent; + + debug("%s(%s)\n", __func__, id); + + if (!cmd_db_header) { + log_err("%s: Command DB not initialized\n", __func__); + return 0; + } + + ret = cmd_db_get_header(id, &ent, NULL); + + return ret < 0 ? 0 : le32_to_cpu(ent->addr); +} +EXPORT_SYMBOL_GPL(cmd_db_read_addr); + +int cmd_db_bind(struct udevice *dev) +{ + void __iomem *base; + ofnode node; + + if (cmd_db_header) + return 0; + + node = dev_ofnode(dev); + + debug("%s(%s)\n", __func__, ofnode_get_name(node)); + + base = (void __iomem *)ofnode_get_addr(node); + if ((fdt_addr_t)base == FDT_ADDR_T_NONE) { + log_err("%s: Failed to read base address\n", __func__); + return -ENOENT; + } + + cmd_db_header = base; + if (!cmd_db_magic_matches(cmd_db_header)) { + log_err("%s: Invalid Command DB Magic\n", __func__); + return -EINVAL; + } + + return 0; +} + +static const struct udevice_id cmd_db_ids[] = { + { .compatible = "qcom,cmd-db" }, + { } +}; + +U_BOOT_DRIVER(qcom_cmd_db) = { + .name = "qcom_cmd_db", + .id = UCLASS_MISC, + .bind = cmd_db_bind, + .of_match = cmd_db_ids, +}; + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Command DB Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h new file mode 100644 index 00000000000..ac8f6c35a7a --- /dev/null +++ b/drivers/soc/qcom/rpmh-internal.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + + +#ifndef __RPM_INTERNAL_H__ +#define __RPM_INTERNAL_H__ + +#include <linux/bitmap.h> +#include <soc/qcom/tcs.h> + +#define TCS_TYPE_NR 4 +#define MAX_CMDS_PER_TCS 16 +#define MAX_TCS_PER_TYPE 3 +#define MAX_TCS_NR (MAX_TCS_PER_TYPE * TCS_TYPE_NR) +#define MAX_TCS_SLOTS (MAX_CMDS_PER_TCS * MAX_TCS_PER_TYPE) + +#define USEC_PER_SEC 1000000UL + +struct rsc_drv; + +/** + * struct tcs_group: group of Trigger Command Sets (TCS) to send state requests + * to the controller + * + * @drv: The controller. + * @type: Type of the TCS in this group - active, sleep, wake. + * @mask: Mask of the TCSes relative to all the TCSes in the RSC. + * @offset: Start of the TCS group relative to the TCSes in the RSC. + * @num_tcs: Number of TCSes in this type. + * @ncpt: Number of commands in each TCS. + * @req: Requests that are sent from the TCS; only used for ACTIVE_ONLY + * transfers (could be on a wake/sleep TCS if we are borrowing for + * an ACTIVE_ONLY transfer). + * Start: grab drv->lock, set req, set tcs_in_use, drop drv->lock, + * trigger + * End: get irq, access req, + * grab drv->lock, clear tcs_in_use, drop drv->lock + * @slots: Indicates which of @cmd_addr are occupied; only used for + * SLEEP / WAKE TCSs. Things are tightly packed in the + * case that (ncpt < MAX_CMDS_PER_TCS). That is if ncpt = 2 and + * MAX_CMDS_PER_TCS = 16 then bit[2] = the first bit in 2nd TCS. + */ +struct tcs_group { + struct rsc_drv *drv; + int type; + u32 mask; + u32 offset; + int num_tcs; + int ncpt; + const struct tcs_request *req[MAX_TCS_PER_TYPE]; + DECLARE_BITMAP(slots, MAX_TCS_SLOTS); +}; + +/** + * struct rpmh_request: the message to be sent to rpmh-rsc + * + * @msg: the request + * @cmd: the payload that will be part of the @msg + * @completion: triggered when request is done + * @dev: the device making the request + * @needs_free: check to free dynamically allocated request object + */ +struct rpmh_request { + struct tcs_request msg; + struct tcs_cmd cmd[MAX_RPMH_PAYLOAD]; + const struct udevice *dev; + bool needs_free; +}; + +/** + * struct rpmh_ctrlr: our representation of the controller + * + * @cache: the list of cached requests + * @cache_lock: synchronize access to the cache data + * @dirty: was the cache updated since flush + * @batch_cache: Cache sleep and wake requests sent as batch + */ +struct rpmh_ctrlr { + struct list_head cache; + bool dirty; + struct list_head batch_cache; +}; + +struct rsc_ver { + u32 major; + u32 minor; +}; + +/** + * struct rsc_drv: the Direct Resource Voter (DRV) of the + * Resource State Coordinator controller (RSC) + * + * @name: Controller identifier. + * @base: Start address of the DRV registers in this controller. + * @tcs_base: Start address of the TCS registers in this controller. + * @id: Instance id in the controller (Direct Resource Voter). + * @num_tcs: Number of TCSes in this DRV. + * @rsc_pm: CPU PM notifier for controller. + * Used when solver mode is not present. + * @cpus_in_pm: Number of CPUs not in idle power collapse. + * Used when solver mode and "power-domains" is not present. + * @genpd_nb: PM Domain notifier for cluster genpd notifications. + * @tcs: TCS groups. + * @tcs_in_use: S/W state of the TCS; only set for ACTIVE_ONLY + * transfers, but might show a sleep/wake TCS in use if + * it was borrowed for an active_only transfer. You + * must hold the lock in this struct (AKA drv->lock) in + * order to update this. + * @lock: Synchronize state of the controller. If RPMH's cache + * lock will also be held, the order is: drv->lock then + * cache_lock. + * @tcs_wait: Wait queue used to wait for @tcs_in_use to free up a + * slot + * @client: Handle to the DRV's client. + * @dev: RSC device. + */ +struct rsc_drv { + const char *name; + void __iomem *base; + void __iomem *tcs_base; + int id; + int num_tcs; + struct tcs_group tcs[TCS_TYPE_NR]; + DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); + struct rpmh_ctrlr client; + struct udevice *dev; + struct rsc_ver ver; + u32 *regs; +}; + +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg); +int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, + const struct tcs_request *msg); +void rpmh_rsc_invalidate(struct rsc_drv *drv); + +#endif /* __RPM_INTERNAL_H__ */ diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c new file mode 100644 index 00000000000..61fb2e69558 --- /dev/null +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "%s " fmt, KBUILD_MODNAME + +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/devres.h> +#include <dm/lists.h> +#include <dm/ofnode.h> +#include <linux/bitmap.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/types.h> +#include <asm/bitops.h> +#include <asm/io.h> + +#include <log.h> + +#include <soc/qcom/tcs.h> +#include <dt-bindings/soc/qcom,rpmh-rsc.h> + +#include "rpmh-internal.h" + + +#define RSC_DRV_ID 0 + +#define MAJOR_VER_MASK 0xFF +#define MAJOR_VER_SHIFT 16 +#define MINOR_VER_MASK 0xFF +#define MINOR_VER_SHIFT 8 + +enum { + RSC_DRV_TCS_OFFSET, + RSC_DRV_CMD_OFFSET, + DRV_SOLVER_CONFIG, + DRV_PRNT_CHLD_CONFIG, + RSC_DRV_IRQ_ENABLE, + RSC_DRV_IRQ_STATUS, + RSC_DRV_IRQ_CLEAR, + RSC_DRV_CMD_WAIT_FOR_CMPL, + RSC_DRV_CONTROL, + RSC_DRV_STATUS, + RSC_DRV_CMD_ENABLE, + RSC_DRV_CMD_MSGID, + RSC_DRV_CMD_ADDR, + RSC_DRV_CMD_DATA, + RSC_DRV_CMD_STATUS, + RSC_DRV_CMD_RESP_DATA, +}; + +/* DRV HW Solver Configuration Information Register */ +#define DRV_HW_SOLVER_MASK 1 +#define DRV_HW_SOLVER_SHIFT 24 + +/* DRV TCS Configuration Information Register */ +#define DRV_NUM_TCS_MASK 0x3F +#define DRV_NUM_TCS_SHIFT 6 +#define DRV_NCPT_MASK 0x1F +#define DRV_NCPT_SHIFT 27 + +/* Offsets for CONTROL TCS Registers */ +#define RSC_DRV_CTL_TCS_DATA_HI 0x38 +#define RSC_DRV_CTL_TCS_DATA_HI_MASK 0xFFFFFF +#define RSC_DRV_CTL_TCS_DATA_HI_VALID BIT(31) +#define RSC_DRV_CTL_TCS_DATA_LO 0x40 +#define RSC_DRV_CTL_TCS_DATA_LO_MASK 0xFFFFFFFF +#define RSC_DRV_CTL_TCS_DATA_SIZE 32 + +#define TCS_AMC_MODE_ENABLE BIT(16) +#define TCS_AMC_MODE_TRIGGER BIT(24) + +/* TCS CMD register bit mask */ +#define CMD_MSGID_LEN 8 +#define CMD_MSGID_RESP_REQ BIT(8) +#define CMD_MSGID_WRITE BIT(16) +#define CMD_STATUS_ISSUED BIT(8) +#define CMD_STATUS_COMPL BIT(16) + +/* + * Here's a high level overview of how all the registers in RPMH work + * together: + * + * - The main rpmh-rsc address is the base of a register space that can + * be used to find overall configuration of the hardware + * (DRV_PRNT_CHLD_CONFIG). Also found within the rpmh-rsc register + * space are all the TCS blocks. The offset of the TCS blocks is + * specified in the device tree by "qcom,tcs-offset" and used to + * compute tcs_base. + * - TCS blocks come one after another. Type, count, and order are + * specified by the device tree as "qcom,tcs-config". + * - Each TCS block has some registers, then space for up to 16 commands. + * Note that though address space is reserved for 16 commands, fewer + * might be present. See ncpt (num cmds per TCS). + * + * Here's a picture: + * + * +---------------------------------------------------+ + * |RSC | + * | ctrl | + * | | + * | Drvs: | + * | +-----------------------------------------------+ | + * | |DRV0 | | + * | | ctrl/config | | + * | | IRQ | | + * | | | | + * | | TCSes: | | + * | | +------------------------------------------+ | | + * | | |TCS0 | | | | | | | | | | | | | | | + * | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15| | | + * | | | | | | | | | | | | | | | | | | + * | | +------------------------------------------+ | | + * | | +------------------------------------------+ | | + * | | |TCS1 | | | | | | | | | | | | | | | + * | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15| | | + * | | | | | | | | | | | | | | | | | | + * | | +------------------------------------------+ | | + * | | +------------------------------------------+ | | + * | | |TCS2 | | | | | | | | | | | | | | | + * | | | ctrl | 0| 1| 2| 3| 4| 5| .| .| .| .|14|15| | | + * | | | | | | | | | | | | | | | | | | + * | | +------------------------------------------+ | | + * | | ...... | | + * | +-----------------------------------------------+ | + * | +-----------------------------------------------+ | + * | |DRV1 | | + * | | (same as DRV0) | | + * | +-----------------------------------------------+ | + * | ...... | + * +---------------------------------------------------+ + */ + +static u32 rpmh_rsc_reg_offset_ver_2_7[] = { + [RSC_DRV_TCS_OFFSET] = 672, + [RSC_DRV_CMD_OFFSET] = 20, + [DRV_SOLVER_CONFIG] = 0x04, + [DRV_PRNT_CHLD_CONFIG] = 0x0C, + [RSC_DRV_IRQ_ENABLE] = 0x00, + [RSC_DRV_IRQ_STATUS] = 0x04, + [RSC_DRV_IRQ_CLEAR] = 0x08, + [RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x10, + [RSC_DRV_CONTROL] = 0x14, + [RSC_DRV_STATUS] = 0x18, + [RSC_DRV_CMD_ENABLE] = 0x1C, + [RSC_DRV_CMD_MSGID] = 0x30, + [RSC_DRV_CMD_ADDR] = 0x34, + [RSC_DRV_CMD_DATA] = 0x38, + [RSC_DRV_CMD_STATUS] = 0x3C, + [RSC_DRV_CMD_RESP_DATA] = 0x40, +}; + +static u32 rpmh_rsc_reg_offset_ver_3_0[] = { + [RSC_DRV_TCS_OFFSET] = 672, + [RSC_DRV_CMD_OFFSET] = 24, + [DRV_SOLVER_CONFIG] = 0x04, + [DRV_PRNT_CHLD_CONFIG] = 0x0C, + [RSC_DRV_IRQ_ENABLE] = 0x00, + [RSC_DRV_IRQ_STATUS] = 0x04, + [RSC_DRV_IRQ_CLEAR] = 0x08, + [RSC_DRV_CMD_WAIT_FOR_CMPL] = 0x20, + [RSC_DRV_CONTROL] = 0x24, + [RSC_DRV_STATUS] = 0x28, + [RSC_DRV_CMD_ENABLE] = 0x2C, + [RSC_DRV_CMD_MSGID] = 0x34, + [RSC_DRV_CMD_ADDR] = 0x38, + [RSC_DRV_CMD_DATA] = 0x3C, + [RSC_DRV_CMD_STATUS] = 0x40, + [RSC_DRV_CMD_RESP_DATA] = 0x44, +}; + +static inline void __iomem * +tcs_reg_addr(const struct rsc_drv *drv, int reg, int tcs_id) +{ + return drv->tcs_base + drv->regs[RSC_DRV_TCS_OFFSET] * tcs_id + reg; +} + +static inline void __iomem * +tcs_cmd_addr(const struct rsc_drv *drv, int reg, int tcs_id, int cmd_id) +{ + return tcs_reg_addr(drv, reg, tcs_id) + drv->regs[RSC_DRV_CMD_OFFSET] * cmd_id; +} + +static u32 read_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id, + int cmd_id) +{ + return readl_relaxed(tcs_cmd_addr(drv, reg, tcs_id, cmd_id)); +} + +static u32 read_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id) +{ + return readl_relaxed(tcs_reg_addr(drv, reg, tcs_id)); +} + +static void write_tcs_cmd(const struct rsc_drv *drv, int reg, int tcs_id, + int cmd_id, u32 data) +{ + writel_relaxed(data, tcs_cmd_addr(drv, reg, tcs_id, cmd_id)); +} + +static void write_tcs_reg(const struct rsc_drv *drv, int reg, int tcs_id, + u32 data) +{ + writel_relaxed(data, tcs_reg_addr(drv, reg, tcs_id)); +} + +static void write_tcs_reg_sync(const struct rsc_drv *drv, int reg, int tcs_id, + u32 data) +{ + int i; + + writel(data, tcs_reg_addr(drv, reg, tcs_id)); + + /* + * Wait until we read back the same value. Use a counter rather than + * ktime for timeout since this may be called after timekeeping stops. + */ + for (i = 0; i < USEC_PER_SEC; i++) { + if (readl(tcs_reg_addr(drv, reg, tcs_id)) == data) + return; + udelay(1); + } + pr_err("%s: error writing %#x to %d:%#x\n", drv->name, + data, tcs_id, reg); +} + +/** + * get_tcs_for_msg() - Get the tcs_group used to send the given message. + * @drv: The RSC controller. + * @msg: The message we want to send. + * + * This is normally pretty straightforward except if we are trying to send + * an ACTIVE_ONLY message but don't have any active_only TCSes. + * + * Return: A pointer to a tcs_group or an ERR_PTR. + */ +static struct tcs_group *get_tcs_for_msg(struct rsc_drv *drv, + const struct tcs_request *msg) +{ + /* + * U-Boot: since we're single threaded and running synchronously we can + * just always used the first active TCS. + */ + if (msg->state != RPMH_ACTIVE_ONLY_STATE) { + log_err("WARN: only ACTIVE_ONLY state supported\n"); + return ERR_PTR(-EINVAL); + } + + return &drv->tcs[ACTIVE_TCS]; +} + +/** + * __tcs_buffer_write() - Write to TCS hardware from a request; don't trigger. + * @drv: The controller. + * @tcs_id: The global ID of this TCS. + * @cmd_id: The index within the TCS to start writing. + * @msg: The message we want to send, which will contain several addr/data + * pairs to program (but few enough that they all fit in one TCS). + * + * This is used for all types of transfers (active, sleep, and wake). + */ +static void __tcs_buffer_write(struct rsc_drv *drv, int tcs_id, int cmd_id, + const struct tcs_request *msg) +{ + u32 msgid; + u32 cmd_msgid = CMD_MSGID_LEN | CMD_MSGID_WRITE; + u32 cmd_enable = 0; + struct tcs_cmd *cmd; + int i, j; + + for (i = 0, j = cmd_id; i < msg->num_cmds; i++, j++) { + cmd = &msg->cmds[i]; + cmd_enable |= BIT(j); + msgid = cmd_msgid; + /* + * Additionally, if the cmd->wait is set, make the command + * response reqd even if the overall request was fire-n-forget. + */ + msgid |= cmd->wait ? CMD_MSGID_RESP_REQ : 0; + + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_MSGID], tcs_id, j, msgid); + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], tcs_id, j, cmd->addr); + write_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_DATA], tcs_id, j, cmd->data); + debug("tcs(m): %d [%s] cmd(n): %d msgid: %#x addr: %#x data: %#x complete: %d\n", + tcs_id, msg->state == RPMH_ACTIVE_ONLY_STATE ? "active" : "?", j, msgid, + cmd->addr, cmd->data, cmd->wait); + } + + cmd_enable |= read_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id); + write_tcs_reg(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, cmd_enable); +} + +/** + * rpmh_rsc_send_data() - Write / trigger active-only message. + * @drv: The controller. + * @msg: The data to be sent. + * + * NOTES: + * - This is only used for "ACTIVE_ONLY" since the limitations of this + * function don't make sense for sleep/wake cases. + * - To do the transfer, we will grab a whole TCS for ourselves--we don't + * try to share. If there are none available we'll wait indefinitely + * for a free one. + * - This function will not wait for the commands to be finished, only for + * data to be programmed into the RPMh. See rpmh_tx_done() which will + * be called when the transfer is fully complete. + * - This function must be called with interrupts enabled. If the hardware + * is busy doing someone else's transfer we need that transfer to fully + * finish so that we can have the hardware, and to fully finish it needs + * the interrupt handler to run. If the interrupts is set to run on the + * active CPU this can never happen if interrupts are disabled. + * + * Return: 0 on success, -EINVAL on error. + */ +int rpmh_rsc_send_data(struct rsc_drv *drv, const struct tcs_request *msg) +{ + struct tcs_group *tcs; + int tcs_id, i; + u32 addr; + + tcs = get_tcs_for_msg(drv, msg); + if (IS_ERR(tcs)) + return PTR_ERR(tcs); + + /* u-boot is single-threaded, always use the first TCS as we'll never conflict */ + tcs_id = tcs->offset; + + tcs->req[tcs_id - tcs->offset] = msg; + generic_set_bit(tcs_id, drv->tcs_in_use); + if (msg->state == RPMH_ACTIVE_ONLY_STATE && tcs->type != ACTIVE_TCS) { + /* + * Clear previously programmed WAKE commands in selected + * repurposed TCS to avoid triggering them. tcs->slots will be + * cleaned from rpmh_flush() by invoking rpmh_rsc_invalidate() + */ + write_tcs_reg_sync(drv, drv->regs[RSC_DRV_CMD_ENABLE], tcs_id, 0); + } + + /* + * These two can be done after the lock is released because: + * - We marked "tcs_in_use" under lock. + * - Once "tcs_in_use" has been marked nobody else could be writing + * to these registers until the interrupt goes off. + * - The interrupt can't go off until we trigger w/ the last line + * of __tcs_set_trigger() below. + */ + __tcs_buffer_write(drv, tcs_id, 0, msg); + + /* U-Boot: Now wait for the TCS to be cleared, indicating that we're done */ + for (i = 0; i < USEC_PER_SEC; i++) { + addr = read_tcs_cmd(drv, drv->regs[RSC_DRV_CMD_ADDR], i, 0); + if (addr != msg->cmds[0].addr) + break; + udelay(1); + } + + if (i == USEC_PER_SEC) { + log_err("%s: error writing %#x to %d:%#x\n", drv->name, + msg->cmds[0].addr, tcs_id, drv->regs[RSC_DRV_CMD_ADDR]); + return -EINVAL; + } + + return 0; +} + +static int rpmh_probe_tcs_config(struct udevice *dev, struct rsc_drv *drv) +{ + struct tcs_type_config { + u32 type; + u32 n; + } tcs_cfg[TCS_TYPE_NR] = { { 0 } }; + ofnode dn = dev_ofnode(dev); + u32 config, max_tcs, ncpt, offset; + int i, ret, n, st = 0; + struct tcs_group *tcs; + + ret = ofnode_read_u32(dn, "qcom,tcs-offset", &offset); + if (ret) + return ret; + drv->tcs_base = drv->base + offset; + + config = readl_relaxed(drv->base + drv->regs[DRV_PRNT_CHLD_CONFIG]); + + max_tcs = config; + max_tcs &= DRV_NUM_TCS_MASK << (DRV_NUM_TCS_SHIFT * drv->id); + max_tcs = max_tcs >> (DRV_NUM_TCS_SHIFT * drv->id); + + ncpt = config & (DRV_NCPT_MASK << DRV_NCPT_SHIFT); + ncpt = ncpt >> DRV_NCPT_SHIFT; + + n = ofnode_read_u32_array(dn, "qcom,tcs-config", (u32 *)tcs_cfg, 2 * TCS_TYPE_NR); + if (n < 0) { + log_err("RPMh: %s: error reading qcom,tcs-config %d\n", dev->name, n); + return n; + } + + for (i = 0; i < TCS_TYPE_NR; i++) { + if (tcs_cfg[i].n > MAX_TCS_PER_TYPE) + return -EINVAL; + } + + for (i = 0; i < TCS_TYPE_NR; i++) { + tcs = &drv->tcs[tcs_cfg[i].type]; + if (tcs->drv) + return -EINVAL; + tcs->drv = drv; + tcs->type = tcs_cfg[i].type; + tcs->num_tcs = tcs_cfg[i].n; + tcs->ncpt = ncpt; + + if (!tcs->num_tcs || tcs->type == CONTROL_TCS) + continue; + + if (st + tcs->num_tcs > max_tcs || + st + tcs->num_tcs >= BITS_PER_BYTE * sizeof(tcs->mask)) + return -EINVAL; + + tcs->mask = ((1 << tcs->num_tcs) - 1) << st; + tcs->offset = st; + st += tcs->num_tcs; + } + + drv->num_tcs = st; + + return 0; +} + +static int rpmh_rsc_probe(struct udevice *dev) +{ + ofnode dn = dev_ofnode(dev); + struct rsc_drv *drv; + char drv_id[10] = {0}; + int ret; + u32 rsc_id; + + drv = dev_get_priv(dev); + + ret = ofnode_read_u32(dn, "qcom,drv-id", &drv->id); + if (ret) + return ret; + + drv->name = ofnode_get_property(dn, "label", NULL); + if (!drv->name) + drv->name = dev->name; + + snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id); + drv->base = (void __iomem *)dev_read_addr_name(dev, drv_id); + if (IS_ERR(drv->base)) + return PTR_ERR(drv->base); + + rsc_id = readl_relaxed(drv->base + RSC_DRV_ID); + drv->ver.major = rsc_id & (MAJOR_VER_MASK << MAJOR_VER_SHIFT); + drv->ver.major >>= MAJOR_VER_SHIFT; + drv->ver.minor = rsc_id & (MINOR_VER_MASK << MINOR_VER_SHIFT); + drv->ver.minor >>= MINOR_VER_SHIFT; + + if (drv->ver.major == 3) + drv->regs = rpmh_rsc_reg_offset_ver_3_0; + else + drv->regs = rpmh_rsc_reg_offset_ver_2_7; + + ret = rpmh_probe_tcs_config(dev, drv); + if (ret) + return ret; + + spin_lock_init(&drv->lock); + init_waitqueue_head(&drv->tcs_wait); + bitmap_zero(drv->tcs_in_use, MAX_TCS_NR); + + /* Enable the active TCS to send requests immediately */ + writel_relaxed(drv->tcs[ACTIVE_TCS].mask, + drv->tcs_base + drv->regs[RSC_DRV_IRQ_ENABLE]); + + spin_lock_init(&drv->client.cache_lock); + INIT_LIST_HEAD(&drv->client.cache); + INIT_LIST_HEAD(&drv->client.batch_cache); + + dev_set_drvdata(dev, drv); + drv->dev = dev; + + log_debug("RPMh: %s: v%d.%d\n", dev->name, drv->ver.major, drv->ver.minor); + + return ret; +} + +static const struct udevice_id qcom_rpmh_ids[] = { + { .compatible = "qcom,rpmh-rsc" }, + { } +}; + +U_BOOT_DRIVER(qcom_rpmh_rsc) = { + .name = "qcom_rpmh_rsc", + .id = UCLASS_MISC, + .priv_auto = sizeof(struct rsc_drv), + .probe = rpmh_rsc_probe, + .of_match = qcom_rpmh_ids, + /* rpmh is under CLUSTER_PD which we don't support, so skip trying to enable PDs */ + .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, +}; + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. RPMh Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c new file mode 100644 index 00000000000..96f14a9afdf --- /dev/null +++ b/drivers/soc/qcom/rpmh.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#include <dm/device.h> +#include <dm/device_compat.h> +#include <linux/bug.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/types.h> + +#include <soc/qcom/rpmh.h> + +#include "rpmh-internal.h" + +#define RPMH_TIMEOUT_MS msecs_to_jiffies(10000) + +#define DEFINE_RPMH_MSG_ONSTACK(device, s, name) \ + struct rpmh_request name = { \ + .msg = { \ + .state = s, \ + .cmds = name.cmd, \ + .num_cmds = 0, \ + }, \ + .cmd = { { 0 } }, \ + .dev = device, \ + .needs_free = false, \ + } + +#define ctrlr_to_drv(ctrlr) container_of(ctrlr, struct rsc_drv, client) + +static struct rpmh_ctrlr *get_rpmh_ctrlr(const struct udevice *dev) +{ + struct rsc_drv *drv = (struct rsc_drv *)dev_get_priv(dev->parent); + + if (!drv) { + log_err("BUG: no RPMh driver for %s (parent %s)\n", dev->name, dev->parent->name); + BUG(); + } + + return &drv->client; +} + +/** + * __rpmh_write: Cache and send the RPMH request + * + * @dev: The device making the request + * @state: Active/Sleep request type + * @rpm_msg: The data that needs to be sent (cmds). + * + * Cache the RPMH request and send if the state is ACTIVE_ONLY. + * SLEEP/WAKE_ONLY requests are not sent to the controller at + * this time. Use rpmh_flush() to send them to the controller. + */ +static int __rpmh_write(const struct udevice *dev, enum rpmh_state state, + struct rpmh_request *rpm_msg) +{ + struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev); + + if (state != RPMH_ACTIVE_ONLY_STATE) { + log_err("only ACTIVE_ONLY state supported\n"); + return -EINVAL; + } + + return rpmh_rsc_send_data(ctrlr_to_drv(ctrlr), &rpm_msg->msg); +} + +static int __fill_rpmh_msg(struct rpmh_request *req, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + if (!cmd || !n || n > MAX_RPMH_PAYLOAD) + return -EINVAL; + + memcpy(req->cmd, cmd, n * sizeof(*cmd)); + + req->msg.state = state; + req->msg.cmds = req->cmd; + req->msg.num_cmds = n; + + debug("rpmh_msg: %d, %d cmds [first %#x/%#x]\n", state, n, cmd->addr, cmd->data); + + return 0; +} + +/** + * rpmh_write: Write a set of RPMH commands and block until response + * + * @dev: The device making the request + * @state: Active/sleep set + * @cmd: The payload data + * @n: The number of elements in @cmd + * + * May sleep. Do not call from atomic contexts. + */ +int rpmh_write(const struct udevice *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ + DEFINE_RPMH_MSG_ONSTACK(dev, state, rpm_msg); + int ret; + + ret = __fill_rpmh_msg(&rpm_msg, state, cmd, n); + if (ret) + return ret; + + ret = __rpmh_write(dev, state, &rpm_msg); + + return ret; +} +EXPORT_SYMBOL_GPL(rpmh_write); diff --git a/include/dt-bindings/clock/qcom,gcc-ipq4019.h b/include/dt-bindings/clock/qcom,gcc-ipq4019.h deleted file mode 100644 index 7e8a7be6dcd..00000000000 --- a/include/dt-bindings/clock/qcom,gcc-ipq4019.h +++ /dev/null @@ -1,169 +0,0 @@ -/* Copyright (c) 2015 The Linux Foundation. All rights reserved. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ -#ifndef __QCOM_CLK_IPQ4019_H__ -#define __QCOM_CLK_IPQ4019_H__ - -#define GCC_DUMMY_CLK 0 -#define AUDIO_CLK_SRC 1 -#define BLSP1_QUP1_I2C_APPS_CLK_SRC 2 -#define BLSP1_QUP1_SPI_APPS_CLK_SRC 3 -#define BLSP1_QUP2_I2C_APPS_CLK_SRC 4 -#define BLSP1_QUP2_SPI_APPS_CLK_SRC 5 -#define BLSP1_UART1_APPS_CLK_SRC 6 -#define BLSP1_UART2_APPS_CLK_SRC 7 -#define GCC_USB3_MOCK_UTMI_CLK_SRC 8 -#define GCC_APPS_CLK_SRC 9 -#define GCC_APPS_AHB_CLK_SRC 10 -#define GP1_CLK_SRC 11 -#define GP2_CLK_SRC 12 -#define GP3_CLK_SRC 13 -#define SDCC1_APPS_CLK_SRC 14 -#define FEPHY_125M_DLY_CLK_SRC 15 -#define WCSS2G_CLK_SRC 16 -#define WCSS5G_CLK_SRC 17 -#define GCC_APSS_AHB_CLK 18 -#define GCC_AUDIO_AHB_CLK 19 -#define GCC_AUDIO_PWM_CLK 20 -#define GCC_BLSP1_AHB_CLK 21 -#define GCC_BLSP1_QUP1_I2C_APPS_CLK 22 -#define GCC_BLSP1_QUP1_SPI_APPS_CLK 23 -#define GCC_BLSP1_QUP2_I2C_APPS_CLK 24 -#define GCC_BLSP1_QUP2_SPI_APPS_CLK 25 -#define GCC_BLSP1_UART1_APPS_CLK 26 -#define GCC_BLSP1_UART2_APPS_CLK 27 -#define GCC_DCD_XO_CLK 28 -#define GCC_GP1_CLK 29 -#define GCC_GP2_CLK 30 -#define GCC_GP3_CLK 31 -#define GCC_BOOT_ROM_AHB_CLK 32 -#define GCC_CRYPTO_AHB_CLK 33 -#define GCC_CRYPTO_AXI_CLK 34 -#define GCC_CRYPTO_CLK 35 -#define GCC_ESS_CLK 36 -#define GCC_IMEM_AXI_CLK 37 -#define GCC_IMEM_CFG_AHB_CLK 38 -#define GCC_PCIE_AHB_CLK 39 -#define GCC_PCIE_AXI_M_CLK 40 -#define GCC_PCIE_AXI_S_CLK 41 -#define GCC_PCNOC_AHB_CLK 42 -#define GCC_PRNG_AHB_CLK 43 -#define GCC_QPIC_AHB_CLK 44 -#define GCC_QPIC_CLK 45 -#define GCC_SDCC1_AHB_CLK 46 -#define GCC_SDCC1_APPS_CLK 47 -#define GCC_SNOC_PCNOC_AHB_CLK 48 -#define GCC_SYS_NOC_125M_CLK 49 -#define GCC_SYS_NOC_AXI_CLK 50 -#define GCC_TCSR_AHB_CLK 51 -#define GCC_TLMM_AHB_CLK 52 -#define GCC_USB2_MASTER_CLK 53 -#define GCC_USB2_SLEEP_CLK 54 -#define GCC_USB2_MOCK_UTMI_CLK 55 -#define GCC_USB3_MASTER_CLK 56 -#define GCC_USB3_SLEEP_CLK 57 -#define GCC_USB3_MOCK_UTMI_CLK 58 -#define GCC_WCSS2G_CLK 59 -#define GCC_WCSS2G_REF_CLK 60 -#define GCC_WCSS2G_RTC_CLK 61 -#define GCC_WCSS5G_CLK 62 -#define GCC_WCSS5G_REF_CLK 63 -#define GCC_WCSS5G_RTC_CLK 64 -#define GCC_APSS_DDRPLL_VCO 65 -#define GCC_SDCC_PLLDIV_CLK 66 -#define GCC_FEPLL_VCO 67 -#define GCC_FEPLL125_CLK 68 -#define GCC_FEPLL125DLY_CLK 69 -#define GCC_FEPLL200_CLK 70 -#define GCC_FEPLL500_CLK 71 -#define GCC_FEPLL_WCSS2G_CLK 72 -#define GCC_FEPLL_WCSS5G_CLK 73 -#define GCC_APSS_CPU_PLLDIV_CLK 74 -#define GCC_PCNOC_AHB_CLK_SRC 75 - -#define WIFI0_CPU_INIT_RESET 0 -#define WIFI0_RADIO_SRIF_RESET 1 -#define WIFI0_RADIO_WARM_RESET 2 -#define WIFI0_RADIO_COLD_RESET 3 -#define WIFI0_CORE_WARM_RESET 4 -#define WIFI0_CORE_COLD_RESET 5 -#define WIFI1_CPU_INIT_RESET 6 -#define WIFI1_RADIO_SRIF_RESET 7 -#define WIFI1_RADIO_WARM_RESET 8 -#define WIFI1_RADIO_COLD_RESET 9 -#define WIFI1_CORE_WARM_RESET 10 -#define WIFI1_CORE_COLD_RESET 11 -#define USB3_UNIPHY_PHY_ARES 12 -#define USB3_HSPHY_POR_ARES 13 -#define USB3_HSPHY_S_ARES 14 -#define USB2_HSPHY_POR_ARES 15 -#define USB2_HSPHY_S_ARES 16 -#define PCIE_PHY_AHB_ARES 17 -#define PCIE_AHB_ARES 18 -#define PCIE_PWR_ARES 19 -#define PCIE_PIPE_STICKY_ARES 20 -#define PCIE_AXI_M_STICKY_ARES 21 -#define PCIE_PHY_ARES 22 -#define PCIE_PARF_XPU_ARES 23 -#define PCIE_AXI_S_XPU_ARES 24 -#define PCIE_AXI_M_VMIDMT_ARES 25 -#define PCIE_PIPE_ARES 26 -#define PCIE_AXI_S_ARES 27 -#define PCIE_AXI_M_ARES 28 -#define ESS_RESET 29 -#define GCC_BLSP1_BCR 30 -#define GCC_BLSP1_QUP1_BCR 31 -#define GCC_BLSP1_UART1_BCR 32 -#define GCC_BLSP1_QUP2_BCR 33 -#define GCC_BLSP1_UART2_BCR 34 -#define GCC_BIMC_BCR 35 -#define GCC_TLMM_BCR 36 -#define GCC_IMEM_BCR 37 -#define GCC_ESS_BCR 38 -#define GCC_PRNG_BCR 39 -#define GCC_BOOT_ROM_BCR 40 -#define GCC_CRYPTO_BCR 41 -#define GCC_SDCC1_BCR 42 -#define GCC_SEC_CTRL_BCR 43 -#define GCC_AUDIO_BCR 44 -#define GCC_QPIC_BCR 45 -#define GCC_PCIE_BCR 46 -#define GCC_USB2_BCR 47 -#define GCC_USB2_PHY_BCR 48 -#define GCC_USB3_BCR 49 -#define GCC_USB3_PHY_BCR 50 -#define GCC_SYSTEM_NOC_BCR 51 -#define GCC_PCNOC_BCR 52 -#define GCC_DCD_BCR 53 -#define GCC_SNOC_BUS_TIMEOUT0_BCR 54 -#define GCC_SNOC_BUS_TIMEOUT1_BCR 55 -#define GCC_SNOC_BUS_TIMEOUT2_BCR 56 -#define GCC_SNOC_BUS_TIMEOUT3_BCR 57 -#define GCC_PCNOC_BUS_TIMEOUT0_BCR 58 -#define GCC_PCNOC_BUS_TIMEOUT1_BCR 59 -#define GCC_PCNOC_BUS_TIMEOUT2_BCR 60 -#define GCC_PCNOC_BUS_TIMEOUT3_BCR 61 -#define GCC_PCNOC_BUS_TIMEOUT4_BCR 62 -#define GCC_PCNOC_BUS_TIMEOUT5_BCR 63 -#define GCC_PCNOC_BUS_TIMEOUT6_BCR 64 -#define GCC_PCNOC_BUS_TIMEOUT7_BCR 65 -#define GCC_PCNOC_BUS_TIMEOUT8_BCR 66 -#define GCC_PCNOC_BUS_TIMEOUT9_BCR 67 -#define GCC_TCSR_BCR 68 -#define GCC_QDSS_BCR 69 -#define GCC_MPM_BCR 70 -#define GCC_SPDM_BCR 71 - -#endif diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index 0a8503af9f1..40ca2212cb4 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -196,6 +196,14 @@ static inline void bitmap_fill(unsigned long *dst, unsigned int nbits) } } +static inline bool bitmap_empty(const unsigned long *src, unsigned int nbits) +{ + if (small_const_nbits(nbits)) + return !(*src & BITMAP_LAST_WORD_MASK(nbits)); + + return find_first_bit(src, nbits) == nbits; +} + static inline void bitmap_or(unsigned long *dst, const unsigned long *src1, const unsigned long *src2, unsigned int nbits) { diff --git a/include/soc/qcom/cmd-db.h b/include/soc/qcom/cmd-db.h new file mode 100644 index 00000000000..1190f2c22ca --- /dev/null +++ b/include/soc/qcom/cmd-db.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QCOM_COMMAND_DB_H__ +#define __QCOM_COMMAND_DB_H__ + +#include <linux/err.h> + +enum cmd_db_hw_type { + CMD_DB_HW_INVALID = 0, + CMD_DB_HW_MIN = 3, + CMD_DB_HW_ARC = CMD_DB_HW_MIN, + CMD_DB_HW_VRM = 4, + CMD_DB_HW_BCM = 5, + CMD_DB_HW_MAX = CMD_DB_HW_BCM, + CMD_DB_HW_ALL = 0xff, +}; + +#if IS_ENABLED(CONFIG_QCOM_COMMAND_DB) +u32 cmd_db_read_addr(const char *resource_id); + +#else +static inline u32 cmd_db_read_addr(const char *resource_id) +{ return 0; } + +#endif /* CONFIG_QCOM_COMMAND_DB */ +#endif /* __QCOM_COMMAND_DB_H__ */ diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h new file mode 100644 index 00000000000..3421fbf1ee3 --- /dev/null +++ b/include/soc/qcom/rpmh.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_RPMH_H__ +#define __SOC_QCOM_RPMH_H__ + +#include <dm/device-internal.h> +#include <soc/qcom/tcs.h> + + +#if IS_ENABLED(CONFIG_QCOM_RPMH) +int rpmh_write(const struct udevice *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n); + +#else + +static inline int rpmh_write(const struct device *dev, enum rpmh_state state, + const struct tcs_cmd *cmd, u32 n) +{ return -ENODEV; } + +#endif /* CONFIG_QCOM_RPMH */ + +/* u-boot: no multithreading */ +#define rpmh_write_async(dev, state, cmd, n) rpmh_write(dev, state, cmd, n) + +#endif /* __SOC_QCOM_RPMH_H__ */ diff --git a/include/soc/qcom/tcs.h b/include/soc/qcom/tcs.h new file mode 100644 index 00000000000..3acca067c72 --- /dev/null +++ b/include/soc/qcom/tcs.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_QCOM_TCS_H__ +#define __SOC_QCOM_TCS_H__ + +#define MAX_RPMH_PAYLOAD 16 + +/** + * rpmh_state: state for the request + * + * RPMH_SLEEP_STATE: State of the resource when the processor subsystem + * is powered down. There is no client using the + * resource actively. + * RPMH_WAKE_ONLY_STATE: Resume resource state to the value previously + * requested before the processor was powered down. + * RPMH_ACTIVE_ONLY_STATE: Active or AMC mode requests. Resource state + * is aggregated immediately. + */ +enum rpmh_state { + RPMH_SLEEP_STATE, + RPMH_WAKE_ONLY_STATE, + RPMH_ACTIVE_ONLY_STATE, +}; + +/** + * struct tcs_cmd: an individual request to RPMH. + * + * @addr: the address of the resource slv_id:18:16 | offset:0:15 + * @data: the resource state request + * @wait: ensure that this command is complete before returning. + * Setting "wait" here only makes sense during rpmh_write_batch() for + * active-only transfers, this is because: + * rpmh_write() - Always waits. + * (DEFINE_RPMH_MSG_ONSTACK will set .wait_for_compl) + * rpmh_write_async() - Never waits. + * (There's no request completion callback) + */ +struct tcs_cmd { + u32 addr; + u32 data; + u32 wait; +}; + +/** + * struct tcs_request: A set of tcs_cmds sent together in a TCS + * + * @state: state for the request. + * @wait_for_compl: wait until we get a response from the h/w accelerator + * (same as setting cmd->wait for all commands in the request) + * @num_cmds: the number of @cmds in this request + * @cmds: an array of tcs_cmds + */ +struct tcs_request { + enum rpmh_state state; + u32 wait_for_compl; + u32 num_cmds; + struct tcs_cmd *cmds; +}; + +#define BCM_TCS_CMD_COMMIT_SHFT 30 +#define BCM_TCS_CMD_COMMIT_MASK 0x40000000 +#define BCM_TCS_CMD_VALID_SHFT 29 +#define BCM_TCS_CMD_VALID_MASK 0x20000000 +#define BCM_TCS_CMD_VOTE_X_SHFT 14 +#define BCM_TCS_CMD_VOTE_MASK 0x3fff +#define BCM_TCS_CMD_VOTE_Y_SHFT 0 +#define BCM_TCS_CMD_VOTE_Y_MASK 0xfffc000 + +/* Construct a Bus Clock Manager (BCM) specific TCS command */ +#define BCM_TCS_CMD(commit, valid, vote_x, vote_y) \ + (((commit) << BCM_TCS_CMD_COMMIT_SHFT) | \ + ((valid) << BCM_TCS_CMD_VALID_SHFT) | \ + ((cpu_to_le32(vote_x) & \ + BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_X_SHFT) | \ + ((cpu_to_le32(vote_y) & \ + BCM_TCS_CMD_VOTE_MASK) << BCM_TCS_CMD_VOTE_Y_SHFT)) + +#endif /* __SOC_QCOM_TCS_H__ */ |