ipq806x: add v4.9 support

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin 2017-02-16 12:25:25 +01:00
parent 04c4b6f7fd
commit bb255f7429
50 changed files with 11894 additions and 0 deletions

View file

@ -0,0 +1,457 @@
CONFIG_ALIGNMENT_TRAP=y
# CONFIG_AMBA_PL08X is not set
CONFIG_APQ_GCC_8084=y
CONFIG_APQ_MMCC_8084=y
CONFIG_AR8216_PHY=y
CONFIG_ARCH_CLOCKSOURCE_DATA=y
CONFIG_ARCH_HAS_ELF_RANDOMIZE=y
CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y
CONFIG_ARCH_HAS_SG_CHAIN=y
CONFIG_ARCH_HAS_TICK_BROADCAST=y
CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
# CONFIG_ARCH_MDM9615 is not set
CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y
CONFIG_ARCH_MSM8960=y
CONFIG_ARCH_MSM8974=y
CONFIG_ARCH_MSM8X60=y
CONFIG_ARCH_MULTIPLATFORM=y
# CONFIG_ARCH_MULTI_CPU_AUTO is not set
CONFIG_ARCH_MULTI_V6_V7=y
CONFIG_ARCH_MULTI_V7=y
CONFIG_ARCH_NR_GPIO=0
CONFIG_ARCH_QCOM=y
# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set
# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set
CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y
CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y
CONFIG_ARCH_SUPPORTS_UPROBES=y
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y
CONFIG_ARM=y
CONFIG_ARM_AMBA=y
CONFIG_ARM_APPENDED_DTB=y
CONFIG_ARM_ARCH_TIMER=y
CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y
CONFIG_ARM_ATAG_DTB_COMPAT=y
# CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND is not set
CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_FROM_BOOTLOADER=y
# CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE is not set
CONFIG_ARM_CPU_SUSPEND=y
# CONFIG_ARM_CPU_TOPOLOGY is not set
CONFIG_ARM_GIC=y
CONFIG_ARM_HAS_SG_CHAIN=y
CONFIG_ARM_L1_CACHE_SHIFT=6
CONFIG_ARM_L1_CACHE_SHIFT_6=y
# CONFIG_ARM_LPAE is not set
CONFIG_ARM_PATCH_IDIV=y
CONFIG_ARM_PATCH_PHYS_VIRT=y
# CONFIG_ARM_SMMU is not set
# CONFIG_ARM_SP805_WATCHDOG is not set
CONFIG_ARM_THUMB=y
# CONFIG_ARM_THUMBEE is not set
CONFIG_ARM_UNWIND=y
CONFIG_ARM_VIRT_EXT=y
CONFIG_AT803X_PHY=y
CONFIG_AUTO_ZRELADDR=y
# CONFIG_B53 is not set
# CONFIG_BINFMT_FLAT is not set
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_MQ_PCI=y
# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0
CONFIG_BOUNCE=y
# CONFIG_CACHE_L2X0 is not set
CONFIG_CLKDEV_LOOKUP=y
CONFIG_CLKSRC_OF=y
CONFIG_CLKSRC_PROBE=y
CONFIG_CLKSRC_QCOM=y
CONFIG_CLONE_BACKWARDS=y
CONFIG_COMMON_CLK=y
CONFIG_COMMON_CLK_QCOM=y
CONFIG_CPUFREQ_DT=y
CONFIG_CPUFREQ_DT_PLATDEV=y
CONFIG_CPU_32v6K=y
CONFIG_CPU_32v7=y
CONFIG_CPU_ABRT_EV7=y
# CONFIG_CPU_BIG_ENDIAN is not set
# CONFIG_CPU_BPREDICT_DISABLE is not set
CONFIG_CPU_CACHE_V7=y
CONFIG_CPU_CACHE_VIPT=y
CONFIG_CPU_COPY_V6=y
CONFIG_CPU_CP15=y
CONFIG_CPU_CP15_MMU=y
CONFIG_CPU_FREQ=y
CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set
CONFIG_CPU_FREQ_GOV_ATTR_SET=y
CONFIG_CPU_FREQ_GOV_COMMON=y
# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
CONFIG_CPU_FREQ_STAT=y
CONFIG_CPU_HAS_ASID=y
# CONFIG_CPU_ICACHE_DISABLE is not set
CONFIG_CPU_PABRT_V7=y
CONFIG_CPU_RMAP=y
CONFIG_CPU_THERMAL=y
CONFIG_CPU_TLB_V7=y
CONFIG_CPU_V7=y
CONFIG_CRC16=y
# CONFIG_CRC32_SARWATE is not set
CONFIG_CRC32_SLICEBY8=y
CONFIG_CRYPTO_AEAD=y
CONFIG_CRYPTO_AEAD2=y
CONFIG_CRYPTO_DEFLATE=y
CONFIG_CRYPTO_HASH2=y
CONFIG_CRYPTO_LZO=y
CONFIG_CRYPTO_MANAGER=y
CONFIG_CRYPTO_MANAGER2=y
CONFIG_CRYPTO_NULL2=y
CONFIG_CRYPTO_RNG2=y
CONFIG_CRYPTO_WORKQUEUE=y
CONFIG_DCACHE_WORD_ACCESS=y
CONFIG_DEBUG_GPIO=y
CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S"
# CONFIG_DEBUG_UART_8250 is not set
# CONFIG_DEBUG_USER is not set
CONFIG_DMADEVICES=y
CONFIG_DMA_ENGINE=y
CONFIG_DMA_OF=y
CONFIG_DMA_VIRTUAL_CHANNELS=y
CONFIG_DTC=y
# CONFIG_DWMAC_GENERIC is not set
CONFIG_DWMAC_IPQ806X=y
CONFIG_DYNAMIC_DEBUG=y
CONFIG_EDAC_ATOMIC_SCRUB=y
CONFIG_EDAC_SUPPORT=y
CONFIG_ETHERNET_PACKET_MANGLE=y
CONFIG_FIXED_PHY=y
CONFIG_FIX_EARLYCON_MEM=y
CONFIG_GENERIC_ALLOCATOR=y
CONFIG_GENERIC_BUG=y
CONFIG_GENERIC_CLOCKEVENTS=y
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
CONFIG_GENERIC_EARLY_IOREMAP=y
CONFIG_GENERIC_IDLE_POLL_SETUP=y
CONFIG_GENERIC_IO=y
CONFIG_GENERIC_IRQ_SHOW=y
CONFIG_GENERIC_IRQ_SHOW_LEVEL=y
CONFIG_GENERIC_MSI_IRQ=y
CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
CONFIG_GENERIC_PCI_IOMAP=y
CONFIG_GENERIC_PHY=y
CONFIG_GENERIC_PINCONF=y
CONFIG_GENERIC_SCHED_CLOCK=y
CONFIG_GENERIC_SMP_IDLE_THREAD=y
CONFIG_GENERIC_STRNCPY_FROM_USER=y
CONFIG_GENERIC_STRNLEN_USER=y
CONFIG_GENERIC_TIME_VSYSCALL=y
CONFIG_GPIOLIB=y
CONFIG_GPIOLIB_IRQCHIP=y
CONFIG_GPIO_SYSFS=y
CONFIG_HANDLE_DOMAIN_IRQ=y
CONFIG_HARDIRQS_SW_RESEND=y
CONFIG_HAS_DMA=y
CONFIG_HAS_IOMEM=y
CONFIG_HAS_IOPORT_MAP=y
# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set
CONFIG_HAVE_ARCH_AUDITSYSCALL=y
CONFIG_HAVE_ARCH_BITREVERSE=y
CONFIG_HAVE_ARCH_JUMP_LABEL=y
CONFIG_HAVE_ARCH_KGDB=y
CONFIG_HAVE_ARCH_PFN_VALID=y
CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
CONFIG_HAVE_ARCH_TRACEHOOK=y
CONFIG_HAVE_ARM_ARCH_TIMER=y
CONFIG_HAVE_ARM_SMCCC=y
# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set
CONFIG_HAVE_CBPF_JIT=y
CONFIG_HAVE_CC_STACKPROTECTOR=y
CONFIG_HAVE_CLK=y
CONFIG_HAVE_CLK_PREPARE=y
CONFIG_HAVE_CONTEXT_TRACKING=y
CONFIG_HAVE_C_RECORDMCOUNT=y
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_HAVE_DMA_API_DEBUG=y
CONFIG_HAVE_DMA_CONTIGUOUS=y
CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y
CONFIG_HAVE_FUNCTION_TRACER=y
CONFIG_HAVE_GENERIC_DMA_COHERENT=y
CONFIG_HAVE_IDE=y
CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y
CONFIG_HAVE_MEMBLOCK=y
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
CONFIG_HAVE_NET_DSA=y
CONFIG_HAVE_OPROFILE=y
CONFIG_HAVE_OPTPROBES=y
CONFIG_HAVE_PERF_EVENTS=y
CONFIG_HAVE_PERF_REGS=y
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
CONFIG_HAVE_PROC_CPU=y
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
CONFIG_HAVE_SMP=y
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
CONFIG_HAVE_UID16=y
CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y
CONFIG_HIGHMEM=y
# CONFIG_HIGHPTE is not set
CONFIG_HWMON=y
CONFIG_HWSPINLOCK=y
CONFIG_HWSPINLOCK_QCOM=y
CONFIG_HW_RANDOM=y
CONFIG_HW_RANDOM_MSM=y
CONFIG_HZ_FIXED=0
CONFIG_I2C=y
CONFIG_I2C_BOARDINFO=y
CONFIG_I2C_CHARDEV=y
CONFIG_I2C_HELPER_AUTO=y
CONFIG_I2C_QUP=y
CONFIG_INITRAMFS_SOURCE=""
CONFIG_IOMMU_HELPER=y
# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set
# CONFIG_IOMMU_IO_PGTABLE_LPAE is not set
CONFIG_IOMMU_SUPPORT=y
CONFIG_IPQ_GCC_4019=y
CONFIG_IPQ_GCC_806X=y
# CONFIG_IPQ_LCC_806X is not set
CONFIG_IRQCHIP=y
CONFIG_IRQ_DOMAIN=y
CONFIG_IRQ_DOMAIN_HIERARCHY=y
CONFIG_IRQ_FORCED_THREADING=y
CONFIG_IRQ_WORK=y
CONFIG_LIBFDT=y
CONFIG_LOCKUP_DETECTOR=y
CONFIG_LOCK_SPIN_ON_OWNER=y
CONFIG_LZO_COMPRESS=y
CONFIG_LZO_DECOMPRESS=y
CONFIG_MDIO_BITBANG=y
CONFIG_MDIO_BOARDINFO=y
CONFIG_MDIO_GPIO=y
# CONFIG_MDM_GCC_9615 is not set
# CONFIG_MDM_LCC_9615 is not set
# CONFIG_MFD_MAX77620 is not set
CONFIG_MFD_QCOM_RPM=y
# CONFIG_MFD_SPMI_PMIC is not set
CONFIG_MFD_SYSCON=y
CONFIG_MIGHT_HAVE_CACHE_L2X0=y
CONFIG_MIGHT_HAVE_PCI=y
CONFIG_MMC=y
CONFIG_MMC_ARMMMCI=y
CONFIG_MMC_BLOCK=y
CONFIG_MMC_BLOCK_MINORS=16
CONFIG_MMC_QCOM_DML=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_MSM=y
# CONFIG_MMC_SDHCI_PCI is not set
CONFIG_MMC_SDHCI_PLTFM=y
# CONFIG_MMC_TIFM_SD is not set
CONFIG_MODULES_USE_ELF_REL=y
CONFIG_MSM_GCC_8660=y
# CONFIG_MSM_GCC_8916 is not set
CONFIG_MSM_GCC_8960=y
CONFIG_MSM_GCC_8974=y
# CONFIG_MSM_GCC_8996 is not set
# CONFIG_MSM_IOMMU is not set
# CONFIG_MSM_LCC_8960 is not set
CONFIG_MSM_MMCC_8960=y
CONFIG_MSM_MMCC_8974=y
# CONFIG_MSM_MMCC_8996 is not set
CONFIG_MTD_CMDLINE_PARTS=y
CONFIG_MTD_M25P80=y
CONFIG_MTD_NAND=y
CONFIG_MTD_NAND_ECC=y
CONFIG_MTD_NAND_QCOM=y
# CONFIG_MTD_PHYSMAP_OF_VERSATILE is not set
CONFIG_MTD_SPI_NOR=y
CONFIG_MTD_SPLIT_FIRMWARE=y
CONFIG_MTD_SPLIT_FIT_FW=y
CONFIG_MTD_UBI=y
CONFIG_MTD_UBI_BEB_LIMIT=20
CONFIG_MTD_UBI_BLOCK=y
# CONFIG_MTD_UBI_FASTMAP is not set
# CONFIG_MTD_UBI_GLUEBI is not set
CONFIG_MTD_UBI_WL_THRESHOLD=4096
CONFIG_MULTI_IRQ_HANDLER=y
CONFIG_MUTEX_SPIN_ON_OWNER=y
CONFIG_NEED_DMA_MAP_STATE=y
CONFIG_NEON=y
CONFIG_NET_DSA=y
CONFIG_NET_DSA_HWMON=y
CONFIG_NET_DSA_QCA8K=y
CONFIG_NET_DSA_TAG_QCA=y
CONFIG_NET_FLOW_LIMIT=y
CONFIG_NET_PTP_CLASSIFY=y
CONFIG_NET_SWITCHDEV=y
CONFIG_NLS=y
CONFIG_NO_BOOTMEM=y
CONFIG_NO_HZ=y
CONFIG_NO_HZ_COMMON=y
CONFIG_NO_HZ_IDLE=y
CONFIG_NR_CPUS=4
CONFIG_NVMEM=y
CONFIG_OF=y
CONFIG_OF_ADDRESS=y
CONFIG_OF_ADDRESS_PCI=y
CONFIG_OF_EARLY_FLATTREE=y
CONFIG_OF_FLATTREE=y
CONFIG_OF_GPIO=y
CONFIG_OF_IRQ=y
CONFIG_OF_MDIO=y
CONFIG_OF_NET=y
CONFIG_OF_PCI=y
CONFIG_OF_PCI_IRQ=y
CONFIG_OF_RESERVED_MEM=y
CONFIG_OLD_SIGACTION=y
CONFIG_OLD_SIGSUSPEND3=y
CONFIG_PADATA=y
CONFIG_PAGE_OFFSET=0xC0000000
CONFIG_PCI=y
CONFIG_PCIEAER=y
CONFIG_PCIEPORTBUS=y
CONFIG_PCIE_DW=y
CONFIG_PCIE_QCOM=y
CONFIG_PCI_DEBUG=y
CONFIG_PCI_DISABLE_COMMON_QUIRKS=y
CONFIG_PCI_DOMAINS=y
CONFIG_PCI_DOMAINS_GENERIC=y
CONFIG_PCI_MSI=y
CONFIG_PCI_MSI_IRQ_DOMAIN=y
CONFIG_PERF_USE_VMALLOC=y
CONFIG_PGTABLE_LEVELS=2
CONFIG_PHYLIB=y
# CONFIG_PHY_QCOM_APQ8064_SATA is not set
CONFIG_PHY_QCOM_IPQ806X_SATA=y
# CONFIG_PHY_QCOM_UFS is not set
CONFIG_PINCTRL=y
CONFIG_PINCTRL_APQ8064=y
# CONFIG_PINCTRL_APQ8084 is not set
CONFIG_PINCTRL_IPQ4019=y
CONFIG_PINCTRL_IPQ8064=y
# CONFIG_PINCTRL_MDM9615 is not set
CONFIG_PINCTRL_MSM=y
# CONFIG_PINCTRL_MSM8660 is not set
# CONFIG_PINCTRL_MSM8916 is not set
# CONFIG_PINCTRL_MSM8960 is not set
# CONFIG_PINCTRL_MSM8996 is not set
CONFIG_PINCTRL_MSM8X74=y
# CONFIG_PINCTRL_QCOM_SPMI_PMIC is not set
# CONFIG_PINCTRL_QCOM_SSBI_PMIC is not set
# CONFIG_PL330_DMA is not set
CONFIG_PM_OPP=y
CONFIG_POWER_RESET=y
# CONFIG_POWER_RESET_BRCMKONA is not set
CONFIG_POWER_RESET_MSM=y
CONFIG_POWER_SUPPLY=y
CONFIG_PPS=y
CONFIG_PRINTK_TIME=y
CONFIG_PTP_1588_CLOCK=y
CONFIG_QCOM_ADM=y
CONFIG_QCOM_BAM_DMA=y
CONFIG_QCOM_CLK_RPM=y
# CONFIG_QCOM_EBI2 is not set
CONFIG_QCOM_GDSC=y
CONFIG_QCOM_GSBI=y
CONFIG_QCOM_PM=y
# CONFIG_QCOM_Q6V5_PIL is not set
CONFIG_QCOM_QFPROM=y
CONFIG_QCOM_RPMCC=y
CONFIG_QCOM_SCM=y
CONFIG_QCOM_SCM_32=y
# CONFIG_QCOM_SMD is not set
CONFIG_QCOM_SMEM=y
# CONFIG_QCOM_SMP2P is not set
# CONFIG_QCOM_SMSM is not set
CONFIG_QCOM_TSENS=y
# CONFIG_QCOM_WCNSS_PIL is not set
CONFIG_QCOM_WDT=y
# CONFIG_QRTR is not set
CONFIG_RAS=y
CONFIG_RATIONAL=y
CONFIG_RCU_CPU_STALL_TIMEOUT=21
CONFIG_RCU_STALL_COMMON=y
CONFIG_REGMAP=y
CONFIG_REGMAP_I2C=y
CONFIG_REGMAP_MMIO=y
CONFIG_REGMAP_SPI=y
CONFIG_REGULATOR=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_REGULATOR_QCOM_RPM=y
# CONFIG_REGULATOR_QCOM_SPMI is not set
CONFIG_RESET_CONTROLLER=y
CONFIG_RFS_ACCEL=y
# CONFIG_RPMSG_QCOM_SMD is not set
CONFIG_RPS=y
CONFIG_RTC_CLASS=y
# CONFIG_RTC_DRV_CMOS is not set
CONFIG_RTC_I2C_AND_SPI=y
CONFIG_RWSEM_SPIN_ON_OWNER=y
CONFIG_RWSEM_XCHGADD_ALGORITHM=y
CONFIG_SCHED_HRTICK=y
# CONFIG_SCHED_INFO is not set
# CONFIG_SCSI_DMA is not set
CONFIG_SERIAL_8250_FSL=y
# CONFIG_SERIAL_AMBA_PL010 is not set
# CONFIG_SERIAL_AMBA_PL011 is not set
CONFIG_SERIAL_MSM=y
CONFIG_SERIAL_MSM_CONSOLE=y
# CONFIG_SG_POOL is not set
CONFIG_SMP=y
CONFIG_SMP_ON_UP=y
CONFIG_SPARSE_IRQ=y
CONFIG_SPI=y
# CONFIG_SPI_CADENCE_QUADSPI is not set
CONFIG_SPI_MASTER=y
CONFIG_SPI_QUP=y
CONFIG_SPMI=y
CONFIG_SPMI_MSM_PMIC_ARB=y
CONFIG_SRCU=y
CONFIG_STMMAC_ETH=y
CONFIG_STMMAC_PLATFORM=y
CONFIG_SWCONFIG=y
CONFIG_SWCONFIG_LEDS=y
CONFIG_SWIOTLB=y
CONFIG_SWPHY=y
CONFIG_SWP_EMULATE=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
CONFIG_THERMAL=y
CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
CONFIG_THERMAL_GOV_STEP_WISE=y
CONFIG_THERMAL_HWMON=y
CONFIG_THERMAL_OF=y
# CONFIG_THUMB2_KERNEL is not set
CONFIG_TICK_CPU_ACCOUNTING=y
CONFIG_TREE_RCU=y
CONFIG_UBIFS_FS=y
CONFIG_UBIFS_FS_ADVANCED_COMPR=y
CONFIG_UBIFS_FS_LZO=y
CONFIG_UBIFS_FS_ZLIB=y
CONFIG_UEVENT_HELPER_PATH=""
CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h"
CONFIG_USB=y
CONFIG_USB_COMMON=y
# CONFIG_USB_EHCI_HCD is not set
# CONFIG_USB_IPQ4019_PHY is not set
CONFIG_USB_SUPPORT=y
# CONFIG_USB_UHCI_HCD is not set
CONFIG_USE_OF=y
CONFIG_VDSO=y
CONFIG_VECTORS_BASE=0xffff0000
CONFIG_VFP=y
CONFIG_VFPv3=y
CONFIG_WATCHDOG_CORE=y
CONFIG_XPS=y
CONFIG_XZ_DEC_ARM=y
CONFIG_XZ_DEC_BCJ=y
CONFIG_ZBOOT_ROM_BSS=0
CONFIG_ZBOOT_ROM_TEXT=0
CONFIG_ZLIB_DEFLATE=y
CONFIG_ZLIB_INFLATE=y

View file

@ -0,0 +1,71 @@
From f275ae09b82aa423d2ea5a2be3ea315c8fcf6143 Mon Sep 17 00:00:00 2001
From: Thomas Pedersen <twp@codeaurora.org>
Date: Mon, 16 May 2016 17:58:50 -0700
Subject: [PATCH 01/37] dtbindings: qcom_adm: Fix channel specifiers
Original patch from Andy Gross.
This patch removes the crci information from the dma
channel property. At least one client device requires
using more than one CRCI value for a channel. This does
not match the current binding and the crci information
needs to be removed.
Instead, the client device will provide this information
via other means.
Signed-off-by: Andy Gross <agross@codeaurora.org>
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
---
Documentation/devicetree/bindings/dma/qcom_adm.txt | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
--- a/Documentation/devicetree/bindings/dma/qcom_adm.txt
+++ b/Documentation/devicetree/bindings/dma/qcom_adm.txt
@@ -4,8 +4,7 @@ Required properties:
- compatible: must contain "qcom,adm" for IPQ/APQ8064 and MSM8960
- reg: Address range for DMA registers
- interrupts: Should contain one interrupt shared by all channels
-- #dma-cells: must be <2>. First cell denotes the channel number. Second cell
- denotes CRCI (client rate control interface) flow control assignment.
+- #dma-cells: must be <1>. First cell denotes the channel number.
- clocks: Should contain the core clock and interface clock.
- clock-names: Must contain "core" for the core clock and "iface" for the
interface clock.
@@ -22,7 +21,7 @@ Example:
compatible = "qcom,adm";
reg = <0x18300000 0x100000>;
interrupts = <0 170 0>;
- #dma-cells = <2>;
+ #dma-cells = <1>;
clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>;
clock-names = "core", "iface";
@@ -35,15 +34,12 @@ Example:
qcom,ee = <0>;
};
-DMA clients must use the format descripted in the dma.txt file, using a three
+DMA clients must use the format descripted in the dma.txt file, using a two
cell specifier for each channel.
-Each dmas request consists of 3 cells:
+Each dmas request consists of two cells:
1. phandle pointing to the DMA controller
2. channel number
- 3. CRCI assignment, if applicable. If no CRCI flow control is required, use 0.
- The CRCI is used for flow control. It identifies the peripheral device that
- is the source/destination for the transferred data.
Example:
@@ -56,7 +52,7 @@ Example:
cs-gpios = <&qcom_pinmux 20 0>;
- dmas = <&adm_dma 6 9>,
- <&adm_dma 5 10>;
+ dmas = <&adm_dma 6>,
+ <&adm_dma 5>;
dma-names = "rx", "tx";
};

View file

@ -0,0 +1,952 @@
From 1d32bf93c8e83db0aca04d2961badef7e86d663b Mon Sep 17 00:00:00 2001
From: Thomas Pedersen <twp@codeaurora.org>
Date: Mon, 16 May 2016 17:58:51 -0700
Subject: [PATCH 02/37] dmaengine: Add ADM driver
Original patch by Andy Gross.
Add the DMA engine driver for the QCOM Application Data Mover (ADM) DMA
controller found in the MSM8x60 and IPQ/APQ8064 platforms.
The ADM supports both memory to memory transactions and memory
to/from peripheral device transactions. The controller also provides flow
control capabilities for transactions to/from peripheral devices.
The initial release of this driver supports slave transfers to/from peripherals
and also incorporates CRCI (client rate control interface) flow control.
Signed-off-by: Andy Gross <agross@codeaurora.org>
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
---
drivers/dma/qcom/Kconfig | 10 +
drivers/dma/qcom/Makefile | 1 +
drivers/dma/qcom/qcom_adm.c | 900 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 911 insertions(+)
create mode 100644 drivers/dma/qcom/qcom_adm.c
--- a/drivers/dma/qcom/Kconfig
+++ b/drivers/dma/qcom/Kconfig
@@ -27,3 +27,13 @@ config QCOM_HIDMA
(user to kernel, kernel to kernel, etc.). It only supports
memcpy interface. The core is not intended for general
purpose slave DMA.
+
+config QCOM_ADM
+ tristate "Qualcomm ADM support"
+ depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM)
+ select DMA_ENGINE
+ select DMA_VIRTUAL_CHANNELS
+ ---help---
+ Enable support for the Qualcomm ADM DMA controller. This controller
+ provides DMA capabilities for both general purpose and on-chip
+ peripheral devices.
--- a/drivers/dma/qcom/Makefile
+++ b/drivers/dma/qcom/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_QCOM_HIDMA_MGMT) += hdma_mg
hdma_mgmt-objs := hidma_mgmt.o hidma_mgmt_sys.o
obj-$(CONFIG_QCOM_HIDMA) += hdma.o
hdma-objs := hidma_ll.o hidma.o hidma_dbg.o
+obj-$(CONFIG_QCOM_ADM) += qcom_adm.o
--- /dev/null
+++ b/drivers/dma/qcom/qcom_adm.c
@@ -0,0 +1,900 @@
+/*
+ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_dma.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+#include <linux/dmaengine.h>
+
+#include "../dmaengine.h"
+#include "../virt-dma.h"
+
+/* ADM registers - calculated from channel number and security domain */
+#define ADM_CHAN_MULTI 0x4
+#define ADM_CI_MULTI 0x4
+#define ADM_CRCI_MULTI 0x4
+#define ADM_EE_MULTI 0x800
+#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan)
+#define ADM_EE_OFFS(ee) (ADM_EE_MULTI * ee)
+#define ADM_CHAN_EE_OFFS(chan, ee) (ADM_CHAN_OFFS(chan) + ADM_EE_OFFS(ee))
+#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan)
+#define ADM_CI_OFFS(ci) (ADM_CHAN_OFF(ci))
+#define ADM_CH_CMD_PTR(chan, ee) (ADM_CHAN_EE_OFFS(chan, ee))
+#define ADM_CH_RSLT(chan, ee) (0x40 + ADM_CHAN_EE_OFFS(chan, ee))
+#define ADM_CH_FLUSH_STATE0(chan, ee) (0x80 + ADM_CHAN_EE_OFFS(chan, ee))
+#define ADM_CH_STATUS_SD(chan, ee) (0x200 + ADM_CHAN_EE_OFFS(chan, ee))
+#define ADM_CH_CONF(chan) (0x240 + ADM_CHAN_OFFS(chan))
+#define ADM_CH_RSLT_CONF(chan, ee) (0x300 + ADM_CHAN_EE_OFFS(chan, ee))
+#define ADM_SEC_DOMAIN_IRQ_STATUS(ee) (0x380 + ADM_EE_OFFS(ee))
+#define ADM_CI_CONF(ci) (0x390 + ci * ADM_CI_MULTI)
+#define ADM_GP_CTL 0x3d8
+#define ADM_CRCI_CTL(crci, ee) (0x400 + crci * ADM_CRCI_MULTI + \
+ ADM_EE_OFFS(ee))
+
+/* channel status */
+#define ADM_CH_STATUS_VALID BIT(1)
+
+/* channel result */
+#define ADM_CH_RSLT_VALID BIT(31)
+#define ADM_CH_RSLT_ERR BIT(3)
+#define ADM_CH_RSLT_FLUSH BIT(2)
+#define ADM_CH_RSLT_TPD BIT(1)
+
+/* channel conf */
+#define ADM_CH_CONF_SHADOW_EN BIT(12)
+#define ADM_CH_CONF_MPU_DISABLE BIT(11)
+#define ADM_CH_CONF_PERM_MPU_CONF BIT(9)
+#define ADM_CH_CONF_FORCE_RSLT_EN BIT(7)
+#define ADM_CH_CONF_SEC_DOMAIN(ee) (((ee & 0x3) << 4) | ((ee & 0x4) << 11))
+
+/* channel result conf */
+#define ADM_CH_RSLT_CONF_FLUSH_EN BIT(1)
+#define ADM_CH_RSLT_CONF_IRQ_EN BIT(0)
+
+/* CRCI CTL */
+#define ADM_CRCI_CTL_MUX_SEL BIT(18)
+#define ADM_CRCI_CTL_RST BIT(17)
+
+/* CI configuration */
+#define ADM_CI_RANGE_END(x) (x << 24)
+#define ADM_CI_RANGE_START(x) (x << 16)
+#define ADM_CI_BURST_4_WORDS BIT(2)
+#define ADM_CI_BURST_8_WORDS BIT(3)
+
+/* GP CTL */
+#define ADM_GP_CTL_LP_EN BIT(12)
+#define ADM_GP_CTL_LP_CNT(x) (x << 8)
+
+/* Command pointer list entry */
+#define ADM_CPLE_LP BIT(31)
+#define ADM_CPLE_CMD_PTR_LIST BIT(29)
+
+/* Command list entry */
+#define ADM_CMD_LC BIT(31)
+#define ADM_CMD_DST_CRCI(n) (((n) & 0xf) << 7)
+#define ADM_CMD_SRC_CRCI(n) (((n) & 0xf) << 3)
+
+#define ADM_CMD_TYPE_SINGLE 0x0
+#define ADM_CMD_TYPE_BOX 0x3
+
+#define ADM_CRCI_MUX_SEL BIT(4)
+#define ADM_DESC_ALIGN 8
+#define ADM_MAX_XFER (SZ_64K-1)
+#define ADM_MAX_ROWS (SZ_64K-1)
+#define ADM_MAX_CHANNELS 16
+
+struct adm_desc_hw_box {
+ u32 cmd;
+ u32 src_addr;
+ u32 dst_addr;
+ u32 row_len;
+ u32 num_rows;
+ u32 row_offset;
+};
+
+struct adm_desc_hw_single {
+ u32 cmd;
+ u32 src_addr;
+ u32 dst_addr;
+ u32 len;
+};
+
+struct adm_async_desc {
+ struct virt_dma_desc vd;
+ struct adm_device *adev;
+
+ size_t length;
+ enum dma_transfer_direction dir;
+ dma_addr_t dma_addr;
+ size_t dma_len;
+
+ void *cpl;
+ dma_addr_t cp_addr;
+ u32 crci;
+ u32 mux;
+ u32 blk_size;
+};
+
+struct adm_chan {
+ struct virt_dma_chan vc;
+ struct adm_device *adev;
+
+ /* parsed from DT */
+ u32 id; /* channel id */
+
+ struct adm_async_desc *curr_txd;
+ struct dma_slave_config slave;
+ struct list_head node;
+
+ int error;
+ int initialized;
+};
+
+static inline struct adm_chan *to_adm_chan(struct dma_chan *common)
+{
+ return container_of(common, struct adm_chan, vc.chan);
+}
+
+struct adm_device {
+ void __iomem *regs;
+ struct device *dev;
+ struct dma_device common;
+ struct device_dma_parameters dma_parms;
+ struct adm_chan *channels;
+
+ u32 ee;
+
+ struct clk *core_clk;
+ struct clk *iface_clk;
+
+ struct reset_control *clk_reset;
+ struct reset_control *c0_reset;
+ struct reset_control *c1_reset;
+ struct reset_control *c2_reset;
+ int irq;
+};
+
+/**
+ * adm_free_chan - Frees dma resources associated with the specific channel
+ *
+ * Free all allocated descriptors associated with this channel
+ *
+ */
+static void adm_free_chan(struct dma_chan *chan)
+{
+ /* free all queued descriptors */
+ vchan_free_chan_resources(to_virt_chan(chan));
+}
+
+/**
+ * adm_get_blksize - Get block size from burst value
+ *
+ */
+static int adm_get_blksize(unsigned int burst)
+{
+ int ret;
+
+ switch (burst) {
+ case 16:
+ case 32:
+ case 64:
+ case 128:
+ ret = ffs(burst>>4) - 1;
+ break;
+ case 192:
+ ret = 4;
+ break;
+ case 256:
+ ret = 5;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * adm_process_fc_descriptors - Process descriptors for flow controlled xfers
+ *
+ * @achan: ADM channel
+ * @desc: Descriptor memory pointer
+ * @sg: Scatterlist entry
+ * @crci: CRCI value
+ * @burst: Burst size of transaction
+ * @direction: DMA transfer direction
+ */
+static void *adm_process_fc_descriptors(struct adm_chan *achan,
+ void *desc, struct scatterlist *sg, u32 crci, u32 burst,
+ enum dma_transfer_direction direction)
+{
+ struct adm_desc_hw_box *box_desc = NULL;
+ struct adm_desc_hw_single *single_desc;
+ u32 remainder = sg_dma_len(sg);
+ u32 rows, row_offset, crci_cmd;
+ u32 mem_addr = sg_dma_address(sg);
+ u32 *incr_addr = &mem_addr;
+ u32 *src, *dst;
+
+ if (direction == DMA_DEV_TO_MEM) {
+ crci_cmd = ADM_CMD_SRC_CRCI(crci);
+ row_offset = burst;
+ src = &achan->slave.src_addr;
+ dst = &mem_addr;
+ } else {
+ crci_cmd = ADM_CMD_DST_CRCI(crci);
+ row_offset = burst << 16;
+ src = &mem_addr;
+ dst = &achan->slave.dst_addr;
+ }
+
+ while (remainder >= burst) {
+ box_desc = desc;
+ box_desc->cmd = ADM_CMD_TYPE_BOX | crci_cmd;
+ box_desc->row_offset = row_offset;
+ box_desc->src_addr = *src;
+ box_desc->dst_addr = *dst;
+
+ rows = remainder / burst;
+ rows = min_t(u32, rows, ADM_MAX_ROWS);
+ box_desc->num_rows = rows << 16 | rows;
+ box_desc->row_len = burst << 16 | burst;
+
+ *incr_addr += burst * rows;
+ remainder -= burst * rows;
+ desc += sizeof(*box_desc);
+ }
+
+ /* if leftover bytes, do one single descriptor */
+ if (remainder) {
+ single_desc = desc;
+ single_desc->cmd = ADM_CMD_TYPE_SINGLE | crci_cmd;
+ single_desc->len = remainder;
+ single_desc->src_addr = *src;
+ single_desc->dst_addr = *dst;
+ desc += sizeof(*single_desc);
+
+ if (sg_is_last(sg))
+ single_desc->cmd |= ADM_CMD_LC;
+ } else {
+ if (box_desc && sg_is_last(sg))
+ box_desc->cmd |= ADM_CMD_LC;
+ }
+
+ return desc;
+}
+
+/**
+ * adm_process_non_fc_descriptors - Process descriptors for non-fc xfers
+ *
+ * @achan: ADM channel
+ * @desc: Descriptor memory pointer
+ * @sg: Scatterlist entry
+ * @direction: DMA transfer direction
+ */
+static void *adm_process_non_fc_descriptors(struct adm_chan *achan,
+ void *desc, struct scatterlist *sg,
+ enum dma_transfer_direction direction)
+{
+ struct adm_desc_hw_single *single_desc;
+ u32 remainder = sg_dma_len(sg);
+ u32 mem_addr = sg_dma_address(sg);
+ u32 *incr_addr = &mem_addr;
+ u32 *src, *dst;
+
+ if (direction == DMA_DEV_TO_MEM) {
+ src = &achan->slave.src_addr;
+ dst = &mem_addr;
+ } else {
+ src = &mem_addr;
+ dst = &achan->slave.dst_addr;
+ }
+
+ do {
+ single_desc = desc;
+ single_desc->cmd = ADM_CMD_TYPE_SINGLE;
+ single_desc->src_addr = *src;
+ single_desc->dst_addr = *dst;
+ single_desc->len = (remainder > ADM_MAX_XFER) ?
+ ADM_MAX_XFER : remainder;
+
+ remainder -= single_desc->len;
+ *incr_addr += single_desc->len;
+ desc += sizeof(*single_desc);
+ } while (remainder);
+
+ /* set last command if this is the end of the whole transaction */
+ if (sg_is_last(sg))
+ single_desc->cmd |= ADM_CMD_LC;
+
+ return desc;
+}
+
+/**
+ * adm_prep_slave_sg - Prep slave sg transaction
+ *
+ * @chan: dma channel
+ * @sgl: scatter gather list
+ * @sg_len: length of sg
+ * @direction: DMA transfer direction
+ * @flags: DMA flags
+ * @context: transfer context (unused)
+ */
+static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan,
+ struct scatterlist *sgl, unsigned int sg_len,
+ enum dma_transfer_direction direction, unsigned long flags,
+ void *context)
+{
+ struct adm_chan *achan = to_adm_chan(chan);
+ struct adm_device *adev = achan->adev;
+ struct adm_async_desc *async_desc;
+ struct scatterlist *sg;
+ u32 i, burst;
+ u32 single_count = 0, box_count = 0, crci = 0;
+ void *desc;
+ u32 *cple;
+ int blk_size = 0;
+
+ if (!is_slave_direction(direction)) {
+ dev_err(adev->dev, "invalid dma direction\n");
+ return NULL;
+ }
+
+ /*
+ * get burst value from slave configuration
+ */
+ burst = (direction == DMA_MEM_TO_DEV) ?
+ achan->slave.dst_maxburst :
+ achan->slave.src_maxburst;
+
+ /* if using flow control, validate burst and crci values */
+ if (achan->slave.device_fc) {
+
+ blk_size = adm_get_blksize(burst);
+ if (blk_size < 0) {
+ dev_err(adev->dev, "invalid burst value: %d\n",
+ burst);
+ return ERR_PTR(-EINVAL);
+ }
+
+ crci = achan->slave.slave_id & 0xf;
+ if (!crci || achan->slave.slave_id > 0x1f) {
+ dev_err(adev->dev, "invalid crci value\n");
+ return ERR_PTR(-EINVAL);
+ }
+ }
+
+ /* iterate through sgs and compute allocation size of structures */
+ for_each_sg(sgl, sg, sg_len, i) {
+ if (achan->slave.device_fc) {
+ box_count += DIV_ROUND_UP(sg_dma_len(sg) / burst,
+ ADM_MAX_ROWS);
+ if (sg_dma_len(sg) % burst)
+ single_count++;
+ } else {
+ single_count += DIV_ROUND_UP(sg_dma_len(sg),
+ ADM_MAX_XFER);
+ }
+ }
+
+ async_desc = kzalloc(sizeof(*async_desc), GFP_NOWAIT);
+ if (!async_desc)
+ return ERR_PTR(-ENOMEM);
+
+ if (crci)
+ async_desc->mux = achan->slave.slave_id & ADM_CRCI_MUX_SEL ?
+ ADM_CRCI_CTL_MUX_SEL : 0;
+ async_desc->crci = crci;
+ async_desc->blk_size = blk_size;
+ async_desc->dma_len = single_count * sizeof(struct adm_desc_hw_single) +
+ box_count * sizeof(struct adm_desc_hw_box) +
+ sizeof(*cple) + 2 * ADM_DESC_ALIGN;
+
+ async_desc->cpl = dma_alloc_writecombine(adev->dev, async_desc->dma_len,
+ &async_desc->dma_addr, GFP_NOWAIT);
+
+ if (!async_desc->cpl) {
+ kfree(async_desc);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ async_desc->adev = adev;
+
+ /* both command list entry and descriptors must be 8 byte aligned */
+ cple = PTR_ALIGN(async_desc->cpl, ADM_DESC_ALIGN);
+ desc = PTR_ALIGN(cple + 1, ADM_DESC_ALIGN);
+
+ /* init cmd list */
+ *cple = ADM_CPLE_LP;
+ *cple |= (desc - async_desc->cpl + async_desc->dma_addr) >> 3;
+
+ for_each_sg(sgl, sg, sg_len, i) {
+ async_desc->length += sg_dma_len(sg);
+
+ if (achan->slave.device_fc)
+ desc = adm_process_fc_descriptors(achan, desc, sg, crci,
+ burst, direction);
+ else
+ desc = adm_process_non_fc_descriptors(achan, desc, sg,
+ direction);
+ }
+
+ return vchan_tx_prep(&achan->vc, &async_desc->vd, flags);
+}
+
+/**
+ * adm_terminate_all - terminate all transactions on a channel
+ * @achan: adm dma channel
+ *
+ * Dequeues and frees all transactions, aborts current transaction
+ * No callbacks are done
+ *
+ */
+static int adm_terminate_all(struct dma_chan *chan)
+{
+ struct adm_chan *achan = to_adm_chan(chan);
+ struct adm_device *adev = achan->adev;
+ unsigned long flags;
+ LIST_HEAD(head);
+
+ spin_lock_irqsave(&achan->vc.lock, flags);
+ vchan_get_all_descriptors(&achan->vc, &head);
+
+ /* send flush command to terminate current transaction */
+ writel_relaxed(0x0,
+ adev->regs + ADM_CH_FLUSH_STATE0(achan->id, adev->ee));
+
+ spin_unlock_irqrestore(&achan->vc.lock, flags);
+
+ vchan_dma_desc_free_list(&achan->vc, &head);
+
+ return 0;
+}
+
+static int adm_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg)
+{
+ struct adm_chan *achan = to_adm_chan(chan);
+ unsigned long flag;
+
+ spin_lock_irqsave(&achan->vc.lock, flag);
+ memcpy(&achan->slave, cfg, sizeof(struct dma_slave_config));
+ spin_unlock_irqrestore(&achan->vc.lock, flag);
+
+ return 0;
+}
+
+/**
+ * adm_start_dma - start next transaction
+ * @achan - ADM dma channel
+ */
+static void adm_start_dma(struct adm_chan *achan)
+{
+ struct virt_dma_desc *vd = vchan_next_desc(&achan->vc);
+ struct adm_device *adev = achan->adev;
+ struct adm_async_desc *async_desc;
+
+ lockdep_assert_held(&achan->vc.lock);
+
+ if (!vd)
+ return;
+
+ list_del(&vd->node);
+
+ /* write next command list out to the CMD FIFO */
+ async_desc = container_of(vd, struct adm_async_desc, vd);
+ achan->curr_txd = async_desc;
+
+ /* reset channel error */
+ achan->error = 0;
+
+ if (!achan->initialized) {
+ /* enable interrupts */
+ writel(ADM_CH_CONF_SHADOW_EN |
+ ADM_CH_CONF_PERM_MPU_CONF |
+ ADM_CH_CONF_MPU_DISABLE |
+ ADM_CH_CONF_SEC_DOMAIN(adev->ee),
+ adev->regs + ADM_CH_CONF(achan->id));
+
+ writel(ADM_CH_RSLT_CONF_IRQ_EN | ADM_CH_RSLT_CONF_FLUSH_EN,
+ adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
+
+ achan->initialized = 1;
+ }
+
+ /* set the crci block size if this transaction requires CRCI */
+ if (async_desc->crci) {
+ writel(async_desc->mux | async_desc->blk_size,
+ adev->regs + ADM_CRCI_CTL(async_desc->crci, adev->ee));
+ }
+
+ /* make sure IRQ enable doesn't get reordered */
+ wmb();
+
+ /* write next command list out to the CMD FIFO */
+ writel(ALIGN(async_desc->dma_addr, ADM_DESC_ALIGN) >> 3,
+ adev->regs + ADM_CH_CMD_PTR(achan->id, adev->ee));
+}
+
+/**
+ * adm_dma_irq - irq handler for ADM controller
+ * @irq: IRQ of interrupt
+ * @data: callback data
+ *
+ * IRQ handler for the bam controller
+ */
+static irqreturn_t adm_dma_irq(int irq, void *data)
+{
+ struct adm_device *adev = data;
+ u32 srcs, i;
+ struct adm_async_desc *async_desc;
+ unsigned long flags;
+
+ srcs = readl_relaxed(adev->regs +
+ ADM_SEC_DOMAIN_IRQ_STATUS(adev->ee));
+
+ for (i = 0; i < ADM_MAX_CHANNELS; i++) {
+ struct adm_chan *achan = &adev->channels[i];
+ u32 status, result;
+
+ if (srcs & BIT(i)) {
+ status = readl_relaxed(adev->regs +
+ ADM_CH_STATUS_SD(i, adev->ee));
+
+ /* if no result present, skip */
+ if (!(status & ADM_CH_STATUS_VALID))
+ continue;
+
+ result = readl_relaxed(adev->regs +
+ ADM_CH_RSLT(i, adev->ee));
+
+ /* no valid results, skip */
+ if (!(result & ADM_CH_RSLT_VALID))
+ continue;
+
+ /* flag error if transaction was flushed or failed */
+ if (result & (ADM_CH_RSLT_ERR | ADM_CH_RSLT_FLUSH))
+ achan->error = 1;
+
+ spin_lock_irqsave(&achan->vc.lock, flags);
+ async_desc = achan->curr_txd;
+
+ achan->curr_txd = NULL;
+
+ if (async_desc) {
+ vchan_cookie_complete(&async_desc->vd);
+
+ /* kick off next DMA */
+ adm_start_dma(achan);
+ }
+
+ spin_unlock_irqrestore(&achan->vc.lock, flags);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * adm_tx_status - returns status of transaction
+ * @chan: dma channel
+ * @cookie: transaction cookie
+ * @txstate: DMA transaction state
+ *
+ * Return status of dma transaction
+ */
+static enum dma_status adm_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
+ struct dma_tx_state *txstate)
+{
+ struct adm_chan *achan = to_adm_chan(chan);
+ struct virt_dma_desc *vd;
+ enum dma_status ret;
+ unsigned long flags;
+ size_t residue = 0;
+
+ ret = dma_cookie_status(chan, cookie, txstate);
+ if (ret == DMA_COMPLETE || !txstate)
+ return ret;
+
+ spin_lock_irqsave(&achan->vc.lock, flags);
+
+ vd = vchan_find_desc(&achan->vc, cookie);
+ if (vd)
+ residue = container_of(vd, struct adm_async_desc, vd)->length;
+
+ spin_unlock_irqrestore(&achan->vc.lock, flags);
+
+ /*
+ * residue is either the full length if it is in the issued list, or 0
+ * if it is in progress. We have no reliable way of determining
+ * anything inbetween
+ */
+ dma_set_residue(txstate, residue);
+
+ if (achan->error)
+ return DMA_ERROR;
+
+ return ret;
+}
+
+/**
+ * adm_issue_pending - starts pending transactions
+ * @chan: dma channel
+ *
+ * Issues all pending transactions and starts DMA
+ */
+static void adm_issue_pending(struct dma_chan *chan)
+{
+ struct adm_chan *achan = to_adm_chan(chan);
+ unsigned long flags;
+
+ spin_lock_irqsave(&achan->vc.lock, flags);
+
+ if (vchan_issue_pending(&achan->vc) && !achan->curr_txd)
+ adm_start_dma(achan);
+ spin_unlock_irqrestore(&achan->vc.lock, flags);
+}
+
+/**
+ * adm_dma_free_desc - free descriptor memory
+ * @vd: virtual descriptor
+ *
+ */
+static void adm_dma_free_desc(struct virt_dma_desc *vd)
+{
+ struct adm_async_desc *async_desc = container_of(vd,
+ struct adm_async_desc, vd);
+
+ dma_free_writecombine(async_desc->adev->dev, async_desc->dma_len,
+ async_desc->cpl, async_desc->dma_addr);
+ kfree(async_desc);
+}
+
+static void adm_channel_init(struct adm_device *adev, struct adm_chan *achan,
+ u32 index)
+{
+ achan->id = index;
+ achan->adev = adev;
+
+ vchan_init(&achan->vc, &adev->common);
+ achan->vc.desc_free = adm_dma_free_desc;
+}
+
+static int adm_dma_probe(struct platform_device *pdev)
+{
+ struct adm_device *adev;
+ struct resource *iores;
+ int ret;
+ u32 i;
+
+ adev = devm_kzalloc(&pdev->dev, sizeof(*adev), GFP_KERNEL);
+ if (!adev)
+ return -ENOMEM;
+
+ adev->dev = &pdev->dev;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ adev->regs = devm_ioremap_resource(&pdev->dev, iores);
+ if (IS_ERR(adev->regs))
+ return PTR_ERR(adev->regs);
+
+ adev->irq = platform_get_irq(pdev, 0);
+ if (adev->irq < 0)
+ return adev->irq;
+
+ ret = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &adev->ee);
+ if (ret) {
+ dev_err(adev->dev, "Execution environment unspecified\n");
+ return ret;
+ }
+
+ adev->core_clk = devm_clk_get(adev->dev, "core");
+ if (IS_ERR(adev->core_clk))
+ return PTR_ERR(adev->core_clk);
+
+ ret = clk_prepare_enable(adev->core_clk);
+ if (ret) {
+ dev_err(adev->dev, "failed to prepare/enable core clock\n");
+ return ret;
+ }
+
+ adev->iface_clk = devm_clk_get(adev->dev, "iface");
+ if (IS_ERR(adev->iface_clk)) {
+ ret = PTR_ERR(adev->iface_clk);
+ goto err_disable_core_clk;
+ }
+
+ ret = clk_prepare_enable(adev->iface_clk);
+ if (ret) {
+ dev_err(adev->dev, "failed to prepare/enable iface clock\n");
+ goto err_disable_core_clk;
+ }
+
+ adev->clk_reset = devm_reset_control_get(&pdev->dev, "clk");
+ if (IS_ERR(adev->clk_reset)) {
+ dev_err(adev->dev, "failed to get ADM0 reset\n");
+ ret = PTR_ERR(adev->clk_reset);
+ goto err_disable_clks;
+ }
+
+ adev->c0_reset = devm_reset_control_get(&pdev->dev, "c0");
+ if (IS_ERR(adev->c0_reset)) {
+ dev_err(adev->dev, "failed to get ADM0 C0 reset\n");
+ ret = PTR_ERR(adev->c0_reset);
+ goto err_disable_clks;
+ }
+
+ adev->c1_reset = devm_reset_control_get(&pdev->dev, "c1");
+ if (IS_ERR(adev->c1_reset)) {
+ dev_err(adev->dev, "failed to get ADM0 C1 reset\n");
+ ret = PTR_ERR(adev->c1_reset);
+ goto err_disable_clks;
+ }
+
+ adev->c2_reset = devm_reset_control_get(&pdev->dev, "c2");
+ if (IS_ERR(adev->c2_reset)) {
+ dev_err(adev->dev, "failed to get ADM0 C2 reset\n");
+ ret = PTR_ERR(adev->c2_reset);
+ goto err_disable_clks;
+ }
+
+ reset_control_assert(adev->clk_reset);
+ reset_control_assert(adev->c0_reset);
+ reset_control_assert(adev->c1_reset);
+ reset_control_assert(adev->c2_reset);
+
+ reset_control_deassert(adev->clk_reset);
+ reset_control_deassert(adev->c0_reset);
+ reset_control_deassert(adev->c1_reset);
+ reset_control_deassert(adev->c2_reset);
+
+ adev->channels = devm_kcalloc(adev->dev, ADM_MAX_CHANNELS,
+ sizeof(*adev->channels), GFP_KERNEL);
+
+ if (!adev->channels) {
+ ret = -ENOMEM;
+ goto err_disable_clks;
+ }
+
+ /* allocate and initialize channels */
+ INIT_LIST_HEAD(&adev->common.channels);
+
+ for (i = 0; i < ADM_MAX_CHANNELS; i++)
+ adm_channel_init(adev, &adev->channels[i], i);
+
+ /* reset CRCIs */
+ for (i = 0; i < 16; i++)
+ writel(ADM_CRCI_CTL_RST, adev->regs +
+ ADM_CRCI_CTL(i, adev->ee));
+
+ /* configure client interfaces */
+ writel(ADM_CI_RANGE_START(0x40) | ADM_CI_RANGE_END(0xb0) |
+ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(0));
+ writel(ADM_CI_RANGE_START(0x2a) | ADM_CI_RANGE_END(0x2c) |
+ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(1));
+ writel(ADM_CI_RANGE_START(0x12) | ADM_CI_RANGE_END(0x28) |
+ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(2));
+ writel(ADM_GP_CTL_LP_EN | ADM_GP_CTL_LP_CNT(0xf),
+ adev->regs + ADM_GP_CTL);
+
+ ret = devm_request_irq(adev->dev, adev->irq, adm_dma_irq,
+ 0, "adm_dma", adev);
+ if (ret)
+ goto err_disable_clks;
+
+ platform_set_drvdata(pdev, adev);
+
+ adev->common.dev = adev->dev;
+ adev->common.dev->dma_parms = &adev->dma_parms;
+
+ /* set capabilities */
+ dma_cap_zero(adev->common.cap_mask);
+ dma_cap_set(DMA_SLAVE, adev->common.cap_mask);
+ dma_cap_set(DMA_PRIVATE, adev->common.cap_mask);
+
+ /* initialize dmaengine apis */
+ adev->common.directions = BIT(DMA_DEV_TO_MEM | DMA_MEM_TO_DEV);
+ adev->common.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR;
+ adev->common.src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ adev->common.dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ adev->common.device_free_chan_resources = adm_free_chan;
+ adev->common.device_prep_slave_sg = adm_prep_slave_sg;
+ adev->common.device_issue_pending = adm_issue_pending;
+ adev->common.device_tx_status = adm_tx_status;
+ adev->common.device_terminate_all = adm_terminate_all;
+ adev->common.device_config = adm_slave_config;
+
+ ret = dma_async_device_register(&adev->common);
+ if (ret) {
+ dev_err(adev->dev, "failed to register dma async device\n");
+ goto err_disable_clks;
+ }
+
+ ret = of_dma_controller_register(pdev->dev.of_node,
+ of_dma_xlate_by_chan_id,
+ &adev->common);
+ if (ret)
+ goto err_unregister_dma;
+
+ return 0;
+
+err_unregister_dma:
+ dma_async_device_unregister(&adev->common);
+err_disable_clks:
+ clk_disable_unprepare(adev->iface_clk);
+err_disable_core_clk:
+ clk_disable_unprepare(adev->core_clk);
+
+ return ret;
+}
+
+static int adm_dma_remove(struct platform_device *pdev)
+{
+ struct adm_device *adev = platform_get_drvdata(pdev);
+ struct adm_chan *achan;
+ u32 i;
+
+ of_dma_controller_free(pdev->dev.of_node);
+ dma_async_device_unregister(&adev->common);
+
+ for (i = 0; i < ADM_MAX_CHANNELS; i++) {
+ achan = &adev->channels[i];
+
+ /* mask IRQs for this channel/EE pair */
+ writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee));
+
+ adm_terminate_all(&adev->channels[i].vc.chan);
+ }
+
+ devm_free_irq(adev->dev, adev->irq, adev);
+
+ clk_disable_unprepare(adev->core_clk);
+ clk_disable_unprepare(adev->iface_clk);
+
+ return 0;
+}
+
+static const struct of_device_id adm_of_match[] = {
+ { .compatible = "qcom,adm", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, adm_of_match);
+
+static struct platform_driver adm_dma_driver = {
+ .probe = adm_dma_probe,
+ .remove = adm_dma_remove,
+ .driver = {
+ .name = "adm-dma-engine",
+ .of_match_table = adm_of_match,
+ },
+};
+
+module_platform_driver(adm_dma_driver);
+
+MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>");
+MODULE_DESCRIPTION("QCOM ADM DMA engine driver");
+MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,52 @@
From eb694e964310ba402d6ff99f08e0ef78345e7397 Mon Sep 17 00:00:00 2001
From: Thomas Pedersen <twp@codeaurora.org>
Date: Mon, 16 May 2016 17:58:52 -0700
Subject: [PATCH 03/37] arm: qcom: dts: ipq8064: Add ADM device node
Original patch by Andy Gross.
Signed-off-by: Andy Gross <agross@codeaurora.org>
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq8064.dtsi | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
@@ -1,9 +1,11 @@
/dts-v1/;
#include "skeleton.dtsi"
+#include <dt-bindings/reset/qcom,gcc-ipq806x.h>
#include <dt-bindings/clock/qcom,gcc-ipq806x.h>
#include <dt-bindings/clock/qcom,lcc-ipq806x.h>
#include <dt-bindings/soc/qcom,gsbi.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
/ {
model = "Qualcomm IPQ8064";
@@ -342,5 +344,24 @@
#reset-cells = <1>;
};
+ adm_dma: dma@18300000 {
+ compatible = "qcom,adm";
+ reg = <0x18300000 0x100000>;
+ interrupts = <GIC_SPI 170 IRQ_TYPE_NONE>;
+ #dma-cells = <1>;
+
+ clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>;
+ clock-names = "core", "iface";
+
+ resets = <&gcc ADM0_RESET>,
+ <&gcc ADM0_PBUS_RESET>,
+ <&gcc ADM0_C0_RESET>,
+ <&gcc ADM0_C1_RESET>,
+ <&gcc ADM0_C2_RESET>;
+ reset-names = "clk", "pbus", "c0", "c1", "c2";
+ qcom,ee = <0>;
+
+ status = "disabled";
+ };
};
};

View file

@ -0,0 +1,38 @@
From c6d45af259eb4fb2a598c0396c6dd580b1658558 Mon Sep 17 00:00:00 2001
From: Thomas Pedersen <twp@codeaurora.org>
Date: Mon, 16 May 2016 17:58:53 -0700
Subject: [PATCH 04/37] arm: qcom: dts: Add NAND controller node for ipq806x
Original patch by Archit Taneja.
Signed-off-by: Archit Taneja <architt@codeaurora.org>
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq8064.dtsi | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
@@ -363,5 +363,22 @@
status = "disabled";
};
+
+ nand@1ac00000 {
+ compatible = "qcom,ipq806x-nand";
+ reg = <0x1ac00000 0x800>;
+
+ clocks = <&gcc EBI2_CLK>,
+ <&gcc EBI2_AON_CLK>;
+ clock-names = "core", "aon";
+
+ dmas = <&adm_dma 3>;
+ dma-names = "rxtx";
+ qcom,cmd-crci = <15>;
+ qcom,data-crci = <3>;
+
+ status = "disabled";
+ };
+
};
};

View file

@ -0,0 +1,74 @@
From f8d0939eca47f56449ec583810a6ff41a1caaa91 Mon Sep 17 00:00:00 2001
From: Thomas Pedersen <twp@codeaurora.org>
Date: Mon, 16 May 2016 17:58:54 -0700
Subject: [PATCH 05/37] arm: qcom: dts: Enable NAND node on IPQ8064 AP148
platform
Original patch by Archit Taneja.
Enable the NAND controller node on the AP148 platform. Provide pinmux
information.
Signed-off-by: Archit Taneja <architt@codeaurora.org>
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 42 ++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
@@ -38,6 +38,28 @@
bias-none;
};
};
+ nand_pins: nand_pins {
+ mux {
+ pins = "gpio34", "gpio35", "gpio36",
+ "gpio37", "gpio38", "gpio39",
+ "gpio40", "gpio41", "gpio42",
+ "gpio43", "gpio44", "gpio45",
+ "gpio46", "gpio47";
+ function = "nand";
+ drive-strength = <10>;
+ bias-disable;
+ };
+ pullups {
+ pins = "gpio39";
+ bias-pull-up;
+ };
+ hold {
+ pins = "gpio40", "gpio41", "gpio42",
+ "gpio43", "gpio44", "gpio45",
+ "gpio46", "gpio47";
+ bias-bus-hold;
+ };
+ };
};
gsbi@16300000 {
@@ -98,5 +120,25 @@
ports-implemented = <0x1>;
status = "ok";
};
+
+ nand@1ac00000 {
+ status = "ok";
+
+ pinctrl-0 = <&nand_pins>;
+ pinctrl-names = "default";
+
+ nandcs@0 {
+ compatible = "qcom,nandcs";
+ reg = <0>;
+
+ nand-ecc-strength = <4>;
+ nand-ecc-step-size = <512>;
+ nand-bus-width = <8>;
+ };
+ };
};
};
+
+&adm_dma {
+ status = "ok";
+};

View file

@ -0,0 +1,208 @@
From 2852d6df4b60987f9248c3d36d5fe2462e6556b9 Mon Sep 17 00:00:00 2001
From: Andy Gross <andy.gross@linaro.org>
Date: Tue, 12 Apr 2016 09:11:47 -0500
Subject: [PATCH 06/37] spi: qup: Make sure mode is only determined once
This patch calculates the mode once. All decisions on the current
transaction
is made using the mode instead of use_dma
Signed-off-by: Andy Gross <andy.gross@linaro.org>
---
drivers/spi/spi-qup.c | 87 +++++++++++++++++++++----------------------------
1 file changed, 37 insertions(+), 50 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -149,12 +149,20 @@ struct spi_qup {
int rx_bytes;
int qup_v1;
- int use_dma;
+ int mode;
struct dma_slave_config rx_conf;
struct dma_slave_config tx_conf;
};
+static inline bool spi_qup_is_dma_xfer(int mode)
+{
+ if (mode == QUP_IO_M_MODE_DMOV || mode == QUP_IO_M_MODE_BAM)
+ return true;
+
+ return false;
+}
+
static inline bool spi_qup_is_valid_state(struct spi_qup *controller)
{
u32 opstate = readl_relaxed(controller->base + QUP_STATE);
@@ -424,7 +432,7 @@ static irqreturn_t spi_qup_qup_irq(int i
error = -EIO;
}
- if (!controller->use_dma) {
+ if (!spi_qup_is_dma_xfer(controller->mode)) {
if (opflags & QUP_OP_IN_SERVICE_FLAG)
spi_qup_fifo_read(controller, xfer);
@@ -443,34 +451,11 @@ static irqreturn_t spi_qup_qup_irq(int i
return IRQ_HANDLED;
}
-static u32
-spi_qup_get_mode(struct spi_master *master, struct spi_transfer *xfer)
-{
- struct spi_qup *qup = spi_master_get_devdata(master);
- u32 mode;
-
- qup->w_size = 4;
-
- if (xfer->bits_per_word <= 8)
- qup->w_size = 1;
- else if (xfer->bits_per_word <= 16)
- qup->w_size = 2;
-
- qup->n_words = xfer->len / qup->w_size;
-
- if (qup->n_words <= (qup->in_fifo_sz / sizeof(u32)))
- mode = QUP_IO_M_MODE_FIFO;
- else
- mode = QUP_IO_M_MODE_BLOCK;
-
- return mode;
-}
-
/* set clock freq ... bits per word */
static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
{
struct spi_qup *controller = spi_master_get_devdata(spi->master);
- u32 config, iomode, mode, control;
+ u32 config, iomode, control;
int ret, n_words;
if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) {
@@ -491,23 +476,22 @@ static int spi_qup_io_config(struct spi_
return -EIO;
}
- mode = spi_qup_get_mode(spi->master, xfer);
+ controller->w_size = DIV_ROUND_UP(xfer->bits_per_word, 8);
+ controller->n_words = xfer->len / controller->w_size;
n_words = controller->n_words;
- if (mode == QUP_IO_M_MODE_FIFO) {
+ if (n_words <= (controller->in_fifo_sz / sizeof(u32))) {
+ controller->mode = QUP_IO_M_MODE_FIFO;
writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT);
writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT);
/* must be zero for FIFO */
writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- } else if (!controller->use_dma) {
- writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
- /* must be zero for BLOCK and BAM */
- writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
- writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
- } else {
- mode = QUP_IO_M_MODE_BAM;
+
+ } else if (spi->master->can_dma &&
+ spi->master->can_dma(spi->master, spi, xfer) &&
+ spi->master->cur_msg_mapped) {
+ controller->mode = QUP_IO_M_MODE_BAM;
writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
@@ -528,19 +512,26 @@ static int spi_qup_io_config(struct spi_
writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
}
+ } else {
+ controller->mode = QUP_IO_M_MODE_BLOCK;
+ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
+ /* must be zero for BLOCK and BAM */
+ writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
}
iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
/* Set input and output transfer mode */
iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK);
- if (!controller->use_dma)
+ if (!spi_qup_is_dma_xfer(controller->mode))
iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
else
iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN;
- iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
- iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
+ iomode |= (controller->mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
+ iomode |= (controller->mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
writel_relaxed(iomode, controller->base + QUP_IO_M_MODES);
@@ -581,7 +572,7 @@ static int spi_qup_io_config(struct spi_
config |= xfer->bits_per_word - 1;
config |= QUP_CONFIG_SPI_MODE;
- if (controller->use_dma) {
+ if (spi_qup_is_dma_xfer(controller->mode)) {
if (!xfer->tx_buf)
config |= QUP_CONFIG_NO_OUTPUT;
if (!xfer->rx_buf)
@@ -599,7 +590,7 @@ static int spi_qup_io_config(struct spi_
* status change in BAM mode
*/
- if (mode == QUP_IO_M_MODE_BAM)
+ if (spi_qup_is_dma_xfer(controller->mode))
mask = QUP_OP_IN_SERVICE_FLAG | QUP_OP_OUT_SERVICE_FLAG;
writel_relaxed(mask, controller->base + QUP_OPERATIONAL_MASK);
@@ -633,7 +624,7 @@ static int spi_qup_transfer_one(struct s
controller->tx_bytes = 0;
spin_unlock_irqrestore(&controller->lock, flags);
- if (controller->use_dma)
+ if (spi_qup_is_dma_xfer(controller->mode))
ret = spi_qup_do_dma(master, xfer);
else
ret = spi_qup_do_pio(master, xfer);
@@ -657,7 +648,7 @@ exit:
ret = controller->error;
spin_unlock_irqrestore(&controller->lock, flags);
- if (ret && controller->use_dma)
+ if (ret && spi_qup_is_dma_xfer(controller->mode))
spi_qup_dma_terminate(master, xfer);
return ret;
@@ -668,9 +659,7 @@ static bool spi_qup_can_dma(struct spi_m
{
struct spi_qup *qup = spi_master_get_devdata(master);
size_t dma_align = dma_get_cache_alignment();
- u32 mode;
-
- qup->use_dma = 0;
+ int n_words;
if (xfer->rx_buf && (xfer->len % qup->in_blk_sz ||
IS_ERR_OR_NULL(master->dma_rx) ||
@@ -682,12 +671,10 @@ static bool spi_qup_can_dma(struct spi_m
!IS_ALIGNED((size_t)xfer->tx_buf, dma_align)))
return false;
- mode = spi_qup_get_mode(master, xfer);
- if (mode == QUP_IO_M_MODE_FIFO)
+ n_words = xfer->len / DIV_ROUND_UP(xfer->bits_per_word, 8);
+ if (n_words <= (qup->in_fifo_sz / sizeof(u32)))
return false;
- qup->use_dma = 1;
-
return true;
}

View file

@ -0,0 +1,29 @@
From 826290ee1fd1bcd26b6771f94f6680a4ff8951c4 Mon Sep 17 00:00:00 2001
From: Andy Gross <andy.gross@linaro.org>
Date: Fri, 29 Jan 2016 22:06:50 -0600
Subject: [PATCH 07/37] spi: qup: Fix transaction done signaling
Wait to signal done until we get all of the interrupts we are expecting
to get for a transaction. If we don't wait for the input done flag, we
can be inbetween transactions when the done flag comes in and this can
mess up the next transaction.
CC: Grant Grundler <grundler@chromium.org>
CC: Sarthak Kukreti <skukreti@codeaurora.org>
Signed-off-by: Andy Gross <andy.gross@linaro.org>
---
drivers/spi/spi-qup.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -445,7 +445,8 @@ static irqreturn_t spi_qup_qup_irq(int i
controller->xfer = xfer;
spin_unlock_irqrestore(&controller->lock, flags);
- if (controller->rx_bytes == xfer->len || error)
+ if ((controller->rx_bytes == xfer->len &&
+ (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error)
complete(&controller->done);
return IRQ_HANDLED;

View file

@ -0,0 +1,219 @@
From 715d008b67b21fb8bfefaeeefa5b8ddf23777872 Mon Sep 17 00:00:00 2001
From: Andy Gross <andy.gross@linaro.org>
Date: Tue, 2 Feb 2016 17:00:53 -0600
Subject: [PATCH 08/37] spi: qup: Fix DMA mode to work correctly
This patch fixes a few issues with the DMA mode. The QUP needs to be
placed in the run mode before the DMA transactions are executed. The
conditions for being able to DMA vary between revisions of the QUP.
This is due to v1.1.1 using ADM DMA and later revisions using BAM.
Signed-off-by: Andy Gross <andy.gross@linaro.org>
---
drivers/spi/spi-qup.c | 94 ++++++++++++++++++++++++++++++++-----------------
1 file changed, 62 insertions(+), 32 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -142,6 +142,7 @@ struct spi_qup {
struct spi_transfer *xfer;
struct completion done;
+ struct completion dma_tx_done;
int error;
int w_size; /* bytes per SPI word */
int n_words;
@@ -284,16 +285,16 @@ static void spi_qup_fifo_write(struct sp
static void spi_qup_dma_done(void *data)
{
- struct spi_qup *qup = data;
+ struct completion *done = data;
- complete(&qup->done);
+ complete(done);
}
static int spi_qup_prep_sg(struct spi_master *master, struct spi_transfer *xfer,
enum dma_transfer_direction dir,
- dma_async_tx_callback callback)
+ dma_async_tx_callback callback,
+ void *data)
{
- struct spi_qup *qup = spi_master_get_devdata(master);
unsigned long flags = DMA_PREP_INTERRUPT | DMA_PREP_FENCE;
struct dma_async_tx_descriptor *desc;
struct scatterlist *sgl;
@@ -312,11 +313,11 @@ static int spi_qup_prep_sg(struct spi_ma
}
desc = dmaengine_prep_slave_sg(chan, sgl, nents, dir, flags);
- if (!desc)
- return -EINVAL;
+ if (IS_ERR_OR_NULL(desc))
+ return desc ? PTR_ERR(desc) : -EINVAL;
desc->callback = callback;
- desc->callback_param = qup;
+ desc->callback_param = data;
cookie = dmaengine_submit(desc);
@@ -332,18 +333,29 @@ static void spi_qup_dma_terminate(struct
dmaengine_terminate_all(master->dma_rx);
}
-static int spi_qup_do_dma(struct spi_master *master, struct spi_transfer *xfer)
+static int spi_qup_do_dma(struct spi_master *master, struct spi_transfer *xfer,
+unsigned long timeout)
{
+ struct spi_qup *qup = spi_master_get_devdata(master);
dma_async_tx_callback rx_done = NULL, tx_done = NULL;
int ret;
+ /* before issuing the descriptors, set the QUP to run */
+ ret = spi_qup_set_state(qup, QUP_STATE_RUN);
+ if (ret) {
+ dev_warn(qup->dev, "cannot set RUN state\n");
+ return ret;
+ }
+
if (xfer->rx_buf)
rx_done = spi_qup_dma_done;
- else if (xfer->tx_buf)
+
+ if (xfer->tx_buf)
tx_done = spi_qup_dma_done;
if (xfer->rx_buf) {
- ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done);
+ ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done,
+ &qup->done);
if (ret)
return ret;
@@ -351,17 +363,25 @@ static int spi_qup_do_dma(struct spi_mas
}
if (xfer->tx_buf) {
- ret = spi_qup_prep_sg(master, xfer, DMA_MEM_TO_DEV, tx_done);
+ ret = spi_qup_prep_sg(master, xfer, DMA_MEM_TO_DEV, tx_done,
+ &qup->dma_tx_done);
if (ret)
return ret;
dma_async_issue_pending(master->dma_tx);
}
- return 0;
+ if (xfer->rx_buf && !wait_for_completion_timeout(&qup->done, timeout))
+ return -ETIMEDOUT;
+
+ if (xfer->tx_buf && !wait_for_completion_timeout(&qup->dma_tx_done, timeout))
+ ret = -ETIMEDOUT;
+
+ return ret;
}
-static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer)
+static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer,
+ unsigned long timeout)
{
struct spi_qup *qup = spi_master_get_devdata(master);
int ret;
@@ -380,6 +400,15 @@ static int spi_qup_do_pio(struct spi_mas
spi_qup_fifo_write(qup, xfer);
+ ret = spi_qup_set_state(qup, QUP_STATE_RUN);
+ if (ret) {
+ dev_warn(qup->dev, "cannot set RUN state\n");
+ return ret;
+ }
+
+ if (!wait_for_completion_timeout(&qup->done, timeout))
+ return -ETIMEDOUT;
+
return 0;
}
@@ -428,7 +457,6 @@ static irqreturn_t spi_qup_qup_irq(int i
dev_warn(controller->dev, "CLK_OVER_RUN\n");
if (spi_err & SPI_ERROR_CLK_UNDER_RUN)
dev_warn(controller->dev, "CLK_UNDER_RUN\n");
-
error = -EIO;
}
@@ -617,6 +645,7 @@ static int spi_qup_transfer_one(struct s
timeout = 100 * msecs_to_jiffies(timeout);
reinit_completion(&controller->done);
+ reinit_completion(&controller->dma_tx_done);
spin_lock_irqsave(&controller->lock, flags);
controller->xfer = xfer;
@@ -626,21 +655,13 @@ static int spi_qup_transfer_one(struct s
spin_unlock_irqrestore(&controller->lock, flags);
if (spi_qup_is_dma_xfer(controller->mode))
- ret = spi_qup_do_dma(master, xfer);
+ ret = spi_qup_do_dma(master, xfer, timeout);
else
- ret = spi_qup_do_pio(master, xfer);
+ ret = spi_qup_do_pio(master, xfer, timeout);
if (ret)
goto exit;
- if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
- dev_warn(controller->dev, "cannot set EXECUTE state\n");
- goto exit;
- }
-
- if (!wait_for_completion_timeout(&controller->done, timeout))
- ret = -ETIMEDOUT;
-
exit:
spi_qup_set_state(controller, QUP_STATE_RESET);
spin_lock_irqsave(&controller->lock, flags);
@@ -662,15 +683,23 @@ static bool spi_qup_can_dma(struct spi_m
size_t dma_align = dma_get_cache_alignment();
int n_words;
- if (xfer->rx_buf && (xfer->len % qup->in_blk_sz ||
- IS_ERR_OR_NULL(master->dma_rx) ||
- !IS_ALIGNED((size_t)xfer->rx_buf, dma_align)))
- return false;
+ if (xfer->rx_buf) {
+ if (!IS_ALIGNED((size_t)xfer->rx_buf, dma_align) ||
+ IS_ERR_OR_NULL(master->dma_rx))
+ return false;
- if (xfer->tx_buf && (xfer->len % qup->out_blk_sz ||
- IS_ERR_OR_NULL(master->dma_tx) ||
- !IS_ALIGNED((size_t)xfer->tx_buf, dma_align)))
- return false;
+ if (qup->qup_v1 && (xfer->len % qup->in_blk_sz))
+ return false;
+ }
+
+ if (xfer->tx_buf) {
+ if (!IS_ALIGNED((size_t)xfer->tx_buf, dma_align) ||
+ IS_ERR_OR_NULL(master->dma_tx))
+ return false;
+
+ if (qup->qup_v1 && (xfer->len % qup->out_blk_sz))
+ return false;
+ }
n_words = xfer->len / DIV_ROUND_UP(xfer->bits_per_word, 8);
if (n_words <= (qup->in_fifo_sz / sizeof(u32)))
@@ -836,6 +865,7 @@ static int spi_qup_probe(struct platform
spin_lock_init(&controller->lock);
init_completion(&controller->done);
+ init_completion(&controller->dma_tx_done);
iomode = readl_relaxed(base + QUP_IO_M_MODES);

View file

@ -0,0 +1,310 @@
From 4dc7631bbf7c7ac7548026ce45d889235e4f5892 Mon Sep 17 00:00:00 2001
From: Andy Gross <andy.gross@linaro.org>
Date: Sun, 31 Jan 2016 21:28:13 -0600
Subject: [PATCH 09/37] spi: qup: Fix block mode to work correctly
This patch corrects the behavior of the BLOCK transactions. During block
transactions, the controller must be read/written to in block size transactions.
Signed-off-by: Andy Gross <andy.gross@linaro.org>
---
drivers/spi/spi-qup.c | 182 ++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 142 insertions(+), 40 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -82,6 +82,8 @@
#define QUP_IO_M_MODE_BAM 3
/* QUP_OPERATIONAL fields */
+#define QUP_OP_IN_BLOCK_READ_REQ BIT(13)
+#define QUP_OP_OUT_BLOCK_WRITE_REQ BIT(12)
#define QUP_OP_MAX_INPUT_DONE_FLAG BIT(11)
#define QUP_OP_MAX_OUTPUT_DONE_FLAG BIT(10)
#define QUP_OP_IN_SERVICE_FLAG BIT(9)
@@ -155,6 +157,12 @@ struct spi_qup {
struct dma_slave_config tx_conf;
};
+static inline bool spi_qup_is_flag_set(struct spi_qup *controller, u32 flag)
+{
+ u32 opflag = readl_relaxed(controller->base + QUP_OPERATIONAL);
+
+ return opflag & flag;
+}
static inline bool spi_qup_is_dma_xfer(int mode)
{
@@ -216,29 +224,26 @@ static int spi_qup_set_state(struct spi_
return 0;
}
-static void spi_qup_fifo_read(struct spi_qup *controller,
- struct spi_transfer *xfer)
+static void spi_qup_read_from_fifo(struct spi_qup *controller,
+ struct spi_transfer *xfer, u32 num_words)
{
u8 *rx_buf = xfer->rx_buf;
- u32 word, state;
- int idx, shift, w_size;
-
- w_size = controller->w_size;
+ int i, shift, num_bytes;
+ u32 word;
- while (controller->rx_bytes < xfer->len) {
-
- state = readl_relaxed(controller->base + QUP_OPERATIONAL);
- if (0 == (state & QUP_OP_IN_FIFO_NOT_EMPTY))
- break;
+ for (; num_words; num_words--) {
word = readl_relaxed(controller->base + QUP_INPUT_FIFO);
+ num_bytes = min_t(int, xfer->len - controller->rx_bytes,
+ controller->w_size);
+
if (!rx_buf) {
- controller->rx_bytes += w_size;
+ controller->rx_bytes += num_bytes;
continue;
}
- for (idx = 0; idx < w_size; idx++, controller->rx_bytes++) {
+ for (i = 0; i < num_bytes; i++, controller->rx_bytes++) {
/*
* The data format depends on bytes per SPI word:
* 4 bytes: 0x12345678
@@ -246,38 +251,80 @@ static void spi_qup_fifo_read(struct spi
* 1 byte : 0x00000012
*/
shift = BITS_PER_BYTE;
- shift *= (w_size - idx - 1);
+ shift *= (controller->w_size - i - 1);
rx_buf[controller->rx_bytes] = word >> shift;
}
}
}
-static void spi_qup_fifo_write(struct spi_qup *controller,
+static void spi_qup_read(struct spi_qup *controller,
struct spi_transfer *xfer)
{
- const u8 *tx_buf = xfer->tx_buf;
- u32 word, state, data;
- int idx, w_size;
+ u32 remainder, words_per_block, num_words;
+ bool is_block_mode = controller->mode == QUP_IO_M_MODE_BLOCK;
+
+ remainder = DIV_ROUND_UP(xfer->len - controller->rx_bytes,
+ controller->w_size);
+ words_per_block = controller->in_blk_sz >> 2;
+
+ do {
+ /* ACK by clearing service flag */
+ writel_relaxed(QUP_OP_IN_SERVICE_FLAG,
+ controller->base + QUP_OPERATIONAL);
+
+ if (is_block_mode) {
+ num_words = (remainder > words_per_block) ?
+ words_per_block : remainder;
+ } else {
+ if (!spi_qup_is_flag_set(controller,
+ QUP_OP_IN_FIFO_NOT_EMPTY))
+ break;
- w_size = controller->w_size;
+ num_words = 1;
+ }
+
+ /* read up to the maximum transfer size available */
+ spi_qup_read_from_fifo(controller, xfer, num_words);
- while (controller->tx_bytes < xfer->len) {
+ remainder -= num_words;
- state = readl_relaxed(controller->base + QUP_OPERATIONAL);
- if (state & QUP_OP_OUT_FIFO_FULL)
+ /* if block mode, check to see if next block is available */
+ if (is_block_mode && !spi_qup_is_flag_set(controller,
+ QUP_OP_IN_BLOCK_READ_REQ))
break;
+ } while (remainder);
+
+ /*
+ * Due to extra stickiness of the QUP_OP_IN_SERVICE_FLAG during block
+ * mode reads, it has to be cleared again at the very end
+ */
+ if (is_block_mode && spi_qup_is_flag_set(controller,
+ QUP_OP_MAX_INPUT_DONE_FLAG))
+ writel_relaxed(QUP_OP_IN_SERVICE_FLAG,
+ controller->base + QUP_OPERATIONAL);
+
+}
+
+static void spi_qup_write_to_fifo(struct spi_qup *controller,
+ struct spi_transfer *xfer, u32 num_words)
+{
+ const u8 *tx_buf = xfer->tx_buf;
+ int i, num_bytes;
+ u32 word, data;
+
+ for (; num_words; num_words--) {
word = 0;
- for (idx = 0; idx < w_size; idx++, controller->tx_bytes++) {
- if (!tx_buf) {
- controller->tx_bytes += w_size;
- break;
+ num_bytes = min_t(int, xfer->len - controller->tx_bytes,
+ controller->w_size);
+ if (tx_buf)
+ for (i = 0; i < num_bytes; i++) {
+ data = tx_buf[controller->tx_bytes + i];
+ word |= data << (BITS_PER_BYTE * (3 - i));
}
- data = tx_buf[controller->tx_bytes];
- word |= data << (BITS_PER_BYTE * (3 - idx));
- }
+ controller->tx_bytes += num_bytes;
writel_relaxed(word, controller->base + QUP_OUTPUT_FIFO);
}
@@ -290,6 +337,44 @@ static void spi_qup_dma_done(void *data)
complete(done);
}
+static void spi_qup_write(struct spi_qup *controller,
+ struct spi_transfer *xfer)
+{
+ bool is_block_mode = controller->mode == QUP_IO_M_MODE_BLOCK;
+ u32 remainder, words_per_block, num_words;
+
+ remainder = DIV_ROUND_UP(xfer->len - controller->tx_bytes,
+ controller->w_size);
+ words_per_block = controller->out_blk_sz >> 2;
+
+ do {
+ /* ACK by clearing service flag */
+ writel_relaxed(QUP_OP_OUT_SERVICE_FLAG,
+ controller->base + QUP_OPERATIONAL);
+
+ if (is_block_mode) {
+ num_words = (remainder > words_per_block) ?
+ words_per_block : remainder;
+ } else {
+ if (spi_qup_is_flag_set(controller,
+ QUP_OP_OUT_FIFO_FULL))
+ break;
+
+ num_words = 1;
+ }
+
+ spi_qup_write_to_fifo(controller, xfer, num_words);
+
+ remainder -= num_words;
+
+ /* if block mode, check to see if next block is available */
+ if (is_block_mode && !spi_qup_is_flag_set(controller,
+ QUP_OP_OUT_BLOCK_WRITE_REQ))
+ break;
+
+ } while (remainder);
+}
+
static int spi_qup_prep_sg(struct spi_master *master, struct spi_transfer *xfer,
enum dma_transfer_direction dir,
dma_async_tx_callback callback,
@@ -347,11 +432,13 @@ unsigned long timeout)
return ret;
}
- if (xfer->rx_buf)
- rx_done = spi_qup_dma_done;
+ if (!qup->qup_v1) {
+ if (xfer->rx_buf)
+ rx_done = spi_qup_dma_done;
- if (xfer->tx_buf)
- tx_done = spi_qup_dma_done;
+ if (xfer->tx_buf)
+ tx_done = spi_qup_dma_done;
+ }
if (xfer->rx_buf) {
ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done,
@@ -398,7 +485,8 @@ static int spi_qup_do_pio(struct spi_mas
return ret;
}
- spi_qup_fifo_write(qup, xfer);
+ if (qup->mode == QUP_IO_M_MODE_FIFO)
+ spi_qup_write(qup, xfer);
ret = spi_qup_set_state(qup, QUP_STATE_RUN);
if (ret) {
@@ -431,10 +519,11 @@ static irqreturn_t spi_qup_qup_irq(int i
writel_relaxed(qup_err, controller->base + QUP_ERROR_FLAGS);
writel_relaxed(spi_err, controller->base + SPI_ERROR_FLAGS);
- writel_relaxed(opflags, controller->base + QUP_OPERATIONAL);
if (!xfer) {
- dev_err_ratelimited(controller->dev, "unexpected irq %08x %08x %08x\n",
+ writel_relaxed(opflags, controller->base + QUP_OPERATIONAL);
+ dev_err_ratelimited(controller->dev,
+ "unexpected irq %08x %08x %08x\n",
qup_err, spi_err, opflags);
return IRQ_HANDLED;
}
@@ -460,12 +549,20 @@ static irqreturn_t spi_qup_qup_irq(int i
error = -EIO;
}
- if (!spi_qup_is_dma_xfer(controller->mode)) {
+ if (spi_qup_is_dma_xfer(controller->mode)) {
+ writel_relaxed(opflags, controller->base + QUP_OPERATIONAL);
+ if (opflags & QUP_OP_IN_SERVICE_FLAG &&
+ opflags & QUP_OP_MAX_INPUT_DONE_FLAG)
+ complete(&controller->done);
+ if (opflags & QUP_OP_OUT_SERVICE_FLAG &&
+ opflags & QUP_OP_MAX_OUTPUT_DONE_FLAG)
+ complete(&controller->dma_tx_done);
+ } else {
if (opflags & QUP_OP_IN_SERVICE_FLAG)
- spi_qup_fifo_read(controller, xfer);
+ spi_qup_read(controller, xfer);
if (opflags & QUP_OP_OUT_SERVICE_FLAG)
- spi_qup_fifo_write(controller, xfer);
+ spi_qup_write(controller, xfer);
}
spin_lock_irqsave(&controller->lock, flags);
@@ -473,6 +570,9 @@ static irqreturn_t spi_qup_qup_irq(int i
controller->xfer = xfer;
spin_unlock_irqrestore(&controller->lock, flags);
+ /* re-read opflags as flags may have changed due to actions above */
+ opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
+
if ((controller->rx_bytes == xfer->len &&
(opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error)
complete(&controller->done);
@@ -516,11 +616,13 @@ static int spi_qup_io_config(struct spi_
/* must be zero for FIFO */
writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
-
} else if (spi->master->can_dma &&
spi->master->can_dma(spi->master, spi, xfer) &&
spi->master->cur_msg_mapped) {
controller->mode = QUP_IO_M_MODE_BAM;
+ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
+ /* must be zero for BLOCK and BAM */
writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);

View file

@ -0,0 +1,61 @@
From 543618f5388d487ba88e3d5304c161fc3ccf61d1 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Thu, 10 Mar 2016 16:44:55 -0600
Subject: [PATCH 10/37] spi: qup: properly detect extra interrupts
It's possible for a SPI transaction to complete and get another
interrupt and have it processed on the same spi_transfer before the
transfer_one can set it to NULL.
This masks unexpected interrupts, so let's set the spi_transfer to
NULL in the interrupt once the transaction is done. So we can
properly detect these bad interrupts and print warning messages.
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -507,6 +507,7 @@ static irqreturn_t spi_qup_qup_irq(int i
u32 opflags, qup_err, spi_err;
unsigned long flags;
int error = 0;
+ bool done = 0;
spin_lock_irqsave(&controller->lock, flags);
xfer = controller->xfer;
@@ -565,16 +566,19 @@ static irqreturn_t spi_qup_qup_irq(int i
spi_qup_write(controller, xfer);
}
- spin_lock_irqsave(&controller->lock, flags);
- controller->error = error;
- controller->xfer = xfer;
- spin_unlock_irqrestore(&controller->lock, flags);
-
/* re-read opflags as flags may have changed due to actions above */
opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
if ((controller->rx_bytes == xfer->len &&
(opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error)
+ done = true;
+
+ spin_lock_irqsave(&controller->lock, flags);
+ controller->error = error;
+ controller->xfer = done ? NULL : xfer;
+ spin_unlock_irqrestore(&controller->lock, flags);
+
+ if (done)
complete(&controller->done);
return IRQ_HANDLED;
@@ -767,7 +771,6 @@ static int spi_qup_transfer_one(struct s
exit:
spi_qup_set_state(controller, QUP_STATE_RESET);
spin_lock_irqsave(&controller->lock, flags);
- controller->xfer = NULL;
if (!ret)
ret = controller->error;
spin_unlock_irqrestore(&controller->lock, flags);

View file

@ -0,0 +1,26 @@
From ba96e9449a63acd658d8ad0a5b3755b559410999 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Thu, 10 Mar 2016 16:48:27 -0600
Subject: [PATCH 11/37] spi: qup: don't re-read opflags to see if transaction
is done for reads
For reads, we will get another interrupt so we need to handle things
then. For writes, we can finish up now.
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -567,7 +567,8 @@ static irqreturn_t spi_qup_qup_irq(int i
}
/* re-read opflags as flags may have changed due to actions above */
- opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
+ if (opflags & QUP_OP_OUT_SERVICE_FLAG)
+ opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
if ((controller->rx_bytes == xfer->len &&
(opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error)

View file

@ -0,0 +1,202 @@
From ef00ad56d728618203358d9eba7ca8e7eb48e701 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Tue, 26 Apr 2016 12:57:46 -0500
Subject: [PATCH 12/37] spi: qup: refactor spi_qup_io_config in two functions
This is preparation for handling transactions larger than 64K-1 bytes in
block mode which is currently unsupported quietly fails.
We need to break these into two functions 1) prep is called once per
spi_message and 2) io_config is calle once per spi-qup bus transaction
This is just refactoring, there should be no functional change
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 141 ++++++++++++++++++++++++++++++-------------------
1 file changed, 86 insertions(+), 55 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -585,12 +585,11 @@ static irqreturn_t spi_qup_qup_irq(int i
return IRQ_HANDLED;
}
-/* set clock freq ... bits per word */
-static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
+/* set clock freq ... bits per word, determine mode */
+static int spi_qup_io_prep(struct spi_device *spi, struct spi_transfer *xfer)
{
struct spi_qup *controller = spi_master_get_devdata(spi->master);
- u32 config, iomode, control;
- int ret, n_words;
+ int ret;
if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) {
dev_err(controller->dev, "too big size for loopback %d > %d\n",
@@ -605,56 +604,94 @@ static int spi_qup_io_config(struct spi_
return -EIO;
}
- if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
- dev_err(controller->dev, "cannot set RESET state\n");
- return -EIO;
- }
-
controller->w_size = DIV_ROUND_UP(xfer->bits_per_word, 8);
controller->n_words = xfer->len / controller->w_size;
- n_words = controller->n_words;
- if (n_words <= (controller->in_fifo_sz / sizeof(u32))) {
+ if (controller->n_words <= (controller->in_fifo_sz / sizeof(u32)))
controller->mode = QUP_IO_M_MODE_FIFO;
- writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT);
- writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT);
- /* must be zero for FIFO */
- writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- } else if (spi->master->can_dma &&
- spi->master->can_dma(spi->master, spi, xfer) &&
- spi->master->cur_msg_mapped) {
+ else if (spi->master->can_dma &&
+ spi->master->can_dma(spi->master, spi, xfer) &&
+ spi->master->cur_msg_mapped)
controller->mode = QUP_IO_M_MODE_BAM;
- writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
- /* must be zero for BLOCK and BAM */
- writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
- writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
-
- if (!controller->qup_v1) {
- void __iomem *input_cnt;
-
- input_cnt = controller->base + QUP_MX_INPUT_CNT;
- /*
- * for DMA transfers, both QUP_MX_INPUT_CNT and
- * QUP_MX_OUTPUT_CNT must be zero to all cases but one.
- * That case is a non-balanced transfer when there is
- * only a rx_buf.
- */
- if (xfer->tx_buf)
- writel_relaxed(0, input_cnt);
- else
- writel_relaxed(n_words, input_cnt);
+ else
+ controller->mode = QUP_IO_M_MODE_BLOCK;
+
+ return 0;
+}
+/* prep qup for another spi transaction of specific type */
+static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
+{
+ struct spi_qup *controller = spi_master_get_devdata(spi->master);
+ u32 config, iomode, control;
+ unsigned long flags;
+
+ reinit_completion(&controller->done);
+ reinit_completion(&controller->dma_tx_done);
+
+ spin_lock_irqsave(&controller->lock, flags);
+ controller->xfer = xfer;
+ controller->error = 0;
+ controller->rx_bytes = 0;
+ controller->tx_bytes = 0;
+ spin_unlock_irqrestore(&controller->lock, flags);
+
+
+ if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
+ dev_err(controller->dev, "cannot set RESET state\n");
+ return -EIO;
+ }
+
+ switch (controller->mode) {
+ case QUP_IO_M_MODE_FIFO:
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_WRITE_CNT);
+ /* must be zero for FIFO */
+ writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- }
- } else {
- controller->mode = QUP_IO_M_MODE_BLOCK;
- writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
- /* must be zero for BLOCK and BAM */
- writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
- writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ break;
+ case QUP_IO_M_MODE_BAM:
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_OUTPUT_CNT);
+ /* must be zero for BLOCK and BAM */
+ writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ if (!controller->qup_v1) {
+ void __iomem *input_cnt;
+
+ input_cnt = controller->base + QUP_MX_INPUT_CNT;
+ /*
+ * for DMA transfers, both QUP_MX_INPUT_CNT and
+ * QUP_MX_OUTPUT_CNT must be zero to all cases
+ * but one. That case is a non-balanced
+ * transfer when there is only a rx_buf.
+ */
+ if (xfer->tx_buf)
+ writel_relaxed(0, input_cnt);
+ else
+ writel_relaxed(controller->n_words,
+ input_cnt);
+
+ writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
+ }
+ break;
+ case QUP_IO_M_MODE_BLOCK:
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_OUTPUT_CNT);
+ /* must be zero for BLOCK and BAM */
+ writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ break;
+ default:
+ dev_err(controller->dev, "unknown mode = %d\n",
+ controller->mode);
+ return -EIO;
}
iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
@@ -743,6 +780,10 @@ static int spi_qup_transfer_one(struct s
unsigned long timeout, flags;
int ret = -EIO;
+ ret = spi_qup_io_prep(spi, xfer);
+ if (ret)
+ return ret;
+
ret = spi_qup_io_config(spi, xfer);
if (ret)
return ret;
@@ -751,16 +792,6 @@ static int spi_qup_transfer_one(struct s
timeout = DIV_ROUND_UP(xfer->len * 8, timeout);
timeout = 100 * msecs_to_jiffies(timeout);
- reinit_completion(&controller->done);
- reinit_completion(&controller->dma_tx_done);
-
- spin_lock_irqsave(&controller->lock, flags);
- controller->xfer = xfer;
- controller->error = 0;
- controller->rx_bytes = 0;
- controller->tx_bytes = 0;
- spin_unlock_irqrestore(&controller->lock, flags);
-
if (spi_qup_is_dma_xfer(controller->mode))
ret = spi_qup_do_dma(master, xfer, timeout);
else

View file

@ -0,0 +1,391 @@
From 9263d98e255e1d51b41c752d53e39877728a9419 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Tue, 26 Apr 2016 13:14:45 -0500
Subject: [PATCH 13/37] spi: qup: call io_config in mode specific function
DMA transactions should only only need to call io_config only once, but
block mode might call it several times to setup several transactions so
it can handle reads/writes larger than the max size per transaction, so
we move the call to the do_ functions.
This is just refactoring, there should be no functional change
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 327 +++++++++++++++++++++++++------------------------
1 file changed, 166 insertions(+), 161 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -418,13 +418,170 @@ static void spi_qup_dma_terminate(struct
dmaengine_terminate_all(master->dma_rx);
}
-static int spi_qup_do_dma(struct spi_master *master, struct spi_transfer *xfer,
+/* prep qup for another spi transaction of specific type */
+static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
+{
+ struct spi_qup *controller = spi_master_get_devdata(spi->master);
+ u32 config, iomode, control;
+ unsigned long flags;
+
+ reinit_completion(&controller->done);
+ reinit_completion(&controller->dma_tx_done);
+
+ spin_lock_irqsave(&controller->lock, flags);
+ controller->xfer = xfer;
+ controller->error = 0;
+ controller->rx_bytes = 0;
+ controller->tx_bytes = 0;
+ spin_unlock_irqrestore(&controller->lock, flags);
+
+ if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
+ dev_err(controller->dev, "cannot set RESET state\n");
+ return -EIO;
+ }
+
+ switch (controller->mode) {
+ case QUP_IO_M_MODE_FIFO:
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_WRITE_CNT);
+ /* must be zero for FIFO */
+ writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
+ break;
+ case QUP_IO_M_MODE_BAM:
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_OUTPUT_CNT);
+ /* must be zero for BLOCK and BAM */
+ writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ if (!controller->qup_v1) {
+ void __iomem *input_cnt;
+
+ input_cnt = controller->base + QUP_MX_INPUT_CNT;
+ /*
+ * for DMA transfers, both QUP_MX_INPUT_CNT and
+ * QUP_MX_OUTPUT_CNT must be zero to all cases
+ * but one. That case is a non-balanced
+ * transfer when there is only a rx_buf.
+ */
+ if (xfer->tx_buf)
+ writel_relaxed(0, input_cnt);
+ else
+ writel_relaxed(controller->n_words,
+ input_cnt);
+
+ writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
+ }
+ break;
+ case QUP_IO_M_MODE_BLOCK:
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_INPUT_CNT);
+ writel_relaxed(controller->n_words,
+ controller->base + QUP_MX_OUTPUT_CNT);
+ /* must be zero for BLOCK and BAM */
+ writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
+ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
+ break;
+ default:
+ dev_err(controller->dev, "unknown mode = %d\n",
+ controller->mode);
+ return -EIO;
+ }
+
+ iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
+ /* Set input and output transfer mode */
+ iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK);
+
+ if (!spi_qup_is_dma_xfer(controller->mode))
+ iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
+ else
+ iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN;
+
+ iomode |= (controller->mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
+ iomode |= (controller->mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
+
+ writel_relaxed(iomode, controller->base + QUP_IO_M_MODES);
+
+ control = readl_relaxed(controller->base + SPI_IO_CONTROL);
+
+ if (spi->mode & SPI_CPOL)
+ control |= SPI_IO_C_CLK_IDLE_HIGH;
+ else
+ control &= ~SPI_IO_C_CLK_IDLE_HIGH;
+
+ writel_relaxed(control, controller->base + SPI_IO_CONTROL);
+
+ config = readl_relaxed(controller->base + SPI_CONFIG);
+
+ if (spi->mode & SPI_LOOP)
+ config |= SPI_CONFIG_LOOPBACK;
+ else
+ config &= ~SPI_CONFIG_LOOPBACK;
+
+ if (spi->mode & SPI_CPHA)
+ config &= ~SPI_CONFIG_INPUT_FIRST;
+ else
+ config |= SPI_CONFIG_INPUT_FIRST;
+
+ /*
+ * HS_MODE improves signal stability for spi-clk high rates,
+ * but is invalid in loop back mode.
+ */
+ if ((xfer->speed_hz >= SPI_HS_MIN_RATE) && !(spi->mode & SPI_LOOP))
+ config |= SPI_CONFIG_HS_MODE;
+ else
+ config &= ~SPI_CONFIG_HS_MODE;
+
+ writel_relaxed(config, controller->base + SPI_CONFIG);
+
+ config = readl_relaxed(controller->base + QUP_CONFIG);
+ config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N);
+ config |= xfer->bits_per_word - 1;
+ config |= QUP_CONFIG_SPI_MODE;
+
+ if (spi_qup_is_dma_xfer(controller->mode)) {
+ if (!xfer->tx_buf)
+ config |= QUP_CONFIG_NO_OUTPUT;
+ if (!xfer->rx_buf)
+ config |= QUP_CONFIG_NO_INPUT;
+ }
+
+ writel_relaxed(config, controller->base + QUP_CONFIG);
+
+ /* only write to OPERATIONAL_MASK when register is present */
+ if (!controller->qup_v1) {
+ u32 mask = 0;
+
+ /*
+ * mask INPUT and OUTPUT service flags to prevent IRQs on FIFO
+ * status change in BAM mode
+ */
+
+ if (spi_qup_is_dma_xfer(controller->mode))
+ mask = QUP_OP_IN_SERVICE_FLAG | QUP_OP_OUT_SERVICE_FLAG;
+
+ writel_relaxed(mask, controller->base + QUP_OPERATIONAL_MASK);
+ }
+
+ return 0;
+}
+
+static int spi_qup_do_dma(struct spi_device *spi, struct spi_transfer *xfer,
unsigned long timeout)
{
+ struct spi_master *master = spi->master;
struct spi_qup *qup = spi_master_get_devdata(master);
dma_async_tx_callback rx_done = NULL, tx_done = NULL;
int ret;
+ ret = spi_qup_io_config(spi, xfer);
+ if (ret)
+ return ret;
+
/* before issuing the descriptors, set the QUP to run */
ret = spi_qup_set_state(qup, QUP_STATE_RUN);
if (ret) {
@@ -467,12 +624,17 @@ unsigned long timeout)
return ret;
}
-static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer,
+static int spi_qup_do_pio(struct spi_device *spi, struct spi_transfer *xfer,
unsigned long timeout)
{
+ struct spi_master *master = spi->master;
struct spi_qup *qup = spi_master_get_devdata(master);
int ret;
+ ret = spi_qup_io_config(spi, xfer);
+ if (ret)
+ return ret;
+
ret = spi_qup_set_state(qup, QUP_STATE_RUN);
if (ret) {
dev_warn(qup->dev, "cannot set RUN state\n");
@@ -619,159 +781,6 @@ static int spi_qup_io_prep(struct spi_de
return 0;
}
-/* prep qup for another spi transaction of specific type */
-static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
-{
- struct spi_qup *controller = spi_master_get_devdata(spi->master);
- u32 config, iomode, control;
- unsigned long flags;
-
- reinit_completion(&controller->done);
- reinit_completion(&controller->dma_tx_done);
-
- spin_lock_irqsave(&controller->lock, flags);
- controller->xfer = xfer;
- controller->error = 0;
- controller->rx_bytes = 0;
- controller->tx_bytes = 0;
- spin_unlock_irqrestore(&controller->lock, flags);
-
-
- if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
- dev_err(controller->dev, "cannot set RESET state\n");
- return -EIO;
- }
-
- switch (controller->mode) {
- case QUP_IO_M_MODE_FIFO:
- writel_relaxed(controller->n_words,
- controller->base + QUP_MX_READ_CNT);
- writel_relaxed(controller->n_words,
- controller->base + QUP_MX_WRITE_CNT);
- /* must be zero for FIFO */
- writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- break;
- case QUP_IO_M_MODE_BAM:
- writel_relaxed(controller->n_words,
- controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(controller->n_words,
- controller->base + QUP_MX_OUTPUT_CNT);
- /* must be zero for BLOCK and BAM */
- writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
- writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
- if (!controller->qup_v1) {
- void __iomem *input_cnt;
-
- input_cnt = controller->base + QUP_MX_INPUT_CNT;
- /*
- * for DMA transfers, both QUP_MX_INPUT_CNT and
- * QUP_MX_OUTPUT_CNT must be zero to all cases
- * but one. That case is a non-balanced
- * transfer when there is only a rx_buf.
- */
- if (xfer->tx_buf)
- writel_relaxed(0, input_cnt);
- else
- writel_relaxed(controller->n_words,
- input_cnt);
-
- writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
- }
- break;
- case QUP_IO_M_MODE_BLOCK:
- writel_relaxed(controller->n_words,
- controller->base + QUP_MX_INPUT_CNT);
- writel_relaxed(controller->n_words,
- controller->base + QUP_MX_OUTPUT_CNT);
- /* must be zero for BLOCK and BAM */
- writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
- writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
- break;
- default:
- dev_err(controller->dev, "unknown mode = %d\n",
- controller->mode);
- return -EIO;
- }
-
- iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
- /* Set input and output transfer mode */
- iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK);
-
- if (!spi_qup_is_dma_xfer(controller->mode))
- iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
- else
- iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN;
-
- iomode |= (controller->mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
- iomode |= (controller->mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
-
- writel_relaxed(iomode, controller->base + QUP_IO_M_MODES);
-
- control = readl_relaxed(controller->base + SPI_IO_CONTROL);
-
- if (spi->mode & SPI_CPOL)
- control |= SPI_IO_C_CLK_IDLE_HIGH;
- else
- control &= ~SPI_IO_C_CLK_IDLE_HIGH;
-
- writel_relaxed(control, controller->base + SPI_IO_CONTROL);
-
- config = readl_relaxed(controller->base + SPI_CONFIG);
-
- if (spi->mode & SPI_LOOP)
- config |= SPI_CONFIG_LOOPBACK;
- else
- config &= ~SPI_CONFIG_LOOPBACK;
-
- if (spi->mode & SPI_CPHA)
- config &= ~SPI_CONFIG_INPUT_FIRST;
- else
- config |= SPI_CONFIG_INPUT_FIRST;
-
- /*
- * HS_MODE improves signal stability for spi-clk high rates,
- * but is invalid in loop back mode.
- */
- if ((xfer->speed_hz >= SPI_HS_MIN_RATE) && !(spi->mode & SPI_LOOP))
- config |= SPI_CONFIG_HS_MODE;
- else
- config &= ~SPI_CONFIG_HS_MODE;
-
- writel_relaxed(config, controller->base + SPI_CONFIG);
-
- config = readl_relaxed(controller->base + QUP_CONFIG);
- config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N);
- config |= xfer->bits_per_word - 1;
- config |= QUP_CONFIG_SPI_MODE;
-
- if (spi_qup_is_dma_xfer(controller->mode)) {
- if (!xfer->tx_buf)
- config |= QUP_CONFIG_NO_OUTPUT;
- if (!xfer->rx_buf)
- config |= QUP_CONFIG_NO_INPUT;
- }
-
- writel_relaxed(config, controller->base + QUP_CONFIG);
-
- /* only write to OPERATIONAL_MASK when register is present */
- if (!controller->qup_v1) {
- u32 mask = 0;
-
- /*
- * mask INPUT and OUTPUT service flags to prevent IRQs on FIFO
- * status change in BAM mode
- */
-
- if (spi_qup_is_dma_xfer(controller->mode))
- mask = QUP_OP_IN_SERVICE_FLAG | QUP_OP_OUT_SERVICE_FLAG;
-
- writel_relaxed(mask, controller->base + QUP_OPERATIONAL_MASK);
- }
-
- return 0;
-}
-
static int spi_qup_transfer_one(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer)
@@ -784,18 +793,14 @@ static int spi_qup_transfer_one(struct s
if (ret)
return ret;
- ret = spi_qup_io_config(spi, xfer);
- if (ret)
- return ret;
-
timeout = DIV_ROUND_UP(xfer->speed_hz, MSEC_PER_SEC);
timeout = DIV_ROUND_UP(xfer->len * 8, timeout);
timeout = 100 * msecs_to_jiffies(timeout);
if (spi_qup_is_dma_xfer(controller->mode))
- ret = spi_qup_do_dma(master, xfer, timeout);
+ ret = spi_qup_do_dma(spi, xfer, timeout);
else
- ret = spi_qup_do_pio(master, xfer, timeout);
+ ret = spi_qup_do_pio(spi, xfer, timeout);
if (ret)
goto exit;

View file

@ -0,0 +1,268 @@
From 05a08cc5620df0fcf8e260feee04b9671705723e Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Tue, 26 Apr 2016 15:46:24 -0500
Subject: [PATCH 14/37] spi: qup: allow block mode to generate multiple
transactions
This let's you write more to the SPI bus than 64K-1 which is important
if the block size of a SPI device is >= 64K or some other device wants
to something larger.
This has the benefit of completly removing spi_message from the spi-qup
transactions
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 120 ++++++++++++++++++++++++++++++-------------------
1 file changed, 75 insertions(+), 45 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -120,7 +120,7 @@
#define SPI_NUM_CHIPSELECTS 4
-#define SPI_MAX_DMA_XFER (SZ_64K - 64)
+#define SPI_MAX_XFER (SZ_64K - 64)
/* high speed mode is when bus rate is greater then 26MHz */
#define SPI_HS_MIN_RATE 26000000
@@ -150,6 +150,8 @@ struct spi_qup {
int n_words;
int tx_bytes;
int rx_bytes;
+ const u8 *tx_buf;
+ u8 *rx_buf;
int qup_v1;
int mode;
@@ -172,6 +174,12 @@ static inline bool spi_qup_is_dma_xfer(i
return false;
}
+/* get's the transaction size length */
+static inline unsigned spi_qup_len(struct spi_qup *controller)
+{
+ return controller->n_words * controller->w_size;
+}
+
static inline bool spi_qup_is_valid_state(struct spi_qup *controller)
{
u32 opstate = readl_relaxed(controller->base + QUP_STATE);
@@ -224,10 +232,9 @@ static int spi_qup_set_state(struct spi_
return 0;
}
-static void spi_qup_read_from_fifo(struct spi_qup *controller,
- struct spi_transfer *xfer, u32 num_words)
+static void spi_qup_read_from_fifo(struct spi_qup *controller, u32 num_words)
{
- u8 *rx_buf = xfer->rx_buf;
+ u8 *rx_buf = controller->rx_buf;
int i, shift, num_bytes;
u32 word;
@@ -235,7 +242,7 @@ static void spi_qup_read_from_fifo(struc
word = readl_relaxed(controller->base + QUP_INPUT_FIFO);
- num_bytes = min_t(int, xfer->len - controller->rx_bytes,
+ num_bytes = min_t(int, spi_qup_len(controller) - controller->rx_bytes,
controller->w_size);
if (!rx_buf) {
@@ -257,13 +264,12 @@ static void spi_qup_read_from_fifo(struc
}
}
-static void spi_qup_read(struct spi_qup *controller,
- struct spi_transfer *xfer)
+static void spi_qup_read(struct spi_qup *controller)
{
u32 remainder, words_per_block, num_words;
bool is_block_mode = controller->mode == QUP_IO_M_MODE_BLOCK;
- remainder = DIV_ROUND_UP(xfer->len - controller->rx_bytes,
+ remainder = DIV_ROUND_UP(spi_qup_len(controller) - controller->rx_bytes,
controller->w_size);
words_per_block = controller->in_blk_sz >> 2;
@@ -284,7 +290,7 @@ static void spi_qup_read(struct spi_qup
}
/* read up to the maximum transfer size available */
- spi_qup_read_from_fifo(controller, xfer, num_words);
+ spi_qup_read_from_fifo(controller, num_words);
remainder -= num_words;
@@ -306,17 +312,16 @@ static void spi_qup_read(struct spi_qup
}
-static void spi_qup_write_to_fifo(struct spi_qup *controller,
- struct spi_transfer *xfer, u32 num_words)
+static void spi_qup_write_to_fifo(struct spi_qup *controller, u32 num_words)
{
- const u8 *tx_buf = xfer->tx_buf;
+ const u8 *tx_buf = controller->tx_buf;
int i, num_bytes;
u32 word, data;
for (; num_words; num_words--) {
word = 0;
- num_bytes = min_t(int, xfer->len - controller->tx_bytes,
+ num_bytes = min_t(int, spi_qup_len(controller) - controller->tx_bytes,
controller->w_size);
if (tx_buf)
for (i = 0; i < num_bytes; i++) {
@@ -337,13 +342,12 @@ static void spi_qup_dma_done(void *data)
complete(done);
}
-static void spi_qup_write(struct spi_qup *controller,
- struct spi_transfer *xfer)
+static void spi_qup_write(struct spi_qup *controller)
{
bool is_block_mode = controller->mode == QUP_IO_M_MODE_BLOCK;
u32 remainder, words_per_block, num_words;
- remainder = DIV_ROUND_UP(xfer->len - controller->tx_bytes,
+ remainder = DIV_ROUND_UP(spi_qup_len(controller) - controller->tx_bytes,
controller->w_size);
words_per_block = controller->out_blk_sz >> 2;
@@ -363,7 +367,7 @@ static void spi_qup_write(struct spi_qup
num_words = 1;
}
- spi_qup_write_to_fifo(controller, xfer, num_words);
+ spi_qup_write_to_fifo(controller, num_words);
remainder -= num_words;
@@ -629,35 +633,61 @@ static int spi_qup_do_pio(struct spi_dev
{
struct spi_master *master = spi->master;
struct spi_qup *qup = spi_master_get_devdata(master);
- int ret;
+ int ret, n_words, iterations, offset = 0;
- ret = spi_qup_io_config(spi, xfer);
- if (ret)
- return ret;
+ n_words = qup->n_words;
+ iterations = n_words / SPI_MAX_XFER; /* round down */
- ret = spi_qup_set_state(qup, QUP_STATE_RUN);
- if (ret) {
- dev_warn(qup->dev, "cannot set RUN state\n");
- return ret;
- }
+ qup->rx_buf = xfer->rx_buf;
+ qup->tx_buf = xfer->tx_buf;
- ret = spi_qup_set_state(qup, QUP_STATE_PAUSE);
- if (ret) {
- dev_warn(qup->dev, "cannot set PAUSE state\n");
- return ret;
- }
+ do {
+ if (iterations)
+ qup->n_words = SPI_MAX_XFER;
+ else
+ qup->n_words = n_words % SPI_MAX_XFER;
+
+ if (qup->tx_buf && offset)
+ qup->tx_buf = xfer->tx_buf + offset * SPI_MAX_XFER;
+
+ if (qup->rx_buf && offset)
+ qup->rx_buf = xfer->rx_buf + offset * SPI_MAX_XFER;
+
+ /* if the transaction is small enough, we need
+ * to fallback to FIFO mode */
+ if (qup->n_words <= (qup->in_fifo_sz / sizeof(u32)))
+ qup->mode = QUP_IO_M_MODE_FIFO;
- if (qup->mode == QUP_IO_M_MODE_FIFO)
- spi_qup_write(qup, xfer);
+ ret = spi_qup_io_config(spi, xfer);
+ if (ret)
+ return ret;
- ret = spi_qup_set_state(qup, QUP_STATE_RUN);
- if (ret) {
- dev_warn(qup->dev, "cannot set RUN state\n");
- return ret;
- }
+ ret = spi_qup_set_state(qup, QUP_STATE_RUN);
+ if (ret) {
+ dev_warn(qup->dev, "cannot set RUN state\n");
+ return ret;
+ }
- if (!wait_for_completion_timeout(&qup->done, timeout))
- return -ETIMEDOUT;
+ ret = spi_qup_set_state(qup, QUP_STATE_PAUSE);
+ if (ret) {
+ dev_warn(qup->dev, "cannot set PAUSE state\n");
+ return ret;
+ }
+
+ if (qup->mode == QUP_IO_M_MODE_FIFO)
+ spi_qup_write(qup);
+
+ ret = spi_qup_set_state(qup, QUP_STATE_RUN);
+ if (ret) {
+ dev_warn(qup->dev, "cannot set RUN state\n");
+ return ret;
+ }
+
+ if (!wait_for_completion_timeout(&qup->done, timeout))
+ return -ETIMEDOUT;
+
+ offset++;
+ } while (iterations--);
return 0;
}
@@ -722,17 +752,17 @@ static irqreturn_t spi_qup_qup_irq(int i
complete(&controller->dma_tx_done);
} else {
if (opflags & QUP_OP_IN_SERVICE_FLAG)
- spi_qup_read(controller, xfer);
+ spi_qup_read(controller);
if (opflags & QUP_OP_OUT_SERVICE_FLAG)
- spi_qup_write(controller, xfer);
+ spi_qup_write(controller);
}
/* re-read opflags as flags may have changed due to actions above */
if (opflags & QUP_OP_OUT_SERVICE_FLAG)
opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
- if ((controller->rx_bytes == xfer->len &&
+ if ((controller->rx_bytes == spi_qup_len(controller) &&
(opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error)
done = true;
@@ -794,7 +824,7 @@ static int spi_qup_transfer_one(struct s
return ret;
timeout = DIV_ROUND_UP(xfer->speed_hz, MSEC_PER_SEC);
- timeout = DIV_ROUND_UP(xfer->len * 8, timeout);
+ timeout = DIV_ROUND_UP(min_t(unsigned long, SPI_MAX_XFER, xfer->len) * 8, timeout);
timeout = 100 * msecs_to_jiffies(timeout);
if (spi_qup_is_dma_xfer(controller->mode))
@@ -983,7 +1013,7 @@ static int spi_qup_probe(struct platform
master->dev.of_node = pdev->dev.of_node;
master->auto_runtime_pm = true;
master->dma_alignment = dma_get_cache_alignment();
- master->max_dma_len = SPI_MAX_DMA_XFER;
+ master->max_dma_len = SPI_MAX_XFER;
platform_set_drvdata(pdev, master);

View file

@ -0,0 +1,73 @@
From a24914d34a4c6df4323c6d98950166600da79bc6 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Wed, 4 May 2016 16:33:42 -0500
Subject: [PATCH 15/37] spi: qup: refactor spi_qup_prep_sg to be more take
specific sgl and nent
This is in preparation for splitting DMA into multiple transacations,
this contains no code changes just refactoring.
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 28 +++++++++++-----------------
1 file changed, 11 insertions(+), 17 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -379,27 +379,19 @@ static void spi_qup_write(struct spi_qup
} while (remainder);
}
-static int spi_qup_prep_sg(struct spi_master *master, struct spi_transfer *xfer,
- enum dma_transfer_direction dir,
- dma_async_tx_callback callback,
- void *data)
+static int spi_qup_prep_sg(struct spi_master *master, struct scatterlist *sgl,
+ unsigned int nents, enum dma_transfer_direction dir,
+ dma_async_tx_callback callback, void *data)
{
unsigned long flags = DMA_PREP_INTERRUPT | DMA_PREP_FENCE;
struct dma_async_tx_descriptor *desc;
- struct scatterlist *sgl;
struct dma_chan *chan;
dma_cookie_t cookie;
- unsigned int nents;
- if (dir == DMA_MEM_TO_DEV) {
+ if (dir == DMA_MEM_TO_DEV)
chan = master->dma_tx;
- nents = xfer->tx_sg.nents;
- sgl = xfer->tx_sg.sgl;
- } else {
+ else
chan = master->dma_rx;
- nents = xfer->rx_sg.nents;
- sgl = xfer->rx_sg.sgl;
- }
desc = dmaengine_prep_slave_sg(chan, sgl, nents, dir, flags);
if (IS_ERR_OR_NULL(desc))
@@ -602,8 +594,9 @@ unsigned long timeout)
}
if (xfer->rx_buf) {
- ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done,
- &qup->done);
+ ret = spi_qup_prep_sg(master, xfer->rx_sg.sgl,
+ xfer->rx_sg.nents, DMA_DEV_TO_MEM,
+ rx_done, &qup->done);
if (ret)
return ret;
@@ -611,8 +604,9 @@ unsigned long timeout)
}
if (xfer->tx_buf) {
- ret = spi_qup_prep_sg(master, xfer, DMA_MEM_TO_DEV, tx_done,
- &qup->dma_tx_done);
+ ret = spi_qup_prep_sg(master, xfer->tx_sg.sgl,
+ xfer->tx_sg.nents, DMA_MEM_TO_DEV,
+ tx_done, &qup->dma_tx_done);
if (ret)
return ret;

View file

@ -0,0 +1,166 @@
From 6b2bb8803f19116bad41a271f9035d4c853f4553 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Thu, 5 May 2016 10:07:11 -0500
Subject: [PATCH 16/37] spi: qup: allow mulitple DMA transactions per spi xfer
Much like the block mode changes, we are breaking up DMA transactions
into 64K chunks so we can reset the QUP engine.
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/spi/spi-qup.c | 120 +++++++++++++++++++++++++++++++++++--------------
1 file changed, 86 insertions(+), 34 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -566,6 +566,21 @@ static int spi_qup_io_config(struct spi_
return 0;
}
+static unsigned int spi_qup_sgl_get_size(struct scatterlist *sgl, unsigned int nents)
+{
+ struct scatterlist *sg;
+ int i;
+ unsigned int length = 0;
+
+ if (!nents)
+ return 0;
+
+ for_each_sg(sgl, sg, nents, i)
+ length += sg_dma_len(sg);
+
+ return length;
+}
+
static int spi_qup_do_dma(struct spi_device *spi, struct spi_transfer *xfer,
unsigned long timeout)
{
@@ -573,53 +588,90 @@ unsigned long timeout)
struct spi_qup *qup = spi_master_get_devdata(master);
dma_async_tx_callback rx_done = NULL, tx_done = NULL;
int ret;
+ struct scatterlist *tx_sgl, *rx_sgl;
- ret = spi_qup_io_config(spi, xfer);
- if (ret)
- return ret;
-
- /* before issuing the descriptors, set the QUP to run */
- ret = spi_qup_set_state(qup, QUP_STATE_RUN);
- if (ret) {
- dev_warn(qup->dev, "cannot set RUN state\n");
- return ret;
- }
-
- if (!qup->qup_v1) {
- if (xfer->rx_buf)
- rx_done = spi_qup_dma_done;
-
- if (xfer->tx_buf)
- tx_done = spi_qup_dma_done;
- }
-
- if (xfer->rx_buf) {
- ret = spi_qup_prep_sg(master, xfer->rx_sg.sgl,
- xfer->rx_sg.nents, DMA_DEV_TO_MEM,
- rx_done, &qup->done);
- if (ret)
- return ret;
+ rx_sgl = xfer->rx_sg.sgl;
+ tx_sgl = xfer->tx_sg.sgl;
- dma_async_issue_pending(master->dma_rx);
- }
+ do {
+ int rx_nents = 0, tx_nents = 0;
- if (xfer->tx_buf) {
- ret = spi_qup_prep_sg(master, xfer->tx_sg.sgl,
- xfer->tx_sg.nents, DMA_MEM_TO_DEV,
- tx_done, &qup->dma_tx_done);
+ if (rx_sgl) {
+ rx_nents = sg_nents_for_len(rx_sgl, SPI_MAX_XFER);
+ if (rx_nents < 0)
+ rx_nents = sg_nents(rx_sgl);
+
+ qup->n_words = spi_qup_sgl_get_size(rx_sgl, rx_nents) /
+ qup->w_size;
+ }
+
+ if (tx_sgl) {
+ tx_nents = sg_nents_for_len(tx_sgl, SPI_MAX_XFER);
+ if (tx_nents < 0)
+ tx_nents = sg_nents(tx_sgl);
+
+ qup->n_words = spi_qup_sgl_get_size(tx_sgl, tx_nents) /
+ qup->w_size;
+ }
+
+
+ ret = spi_qup_io_config(spi, xfer);
if (ret)
return ret;
- dma_async_issue_pending(master->dma_tx);
- }
+ /* before issuing the descriptors, set the QUP to run */
+ ret = spi_qup_set_state(qup, QUP_STATE_RUN);
+ if (ret) {
+ dev_warn(qup->dev, "cannot set RUN state\n");
+ return ret;
+ }
+
+ if (!qup->qup_v1) {
+ if (rx_sgl) {
+ rx_done = spi_qup_dma_done;
+ }
+
+ if (tx_sgl) {
+ tx_done = spi_qup_dma_done;
+ }
+ }
+
+ if (rx_sgl) {
+ ret = spi_qup_prep_sg(master, rx_sgl, rx_nents,
+ DMA_DEV_TO_MEM, rx_done,
+ &qup->done);
+ if (ret)
+ return ret;
+
+ dma_async_issue_pending(master->dma_rx);
+ }
+
+ if (tx_sgl) {
+ ret = spi_qup_prep_sg(master, tx_sgl, tx_nents,
+ DMA_MEM_TO_DEV, tx_done,
+ &qup->dma_tx_done);
+ if (ret)
+ return ret;
+
+ dma_async_issue_pending(master->dma_tx);
+ }
+
+ if (rx_sgl && !wait_for_completion_timeout(&qup->done, timeout)) {
+ pr_emerg(" rx timed out");
+ return -ETIMEDOUT;
+ }
+
+ if (tx_sgl && !wait_for_completion_timeout(&qup->dma_tx_done, timeout)) {
+ pr_emerg(" tx timed out\n");
+ return -ETIMEDOUT;
+ }
- if (xfer->rx_buf && !wait_for_completion_timeout(&qup->done, timeout))
- return -ETIMEDOUT;
+ for (; rx_sgl && rx_nents--; rx_sgl = sg_next(rx_sgl));
+ for (; tx_sgl && tx_nents--; tx_sgl = sg_next(tx_sgl));
- if (xfer->tx_buf && !wait_for_completion_timeout(&qup->dma_tx_done, timeout))
- ret = -ETIMEDOUT;
+ } while (rx_sgl || tx_sgl);
- return ret;
+ return 0;
}
static int spi_qup_do_pio(struct spi_device *spi, struct spi_transfer *xfer,

View file

@ -0,0 +1,86 @@
From 5ffa559b35dd90469e1f7fc21a77c6a2d5a8ca0f Mon Sep 17 00:00:00 2001
From: Varadarajan Narayanan <varada@codeaurora.org>
Date: Wed, 25 May 2016 13:40:03 +0530
Subject: [PATCH 17/37] spi: qup: Fix sg nents calculation
lib/scatterlist.c:sg_nents_for_len() returns the number of SG
entries that total up to greater than or equal to the given
length. However, the spi-qup driver assumed that the returned
nents is for a total less than or equal to the given length. The
spi-qup driver requests nents for SPI_MAX_XFER, however the API
returns nents for SPI_MAX_XFER+delta (actually SZ_64K).
Based on this, spi_qup_do_dma() calculates n_words and programs
that into QUP_MX_{IN|OUT}PUT_CNT register. The calculated
n_words value is more than the maximum value that can fit in the
the 16-bit COUNT field of the QUP_MX_{IN|OUT}PUT_CNT register.
And, the field gets programmed to zero. Since the COUNT field is
zero, the i/o doesn't start eventually resulting in the i/o
timing out.
Signed-off-by: Varadarajan Narayanan <varada@codeaurora.org>
---
drivers/spi/spi-qup.c | 38 ++++++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)
--- a/drivers/spi/spi-qup.c
+++ b/drivers/spi/spi-qup.c
@@ -581,6 +581,38 @@ static unsigned int spi_qup_sgl_get_size
return length;
}
+/**
+ * spi_qup_sg_nents_for_len - return total count of entries in scatterlist
+ * needed to satisfy the supplied length
+ * @sg: The scatterlist
+ * @len: The total required length
+ *
+ * Description:
+ * Determines the number of entries in sg that sum upto a maximum of
+ * the supplied length, taking into acount chaining as well
+ *
+ * Returns:
+ * the number of sg entries needed, negative error on failure
+ *
+ **/
+int spi_qup_sg_nents_for_len(struct scatterlist *sg, u64 len)
+{
+ int nents;
+ u64 total;
+
+ if (!len)
+ return 0;
+
+ for (nents = 0, total = 0; sg; sg = sg_next(sg)) {
+ nents++;
+ total += sg_dma_len(sg);
+ if (total > len)
+ return (nents - 1);
+ }
+
+ return -EINVAL;
+}
+
static int spi_qup_do_dma(struct spi_device *spi, struct spi_transfer *xfer,
unsigned long timeout)
{
@@ -597,7 +629,8 @@ unsigned long timeout)
int rx_nents = 0, tx_nents = 0;
if (rx_sgl) {
- rx_nents = sg_nents_for_len(rx_sgl, SPI_MAX_XFER);
+ rx_nents = spi_qup_sg_nents_for_len(rx_sgl,
+ SPI_MAX_XFER);
if (rx_nents < 0)
rx_nents = sg_nents(rx_sgl);
@@ -606,7 +639,8 @@ unsigned long timeout)
}
if (tx_sgl) {
- tx_nents = sg_nents_for_len(tx_sgl, SPI_MAX_XFER);
+ tx_nents = spi_qup_sg_nents_for_len(tx_sgl,
+ SPI_MAX_XFER);
if (tx_nents < 0)
tx_nents = sg_nents(tx_sgl);

View file

@ -0,0 +1,28 @@
From 94aa51061597d9db86f3ad4d54eb4a560fa66f2f Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Wed, 13 Apr 2016 14:03:14 -0500
Subject: [PATCH 26/37] cpufreq: dt: qcom: ipq4019: Add compat for qcom
ipq4019
Instantiate cpufreq-dt-platdev driver for ipq4019 to support changing
CPU frequencies.
This depends on Viresh Kumar's patches in this series:
http://comments.gmane.org/gmane.linux.power-management.general/73887
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
drivers/cpufreq/cpufreq-dt-platdev.c | 2 ++
1 file changed, 2 insertions(+)
--- a/drivers/cpufreq/cpufreq-dt-platdev.c
+++ b/drivers/cpufreq/cpufreq-dt-platdev.c
@@ -35,6 +35,8 @@ static const struct of_device_id machine
{ .compatible = "marvell,berlin", },
+ { .compatible = "qcom,ipq4019", },
+
{ .compatible = "samsung,exynos3250", },
{ .compatible = "samsung,exynos4210", },
{ .compatible = "samsung,exynos4212", },

View file

@ -0,0 +1,33 @@
From 4f0328557a670d481f2609474b56890c28ab4694 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@qca.qualcomm.com>
Date: Thu, 28 Apr 2016 12:55:08 -0500
Subject: [PATCH 27/37] clk: ipq4019: report accurate fixed clock rates
This looks like a copy-and-paste gone wrong, but update all
the fixed clock rates to report the correct values.
Signed-off-by: Matthew McClintock <mmcclint@qca.qualcomm.com>
---
drivers/clk/qcom/gcc-ipq4019.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
--- a/drivers/clk/qcom/gcc-ipq4019.c
+++ b/drivers/clk/qcom/gcc-ipq4019.c
@@ -1317,12 +1317,12 @@ static int gcc_ipq4019_probe(struct plat
{
struct device *dev = &pdev->dev;
- clk_register_fixed_rate(dev, "fepll125", "xo", 0, 200000000);
- clk_register_fixed_rate(dev, "fepll125dly", "xo", 0, 200000000);
- clk_register_fixed_rate(dev, "fepllwcss2g", "xo", 0, 200000000);
- clk_register_fixed_rate(dev, "fepllwcss5g", "xo", 0, 200000000);
+ clk_register_fixed_rate(dev, "fepll125", "xo", 0, 125000000);
+ clk_register_fixed_rate(dev, "fepll125dly", "xo", 0, 125000000);
+ clk_register_fixed_rate(dev, "fepllwcss2g", "xo", 0, 250000000);
+ clk_register_fixed_rate(dev, "fepllwcss5g", "xo", 0, 250000000);
clk_register_fixed_rate(dev, "fepll200", "xo", 0, 200000000);
- clk_register_fixed_rate(dev, "fepll500", "xo", 0, 200000000);
+ clk_register_fixed_rate(dev, "fepll500", "xo", 0, 500000000);
clk_register_fixed_rate(dev, "ddrpllapss", "xo", 0, 666000000);
return qcom_cc_probe(pdev, &gcc_ipq4019_desc);

View file

@ -0,0 +1,77 @@
From ce6cb947b9535b2d81717925cb6a3bc9f8500f44 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Thu, 17 Mar 2016 15:01:09 -0500
Subject: [PATCH 28/37] qcom: ipq4019: add cpu operating points for cpufreq
support
This adds some operating points for cpu frequeny scaling
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 34 ++++++++++++++++++++++++++--------
1 file changed, 26 insertions(+), 8 deletions(-)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -40,14 +40,7 @@
reg = <0x0>;
clocks = <&gcc GCC_APPS_CLK_SRC>;
clock-frequency = <0>;
- operating-points = <
- /* kHz uV (fixed) */
- 48000 1100000
- 200000 1100000
- 500000 1100000
- 666000 1100000
- >;
- clock-latency = <256000>;
+ operating-points-v2 = <&cpu0_opp_table>;
};
cpu@1 {
@@ -59,6 +52,7 @@
reg = <0x1>;
clocks = <&gcc GCC_APPS_CLK_SRC>;
clock-frequency = <0>;
+ operating-points-v2 = <&cpu0_opp_table>;
};
cpu@2 {
@@ -70,6 +64,7 @@
reg = <0x2>;
clocks = <&gcc GCC_APPS_CLK_SRC>;
clock-frequency = <0>;
+ operating-points-v2 = <&cpu0_opp_table>;
};
cpu@3 {
@@ -81,6 +76,29 @@
reg = <0x3>;
clocks = <&gcc GCC_APPS_CLK_SRC>;
clock-frequency = <0>;
+ operating-points-v2 = <&cpu0_opp_table>;
+ };
+ };
+
+ cpu0_opp_table: opp_table0 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp@48000000 {
+ opp-hz = /bits/ 64 <48000000>;
+ clock-latency-ns = <256000>;
+ };
+ opp@200000000 {
+ opp-hz = /bits/ 64 <200000000>;
+ clock-latency-ns = <256000>;
+ };
+ opp@500000000 {
+ opp-hz = /bits/ 64 <500000000>;
+ clock-latency-ns = <256000>;
+ };
+ opp@666000000 {
+ opp-hz = /bits/ 64 <666000000>;
+ clock-latency-ns = <256000>;
};
};

View file

@ -0,0 +1,23 @@
From f033e344b8a02e98beaf16c1eb188bc175219756 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Mon, 28 Mar 2016 11:16:51 -0500
Subject: [PATCH 29/37] qcom: ipq4019: turn on DMA for i2c
These are the required nodes for i2c-qup to use DMA
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 2 ++
1 file changed, 2 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -179,6 +179,8 @@
clock-names = "iface", "core";
#address-cells = <1>;
#size-cells = <0>;
+ dmas = <&blsp_dma 9>, <&blsp_dma 8>;
+ dma-names = "rx", "tx";
status = "disabled";
};

View file

@ -0,0 +1,28 @@
From 79f698655871f2eeceb1f9051e60d97bc434f52f Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@qca.qualcomm.com>
Date: Fri, 29 Apr 2016 12:48:02 -0500
Subject: [PATCH 30/37] qcom: ipq4019: use correct clock for i2c bus 0
For the record the mapping is as follows:
QUP0 = SPI QUP1
QUP1 = SPI QUP2
QUP2 = I2C QUP1
QUP3 = I2C QUP2
Signed-off-by: Matthew McClintock <mmcclint@qca.qualcomm.com>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -175,7 +175,7 @@
reg = <0x78b7000 0x6000>;
interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&gcc GCC_BLSP1_AHB_CLK>,
- <&gcc GCC_BLSP1_QUP2_I2C_APPS_CLK>;
+ <&gcc GCC_BLSP1_QUP1_I2C_APPS_CLK>;
clock-names = "iface", "core";
#address-cells = <1>;
#size-cells = <0>;

View file

@ -0,0 +1,23 @@
From 20249a0d746265a6663208fa768614784553edac Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Mon, 11 Apr 2016 14:49:12 -0500
Subject: [PATCH 31/37] qcom: ipq4019: enable DMA for spi
These are the required nodes for spi-qup to use DMA
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 2 ++
1 file changed, 2 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -167,6 +167,8 @@
clock-names = "core", "iface";
#address-cells = <1>;
#size-cells = <0>;
+ dmas = <&blsp_dma 5>, <&blsp_dma 4>;
+ dma-names = "rx", "tx";
status = "disabled";
};

View file

@ -0,0 +1,108 @@
From 4d7fe4171b01cbfc01e4a00f44b3e7f7a8013eb3 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Fri, 8 Apr 2016 15:26:10 -0500
Subject: [PATCH 32/37] qcom: ipq4019: use v2 of the kpss bringup mechanism
v1 was the incorrect choice here and sometimes the board would not come
up properly
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 32 ++++++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -34,7 +34,8 @@
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a7";
- enable-method = "qcom,kpss-acc-v1";
+ enable-method = "qcom,kpss-acc-v2";
+ next-level-cache = <&L2>;
qcom,acc = <&acc0>;
qcom,saw = <&saw0>;
reg = <0x0>;
@@ -46,7 +47,8 @@
cpu@1 {
device_type = "cpu";
compatible = "arm,cortex-a7";
- enable-method = "qcom,kpss-acc-v1";
+ enable-method = "qcom,kpss-acc-v2";
+ next-level-cache = <&L2>;
qcom,acc = <&acc1>;
qcom,saw = <&saw1>;
reg = <0x1>;
@@ -58,7 +60,8 @@
cpu@2 {
device_type = "cpu";
compatible = "arm,cortex-a7";
- enable-method = "qcom,kpss-acc-v1";
+ enable-method = "qcom,kpss-acc-v2";
+ next-level-cache = <&L2>;
qcom,acc = <&acc2>;
qcom,saw = <&saw2>;
reg = <0x2>;
@@ -70,7 +73,8 @@
cpu@3 {
device_type = "cpu";
compatible = "arm,cortex-a7";
- enable-method = "qcom,kpss-acc-v1";
+ enable-method = "qcom,kpss-acc-v2";
+ next-level-cache = <&L2>;
qcom,acc = <&acc3>;
qcom,saw = <&saw3>;
reg = <0x3>;
@@ -100,6 +104,12 @@
opp-hz = /bits/ 64 <666000000>;
clock-latency-ns = <256000>;
};
+
+ L2: l2-cache {
+ compatible = "qcom,arch-cache";
+ cache-level = <2>;
+ qcom,saw = <&saw_l2>;
+ };
};
pmu {
@@ -212,22 +222,22 @@
};
acc0: clock-controller@b088000 {
- compatible = "qcom,kpss-acc-v1";
+ compatible = "qcom,kpss-acc-v2";
reg = <0x0b088000 0x1000>, <0xb008000 0x1000>;
};
acc1: clock-controller@b098000 {
- compatible = "qcom,kpss-acc-v1";
+ compatible = "qcom,kpss-acc-v2";
reg = <0x0b098000 0x1000>, <0xb008000 0x1000>;
};
acc2: clock-controller@b0a8000 {
- compatible = "qcom,kpss-acc-v1";
+ compatible = "qcom,kpss-acc-v2";
reg = <0x0b0a8000 0x1000>, <0xb008000 0x1000>;
};
acc3: clock-controller@b0b8000 {
- compatible = "qcom,kpss-acc-v1";
+ compatible = "qcom,kpss-acc-v2";
reg = <0x0b0b8000 0x1000>, <0xb008000 0x1000>;
};
@@ -255,6 +265,12 @@
regulator;
};
+ saw_l2: regulator@b012000 {
+ compatible = "qcom,saw2";
+ reg = <0xb012000 0x1000>;
+ regulator;
+ };
+
serial@78af000 {
compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
reg = <0x78af000 0x200>;

View file

@ -0,0 +1,28 @@
From 25ee3761c072037b48246b992c3aec671c77a406 Mon Sep 17 00:00:00 2001
From: Thomas Pedersen <twp@codeaurora.org>
Date: Wed, 4 May 2016 12:25:41 -0700
Subject: [PATCH 33/37] dts: ipq4019: support ARMv7 PMU
Add support for cortex-a7-pmu present on ipq4019 SoCs.
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
Signed-off-by: Matthew McClintock <mmcclint@qca.qualcomm.com>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 6 ++++++
1 file changed, 6 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -118,6 +118,12 @@
IRQ_TYPE_LEVEL_HIGH)>;
};
+ pmu {
+ compatible = "arm,cortex-a7-pmu";
+ interrupts = <GIC_PPI 7 (GIC_CPU_MASK_SIMPLE(4) |
+ IRQ_TYPE_LEVEL_HIGH)>;
+ };
+
clocks {
sleep_clk: sleep_clk {
compatible = "fixed-clock";

View file

@ -0,0 +1,488 @@
From e8d6fd46f5f3c5860fa7fa98004de9bd97c0d869 Mon Sep 17 00:00:00 2001
From: Senthilkumar N L <snlakshm@codeaurora.org>
Date: Tue, 6 Jan 2015 12:52:23 +0530
Subject: [PATCH 34/37] qcom: ipq4019: Add IPQ4019 USB HS/SS PHY drivers
These drivers handles control and configuration of the HS
and SS USB PHY transceivers.
Signed-off-by: Senthilkumar N L <snlakshm@codeaurora.org>
---
drivers/usb/phy/Kconfig | 11 ++
drivers/usb/phy/Makefile | 2 +
drivers/usb/phy/phy-qca-baldur.c | 262 ++++++++++++++++++++++++++++++++++++++
drivers/usb/phy/phy-qca-uniphy.c | 171 +++++++++++++++++++++++++
4 files changed, 446 insertions(+)
create mode 100644 drivers/usb/phy/phy-qca-baldur.c
create mode 100644 drivers/usb/phy/phy-qca-uniphy.c
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -194,6 +194,17 @@ config USB_MXS_PHY
MXS Phy is used by some of the i.MX SoCs, for example imx23/28/6x.
+config USB_IPQ4019_PHY
+ tristate "IPQ4019 PHY wrappers support"
+ depends on (USB || USB_GADGET) && ARCH_QCOM
+ select USB_PHY
+ help
+ Enable this to support the USB PHY transceivers on QCA961x chips.
+ It handles PHY initialization, clock management required after
+ resetting the hardware and power management.
+ This driver is required even for peripheral only or host only
+ mode configurations.
+
config USB_ULPI
bool "Generic ULPI Transceiver Driver"
depends on ARM || ARM64
--- a/drivers/usb/phy/Makefile
+++ b/drivers/usb/phy/Makefile
@@ -21,6 +21,8 @@ obj-$(CONFIG_USB_GPIO_VBUS) += phy-gpio
obj-$(CONFIG_USB_ISP1301) += phy-isp1301.o
obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o
obj-$(CONFIG_USB_QCOM_8X16_PHY) += phy-qcom-8x16-usb.o
+obj-$(CONFIG_USB_IPQ4019_PHY) += phy-qca-baldur.o
+obj-$(CONFIG_USB_IPQ4019_PHY) += phy-qca-uniphy.o
obj-$(CONFIG_USB_MV_OTG) += phy-mv-usb.o
obj-$(CONFIG_USB_MXS_PHY) += phy-mxs-usb.o
obj-$(CONFIG_USB_ULPI) += phy-ulpi.o
--- /dev/null
+++ b/drivers/usb/phy/phy-qca-baldur.c
@@ -0,0 +1,262 @@
+/* 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.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/phy.h>
+#include <linux/reset.h>
+#include <linux/of_device.h>
+
+/**
+ * USB Hardware registers
+ */
+#define PHY_CTRL0_ADDR 0x000
+#define PHY_CTRL1_ADDR 0x004
+#define PHY_CTRL2_ADDR 0x008
+#define PHY_CTRL3_ADDR 0x00C
+#define PHY_CTRL4_ADDR 0x010
+#define PHY_MISC_ADDR 0x024
+#define PHY_IPG_ADDR 0x030
+
+#define PHY_CTRL0_EMU_ADDR 0x180
+#define PHY_CTRL1_EMU_ADDR 0x184
+#define PHY_CTRL2_EMU_ADDR 0x188
+#define PHY_CTRL3_EMU_ADDR 0x18C
+#define PHY_CTRL4_EMU_ADDR 0x190
+#define PHY_MISC_EMU_ADDR 0x1A4
+#define PHY_IPG_EMU_ADDR 0x1B0
+
+#define PHY_CTRL0_VAL 0xA4600015
+#define PHY_CTRL1_VAL 0x09500000
+#define PHY_CTRL2_VAL 0x00058180
+#define PHY_CTRL3_VAL 0x6DB6DCD6
+#define PHY_CTRL4_VAL 0x836DB6DB
+#define PHY_MISC_VAL 0x3803FB0C
+#define PHY_IPG_VAL 0x47323232
+
+#define PHY_CTRL0_EMU_VAL 0xb4000015
+#define PHY_CTRL1_EMU_VAL 0x09500000
+#define PHY_CTRL2_EMU_VAL 0x00058180
+#define PHY_CTRL3_EMU_VAL 0x6DB6DCD6
+#define PHY_CTRL4_EMU_VAL 0x836DB6DB
+#define PHY_MISC_EMU_VAL 0x3803FB0C
+#define PHY_IPG_EMU_VAL 0x47323232
+
+#define USB30_HS_PHY_HOST_MODE (0x01 << 21)
+#define USB20_HS_PHY_HOST_MODE (0x01 << 5)
+
+/* used to differentiate between USB3 HS and USB2 HS PHY */
+struct qca_baldur_hs_data {
+ unsigned int usb3_hs_phy;
+ unsigned int phy_config_offset;
+};
+
+struct qca_baldur_hs_phy {
+ struct device *dev;
+ struct usb_phy phy;
+
+ void __iomem *base;
+ void __iomem *qscratch_base;
+
+ struct reset_control *por_rst;
+ struct reset_control *srif_rst;
+
+ unsigned int host;
+ unsigned int emulation;
+ const struct qca_baldur_hs_data *data;
+};
+
+#define phy_to_dw_phy(x) container_of((x), struct qca_baldur_hs_phy, phy)
+
+static int qca_baldur_phy_read(struct usb_phy *x, u32 reg)
+{
+ struct qca_baldur_hs_phy *phy = phy_to_dw_phy(x);
+
+ return readl(phy->base + reg);
+}
+
+static int qca_baldur_phy_write(struct usb_phy *x, u32 val, u32 reg)
+{
+ struct qca_baldur_hs_phy *phy = phy_to_dw_phy(x);
+
+ writel(val, phy->base + reg);
+ return 0;
+}
+
+static int qca_baldur_hs_phy_init(struct usb_phy *x)
+{
+ struct qca_baldur_hs_phy *phy = phy_to_dw_phy(x);
+
+ /* assert HS PHY POR reset */
+ reset_control_assert(phy->por_rst);
+ msleep(10);
+
+ /* assert HS PHY SRIF reset */
+ reset_control_assert(phy->srif_rst);
+ msleep(10);
+
+ /* deassert HS PHY SRIF reset and program HS PHY registers */
+ reset_control_deassert(phy->srif_rst);
+ msleep(10);
+
+ if (!phy->emulation) {
+ /* perform PHY register writes */
+ writel(PHY_CTRL0_VAL, phy->base + PHY_CTRL0_ADDR);
+ writel(PHY_CTRL1_VAL, phy->base + PHY_CTRL1_ADDR);
+ writel(PHY_CTRL2_VAL, phy->base + PHY_CTRL2_ADDR);
+ writel(PHY_CTRL3_VAL, phy->base + PHY_CTRL3_ADDR);
+ writel(PHY_CTRL4_VAL, phy->base + PHY_CTRL4_ADDR);
+ writel(PHY_MISC_VAL, phy->base + PHY_MISC_ADDR);
+ writel(PHY_IPG_VAL, phy->base + PHY_IPG_ADDR);
+ } else {
+ /* perform PHY register writes */
+ writel(PHY_CTRL0_EMU_VAL, phy->base + PHY_CTRL0_EMU_ADDR);
+ writel(PHY_CTRL1_EMU_VAL, phy->base + PHY_CTRL1_EMU_ADDR);
+ writel(PHY_CTRL2_EMU_VAL, phy->base + PHY_CTRL2_EMU_ADDR);
+ writel(PHY_CTRL3_EMU_VAL, phy->base + PHY_CTRL3_EMU_ADDR);
+ writel(PHY_CTRL4_EMU_VAL, phy->base + PHY_CTRL4_EMU_ADDR);
+ writel(PHY_MISC_EMU_VAL, phy->base + PHY_MISC_EMU_ADDR);
+ writel(PHY_IPG_EMU_VAL, phy->base + PHY_IPG_EMU_ADDR);
+ }
+
+ msleep(10);
+
+ /* de-assert USB3 HS PHY POR reset */
+ reset_control_deassert(phy->por_rst);
+
+ return 0;
+}
+
+static int qca_baldur_hs_get_resources(struct qca_baldur_hs_phy *phy)
+{
+ struct platform_device *pdev = to_platform_device(phy->dev);
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->base = devm_ioremap_resource(phy->dev, res);
+ if (IS_ERR(phy->base))
+ return PTR_ERR(phy->base);
+
+ phy->por_rst = devm_reset_control_get(phy->dev, "por_rst");
+ if (IS_ERR(phy->por_rst))
+ return PTR_ERR(phy->por_rst);
+
+ phy->srif_rst = devm_reset_control_get(phy->dev, "srif_rst");
+ if (IS_ERR(phy->srif_rst))
+ return PTR_ERR(phy->srif_rst);
+
+ return 0;
+}
+
+static void qca_baldur_hs_put_resources(struct qca_baldur_hs_phy *phy)
+{
+ reset_control_assert(phy->srif_rst);
+ reset_control_assert(phy->por_rst);
+}
+
+static int qca_baldur_hs_remove(struct platform_device *pdev)
+{
+ struct qca_baldur_hs_phy *phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&phy->phy);
+ return 0;
+}
+
+static void qca_baldur_hs_phy_shutdown(struct usb_phy *x)
+{
+ struct qca_baldur_hs_phy *phy = phy_to_dw_phy(x);
+
+ qca_baldur_hs_put_resources(phy);
+}
+
+static struct usb_phy_io_ops qca_baldur_io_ops = {
+ .read = qca_baldur_phy_read,
+ .write = qca_baldur_phy_write,
+};
+
+static const struct qca_baldur_hs_data usb3_hs_data = {
+ .usb3_hs_phy = 1,
+ .phy_config_offset = USB30_HS_PHY_HOST_MODE,
+};
+
+static const struct qca_baldur_hs_data usb2_hs_data = {
+ .usb3_hs_phy = 0,
+ .phy_config_offset = USB20_HS_PHY_HOST_MODE,
+};
+
+static const struct of_device_id qca_baldur_hs_id_table[] = {
+ { .compatible = "qca,baldur-usb3-hsphy", .data = &usb3_hs_data },
+ { .compatible = "qca,baldur-usb2-hsphy", .data = &usb2_hs_data },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, qca_baldur_hs_id_table);
+
+static int qca_baldur_hs_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct qca_baldur_hs_phy *phy;
+ int err;
+
+ match = of_match_device(qca_baldur_hs_id_table, &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, phy);
+ phy->dev = &pdev->dev;
+
+ phy->data = match->data;
+
+ err = qca_baldur_hs_get_resources(phy);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed to request resources: %d\n", err);
+ return err;
+ }
+
+ phy->phy.dev = phy->dev;
+ phy->phy.label = "qca-baldur-hsphy";
+ phy->phy.init = qca_baldur_hs_phy_init;
+ phy->phy.shutdown = qca_baldur_hs_phy_shutdown;
+ phy->phy.type = USB_PHY_TYPE_USB2;
+ phy->phy.io_ops = &qca_baldur_io_ops;
+
+ err = usb_add_phy_dev(&phy->phy);
+ return err;
+}
+
+static struct platform_driver qca_baldur_hs_driver = {
+ .probe = qca_baldur_hs_probe,
+ .remove = qca_baldur_hs_remove,
+ .driver = {
+ .name = "qca-baldur-hsphy",
+ .owner = THIS_MODULE,
+ .of_match_table = qca_baldur_hs_id_table,
+ },
+};
+
+module_platform_driver(qca_baldur_hs_driver);
+
+MODULE_ALIAS("platform:qca-baldur-hsphy");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("USB3 QCA BALDUR HSPHY driver");
--- /dev/null
+++ b/drivers/usb/phy/phy-qca-uniphy.c
@@ -0,0 +1,171 @@
+/* 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.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/usb/phy.h>
+#include <linux/reset.h>
+#include <linux/of_device.h>
+
+struct qca_uni_ss_phy {
+ struct usb_phy phy;
+ struct device *dev;
+
+ void __iomem *base;
+
+ struct reset_control *por_rst;
+
+ unsigned int host;
+};
+
+#define phy_to_dw_phy(x) container_of((x), struct qca_uni_ss_phy, phy)
+
+/**
+ * Write register
+ *
+ * @base - PHY base virtual address.
+ * @offset - register offset.
+ */
+static u32 qca_uni_ss_read(void __iomem *base, u32 offset)
+{
+ u32 value;
+ value = readl_relaxed(base + offset);
+ return value;
+}
+
+/**
+ * Write register
+ *
+ * @base - PHY base virtual address.
+ * @offset - register offset.
+ * @val - value to write.
+ */
+static void qca_uni_ss_write(void __iomem *base, u32 offset, u32 val)
+{
+ writel(val, base + offset);
+ udelay(100);
+}
+
+static void qca_uni_ss_phy_shutdown(struct usb_phy *x)
+{
+ struct qca_uni_ss_phy *phy = phy_to_dw_phy(x);
+
+ /* assert SS PHY POR reset */
+ reset_control_assert(phy->por_rst);
+}
+
+static int qca_uni_ss_phy_init(struct usb_phy *x)
+{
+ struct qca_uni_ss_phy *phy = phy_to_dw_phy(x);
+
+ /* assert SS PHY POR reset */
+ reset_control_assert(phy->por_rst);
+
+ msleep(10);
+
+ msleep(10);
+
+ /* deassert SS PHY POR reset */
+ reset_control_deassert(phy->por_rst);
+
+ return 0;
+}
+
+static int qca_uni_ss_get_resources(struct platform_device *pdev,
+ struct qca_uni_ss_phy *phy)
+{
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ phy->base = devm_ioremap_resource(phy->dev, res);
+ if (IS_ERR(phy->base))
+ return PTR_ERR(phy->base);
+
+ phy->por_rst = devm_reset_control_get(phy->dev, "por_rst");
+ if (IS_ERR(phy->por_rst))
+ return PTR_ERR(phy->por_rst);
+
+ return 0;
+}
+
+static int qca_uni_ss_remove(struct platform_device *pdev)
+{
+ struct qca_uni_ss_phy *phy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&phy->phy);
+ return 0;
+}
+
+static const struct of_device_id qca_uni_ss_id_table[] = {
+ { .compatible = "qca,uni-ssphy" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, qca_uni_ss_id_table);
+
+static int qca_uni_ss_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct device_node *np = pdev->dev.of_node;
+ struct qca_uni_ss_phy *phy;
+ int ret;
+
+ match = of_match_device(qca_uni_ss_id_table, &pdev->dev);
+ if (!match)
+ return -ENODEV;
+
+ phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
+ if (!phy)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, phy);
+ phy->dev = &pdev->dev;
+
+ ret = qca_uni_ss_get_resources(pdev, phy);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request resources: %d\n", ret);
+ return ret;
+ }
+
+ phy->phy.dev = phy->dev;
+ phy->phy.label = "qca-uni-ssphy";
+ phy->phy.init = qca_uni_ss_phy_init;
+ phy->phy.shutdown = qca_uni_ss_phy_shutdown;
+ phy->phy.type = USB_PHY_TYPE_USB3;
+
+ ret = usb_add_phy_dev(&phy->phy);
+ return ret;
+}
+
+static struct platform_driver qca_uni_ss_driver = {
+ .probe = qca_uni_ss_probe,
+ .remove = qca_uni_ss_remove,
+ .driver = {
+ .name = "qca-uni-ssphy",
+ .owner = THIS_MODULE,
+ .of_match_table = qca_uni_ss_id_table,
+ },
+};
+
+module_platform_driver(qca_uni_ss_driver);
+
+MODULE_ALIAS("platform:qca-uni-ssphy");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("USB3 QCA UNI SSPHY driver");

View file

@ -0,0 +1,88 @@
From d2ed553484fecdf02fa53bf431599412348afa95 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Thu, 17 Mar 2016 16:22:28 -0500
Subject: [PATCH 35/37] qcom: ipq4019: add USB nodes to ipq4019 SoC device
tree
This adds the SoC nodes to the ipq4019 device tree
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq4019.dtsi | 67 +++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq4019.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019.dtsi
@@ -313,5 +313,72 @@
compatible = "qcom,pshold";
reg = <0x4ab000 0x4>;
};
+
+ usb3_ss_phy: ssphy@9a000 {
+ compatible = "qca,uni-ssphy";
+ reg = <0x9a000 0x800>;
+ reg-names = "phy_base";
+ resets = <&gcc USB3_UNIPHY_PHY_ARES>;
+ reset-names = "por_rst";
+ status = "disabled";
+ };
+
+ usb3_hs_phy: hsphy@a6000 {
+ compatible = "qca,baldur-usb3-hsphy";
+ reg = <0xa6000 0x40>;
+ reg-names = "phy_base";
+ resets = <&gcc USB3_HSPHY_POR_ARES>, <&gcc USB3_HSPHY_S_ARES>;
+ reset-names = "por_rst", "srif_rst";
+ status = "disabled";
+ };
+
+ usb3@0 {
+ compatible = "qcom,dwc3";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ clocks = <&gcc GCC_USB3_MASTER_CLK>;
+ clock-names = "core";
+ ranges;
+ status = "disabled";
+
+ dwc3@8a00000 {
+ compatible = "snps,dwc3";
+ reg = <0x8a00000 0xf8000>;
+ interrupts = <0 132 0>;
+ usb-phy = <&usb3_hs_phy>, <&usb3_ss_phy>;
+ phy-names = "usb2-phy", "usb3-phy";
+ tx-fifo-resize;
+ dr_mode = "host";
+ };
+ };
+
+ usb2_hs_phy: hsphy@a8000 {
+ compatible = "qca,baldur-usb2-hsphy";
+ reg = <0xa8000 0x40>;
+ reg-names = "phy_base";
+ resets = <&gcc USB2_HSPHY_POR_ARES>, <&gcc USB2_HSPHY_S_ARES>;
+ reset-names = "por_rst", "srif_rst";
+ status = "disabled";
+ };
+
+ usb2@0 {
+ compatible = "qcom,dwc3";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ clocks = <&gcc GCC_USB2_MASTER_CLK>;
+ clock-names = "core";
+ ranges;
+ status = "disabled";
+
+ dwc3@6000000 {
+ compatible = "snps,dwc3";
+ reg = <0x6000000 0xf8000>;
+ interrupts = <0 136 0>;
+ usb-phy = <&usb2_hs_phy>;
+ phy-names = "usb2-phy";
+ tx-fifo-resize;
+ dr_mode = "host";
+ };
+ };
};
};

View file

@ -0,0 +1,45 @@
From c15caf58ff5c3b4093fa388b9c6228bb3c9c5413 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Mon, 21 Mar 2016 16:12:05 -0500
Subject: [PATCH 36/37] qcom: ipq4019: enable USB bus for DK01.1 board
This enables the USB block
Change-Id: I384dd1874bba341713f384cf6199abd446e3f3c0
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/qcom-ipq4019-ap.dk01.1.dtsi | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
--- a/arch/arm/boot/dts/qcom-ipq4019-ap.dk01.1.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019-ap.dk01.1.dtsi
@@ -108,5 +108,29 @@
watchdog@b017000 {
status = "ok";
};
+
+ usb3_ss_phy: ssphy@0 {
+ status = "ok";
+ };
+
+ dummy_ss_phy: ssphy@1 {
+ status = "ok";
+ };
+
+ usb3_hs_phy: hsphy@a6000 {
+ status = "ok";
+ };
+
+ usb2_hs_phy: hsphy@a8000 {
+ status = "ok";
+ };
+
+ usb3: usb3@8a00000 {
+ status = "ok";
+ };
+
+ usb2: usb2@6000000 {
+ status = "ok";
+ };
};
};

View file

@ -0,0 +1,258 @@
From ff1ecc5bfc11377e82894d05aa45a92657ef8a06 Mon Sep 17 00:00:00 2001
From: Matthew McClintock <mmcclint@codeaurora.org>
Date: Mon, 21 Mar 2016 15:55:21 -0500
Subject: [PATCH 37/37] dts: ipq4019: Add support for IPQ4019 DK04 board
This is pretty similiar to a DK01 but has a bit more IO. Some notable
differences are listed below however they are not in the device tree yet
as we continue adding more support
- second serial port
- PCIe
- NAND
- SD/EMMC
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
---
arch/arm/boot/dts/Makefile | 1 +
arch/arm/boot/dts/qcom-ipq4019-ap.dk01.1.dtsi | 12 +-
arch/arm/boot/dts/qcom-ipq4019-ap.dk04.1-c1.dts | 21 +++
arch/arm/boot/dts/qcom-ipq4019-ap.dk04.1.dtsi | 163 +++++++++++++++++++++++
4 files changed, 189 insertions(+), 8 deletions(-)
create mode 100644 arch/arm/boot/dts/qcom-ipq4019-ap.dk04.1-c1.dts
create mode 100644 arch/arm/boot/dts/qcom-ipq4019-ap.dk04.1.dtsi
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -617,6 +617,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \
qcom-apq8084-ifc6540.dtb \
qcom-apq8084-mtp.dtb \
qcom-ipq4019-ap.dk01.1-c1.dtb \
+ qcom-ipq4019-ap.dk04.1-c1.dtb \
qcom-ipq8064-ap148.dtb \
qcom-msm8660-surf.dtb \
qcom-msm8960-cdp.dtb \
--- a/arch/arm/boot/dts/qcom-ipq4019-ap.dk01.1.dtsi
+++ b/arch/arm/boot/dts/qcom-ipq4019-ap.dk01.1.dtsi
@@ -109,11 +109,7 @@
status = "ok";
};
- usb3_ss_phy: ssphy@0 {
- status = "ok";
- };
-
- dummy_ss_phy: ssphy@1 {
+ usb3_ss_phy: ssphy@9a000 {
status = "ok";
};
@@ -121,15 +117,15 @@
status = "ok";
};
- usb2_hs_phy: hsphy@a8000 {
+ usb3@0 {
status = "ok";
};
- usb3: usb3@8a00000 {
+ usb2_hs_phy: hsphy@a8000 {
status = "ok";
};
- usb2: usb2@6000000 {
+ usb2@0{
status = "ok";
};
};
--- /dev/null
+++ b/arch/arm/boot/dts/qcom-ipq4019-ap.dk04.1-c1.dts
@@ -0,0 +1,21 @@
+/* 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.
+ *
+ */
+
+#include "qcom-ipq4019-ap.dk04.1.dtsi"
+
+/ {
+ model = "Qualcomm Technologies, Inc. IPQ40xx/AP-DK04.1-C1";
+};
--- /dev/null
+++ b/arch/arm/boot/dts/qcom-ipq4019-ap.dk04.1.dtsi
@@ -0,0 +1,163 @@
+/* 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.
+ *
+ */
+
+#include "qcom-ipq4019.dtsi"
+
+/ {
+ model = "Qualcomm Technologies, Inc. IPQ4019/AP-DK04.1";
+ compatible = "qcom,ipq4019";
+
+ clocks {
+ xo: xo {
+ compatible = "fixed-clock";
+ clock-frequency = <48000000>;
+ #clock-cells = <0>;
+ };
+ };
+
+ soc {
+ timer {
+ compatible = "arm,armv7-timer";
+ interrupts = <1 2 0xf08>,
+ <1 3 0xf08>,
+ <1 4 0xf08>,
+ <1 1 0xf08>;
+ clock-frequency = <48000000>;
+ };
+
+ pinctrl@0x01000000 {
+ serial_0_pins: serial_pinmux {
+ mux {
+ pins = "gpio16", "gpio17";
+ function = "blsp_uart0";
+ bias-disable;
+ };
+ };
+
+ serial_1_pins: serial1_pinmux {
+ mux {
+ pins = "gpio8", "gpio9";
+ function = "blsp_uart1";
+ bias-disable;
+ };
+ };
+
+ spi_0_pins: spi_0_pinmux {
+ pinmux {
+ function = "blsp_spi0";
+ pins = "gpio13", "gpio14", "gpio15";
+ };
+ pinmux_cs {
+ function = "gpio";
+ pins = "gpio12";
+ };
+ pinconf {
+ pins = "gpio13", "gpio14", "gpio15";
+ drive-strength = <12>;
+ bias-disable;
+ };
+ pinconf_cs {
+ pins = "gpio12";
+ drive-strength = <2>;
+ bias-disable;
+ output-high;
+ };
+ };
+
+ i2c_0_pins: i2c_0_pinmux {
+ pinmux {
+ function = "blsp_i2c0";
+ pins = "gpio10", "gpio11";
+ };
+ pinconf {
+ pins = "gpio10", "gpio11";
+ drive-strength = <16>;
+ bias-disable;
+ };
+ };
+ };
+
+ blsp_dma: dma@7884000 {
+ status = "ok";
+ };
+
+ spi_0: spi@78b5000 {
+ pinctrl-0 = <&spi_0_pins>;
+ pinctrl-names = "default";
+ status = "ok";
+ cs-gpios = <&tlmm 12 0>;
+
+ mx25l25635e@0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ reg = <0>;
+ compatible = "mx25l25635e";
+ spi-max-frequency = <24000000>;
+ };
+ };
+
+ i2c_0: i2c@78b7000 { /* BLSP1 QUP2 */
+ pinctrl-0 = <&i2c_0_pins>;
+ pinctrl-names = "default";
+
+ status = "ok";
+ };
+
+ serial@78af000 {
+ pinctrl-0 = <&serial_0_pins>;
+ pinctrl-names = "default";
+ status = "ok";
+ };
+
+ serial@78b0000 {
+ pinctrl-0 = <&serial_1_pins>;
+ pinctrl-names = "default";
+ status = "ok";
+ };
+
+ usb3_ss_phy: ssphy@9a000 {
+ status = "ok";
+ };
+
+ usb3_hs_phy: hsphy@a6000 {
+ status = "ok";
+ };
+
+ usb3: usb3@0 {
+ status = "ok";
+ };
+
+ usb2_hs_phy: hsphy@a8000 {
+ status = "ok";
+ };
+
+ usb2: usb2@6000000 {
+ status = "ok";
+ };
+
+ cryptobam: dma@8e04000 {
+ status = "ok";
+ };
+
+ crypto@8e3a000 {
+ status = "ok";
+ };
+
+ watchdog@b017000 {
+ status = "ok";
+ };
+ };
+};

View file

@ -0,0 +1,746 @@
From patchwork Wed Nov 2 15:56:56 2016
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: [v9,1/3] clk: qcom: Add support for SMD-RPM Clocks
From: Georgi Djakov <georgi.djakov@linaro.org>
X-Patchwork-Id: 9409419
Message-Id: <20161102155658.32203-2-georgi.djakov@linaro.org>
To: sboyd@codeaurora.org, mturquette@baylibre.com
Cc: linux-clk@vger.kernel.org, devicetree@vger.kernel.org,
robh+dt@kernel.org, mark.rutland@arm.com,
linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
georgi.djakov@linaro.org
Date: Wed, 2 Nov 2016 17:56:56 +0200
This adds initial support for clocks controlled by the Resource
Power Manager (RPM) processor on some Qualcomm SoCs, which use
the qcom_smd_rpm driver to communicate with RPM.
Such platforms are msm8916, apq8084 and msm8974.
The RPM is a dedicated hardware engine for managing the shared
SoC resources in order to keep the lowest power profile. It
communicates with other hardware subsystems via shared memory
and accepts clock requests, aggregates the requests and turns
the clocks on/off or scales them on demand.
This driver is based on the codeaurora.org driver:
https://www.codeaurora.org/cgit/quic/la/kernel/msm-3.10/tree/drivers/clk/qcom/clock-rpm.c
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
---
.../devicetree/bindings/clock/qcom,rpmcc.txt | 36 ++
drivers/clk/qcom/Kconfig | 16 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/clk-smd-rpm.c | 571 +++++++++++++++++++++
include/dt-bindings/clock/qcom,rpmcc.h | 45 ++
5 files changed, 669 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/qcom,rpmcc.txt
create mode 100644 drivers/clk/qcom/clk-smd-rpm.c
create mode 100644 include/dt-bindings/clock/qcom,rpmcc.h
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt
@@ -0,0 +1,36 @@
+Qualcomm RPM Clock Controller Binding
+------------------------------------------------
+The RPM is a dedicated hardware engine for managing the shared
+SoC resources in order to keep the lowest power profile. It
+communicates with other hardware subsystems via shared memory
+and accepts clock requests, aggregates the requests and turns
+the clocks on/off or scales them on demand.
+
+Required properties :
+- compatible : shall contain only one of the following. The generic
+ compatible "qcom,rpmcc" should be also included.
+
+ "qcom,rpmcc-msm8916", "qcom,rpmcc"
+
+- #clock-cells : shall contain 1
+
+Example:
+ smd {
+ compatible = "qcom,smd";
+
+ rpm {
+ interrupts = <0 168 1>;
+ qcom,ipc = <&apcs 8 0>;
+ qcom,smd-edge = <15>;
+
+ rpm_requests {
+ compatible = "qcom,rpm-msm8916";
+ qcom,smd-channels = "rpm_requests";
+
+ rpmcc: clock-controller {
+ compatible = "qcom,rpmcc-msm8916", "qcom,rpmcc";
+ #clock-cells = <1>;
+ };
+ };
+ };
+ };
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -2,6 +2,9 @@
bool
select PM_GENERIC_DOMAINS if PM
+config QCOM_RPMCC
+ bool
+
config COMMON_CLK_QCOM
tristate "Support for Qualcomm's clock controllers"
depends on OF
@@ -9,6 +12,19 @@
select REGMAP_MMIO
select RESET_CONTROLLER
+config QCOM_CLK_SMD_RPM
+ tristate "RPM over SMD based Clock Controller"
+ depends on COMMON_CLK_QCOM && QCOM_SMD_RPM
+ select QCOM_RPMCC
+ help
+ The RPM (Resource Power Manager) is a dedicated hardware engine for
+ managing the shared SoC resources in order to keep the lowest power
+ profile. It communicates with other hardware subsystems via shared
+ memory and accepts clock requests, aggregates the requests and turns
+ the clocks on/off or scales them on demand.
+ Say Y if you want to support the clocks exposed by the RPM on
+ platforms such as apq8016, apq8084, msm8974 etc.
+
config APQ_GCC_8084
tristate "APQ8084 Global Clock Controller"
select QCOM_GDSC
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -29,3 +29,4 @@
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
+obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o
--- /dev/null
+++ b/drivers/clk/qcom/clk-smd-rpm.c
@@ -0,0 +1,571 @@
+/*
+ * Copyright (c) 2016, Linaro Limited
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/soc/qcom/smd-rpm.h>
+
+#include <dt-bindings/clock/qcom,rpmcc.h>
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+#define QCOM_RPM_KEY_SOFTWARE_ENABLE 0x6e657773
+#define QCOM_RPM_KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY 0x62636370
+#define QCOM_RPM_SMD_KEY_RATE 0x007a484b
+#define QCOM_RPM_SMD_KEY_ENABLE 0x62616e45
+#define QCOM_RPM_SMD_KEY_STATE 0x54415453
+#define QCOM_RPM_SCALING_ENABLE_ID 0x2
+
+#define __DEFINE_CLK_SMD_RPM(_platform, _name, _active, type, r_id, stat_id, \
+ key) \
+ static struct clk_smd_rpm _platform##_##_active; \
+ static struct clk_smd_rpm _platform##_##_name = { \
+ .rpm_res_type = (type), \
+ .rpm_clk_id = (r_id), \
+ .rpm_status_id = (stat_id), \
+ .rpm_key = (key), \
+ .peer = &_platform##_##_active, \
+ .rate = INT_MAX, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_smd_rpm_ops, \
+ .name = #_name, \
+ .parent_names = (const char *[]){ "xo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }; \
+ static struct clk_smd_rpm _platform##_##_active = { \
+ .rpm_res_type = (type), \
+ .rpm_clk_id = (r_id), \
+ .rpm_status_id = (stat_id), \
+ .active_only = true, \
+ .rpm_key = (key), \
+ .peer = &_platform##_##_name, \
+ .rate = INT_MAX, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_smd_rpm_ops, \
+ .name = #_active, \
+ .parent_names = (const char *[]){ "xo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }
+
+#define __DEFINE_CLK_SMD_RPM_BRANCH(_platform, _name, _active, type, r_id, \
+ stat_id, r, key) \
+ static struct clk_smd_rpm _platform##_##_active; \
+ static struct clk_smd_rpm _platform##_##_name = { \
+ .rpm_res_type = (type), \
+ .rpm_clk_id = (r_id), \
+ .rpm_status_id = (stat_id), \
+ .rpm_key = (key), \
+ .branch = true, \
+ .peer = &_platform##_##_active, \
+ .rate = (r), \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_smd_rpm_branch_ops, \
+ .name = #_name, \
+ .parent_names = (const char *[]){ "xo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }; \
+ static struct clk_smd_rpm _platform##_##_active = { \
+ .rpm_res_type = (type), \
+ .rpm_clk_id = (r_id), \
+ .rpm_status_id = (stat_id), \
+ .active_only = true, \
+ .rpm_key = (key), \
+ .branch = true, \
+ .peer = &_platform##_##_name, \
+ .rate = (r), \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_smd_rpm_branch_ops, \
+ .name = #_active, \
+ .parent_names = (const char *[]){ "xo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }
+
+#define DEFINE_CLK_SMD_RPM(_platform, _name, _active, type, r_id) \
+ __DEFINE_CLK_SMD_RPM(_platform, _name, _active, type, r_id, \
+ 0, QCOM_RPM_SMD_KEY_RATE)
+
+#define DEFINE_CLK_SMD_RPM_BRANCH(_platform, _name, _active, type, r_id, r) \
+ __DEFINE_CLK_SMD_RPM_BRANCH(_platform, _name, _active, type, \
+ r_id, 0, r, QCOM_RPM_SMD_KEY_ENABLE)
+
+#define DEFINE_CLK_SMD_RPM_QDSS(_platform, _name, _active, type, r_id) \
+ __DEFINE_CLK_SMD_RPM(_platform, _name, _active, type, r_id, \
+ 0, QCOM_RPM_SMD_KEY_STATE)
+
+#define DEFINE_CLK_SMD_RPM_XO_BUFFER(_platform, _name, _active, r_id) \
+ __DEFINE_CLK_SMD_RPM_BRANCH(_platform, _name, _active, \
+ QCOM_SMD_RPM_CLK_BUF_A, r_id, 0, 1000, \
+ QCOM_RPM_KEY_SOFTWARE_ENABLE)
+
+#define DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(_platform, _name, _active, r_id) \
+ __DEFINE_CLK_SMD_RPM_BRANCH(_platform, _name, _active, \
+ QCOM_SMD_RPM_CLK_BUF_A, r_id, 0, 1000, \
+ QCOM_RPM_KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY)
+
+#define to_clk_smd_rpm(_hw) container_of(_hw, struct clk_smd_rpm, hw)
+
+struct clk_smd_rpm {
+ const int rpm_res_type;
+ const int rpm_key;
+ const int rpm_clk_id;
+ const int rpm_status_id;
+ const bool active_only;
+ bool enabled;
+ bool branch;
+ struct clk_smd_rpm *peer;
+ struct clk_hw hw;
+ unsigned long rate;
+ struct qcom_smd_rpm *rpm;
+};
+
+struct clk_smd_rpm_req {
+ __le32 key;
+ __le32 nbytes;
+ __le32 value;
+};
+
+struct rpm_cc {
+ struct qcom_rpm *rpm;
+ struct clk_hw_onecell_data data;
+ struct clk_hw *hws[];
+};
+
+struct rpm_smd_clk_desc {
+ struct clk_smd_rpm **clks;
+ size_t num_clks;
+};
+
+static DEFINE_MUTEX(rpm_smd_clk_lock);
+
+static int clk_smd_rpm_handoff(struct clk_smd_rpm *r)
+{
+ int ret;
+ struct clk_smd_rpm_req req = {
+ .key = cpu_to_le32(r->rpm_key),
+ .nbytes = cpu_to_le32(sizeof(u32)),
+ .value = cpu_to_le32(INT_MAX),
+ };
+
+ ret = qcom_rpm_smd_write(r->rpm, QCOM_SMD_RPM_ACTIVE_STATE,
+ r->rpm_res_type, r->rpm_clk_id, &req,
+ sizeof(req));
+ if (ret)
+ return ret;
+ ret = qcom_rpm_smd_write(r->rpm, QCOM_SMD_RPM_SLEEP_STATE,
+ r->rpm_res_type, r->rpm_clk_id, &req,
+ sizeof(req));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int clk_smd_rpm_set_rate_active(struct clk_smd_rpm *r,
+ unsigned long rate)
+{
+ struct clk_smd_rpm_req req = {
+ .key = cpu_to_le32(r->rpm_key),
+ .nbytes = cpu_to_le32(sizeof(u32)),
+ .value = cpu_to_le32(DIV_ROUND_UP(rate, 1000)), /* to kHz */
+ };
+
+ return qcom_rpm_smd_write(r->rpm, QCOM_SMD_RPM_ACTIVE_STATE,
+ r->rpm_res_type, r->rpm_clk_id, &req,
+ sizeof(req));
+}
+
+static int clk_smd_rpm_set_rate_sleep(struct clk_smd_rpm *r,
+ unsigned long rate)
+{
+ struct clk_smd_rpm_req req = {
+ .key = cpu_to_le32(r->rpm_key),
+ .nbytes = cpu_to_le32(sizeof(u32)),
+ .value = cpu_to_le32(DIV_ROUND_UP(rate, 1000)), /* to kHz */
+ };
+
+ return qcom_rpm_smd_write(r->rpm, QCOM_SMD_RPM_SLEEP_STATE,
+ r->rpm_res_type, r->rpm_clk_id, &req,
+ sizeof(req));
+}
+
+static void to_active_sleep(struct clk_smd_rpm *r, unsigned long rate,
+ unsigned long *active, unsigned long *sleep)
+{
+ *active = rate;
+
+ /*
+ * Active-only clocks don't care what the rate is during sleep. So,
+ * they vote for zero.
+ */
+ if (r->active_only)
+ *sleep = 0;
+ else
+ *sleep = *active;
+}
+
+static int clk_smd_rpm_prepare(struct clk_hw *hw)
+{
+ struct clk_smd_rpm *r = to_clk_smd_rpm(hw);
+ struct clk_smd_rpm *peer = r->peer;
+ unsigned long this_rate = 0, this_sleep_rate = 0;
+ unsigned long peer_rate = 0, peer_sleep_rate = 0;
+ unsigned long active_rate, sleep_rate;
+ int ret = 0;
+
+ mutex_lock(&rpm_smd_clk_lock);
+
+ /* Don't send requests to the RPM if the rate has not been set. */
+ if (!r->rate)
+ goto out;
+
+ to_active_sleep(r, r->rate, &this_rate, &this_sleep_rate);
+
+ /* Take peer clock's rate into account only if it's enabled. */
+ if (peer->enabled)
+ to_active_sleep(peer, peer->rate,
+ &peer_rate, &peer_sleep_rate);
+
+ active_rate = max(this_rate, peer_rate);
+
+ if (r->branch)
+ active_rate = !!active_rate;
+
+ ret = clk_smd_rpm_set_rate_active(r, active_rate);
+ if (ret)
+ goto out;
+
+ sleep_rate = max(this_sleep_rate, peer_sleep_rate);
+ if (r->branch)
+ sleep_rate = !!sleep_rate;
+
+ ret = clk_smd_rpm_set_rate_sleep(r, sleep_rate);
+ if (ret)
+ /* Undo the active set vote and restore it */
+ ret = clk_smd_rpm_set_rate_active(r, peer_rate);
+
+out:
+ if (!ret)
+ r->enabled = true;
+
+ mutex_unlock(&rpm_smd_clk_lock);
+
+ return ret;
+}
+
+static void clk_smd_rpm_unprepare(struct clk_hw *hw)
+{
+ struct clk_smd_rpm *r = to_clk_smd_rpm(hw);
+ struct clk_smd_rpm *peer = r->peer;
+ unsigned long peer_rate = 0, peer_sleep_rate = 0;
+ unsigned long active_rate, sleep_rate;
+ int ret;
+
+ mutex_lock(&rpm_smd_clk_lock);
+
+ if (!r->rate)
+ goto out;
+
+ /* Take peer clock's rate into account only if it's enabled. */
+ if (peer->enabled)
+ to_active_sleep(peer, peer->rate, &peer_rate,
+ &peer_sleep_rate);
+
+ active_rate = r->branch ? !!peer_rate : peer_rate;
+ ret = clk_smd_rpm_set_rate_active(r, active_rate);
+ if (ret)
+ goto out;
+
+ sleep_rate = r->branch ? !!peer_sleep_rate : peer_sleep_rate;
+ ret = clk_smd_rpm_set_rate_sleep(r, sleep_rate);
+ if (ret)
+ goto out;
+
+ r->enabled = false;
+
+out:
+ mutex_unlock(&rpm_smd_clk_lock);
+}
+
+static int clk_smd_rpm_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_smd_rpm *r = to_clk_smd_rpm(hw);
+ struct clk_smd_rpm *peer = r->peer;
+ unsigned long active_rate, sleep_rate;
+ unsigned long this_rate = 0, this_sleep_rate = 0;
+ unsigned long peer_rate = 0, peer_sleep_rate = 0;
+ int ret = 0;
+
+ mutex_lock(&rpm_smd_clk_lock);
+
+ if (!r->enabled)
+ goto out;
+
+ to_active_sleep(r, rate, &this_rate, &this_sleep_rate);
+
+ /* Take peer clock's rate into account only if it's enabled. */
+ if (peer->enabled)
+ to_active_sleep(peer, peer->rate,
+ &peer_rate, &peer_sleep_rate);
+
+ active_rate = max(this_rate, peer_rate);
+ ret = clk_smd_rpm_set_rate_active(r, active_rate);
+ if (ret)
+ goto out;
+
+ sleep_rate = max(this_sleep_rate, peer_sleep_rate);
+ ret = clk_smd_rpm_set_rate_sleep(r, sleep_rate);
+ if (ret)
+ goto out;
+
+ r->rate = rate;
+
+out:
+ mutex_unlock(&rpm_smd_clk_lock);
+
+ return ret;
+}
+
+static long clk_smd_rpm_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ /*
+ * RPM handles rate rounding and we don't have a way to
+ * know what the rate will be, so just return whatever
+ * rate is requested.
+ */
+ return rate;
+}
+
+static unsigned long clk_smd_rpm_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_smd_rpm *r = to_clk_smd_rpm(hw);
+
+ /*
+ * RPM handles rate rounding and we don't have a way to
+ * know what the rate will be, so just return whatever
+ * rate was set.
+ */
+ return r->rate;
+}
+
+static int clk_smd_rpm_enable_scaling(struct qcom_smd_rpm *rpm)
+{
+ int ret;
+ struct clk_smd_rpm_req req = {
+ .key = cpu_to_le32(QCOM_RPM_SMD_KEY_ENABLE),
+ .nbytes = cpu_to_le32(sizeof(u32)),
+ .value = cpu_to_le32(1),
+ };
+
+ ret = qcom_rpm_smd_write(rpm, QCOM_SMD_RPM_SLEEP_STATE,
+ QCOM_SMD_RPM_MISC_CLK,
+ QCOM_RPM_SCALING_ENABLE_ID, &req, sizeof(req));
+ if (ret) {
+ pr_err("RPM clock scaling (sleep set) not enabled!\n");
+ return ret;
+ }
+
+ ret = qcom_rpm_smd_write(rpm, QCOM_SMD_RPM_ACTIVE_STATE,
+ QCOM_SMD_RPM_MISC_CLK,
+ QCOM_RPM_SCALING_ENABLE_ID, &req, sizeof(req));
+ if (ret) {
+ pr_err("RPM clock scaling (active set) not enabled!\n");
+ return ret;
+ }
+
+ pr_debug("%s: RPM clock scaling is enabled\n", __func__);
+ return 0;
+}
+
+static const struct clk_ops clk_smd_rpm_ops = {
+ .prepare = clk_smd_rpm_prepare,
+ .unprepare = clk_smd_rpm_unprepare,
+ .set_rate = clk_smd_rpm_set_rate,
+ .round_rate = clk_smd_rpm_round_rate,
+ .recalc_rate = clk_smd_rpm_recalc_rate,
+};
+
+static const struct clk_ops clk_smd_rpm_branch_ops = {
+ .prepare = clk_smd_rpm_prepare,
+ .unprepare = clk_smd_rpm_unprepare,
+ .round_rate = clk_smd_rpm_round_rate,
+ .recalc_rate = clk_smd_rpm_recalc_rate,
+};
+
+/* msm8916 */
+DEFINE_CLK_SMD_RPM(msm8916, pcnoc_clk, pcnoc_a_clk, QCOM_SMD_RPM_BUS_CLK, 0);
+DEFINE_CLK_SMD_RPM(msm8916, snoc_clk, snoc_a_clk, QCOM_SMD_RPM_BUS_CLK, 1);
+DEFINE_CLK_SMD_RPM(msm8916, bimc_clk, bimc_a_clk, QCOM_SMD_RPM_MEM_CLK, 0);
+DEFINE_CLK_SMD_RPM_QDSS(msm8916, qdss_clk, qdss_a_clk, QCOM_SMD_RPM_MISC_CLK, 1);
+DEFINE_CLK_SMD_RPM_XO_BUFFER(msm8916, bb_clk1, bb_clk1_a, 1);
+DEFINE_CLK_SMD_RPM_XO_BUFFER(msm8916, bb_clk2, bb_clk2_a, 2);
+DEFINE_CLK_SMD_RPM_XO_BUFFER(msm8916, rf_clk1, rf_clk1_a, 4);
+DEFINE_CLK_SMD_RPM_XO_BUFFER(msm8916, rf_clk2, rf_clk2_a, 5);
+DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8916, bb_clk1_pin, bb_clk1_a_pin, 1);
+DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8916, bb_clk2_pin, bb_clk2_a_pin, 2);
+DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8916, rf_clk1_pin, rf_clk1_a_pin, 4);
+DEFINE_CLK_SMD_RPM_XO_BUFFER_PINCTRL(msm8916, rf_clk2_pin, rf_clk2_a_pin, 5);
+
+static struct clk_smd_rpm *msm8916_clks[] = {
+ [RPM_SMD_PCNOC_CLK] = &msm8916_pcnoc_clk,
+ [RPM_SMD_PCNOC_A_CLK] = &msm8916_pcnoc_a_clk,
+ [RPM_SMD_SNOC_CLK] = &msm8916_snoc_clk,
+ [RPM_SMD_SNOC_A_CLK] = &msm8916_snoc_a_clk,
+ [RPM_SMD_BIMC_CLK] = &msm8916_bimc_clk,
+ [RPM_SMD_BIMC_A_CLK] = &msm8916_bimc_a_clk,
+ [RPM_SMD_QDSS_CLK] = &msm8916_qdss_clk,
+ [RPM_SMD_QDSS_A_CLK] = &msm8916_qdss_a_clk,
+ [RPM_SMD_BB_CLK1] = &msm8916_bb_clk1,
+ [RPM_SMD_BB_CLK1_A] = &msm8916_bb_clk1_a,
+ [RPM_SMD_BB_CLK2] = &msm8916_bb_clk2,
+ [RPM_SMD_BB_CLK2_A] = &msm8916_bb_clk2_a,
+ [RPM_SMD_RF_CLK1] = &msm8916_rf_clk1,
+ [RPM_SMD_RF_CLK1_A] = &msm8916_rf_clk1_a,
+ [RPM_SMD_RF_CLK2] = &msm8916_rf_clk2,
+ [RPM_SMD_RF_CLK2_A] = &msm8916_rf_clk2_a,
+ [RPM_SMD_BB_CLK1_PIN] = &msm8916_bb_clk1_pin,
+ [RPM_SMD_BB_CLK1_A_PIN] = &msm8916_bb_clk1_a_pin,
+ [RPM_SMD_BB_CLK2_PIN] = &msm8916_bb_clk2_pin,
+ [RPM_SMD_BB_CLK2_A_PIN] = &msm8916_bb_clk2_a_pin,
+ [RPM_SMD_RF_CLK1_PIN] = &msm8916_rf_clk1_pin,
+ [RPM_SMD_RF_CLK1_A_PIN] = &msm8916_rf_clk1_a_pin,
+ [RPM_SMD_RF_CLK2_PIN] = &msm8916_rf_clk2_pin,
+ [RPM_SMD_RF_CLK2_A_PIN] = &msm8916_rf_clk2_a_pin,
+};
+
+static const struct rpm_smd_clk_desc rpm_clk_msm8916 = {
+ .clks = msm8916_clks,
+ .num_clks = ARRAY_SIZE(msm8916_clks),
+};
+
+static const struct of_device_id rpm_smd_clk_match_table[] = {
+ { .compatible = "qcom,rpmcc-msm8916", .data = &rpm_clk_msm8916 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rpm_smd_clk_match_table);
+
+static int rpm_smd_clk_probe(struct platform_device *pdev)
+{
+ struct clk_hw **hws;
+ struct rpm_cc *rcc;
+ struct clk_hw_onecell_data *data;
+ int ret;
+ size_t num_clks, i;
+ struct qcom_smd_rpm *rpm;
+ struct clk_smd_rpm **rpm_smd_clks;
+ const struct rpm_smd_clk_desc *desc;
+
+ rpm = dev_get_drvdata(pdev->dev.parent);
+ if (!rpm) {
+ dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n");
+ return -ENODEV;
+ }
+
+ desc = of_device_get_match_data(&pdev->dev);
+ if (!desc)
+ return -EINVAL;
+
+ rpm_smd_clks = desc->clks;
+ num_clks = desc->num_clks;
+
+ rcc = devm_kzalloc(&pdev->dev, sizeof(*rcc) + sizeof(*hws) * num_clks,
+ GFP_KERNEL);
+ if (!rcc)
+ return -ENOMEM;
+
+ hws = rcc->hws;
+ data = &rcc->data;
+ data->num = num_clks;
+
+ for (i = 0; i < num_clks; i++) {
+ if (!rpm_smd_clks[i]) {
+ continue;
+ }
+
+ rpm_smd_clks[i]->rpm = rpm;
+
+ ret = clk_smd_rpm_handoff(rpm_smd_clks[i]);
+ if (ret)
+ goto err;
+ }
+
+ ret = clk_smd_rpm_enable_scaling(rpm);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < num_clks; i++) {
+ if (!rpm_smd_clks[i]) {
+ data->hws[i] = ERR_PTR(-ENOENT);
+ continue;
+ }
+
+ ret = devm_clk_hw_register(&pdev->dev, &rpm_smd_clks[i]->hw);
+ if (ret)
+ goto err;
+ }
+
+ ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get,
+ data);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_err(&pdev->dev, "Error registering SMD clock driver (%d)\n", ret);
+ return ret;
+}
+
+static int rpm_smd_clk_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+ return 0;
+}
+
+static struct platform_driver rpm_smd_clk_driver = {
+ .driver = {
+ .name = "qcom-clk-smd-rpm",
+ .of_match_table = rpm_smd_clk_match_table,
+ },
+ .probe = rpm_smd_clk_probe,
+ .remove = rpm_smd_clk_remove,
+};
+
+static int __init rpm_smd_clk_init(void)
+{
+ return platform_driver_register(&rpm_smd_clk_driver);
+}
+core_initcall(rpm_smd_clk_init);
+
+static void __exit rpm_smd_clk_exit(void)
+{
+ platform_driver_unregister(&rpm_smd_clk_driver);
+}
+module_exit(rpm_smd_clk_exit);
+
+MODULE_DESCRIPTION("Qualcomm RPM over SMD Clock Controller Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:qcom-clk-smd-rpm");
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,rpmcc.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Linaro Limited
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DT_BINDINGS_CLK_MSM_RPMCC_H
+#define _DT_BINDINGS_CLK_MSM_RPMCC_H
+
+/* msm8916 */
+#define RPM_SMD_XO_CLK_SRC 0
+#define RPM_SMD_XO_A_CLK_SRC 1
+#define RPM_SMD_PCNOC_CLK 2
+#define RPM_SMD_PCNOC_A_CLK 3
+#define RPM_SMD_SNOC_CLK 4
+#define RPM_SMD_SNOC_A_CLK 5
+#define RPM_SMD_BIMC_CLK 6
+#define RPM_SMD_BIMC_A_CLK 7
+#define RPM_SMD_QDSS_CLK 8
+#define RPM_SMD_QDSS_A_CLK 9
+#define RPM_SMD_BB_CLK1 10
+#define RPM_SMD_BB_CLK1_A 11
+#define RPM_SMD_BB_CLK2 12
+#define RPM_SMD_BB_CLK2_A 13
+#define RPM_SMD_RF_CLK1 14
+#define RPM_SMD_RF_CLK1_A 15
+#define RPM_SMD_RF_CLK2 16
+#define RPM_SMD_RF_CLK2_A 17
+#define RPM_SMD_BB_CLK1_PIN 18
+#define RPM_SMD_BB_CLK1_A_PIN 19
+#define RPM_SMD_BB_CLK2_PIN 20
+#define RPM_SMD_BB_CLK2_A_PIN 21
+#define RPM_SMD_RF_CLK1_PIN 22
+#define RPM_SMD_RF_CLK1_A_PIN 23
+#define RPM_SMD_RF_CLK2_PIN 24
+#define RPM_SMD_RF_CLK2_A_PIN 25
+
+#endif

View file

@ -0,0 +1,586 @@
From 872f91b5ea720c72f81fb46d353c43ecb3263ffa Mon Sep 17 00:00:00 2001
From: Georgi Djakov <georgi.djakov@linaro.org>
Date: Wed, 2 Nov 2016 17:56:57 +0200
Subject: clk: qcom: Add support for RPM Clocks
This adds initial support for clocks controlled by the Resource
Power Manager (RPM) processor on some Qualcomm SoCs, which use
the qcom_rpm driver to communicate with RPM.
Such platforms are apq8064 and msm8960.
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
Acked-by: Rob Herring <robh@kernel.org>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
---
.../devicetree/bindings/clock/qcom,rpmcc.txt | 1 +
drivers/clk/qcom/Kconfig | 13 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/clk-rpm.c | 489 +++++++++++++++++++++
include/dt-bindings/clock/qcom,rpmcc.h | 24 +
5 files changed, 528 insertions(+)
create mode 100644 drivers/clk/qcom/clk-rpm.c
--- a/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt
+++ b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt
@@ -11,6 +11,7 @@ Required properties :
compatible "qcom,rpmcc" should be also included.
"qcom,rpmcc-msm8916", "qcom,rpmcc"
+ "qcom,rpmcc-apq8064", "qcom,rpmcc"
- #clock-cells : shall contain 1
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -12,6 +12,19 @@ config COMMON_CLK_QCOM
select REGMAP_MMIO
select RESET_CONTROLLER
+config QCOM_CLK_RPM
+ tristate "RPM based Clock Controller"
+ depends on COMMON_CLK_QCOM && MFD_QCOM_RPM
+ select QCOM_RPMCC
+ help
+ The RPM (Resource Power Manager) is a dedicated hardware engine for
+ managing the shared SoC resources in order to keep the lowest power
+ profile. It communicates with other hardware subsystems via shared
+ memory and accepts clock requests, aggregates the requests and turns
+ the clocks on/off or scales them on demand.
+ Say Y if you want to support the clocks exposed by the RPM on
+ platforms such as ipq806x, msm8660, msm8960 etc.
+
config QCOM_CLK_SMD_RPM
tristate "RPM over SMD based Clock Controller"
depends on COMMON_CLK_QCOM && QCOM_SMD_RPM
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -23,3 +23,4 @@ obj-$(CONFIG_MSM_GCC_8974) += gcc-msm897
obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o
+obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o
--- /dev/null
+++ b/drivers/clk/qcom/clk-rpm.c
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2016, Linaro Limited
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#include <dt-bindings/mfd/qcom-rpm.h>
+#include <dt-bindings/clock/qcom,rpmcc.h>
+
+#define QCOM_RPM_MISC_CLK_TYPE 0x306b6c63
+#define QCOM_RPM_SCALING_ENABLE_ID 0x2
+
+#define DEFINE_CLK_RPM(_platform, _name, _active, r_id) \
+ static struct clk_rpm _platform##_##_active; \
+ static struct clk_rpm _platform##_##_name = { \
+ .rpm_clk_id = (r_id), \
+ .peer = &_platform##_##_active, \
+ .rate = INT_MAX, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_rpm_ops, \
+ .name = #_name, \
+ .parent_names = (const char *[]){ "pxo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }; \
+ static struct clk_rpm _platform##_##_active = { \
+ .rpm_clk_id = (r_id), \
+ .peer = &_platform##_##_name, \
+ .active_only = true, \
+ .rate = INT_MAX, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_rpm_ops, \
+ .name = #_active, \
+ .parent_names = (const char *[]){ "pxo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }
+
+#define DEFINE_CLK_RPM_PXO_BRANCH(_platform, _name, _active, r_id, r) \
+ static struct clk_rpm _platform##_##_active; \
+ static struct clk_rpm _platform##_##_name = { \
+ .rpm_clk_id = (r_id), \
+ .active_only = true, \
+ .peer = &_platform##_##_active, \
+ .rate = (r), \
+ .branch = true, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_rpm_branch_ops, \
+ .name = #_name, \
+ .parent_names = (const char *[]){ "pxo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }; \
+ static struct clk_rpm _platform##_##_active = { \
+ .rpm_clk_id = (r_id), \
+ .peer = &_platform##_##_name, \
+ .rate = (r), \
+ .branch = true, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_rpm_branch_ops, \
+ .name = #_active, \
+ .parent_names = (const char *[]){ "pxo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }
+
+#define DEFINE_CLK_RPM_CXO_BRANCH(_platform, _name, _active, r_id, r) \
+ static struct clk_rpm _platform##_##_active; \
+ static struct clk_rpm _platform##_##_name = { \
+ .rpm_clk_id = (r_id), \
+ .peer = &_platform##_##_active, \
+ .rate = (r), \
+ .branch = true, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_rpm_branch_ops, \
+ .name = #_name, \
+ .parent_names = (const char *[]){ "cxo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }; \
+ static struct clk_rpm _platform##_##_active = { \
+ .rpm_clk_id = (r_id), \
+ .active_only = true, \
+ .peer = &_platform##_##_name, \
+ .rate = (r), \
+ .branch = true, \
+ .hw.init = &(struct clk_init_data){ \
+ .ops = &clk_rpm_branch_ops, \
+ .name = #_active, \
+ .parent_names = (const char *[]){ "cxo_board" }, \
+ .num_parents = 1, \
+ }, \
+ }
+
+#define to_clk_rpm(_hw) container_of(_hw, struct clk_rpm, hw)
+
+struct clk_rpm {
+ const int rpm_clk_id;
+ const bool active_only;
+ unsigned long rate;
+ bool enabled;
+ bool branch;
+ struct clk_rpm *peer;
+ struct clk_hw hw;
+ struct qcom_rpm *rpm;
+};
+
+struct rpm_cc {
+ struct qcom_rpm *rpm;
+ struct clk_hw_onecell_data data;
+ struct clk_hw *hws[];
+};
+
+struct rpm_clk_desc {
+ struct clk_rpm **clks;
+ size_t num_clks;
+};
+
+static DEFINE_MUTEX(rpm_clk_lock);
+
+static int clk_rpm_handoff(struct clk_rpm *r)
+{
+ int ret;
+ u32 value = INT_MAX;
+
+ ret = qcom_rpm_write(r->rpm, QCOM_RPM_ACTIVE_STATE,
+ r->rpm_clk_id, &value, 1);
+ if (ret)
+ return ret;
+ ret = qcom_rpm_write(r->rpm, QCOM_RPM_SLEEP_STATE,
+ r->rpm_clk_id, &value, 1);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int clk_rpm_set_rate_active(struct clk_rpm *r, unsigned long rate)
+{
+ u32 value = DIV_ROUND_UP(rate, 1000); /* to kHz */
+
+ return qcom_rpm_write(r->rpm, QCOM_RPM_ACTIVE_STATE,
+ r->rpm_clk_id, &value, 1);
+}
+
+static int clk_rpm_set_rate_sleep(struct clk_rpm *r, unsigned long rate)
+{
+ u32 value = DIV_ROUND_UP(rate, 1000); /* to kHz */
+
+ return qcom_rpm_write(r->rpm, QCOM_RPM_SLEEP_STATE,
+ r->rpm_clk_id, &value, 1);
+}
+
+static void to_active_sleep(struct clk_rpm *r, unsigned long rate,
+ unsigned long *active, unsigned long *sleep)
+{
+ *active = rate;
+
+ /*
+ * Active-only clocks don't care what the rate is during sleep. So,
+ * they vote for zero.
+ */
+ if (r->active_only)
+ *sleep = 0;
+ else
+ *sleep = *active;
+}
+
+static int clk_rpm_prepare(struct clk_hw *hw)
+{
+ struct clk_rpm *r = to_clk_rpm(hw);
+ struct clk_rpm *peer = r->peer;
+ unsigned long this_rate = 0, this_sleep_rate = 0;
+ unsigned long peer_rate = 0, peer_sleep_rate = 0;
+ unsigned long active_rate, sleep_rate;
+ int ret = 0;
+
+ mutex_lock(&rpm_clk_lock);
+
+ /* Don't send requests to the RPM if the rate has not been set. */
+ if (!r->rate)
+ goto out;
+
+ to_active_sleep(r, r->rate, &this_rate, &this_sleep_rate);
+
+ /* Take peer clock's rate into account only if it's enabled. */
+ if (peer->enabled)
+ to_active_sleep(peer, peer->rate,
+ &peer_rate, &peer_sleep_rate);
+
+ active_rate = max(this_rate, peer_rate);
+
+ if (r->branch)
+ active_rate = !!active_rate;
+
+ ret = clk_rpm_set_rate_active(r, active_rate);
+ if (ret)
+ goto out;
+
+ sleep_rate = max(this_sleep_rate, peer_sleep_rate);
+ if (r->branch)
+ sleep_rate = !!sleep_rate;
+
+ ret = clk_rpm_set_rate_sleep(r, sleep_rate);
+ if (ret)
+ /* Undo the active set vote and restore it */
+ ret = clk_rpm_set_rate_active(r, peer_rate);
+
+out:
+ if (!ret)
+ r->enabled = true;
+
+ mutex_unlock(&rpm_clk_lock);
+
+ return ret;
+}
+
+static void clk_rpm_unprepare(struct clk_hw *hw)
+{
+ struct clk_rpm *r = to_clk_rpm(hw);
+ struct clk_rpm *peer = r->peer;
+ unsigned long peer_rate = 0, peer_sleep_rate = 0;
+ unsigned long active_rate, sleep_rate;
+ int ret;
+
+ mutex_lock(&rpm_clk_lock);
+
+ if (!r->rate)
+ goto out;
+
+ /* Take peer clock's rate into account only if it's enabled. */
+ if (peer->enabled)
+ to_active_sleep(peer, peer->rate, &peer_rate,
+ &peer_sleep_rate);
+
+ active_rate = r->branch ? !!peer_rate : peer_rate;
+ ret = clk_rpm_set_rate_active(r, active_rate);
+ if (ret)
+ goto out;
+
+ sleep_rate = r->branch ? !!peer_sleep_rate : peer_sleep_rate;
+ ret = clk_rpm_set_rate_sleep(r, sleep_rate);
+ if (ret)
+ goto out;
+
+ r->enabled = false;
+
+out:
+ mutex_unlock(&rpm_clk_lock);
+}
+
+static int clk_rpm_set_rate(struct clk_hw *hw,
+ unsigned long rate, unsigned long parent_rate)
+{
+ struct clk_rpm *r = to_clk_rpm(hw);
+ struct clk_rpm *peer = r->peer;
+ unsigned long active_rate, sleep_rate;
+ unsigned long this_rate = 0, this_sleep_rate = 0;
+ unsigned long peer_rate = 0, peer_sleep_rate = 0;
+ int ret = 0;
+
+ mutex_lock(&rpm_clk_lock);
+
+ if (!r->enabled)
+ goto out;
+
+ to_active_sleep(r, rate, &this_rate, &this_sleep_rate);
+
+ /* Take peer clock's rate into account only if it's enabled. */
+ if (peer->enabled)
+ to_active_sleep(peer, peer->rate,
+ &peer_rate, &peer_sleep_rate);
+
+ active_rate = max(this_rate, peer_rate);
+ ret = clk_rpm_set_rate_active(r, active_rate);
+ if (ret)
+ goto out;
+
+ sleep_rate = max(this_sleep_rate, peer_sleep_rate);
+ ret = clk_rpm_set_rate_sleep(r, sleep_rate);
+ if (ret)
+ goto out;
+
+ r->rate = rate;
+
+out:
+ mutex_unlock(&rpm_clk_lock);
+
+ return ret;
+}
+
+static long clk_rpm_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ /*
+ * RPM handles rate rounding and we don't have a way to
+ * know what the rate will be, so just return whatever
+ * rate is requested.
+ */
+ return rate;
+}
+
+static unsigned long clk_rpm_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_rpm *r = to_clk_rpm(hw);
+
+ /*
+ * RPM handles rate rounding and we don't have a way to
+ * know what the rate will be, so just return whatever
+ * rate was set.
+ */
+ return r->rate;
+}
+
+static const struct clk_ops clk_rpm_ops = {
+ .prepare = clk_rpm_prepare,
+ .unprepare = clk_rpm_unprepare,
+ .set_rate = clk_rpm_set_rate,
+ .round_rate = clk_rpm_round_rate,
+ .recalc_rate = clk_rpm_recalc_rate,
+};
+
+static const struct clk_ops clk_rpm_branch_ops = {
+ .prepare = clk_rpm_prepare,
+ .unprepare = clk_rpm_unprepare,
+ .round_rate = clk_rpm_round_rate,
+ .recalc_rate = clk_rpm_recalc_rate,
+};
+
+/* apq8064 */
+DEFINE_CLK_RPM(apq8064, afab_clk, afab_a_clk, QCOM_RPM_APPS_FABRIC_CLK);
+DEFINE_CLK_RPM(apq8064, cfpb_clk, cfpb_a_clk, QCOM_RPM_CFPB_CLK);
+DEFINE_CLK_RPM(apq8064, daytona_clk, daytona_a_clk, QCOM_RPM_DAYTONA_FABRIC_CLK);
+DEFINE_CLK_RPM(apq8064, ebi1_clk, ebi1_a_clk, QCOM_RPM_EBI1_CLK);
+DEFINE_CLK_RPM(apq8064, mmfab_clk, mmfab_a_clk, QCOM_RPM_MM_FABRIC_CLK);
+DEFINE_CLK_RPM(apq8064, mmfpb_clk, mmfpb_a_clk, QCOM_RPM_MMFPB_CLK);
+DEFINE_CLK_RPM(apq8064, sfab_clk, sfab_a_clk, QCOM_RPM_SYS_FABRIC_CLK);
+DEFINE_CLK_RPM(apq8064, sfpb_clk, sfpb_a_clk, QCOM_RPM_SFPB_CLK);
+DEFINE_CLK_RPM(apq8064, qdss_clk, qdss_a_clk, QCOM_RPM_QDSS_CLK);
+
+static struct clk_rpm *apq8064_clks[] = {
+ [RPM_APPS_FABRIC_CLK] = &apq8064_afab_clk,
+ [RPM_APPS_FABRIC_A_CLK] = &apq8064_afab_a_clk,
+ [RPM_CFPB_CLK] = &apq8064_cfpb_clk,
+ [RPM_CFPB_A_CLK] = &apq8064_cfpb_a_clk,
+ [RPM_DAYTONA_FABRIC_CLK] = &apq8064_daytona_clk,
+ [RPM_DAYTONA_FABRIC_A_CLK] = &apq8064_daytona_a_clk,
+ [RPM_EBI1_CLK] = &apq8064_ebi1_clk,
+ [RPM_EBI1_A_CLK] = &apq8064_ebi1_a_clk,
+ [RPM_MM_FABRIC_CLK] = &apq8064_mmfab_clk,
+ [RPM_MM_FABRIC_A_CLK] = &apq8064_mmfab_a_clk,
+ [RPM_MMFPB_CLK] = &apq8064_mmfpb_clk,
+ [RPM_MMFPB_A_CLK] = &apq8064_mmfpb_a_clk,
+ [RPM_SYS_FABRIC_CLK] = &apq8064_sfab_clk,
+ [RPM_SYS_FABRIC_A_CLK] = &apq8064_sfab_a_clk,
+ [RPM_SFPB_CLK] = &apq8064_sfpb_clk,
+ [RPM_SFPB_A_CLK] = &apq8064_sfpb_a_clk,
+ [RPM_QDSS_CLK] = &apq8064_qdss_clk,
+ [RPM_QDSS_A_CLK] = &apq8064_qdss_a_clk,
+};
+
+static const struct rpm_clk_desc rpm_clk_apq8064 = {
+ .clks = apq8064_clks,
+ .num_clks = ARRAY_SIZE(apq8064_clks),
+};
+
+static const struct of_device_id rpm_clk_match_table[] = {
+ { .compatible = "qcom,rpmcc-apq8064", .data = &rpm_clk_apq8064 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, rpm_clk_match_table);
+
+static int rpm_clk_probe(struct platform_device *pdev)
+{
+ struct clk_hw **hws;
+ struct rpm_cc *rcc;
+ struct clk_hw_onecell_data *data;
+ int ret;
+ size_t num_clks, i;
+ struct qcom_rpm *rpm;
+ struct clk_rpm **rpm_clks;
+ const struct rpm_clk_desc *desc;
+
+ rpm = dev_get_drvdata(pdev->dev.parent);
+ if (!rpm) {
+ dev_err(&pdev->dev, "Unable to retrieve handle to RPM\n");
+ return -ENODEV;
+ }
+
+ desc = of_device_get_match_data(&pdev->dev);
+ if (!desc)
+ return -EINVAL;
+
+ rpm_clks = desc->clks;
+ num_clks = desc->num_clks;
+
+ rcc = devm_kzalloc(&pdev->dev, sizeof(*rcc) + sizeof(*hws) * num_clks,
+ GFP_KERNEL);
+ if (!rcc)
+ return -ENOMEM;
+
+ hws = rcc->hws;
+ data = &rcc->data;
+ data->num = num_clks;
+
+ for (i = 0; i < num_clks; i++) {
+ if (!rpm_clks[i])
+ continue;
+
+ rpm_clks[i]->rpm = rpm;
+
+ ret = clk_rpm_handoff(rpm_clks[i]);
+ if (ret)
+ goto err;
+ }
+
+ for (i = 0; i < num_clks; i++) {
+ if (!rpm_clks[i]) {
+ data->hws[i] = ERR_PTR(-ENOENT);
+ continue;
+ }
+
+ ret = devm_clk_hw_register(&pdev->dev, &rpm_clks[i]->hw);
+ if (ret)
+ goto err;
+ }
+
+ ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get,
+ data);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ dev_err(&pdev->dev, "Error registering RPM Clock driver (%d)\n", ret);
+ return ret;
+}
+
+static int rpm_clk_remove(struct platform_device *pdev)
+{
+ of_clk_del_provider(pdev->dev.of_node);
+ return 0;
+}
+
+static struct platform_driver rpm_clk_driver = {
+ .driver = {
+ .name = "qcom-clk-rpm",
+ .of_match_table = rpm_clk_match_table,
+ },
+ .probe = rpm_clk_probe,
+ .remove = rpm_clk_remove,
+};
+
+static int __init rpm_clk_init(void)
+{
+ return platform_driver_register(&rpm_clk_driver);
+}
+core_initcall(rpm_clk_init);
+
+static void __exit rpm_clk_exit(void)
+{
+ platform_driver_unregister(&rpm_clk_driver);
+}
+module_exit(rpm_clk_exit);
+
+MODULE_DESCRIPTION("Qualcomm RPM Clock Controller Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:qcom-clk-rpm");
--- a/include/dt-bindings/clock/qcom,rpmcc.h
+++ b/include/dt-bindings/clock/qcom,rpmcc.h
@@ -14,6 +14,30 @@
#ifndef _DT_BINDINGS_CLK_MSM_RPMCC_H
#define _DT_BINDINGS_CLK_MSM_RPMCC_H
+/* apq8064 */
+#define RPM_PXO_CLK 0
+#define RPM_PXO_A_CLK 1
+#define RPM_CXO_CLK 2
+#define RPM_CXO_A_CLK 3
+#define RPM_APPS_FABRIC_CLK 4
+#define RPM_APPS_FABRIC_A_CLK 5
+#define RPM_CFPB_CLK 6
+#define RPM_CFPB_A_CLK 7
+#define RPM_QDSS_CLK 8
+#define RPM_QDSS_A_CLK 9
+#define RPM_DAYTONA_FABRIC_CLK 10
+#define RPM_DAYTONA_FABRIC_A_CLK 11
+#define RPM_EBI1_CLK 12
+#define RPM_EBI1_A_CLK 13
+#define RPM_MM_FABRIC_CLK 14
+#define RPM_MM_FABRIC_A_CLK 15
+#define RPM_MMFPB_CLK 16
+#define RPM_MMFPB_A_CLK 17
+#define RPM_SYS_FABRIC_CLK 18
+#define RPM_SYS_FABRIC_A_CLK 19
+#define RPM_SFPB_CLK 20
+#define RPM_SFPB_A_CLK 21
+
/* msm8916 */
#define RPM_SMD_XO_CLK_SRC 0
#define RPM_SMD_XO_A_CLK_SRC 1

View file

@ -0,0 +1,94 @@
From c260524aba53b57f72b5446ed553080df30b4d09 Mon Sep 17 00:00:00 2001
From: Georgi Djakov <georgi.djakov@linaro.org>
Date: Wed, 23 Nov 2016 16:52:49 +0200
Subject: clk: qcom: clk-rpm: Fix clk_hw references
Fix the clk_hw references to the actual clocks and add a xlate function
to return the hw pointers from the already existing static array.
Reported-by: Michael Scott <michael.scott@linaro.org>
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
---
drivers/clk/qcom/clk-rpm.c | 36 ++++++++++++++++++++++--------------
1 file changed, 22 insertions(+), 14 deletions(-)
--- a/drivers/clk/qcom/clk-rpm.c
+++ b/drivers/clk/qcom/clk-rpm.c
@@ -127,8 +127,8 @@ struct clk_rpm {
struct rpm_cc {
struct qcom_rpm *rpm;
- struct clk_hw_onecell_data data;
- struct clk_hw *hws[];
+ struct clk_rpm **clks;
+ size_t num_clks;
};
struct rpm_clk_desc {
@@ -391,11 +391,23 @@ static const struct of_device_id rpm_clk
};
MODULE_DEVICE_TABLE(of, rpm_clk_match_table);
+static struct clk_hw *qcom_rpm_clk_hw_get(struct of_phandle_args *clkspec,
+ void *data)
+{
+ struct rpm_cc *rcc = data;
+ unsigned int idx = clkspec->args[0];
+
+ if (idx >= rcc->num_clks) {
+ pr_err("%s: invalid index %u\n", __func__, idx);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return rcc->clks[idx] ? &rcc->clks[idx]->hw : ERR_PTR(-ENOENT);
+}
+
static int rpm_clk_probe(struct platform_device *pdev)
{
- struct clk_hw **hws;
struct rpm_cc *rcc;
- struct clk_hw_onecell_data *data;
int ret;
size_t num_clks, i;
struct qcom_rpm *rpm;
@@ -415,14 +427,12 @@ static int rpm_clk_probe(struct platform
rpm_clks = desc->clks;
num_clks = desc->num_clks;
- rcc = devm_kzalloc(&pdev->dev, sizeof(*rcc) + sizeof(*hws) * num_clks,
- GFP_KERNEL);
+ rcc = devm_kzalloc(&pdev->dev, sizeof(*rcc), GFP_KERNEL);
if (!rcc)
return -ENOMEM;
- hws = rcc->hws;
- data = &rcc->data;
- data->num = num_clks;
+ rcc->clks = rpm_clks;
+ rcc->num_clks = num_clks;
for (i = 0; i < num_clks; i++) {
if (!rpm_clks[i])
@@ -436,18 +446,16 @@ static int rpm_clk_probe(struct platform
}
for (i = 0; i < num_clks; i++) {
- if (!rpm_clks[i]) {
- data->hws[i] = ERR_PTR(-ENOENT);
+ if (!rpm_clks[i])
continue;
- }
ret = devm_clk_hw_register(&pdev->dev, &rpm_clks[i]->hw);
if (ret)
goto err;
}
- ret = of_clk_add_hw_provider(pdev->dev.of_node, of_clk_hw_onecell_get,
- data);
+ ret = of_clk_add_hw_provider(pdev->dev.of_node, qcom_rpm_clk_hw_get,
+ rcc);
if (ret)
goto err;

View file

@ -0,0 +1,44 @@
In commit "regulator: qcom: Rework to single platform device" the smb208 regulator
used in IPQ8064 was left out.
Add it to that new framework and update Docs accordingly.
Signed-off-by: Adrian Panella <ianchi74@outlook.com>
--- a/Documentation/devicetree/bindings/mfd/qcom-rpm.txt
+++ b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt
@@ -171,6 +171,9 @@ pm8018:
s1, s2, s3, s4, s5, , l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11,
l12, l14, lvs1
+smb208:
+ s1a, s1b, s2a, s2b
+
The content of each sub-node is defined by the standard binding for regulators -
see regulator.txt - with additional custom properties described below:
--- a/drivers/regulator/qcom_rpm-regulator.c
+++ b/drivers/regulator/qcom_rpm-regulator.c
@@ -933,12 +933,21 @@ static const struct rpm_regulator_data r
{ }
};
+static const struct rpm_regulator_data rpm_smb208_regulators[] = {
+ { "s1a", QCOM_RPM_SMB208_S1a, &smb208_smps, "vin_s1a" },
+ { "s1b", QCOM_RPM_SMB208_S1b, &smb208_smps, "vin_s1b" },
+ { "s2a", QCOM_RPM_SMB208_S2a, &smb208_smps, "vin_s2a" },
+ { "s2b", QCOM_RPM_SMB208_S2b, &smb208_smps, "vin_s2b" },
+ { }
+};
+
static const struct of_device_id rpm_of_match[] = {
{ .compatible = "qcom,rpm-pm8018-regulators",
.data = &rpm_pm8018_regulators },
{ .compatible = "qcom,rpm-pm8058-regulators", .data = &rpm_pm8058_regulators },
{ .compatible = "qcom,rpm-pm8901-regulators", .data = &rpm_pm8901_regulators },
{ .compatible = "qcom,rpm-pm8921-regulators", .data = &rpm_pm8921_regulators },
+ { .compatible = "qcom,rpm-smb208-regulators", .data = &rpm_smb208_regulators },
{ }
};
MODULE_DEVICE_TABLE(of, rpm_of_match);

View file

@ -0,0 +1,25 @@
From dd43e356db678a564ad2cc111962d72ba3162a38 Mon Sep 17 00:00:00 2001
From: Abhishek Sahu <absahu@codeaurora.org>
Date: Wed, 18 Nov 2015 12:38:56 +0530
Subject: ipq806x: gcc: Added the enable regs and mask for PRNG
kernel got hanged while reading from /dev/hwrng at the
time of PRNG clock enable
Change-Id: I89856c7e19e6639508e6a2774304583a3ec91172
Signed-off-by: Abhishek Sahu <absahu@codeaurora.org>
---
drivers/clk/qcom/gcc-ipq806x.c | 2 ++
1 file changed, 2 insertions(+)
--- a/drivers/clk/qcom/gcc-ipq806x.c
+++ b/drivers/clk/qcom/gcc-ipq806x.c
@@ -1153,6 +1153,8 @@ static struct clk_rcg prng_src = {
.parent_map = gcc_pxo_pll8_map,
},
.clkr = {
+ .enable_reg = 0x2e80,
+ .enable_mask = BIT(11),
.hw.init = &(struct clk_init_data){
.name = "prng_src",
.parent_names = gcc_pxo_pll8,

View file

@ -0,0 +1,81 @@
--- a/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt
+++ b/Documentation/devicetree/bindings/clock/qcom,rpmcc.txt
@@ -12,6 +12,7 @@ Required properties :
"qcom,rpmcc-msm8916", "qcom,rpmcc"
"qcom,rpmcc-apq8064", "qcom,rpmcc"
+ "qcom,rpmcc-ipq806x", "qcom,rpmcc"
- #clock-cells : shall contain 1
--- a/drivers/clk/qcom/clk-rpm.c
+++ b/drivers/clk/qcom/clk-rpm.c
@@ -359,6 +359,16 @@ DEFINE_CLK_RPM(apq8064, sfab_clk, sfab_a
DEFINE_CLK_RPM(apq8064, sfpb_clk, sfpb_a_clk, QCOM_RPM_SFPB_CLK);
DEFINE_CLK_RPM(apq8064, qdss_clk, qdss_a_clk, QCOM_RPM_QDSS_CLK);
+/* ipq806x */
+DEFINE_CLK_RPM(ipq806x, afab_clk, afab_a_clk, QCOM_RPM_APPS_FABRIC_CLK);
+DEFINE_CLK_RPM(ipq806x, cfpb_clk, cfpb_a_clk, QCOM_RPM_CFPB_CLK);
+DEFINE_CLK_RPM(ipq806x, daytona_clk, daytona_a_clk, QCOM_RPM_DAYTONA_FABRIC_CLK);
+DEFINE_CLK_RPM(ipq806x, ebi1_clk, ebi1_a_clk, QCOM_RPM_EBI1_CLK);
+DEFINE_CLK_RPM(ipq806x, sfab_clk, sfab_a_clk, QCOM_RPM_SYS_FABRIC_CLK);
+DEFINE_CLK_RPM(ipq806x, sfpb_clk, sfpb_a_clk, QCOM_RPM_SFPB_CLK);
+DEFINE_CLK_RPM(ipq806x, nss_fabric_0_clk, nss_fabric_0_a_clk, QCOM_RPM_NSS_FABRIC_0_CLK);
+DEFINE_CLK_RPM(ipq806x, nss_fabric_1_clk, nss_fabric_1_a_clk, QCOM_RPM_NSS_FABRIC_1_CLK);
+
static struct clk_rpm *apq8064_clks[] = {
[RPM_APPS_FABRIC_CLK] = &apq8064_afab_clk,
[RPM_APPS_FABRIC_A_CLK] = &apq8064_afab_a_clk,
@@ -380,13 +390,38 @@ static struct clk_rpm *apq8064_clks[] =
[RPM_QDSS_A_CLK] = &apq8064_qdss_a_clk,
};
+static struct clk_rpm *ipq806x_clks[] = {
+ [RPM_APPS_FABRIC_CLK] = &ipq806x_afab_clk,
+ [RPM_APPS_FABRIC_A_CLK] = &ipq806x_afab_a_clk,
+ [RPM_CFPB_CLK] = &ipq806x_cfpb_clk,
+ [RPM_CFPB_A_CLK] = &ipq806x_cfpb_a_clk,
+ [RPM_DAYTONA_FABRIC_CLK] = &ipq806x_daytona_clk,
+ [RPM_DAYTONA_FABRIC_A_CLK] = &ipq806x_daytona_a_clk,
+ [RPM_EBI1_CLK] = &ipq806x_ebi1_clk,
+ [RPM_EBI1_A_CLK] = &ipq806x_ebi1_a_clk,
+ [RPM_SYS_FABRIC_CLK] = &ipq806x_sfab_clk,
+ [RPM_SYS_FABRIC_A_CLK] = &ipq806x_sfab_a_clk,
+ [RPM_SFPB_CLK] = &ipq806x_sfpb_clk,
+ [RPM_SFPB_A_CLK] = &ipq806x_sfpb_a_clk,
+ [RPM_NSS_FABRIC_0_CLK] = &ipq806x_nss_fabric_0_clk,
+ [RPM_NSS_FABRIC_0_A_CLK] = &ipq806x_nss_fabric_0_a_clk,
+ [RPM_NSS_FABRIC_1_CLK] = &ipq806x_nss_fabric_1_clk,
+ [RPM_NSS_FABRIC_1_A_CLK] = &ipq806x_nss_fabric_1_a_clk,
+};
+
static const struct rpm_clk_desc rpm_clk_apq8064 = {
.clks = apq8064_clks,
.num_clks = ARRAY_SIZE(apq8064_clks),
};
+static const struct rpm_clk_desc rpm_clk_ipq806x = {
+ .clks = ipq806x_clks,
+ .num_clks = ARRAY_SIZE(ipq806x_clks),
+};
+
static const struct of_device_id rpm_clk_match_table[] = {
{ .compatible = "qcom,rpmcc-apq8064", .data = &rpm_clk_apq8064 },
+ { .compatible = "qcom,rpmcc-ipq806x", .data = &rpm_clk_ipq806x },
{ }
};
MODULE_DEVICE_TABLE(of, rpm_clk_match_table);
--- a/include/dt-bindings/clock/qcom,rpmcc.h
+++ b/include/dt-bindings/clock/qcom,rpmcc.h
@@ -37,6 +37,10 @@
#define RPM_SYS_FABRIC_A_CLK 19
#define RPM_SFPB_CLK 20
#define RPM_SFPB_A_CLK 21
+#define RPM_NSS_FABRIC_0_CLK 22
+#define RPM_NSS_FABRIC_0_A_CLK 23
+#define RPM_NSS_FABRIC_1_CLK 24
+#define RPM_NSS_FABRIC_1_A_CLK 25
/* msm8916 */
#define RPM_SMD_XO_CLK_SRC 0

View file

@ -0,0 +1,11 @@
--- a/arch/arm/Makefile
+++ b/arch/arm/Makefile
@@ -65,7 +65,7 @@ KBUILD_CFLAGS += $(call cc-option,-fno-i
# macro, but instead defines a whole series of macros which makes
# testing for a specific architecture or later rather impossible.
arch-$(CONFIG_CPU_32v7M) =-D__LINUX_ARM_ARCH__=7 -march=armv7-m -Wa,-march=armv7-m
-arch-$(CONFIG_CPU_32v7) =-D__LINUX_ARM_ARCH__=7 $(call cc-option,-march=armv7-a,-march=armv5t -Wa$(comma)-march=armv7-a)
+arch-$(CONFIG_CPU_32v7) =-D__LINUX_ARM_ARCH__=7 -mcpu=cortex-a15
arch-$(CONFIG_CPU_32v6) =-D__LINUX_ARM_ARCH__=6 $(call cc-option,-march=armv6,-march=armv5t -Wa$(comma)-march=armv6)
# Only override the compiler option if ARMv6. The ARMv6K extensions are
# always available in ARMv7

View file

@ -0,0 +1,166 @@
From 4267880319bc1a2270d352e0ded6d6386242a7ef Mon Sep 17 00:00:00 2001
From: John Crispin <blogic@openwrt.org>
Date: Tue, 12 Aug 2014 20:49:27 +0200
Subject: [PATCH 24/53] GPIO: add named gpio exports
Signed-off-by: John Crispin <blogic@openwrt.org>
---
drivers/gpio/gpiolib-of.c | 68 +++++++++++++++++++++++++++++++++++++++++
drivers/gpio/gpiolib-sysfs.c | 10 +++++-
include/asm-generic/gpio.h | 6 ++++
include/linux/gpio/consumer.h | 8 +++++
4 files changed, 91 insertions(+), 1 deletion(-)
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -23,6 +23,8 @@
#include <linux/pinctrl/pinctrl.h>
#include <linux/slab.h>
#include <linux/gpio/machine.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
#include "gpiolib.h"
@@ -538,3 +540,69 @@ void of_gpiochip_remove(struct gpio_chip
gpiochip_remove_pin_ranges(chip);
of_node_put(chip->of_node);
}
+
+static struct of_device_id gpio_export_ids[] = {
+ { .compatible = "gpio-export" },
+ { /* sentinel */ }
+};
+
+static int __init of_gpio_export_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device_node *cnp;
+ u32 val;
+ int nb = 0;
+
+ for_each_child_of_node(np, cnp) {
+ const char *name = NULL;
+ int gpio;
+ bool dmc;
+ int max_gpio = 1;
+ int i;
+
+ of_property_read_string(cnp, "gpio-export,name", &name);
+
+ if (!name)
+ max_gpio = of_gpio_count(cnp);
+
+ for (i = 0; i < max_gpio; i++) {
+ unsigned flags = 0;
+ enum of_gpio_flags of_flags;
+
+ gpio = of_get_gpio_flags(cnp, i, &of_flags);
+
+ if (of_flags == OF_GPIO_ACTIVE_LOW)
+ flags |= GPIOF_ACTIVE_LOW;
+
+ if (!of_property_read_u32(cnp, "gpio-export,output", &val))
+ flags |= val ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
+ else
+ flags |= GPIOF_IN;
+
+ if (devm_gpio_request_one(&pdev->dev, gpio, flags, name ? name : of_node_full_name(np)))
+ continue;
+
+ dmc = of_property_read_bool(cnp, "gpio-export,direction_may_change");
+ gpio_export_with_name(gpio, dmc, name);
+ nb++;
+ }
+ }
+
+ dev_info(&pdev->dev, "%d gpio(s) exported\n", nb);
+
+ return 0;
+}
+
+static struct platform_driver gpio_export_driver = {
+ .driver = {
+ .name = "gpio-export",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(gpio_export_ids),
+ },
+};
+
+static int __init of_gpio_export_init(void)
+{
+ return platform_driver_probe(&gpio_export_driver, of_gpio_export_probe);
+}
+device_initcall(of_gpio_export_init);
--- a/drivers/gpio/gpiolib-sysfs.c
+++ b/drivers/gpio/gpiolib-sysfs.c
@@ -544,7 +544,7 @@ static struct class gpio_class = {
*
* Returns zero on success, else an error.
*/
-int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
+int __gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name)
{
struct gpio_chip *chip;
struct gpio_device *gdev;
@@ -606,6 +606,8 @@ int gpiod_export(struct gpio_desc *desc,
offset = gpio_chip_hwgpio(desc);
if (chip->names && chip->names[offset])
ioname = chip->names[offset];
+ if (name)
+ ioname = name;
dev = device_create_with_groups(&gpio_class, &gdev->dev,
MKDEV(0, 0), data, gpio_groups,
@@ -627,6 +629,12 @@ err_unlock:
gpiod_dbg(desc, "%s: status %d\n", __func__, status);
return status;
}
+EXPORT_SYMBOL_GPL(__gpiod_export);
+
+int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
+{
+ return __gpiod_export(desc, direction_may_change, NULL);
+}
EXPORT_SYMBOL_GPL(gpiod_export);
static int match_export(struct device *dev, const void *desc)
--- a/include/asm-generic/gpio.h
+++ b/include/asm-generic/gpio.h
@@ -126,6 +126,12 @@ static inline int gpio_export(unsigned g
return gpiod_export(gpio_to_desc(gpio), direction_may_change);
}
+int __gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name);
+static inline int gpio_export_with_name(unsigned gpio, bool direction_may_change, const char *name)
+{
+ return __gpiod_export(gpio_to_desc(gpio), direction_may_change, name);
+}
+
static inline int gpio_export_link(struct device *dev, const char *name,
unsigned gpio)
{
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -427,6 +427,7 @@ static inline struct gpio_desc *devm_get
#if IS_ENABLED(CONFIG_GPIOLIB) && IS_ENABLED(CONFIG_GPIO_SYSFS)
+int _gpiod_export(struct gpio_desc *desc, bool direction_may_change, const char *name);
int gpiod_export(struct gpio_desc *desc, bool direction_may_change);
int gpiod_export_link(struct device *dev, const char *name,
struct gpio_desc *desc);
@@ -434,6 +435,13 @@ void gpiod_unexport(struct gpio_desc *de
#else /* CONFIG_GPIOLIB && CONFIG_GPIO_SYSFS */
+static inline int _gpiod_export(struct gpio_desc *desc,
+ bool direction_may_change,
+ const char *name)
+{
+ return -ENOSYS;
+}
+
static inline int gpiod_export(struct gpio_desc *desc,
bool direction_may_change)
{

View file

@ -0,0 +1,185 @@
Author: Adrian Panella <ianchi74@outlook.com>
Date: Fri Jun 10 19:10:15 2016 -0500
generic: Mangle bootloader's kernel arguments
The command-line arguments provided by the boot loader will be
appended to a new device tree property: bootloader-args.
If there is a property "append-rootblock" in DT under /chosen
and a root= option in bootloaders command line it will be parsed
and added to DT bootargs with the form: <append-rootblock>XX.
Only command line ATAG will be processed, the rest of the ATAGs
sent by bootloader will be ignored.
This is usefull in dual boot systems, to get the current root partition
without afecting the rest of the system.
Signed-off-by: Adrian Panella <ianchi74@outlook.com>
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -1949,6 +1949,17 @@ config ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEN
The command-line arguments provided by the boot loader will be
appended to the the device tree bootargs property.
+config ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE
+ bool "Append rootblock parsing bootloader's kernel arguments"
+ help
+ The command-line arguments provided by the boot loader will be
+ appended to a new device tree property: bootloader-args.
+ If there is a property "append-rootblock" in DT under /chosen
+ and a root= option in bootloaders command line it will be parsed
+ and added to DT bootargs with the form: <append-rootblock>XX.
+ Only command line ATAG will be processed, the rest of the ATAGs
+ sent by bootloader will be ignored.
+
endchoice
config CMDLINE
--- a/arch/arm/boot/compressed/atags_to_fdt.c
+++ b/arch/arm/boot/compressed/atags_to_fdt.c
@@ -3,6 +3,8 @@
#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_EXTEND)
#define do_extend_cmdline 1
+#elif defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
+#define do_extend_cmdline 1
#else
#define do_extend_cmdline 0
#endif
@@ -66,6 +68,59 @@ static uint32_t get_cell_size(const void
return cell_size;
}
+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
+
+static char *append_rootblock(char *dest, const char *str, int len, void *fdt)
+{
+ char *ptr, *end;
+ char *root="root=";
+ int i, l;
+ const char *rootblock;
+
+ //ARM doesn't have __HAVE_ARCH_STRSTR, so search manually
+ ptr = str - 1;
+
+ do {
+ //first find an 'r' at the begining or after a space
+ do {
+ ptr++;
+ ptr = strchr(ptr, 'r');
+ if(!ptr) return dest;
+
+ } while (ptr != str && *(ptr-1) != ' ');
+
+ //then check for the rest
+ for(i = 1; i <= 4; i++)
+ if(*(ptr+i) != *(root+i)) break;
+
+ } while (i != 5);
+
+ end = strchr(ptr, ' ');
+ end = end ? (end - 1) : (strchr(ptr, 0) - 1);
+
+ //find partition number (assumes format root=/dev/mtdXX | /dev/mtdblockXX | yy:XX )
+ for( i = 0; end >= ptr && *end >= '0' && *end <= '9'; end--, i++);
+ ptr = end + 1;
+
+ /* if append-rootblock property is set use it to append to command line */
+ rootblock = getprop(fdt, "/chosen", "append-rootblock", &l);
+ if(rootblock != NULL) {
+ if(*dest != ' ') {
+ *dest = ' ';
+ dest++;
+ len++;
+ }
+ if (len + l + i <= COMMAND_LINE_SIZE) {
+ memcpy(dest, rootblock, l);
+ dest += l - 1;
+ memcpy(dest, ptr, i);
+ dest += i;
+ }
+ }
+ return dest;
+}
+#endif
+
static void merge_fdt_bootargs(void *fdt, const char *fdt_cmdline)
{
char cmdline[COMMAND_LINE_SIZE];
@@ -85,12 +140,21 @@ static void merge_fdt_bootargs(void *fdt
/* and append the ATAG_CMDLINE */
if (fdt_cmdline) {
+
+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
+ //save original bootloader args
+ //and append ubi.mtd with root partition number to current cmdline
+ setprop_string(fdt, "/chosen", "bootloader-args", fdt_cmdline);
+ ptr = append_rootblock(ptr, fdt_cmdline, len, fdt);
+
+#else
len = strlen(fdt_cmdline);
if (ptr - cmdline + len + 2 < COMMAND_LINE_SIZE) {
*ptr++ = ' ';
memcpy(ptr, fdt_cmdline, len);
ptr += len;
}
+#endif
}
*ptr = '\0';
@@ -147,7 +211,9 @@ int atags_to_fdt(void *atag_list, void *
else
setprop_string(fdt, "/chosen", "bootargs",
atag->u.cmdline.cmdline);
- } else if (atag->hdr.tag == ATAG_MEM) {
+ }
+#ifndef CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE
+ else if (atag->hdr.tag == ATAG_MEM) {
if (memcount >= sizeof(mem_reg_property)/4)
continue;
if (!atag->u.mem.size)
@@ -186,6 +252,10 @@ int atags_to_fdt(void *atag_list, void *
setprop(fdt, "/memory", "reg", mem_reg_property,
4 * memcount * memsize);
}
+#else
+
+ }
+#endif
return fdt_pack(fdt);
}
--- a/init/main.c
+++ b/init/main.c
@@ -88,6 +88,10 @@
#include <asm/sections.h>
#include <asm/cacheflush.h>
+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
+#include <linux/of.h>
+#endif
+
static int kernel_init(void *);
extern void init_IRQ(void);
@@ -538,6 +542,18 @@ asmlinkage __visible void __init start_k
page_alloc_init();
pr_notice("Kernel command line: %s\n", boot_command_line);
+
+#if defined(CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE)
+ //Show bootloader's original command line for reference
+ if(of_chosen) {
+ const char *prop = of_get_property(of_chosen, "bootloader-args", NULL);
+ if(prop)
+ pr_notice("Bootloader command line (ignored): %s\n", prop);
+ else
+ pr_notice("Bootloader command line not present\n");
+ }
+#endif
+
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,

File diff suppressed because it is too large Load diff