mirror of https://github.com/hak5/openwrt.git
ipq806x: remove v4.4 support
Signed-off-by: John Crispin <john@phrozen.org>openwrt-18.06
parent
ef6c4cc4ee
commit
3a3564ead5
|
@ -1,426 +0,0 @@
|
|||
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_HAS_ATOMIC64_DEC_IF_POSITIVE=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_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_ARCH_WANT_OPTIONAL_GPIOLIB=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 is not set
|
||||
CONFIG_ARM_ATAG_DTB_COMPAT_CMDLINE_MANGLE=y
|
||||
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_PHYS_VIRT=y
|
||||
CONFIG_ARM_QCOM_CPUFREQ=y
|
||||
CONFIG_ARM_QCOM_CPUIDLE=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_BLK_DEV_LOOP=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_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_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_IDLE=y
|
||||
CONFIG_CPU_IDLE_GOV_LADDER=y
|
||||
CONFIG_CPU_IDLE_GOV_MENU=y
|
||||
CONFIG_CPU_PABRT_V7=y
|
||||
CONFIG_CPU_PM=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_DEFLATE=y
|
||||
CONFIG_CRYPTO_LZO=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_DWMAC_SUNXI is not set
|
||||
# CONFIG_DW_DMAC_PCI is not set
|
||||
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_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_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_DEVRES=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_BOOTMEM_INFO_NODE is not set
|
||||
CONFIG_HAVE_BPF_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_ATTRS=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_LPAE is not set
|
||||
CONFIG_IOMMU_SUPPORT=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_KPSS_XCC=y
|
||||
CONFIG_KRAITCC=y
|
||||
CONFIG_KRAIT_CLOCKS=y
|
||||
CONFIG_KRAIT_L2_ACCESSORS=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_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_LCC_8960 is not set
|
||||
CONFIG_MSM_MMCC_8960=y
|
||||
CONFIG_MSM_MMCC_8974=y
|
||||
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_QCOM_SMEM_PARTS=y
|
||||
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_AR8XXX is not set
|
||||
CONFIG_NET_DSA_HWMON=y
|
||||
CONFIG_NET_FLOW_LIMIT=y
|
||||
CONFIG_NET_PTP_CLASSIFY=y
|
||||
CONFIG_NET_SWITCHDEV=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_MTD=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_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_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_IPQ8064=y
|
||||
CONFIG_PINCTRL_MSM=y
|
||||
# CONFIG_PINCTRL_MSM8660 is not set
|
||||
# CONFIG_PINCTRL_MSM8916 is not set
|
||||
# CONFIG_PINCTRL_MSM8960 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_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_GDSC=y
|
||||
CONFIG_QCOM_GSBI=y
|
||||
CONFIG_QCOM_HFPLL=y
|
||||
CONFIG_QCOM_PM=y
|
||||
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_TSENS=y
|
||||
CONFIG_QCOM_WDT=y
|
||||
CONFIG_RAS=y
|
||||
CONFIG_RATIONAL=y
|
||||
CONFIG_RCU_CPU_STALL_TIMEOUT=21
|
||||
CONFIG_RCU_STALL_COMMON=y
|
||||
CONFIG_REGMAP=y
|
||||
CONFIG_REGMAP_MMIO=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_RPS=y
|
||||
CONFIG_RTC_CLASS=y
|
||||
# CONFIG_RTC_DRV_CMOS is not set
|
||||
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_SMP=y
|
||||
CONFIG_SMP_ON_UP=y
|
||||
CONFIG_SPARSE_IRQ=y
|
||||
CONFIG_SPI=y
|
||||
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_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_SUPPORT=y
|
||||
CONFIG_USE_OF=y
|
||||
CONFIG_VDSO=y
|
||||
CONFIG_VECTORS_BASE=0xffff0000
|
||||
CONFIG_VFP=y
|
||||
CONFIG_VFPv3=y
|
||||
CONFIG_WATCHDOG_CORE=y
|
||||
# CONFIG_WATCHDOG_SYSFS is not set
|
||||
# CONFIG_WL_TI is not set
|
||||
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
|
||||
CONFIG_ZONE_DMA_FLAG=0
|
|
@ -1,135 +0,0 @@
|
|||
From ee15faffef11309219aa87a24efc86f6dd13f7cb Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 26 Oct 2015 17:11:32 -0700
|
||||
Subject: clk: qcom: common: Add API to register board clocks backwards
|
||||
compatibly
|
||||
|
||||
We want to put the XO board clocks into the dt files, but we also
|
||||
need to be backwards compatible with an older dtb. Add an API to
|
||||
the common code to do this. This also makes a place for us to
|
||||
handle the case when the RPM clock driver is enabled and we don't
|
||||
want to register the fixed factor clock.
|
||||
|
||||
Cc: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
---
|
||||
drivers/clk/qcom/common.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/common.h | 4 +++
|
||||
2 files changed, 91 insertions(+)
|
||||
|
||||
--- a/drivers/clk/qcom/common.c
|
||||
+++ b/drivers/clk/qcom/common.c
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/reset-controller.h>
|
||||
+#include <linux/of.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "clk-rcg.h"
|
||||
@@ -88,6 +89,92 @@ static void qcom_cc_gdsc_unregister(void
|
||||
gdsc_unregister(data);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Backwards compatibility with old DTs. Register a pass-through factor 1/1
|
||||
+ * clock to translate 'path' clk into 'name' clk and regsiter the 'path'
|
||||
+ * clk as a fixed rate clock if it isn't present.
|
||||
+ */
|
||||
+static int _qcom_cc_register_board_clk(struct device *dev, const char *path,
|
||||
+ const char *name, unsigned long rate,
|
||||
+ bool add_factor)
|
||||
+{
|
||||
+ struct device_node *node = NULL;
|
||||
+ struct device_node *clocks_node;
|
||||
+ struct clk_fixed_factor *factor;
|
||||
+ struct clk_fixed_rate *fixed;
|
||||
+ struct clk *clk;
|
||||
+ struct clk_init_data init_data = { };
|
||||
+
|
||||
+ clocks_node = of_find_node_by_path("/clocks");
|
||||
+ if (clocks_node)
|
||||
+ node = of_find_node_by_name(clocks_node, path);
|
||||
+ of_node_put(clocks_node);
|
||||
+
|
||||
+ if (!node) {
|
||||
+ fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL);
|
||||
+ if (!fixed)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ fixed->fixed_rate = rate;
|
||||
+ fixed->hw.init = &init_data;
|
||||
+
|
||||
+ init_data.name = path;
|
||||
+ init_data.flags = CLK_IS_ROOT;
|
||||
+ init_data.ops = &clk_fixed_rate_ops;
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &fixed->hw);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ }
|
||||
+ of_node_put(node);
|
||||
+
|
||||
+ if (add_factor) {
|
||||
+ factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL);
|
||||
+ if (!factor)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ factor->mult = factor->div = 1;
|
||||
+ factor->hw.init = &init_data;
|
||||
+
|
||||
+ init_data.name = name;
|
||||
+ init_data.parent_names = &path;
|
||||
+ init_data.num_parents = 1;
|
||||
+ init_data.flags = 0;
|
||||
+ init_data.ops = &clk_fixed_factor_ops;
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &factor->hw);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+int qcom_cc_register_board_clk(struct device *dev, const char *path,
|
||||
+ const char *name, unsigned long rate)
|
||||
+{
|
||||
+ bool add_factor = true;
|
||||
+ struct device_node *node;
|
||||
+
|
||||
+ /* The RPM clock driver will add the factor clock if present */
|
||||
+ if (IS_ENABLED(CONFIG_QCOM_RPMCC)) {
|
||||
+ node = of_find_compatible_node(NULL, NULL, "qcom,rpmcc");
|
||||
+ if (of_device_is_available(node))
|
||||
+ add_factor = false;
|
||||
+ of_node_put(node);
|
||||
+ }
|
||||
+
|
||||
+ return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(qcom_cc_register_board_clk);
|
||||
+
|
||||
+int qcom_cc_register_sleep_clk(struct device *dev)
|
||||
+{
|
||||
+ return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src",
|
||||
+ 32768, true);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk);
|
||||
+
|
||||
int qcom_cc_really_probe(struct platform_device *pdev,
|
||||
const struct qcom_cc_desc *desc, struct regmap *regmap)
|
||||
{
|
||||
--- a/drivers/clk/qcom/common.h
|
||||
+++ b/drivers/clk/qcom/common.h
|
||||
@@ -37,6 +37,10 @@ extern const struct freq_tbl *qcom_find_
|
||||
extern int qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map,
|
||||
u8 src);
|
||||
|
||||
+extern int qcom_cc_register_board_clk(struct device *dev, const char *path,
|
||||
+ const char *name, unsigned long rate);
|
||||
+extern int qcom_cc_register_sleep_clk(struct device *dev);
|
||||
+
|
||||
extern struct regmap *qcom_cc_map(struct platform_device *pdev,
|
||||
const struct qcom_cc_desc *desc);
|
||||
extern int qcom_cc_really_probe(struct platform_device *pdev,
|
|
@ -1,35 +0,0 @@
|
|||
From add479eeb1a208a31ab913ae7c97506a81383079 Mon Sep 17 00:00:00 2001
|
||||
From: Philipp Zabel <p.zabel@pengutronix.de>
|
||||
Date: Thu, 25 Feb 2016 10:45:12 +0100
|
||||
Subject: clk: qcom: Make reset_control_ops const
|
||||
|
||||
The qcom_reset_ops structure is never modified. Make it const.
|
||||
|
||||
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
---
|
||||
drivers/clk/qcom/reset.c | 2 +-
|
||||
drivers/clk/qcom/reset.h | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/clk/qcom/reset.c
|
||||
+++ b/drivers/clk/qcom/reset.c
|
||||
@@ -55,7 +55,7 @@ qcom_reset_deassert(struct reset_control
|
||||
return regmap_update_bits(rst->regmap, map->reg, mask, 0);
|
||||
}
|
||||
|
||||
-struct reset_control_ops qcom_reset_ops = {
|
||||
+const struct reset_control_ops qcom_reset_ops = {
|
||||
.reset = qcom_reset,
|
||||
.assert = qcom_reset_assert,
|
||||
.deassert = qcom_reset_deassert,
|
||||
--- a/drivers/clk/qcom/reset.h
|
||||
+++ b/drivers/clk/qcom/reset.h
|
||||
@@ -32,6 +32,6 @@ struct qcom_reset_controller {
|
||||
#define to_qcom_reset_controller(r) \
|
||||
container_of(r, struct qcom_reset_controller, rcdev);
|
||||
|
||||
-extern struct reset_control_ops qcom_reset_ops;
|
||||
+extern const struct reset_control_ops qcom_reset_ops;
|
||||
|
||||
#endif
|
|
@ -1,172 +0,0 @@
|
|||
From a085f877a882b465fce74188c9d8efd12bd5acd4 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 26 Oct 2015 18:10:09 -0700
|
||||
Subject: clk: qcom: Move cxo/pxo/xo into dt files
|
||||
|
||||
Put these clocks into the dt files instead of registering them
|
||||
from C code. This provides a few benefits. It allows us to
|
||||
specify the frequency of these clocks at the board level instead
|
||||
of hard-coding them in the driver. It allows us to insert an RPM
|
||||
clock in between the consumers of the crystals and the actual
|
||||
clock. And finally, it helps us transition the GCC driver to use
|
||||
RPM clocks when that configuration is enabled.
|
||||
|
||||
Cc: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
---
|
||||
drivers/clk/qcom/gcc-apq8084.c | 16 +++++++---------
|
||||
drivers/clk/qcom/gcc-ipq806x.c | 14 ++++++--------
|
||||
drivers/clk/qcom/gcc-msm8660.c | 15 +++++++--------
|
||||
drivers/clk/qcom/gcc-msm8960.c | 14 ++++++--------
|
||||
drivers/clk/qcom/gcc-msm8974.c | 17 +++++++----------
|
||||
5 files changed, 33 insertions(+), 43 deletions(-)
|
||||
|
||||
--- a/drivers/clk/qcom/gcc-apq8084.c
|
||||
+++ b/drivers/clk/qcom/gcc-apq8084.c
|
||||
@@ -3607,18 +3607,16 @@ MODULE_DEVICE_TABLE(of, gcc_apq8084_matc
|
||||
|
||||
static int gcc_apq8084_probe(struct platform_device *pdev)
|
||||
{
|
||||
- struct clk *clk;
|
||||
+ int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
- /* Temporary until RPM clocks supported */
|
||||
- clk = clk_register_fixed_rate(dev, "xo", NULL, CLK_IS_ROOT, 19200000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "xo_board", "xo", 19200000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- clk = clk_register_fixed_rate(dev, "sleep_clk_src", NULL,
|
||||
- CLK_IS_ROOT, 32768);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_sleep_clk(dev);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
return qcom_cc_probe(pdev, &gcc_apq8084_desc);
|
||||
}
|
||||
--- a/drivers/clk/qcom/gcc-ipq806x.c
|
||||
+++ b/drivers/clk/qcom/gcc-ipq806x.c
|
||||
@@ -3023,19 +3023,17 @@ MODULE_DEVICE_TABLE(of, gcc_ipq806x_matc
|
||||
|
||||
static int gcc_ipq806x_probe(struct platform_device *pdev)
|
||||
{
|
||||
- struct clk *clk;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
- /* Temporary until RPM clocks supported */
|
||||
- clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 25000000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "cxo_board", "cxo", 25000000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- clk = clk_register_fixed_rate(dev, "pxo", NULL, CLK_IS_ROOT, 25000000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "pxo_board", "pxo", 25000000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
ret = qcom_cc_probe(pdev, &gcc_ipq806x_desc);
|
||||
if (ret)
|
||||
--- a/drivers/clk/qcom/gcc-msm8660.c
|
||||
+++ b/drivers/clk/qcom/gcc-msm8660.c
|
||||
@@ -2720,17 +2720,16 @@ MODULE_DEVICE_TABLE(of, gcc_msm8660_matc
|
||||
|
||||
static int gcc_msm8660_probe(struct platform_device *pdev)
|
||||
{
|
||||
- struct clk *clk;
|
||||
+ int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
- /* Temporary until RPM clocks supported */
|
||||
- clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 19200000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "cxo_board", "cxo", 19200000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- clk = clk_register_fixed_rate(dev, "pxo", NULL, CLK_IS_ROOT, 27000000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "pxo_board", "pxo", 27000000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
return qcom_cc_probe(pdev, &gcc_msm8660_desc);
|
||||
}
|
||||
--- a/drivers/clk/qcom/gcc-msm8960.c
|
||||
+++ b/drivers/clk/qcom/gcc-msm8960.c
|
||||
@@ -3503,7 +3503,6 @@ MODULE_DEVICE_TABLE(of, gcc_msm8960_matc
|
||||
|
||||
static int gcc_msm8960_probe(struct platform_device *pdev)
|
||||
{
|
||||
- struct clk *clk;
|
||||
struct device *dev = &pdev->dev;
|
||||
const struct of_device_id *match;
|
||||
struct platform_device *tsens;
|
||||
@@ -3513,14 +3512,13 @@ static int gcc_msm8960_probe(struct plat
|
||||
if (!match)
|
||||
return -EINVAL;
|
||||
|
||||
- /* Temporary until RPM clocks supported */
|
||||
- clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 19200000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "cxo_board", "cxo", 19200000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
- clk = clk_register_fixed_rate(dev, "pxo", NULL, CLK_IS_ROOT, 27000000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "pxo_board", "pxo", 27000000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
ret = qcom_cc_probe(pdev, match->data);
|
||||
if (ret)
|
||||
--- a/drivers/clk/qcom/gcc-msm8974.c
|
||||
+++ b/drivers/clk/qcom/gcc-msm8974.c
|
||||
@@ -2717,7 +2717,7 @@ static void msm8974_pro_clock_override(v
|
||||
|
||||
static int gcc_msm8974_probe(struct platform_device *pdev)
|
||||
{
|
||||
- struct clk *clk;
|
||||
+ int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
bool pro;
|
||||
const struct of_device_id *id;
|
||||
@@ -2730,16 +2730,13 @@ static int gcc_msm8974_probe(struct plat
|
||||
if (pro)
|
||||
msm8974_pro_clock_override();
|
||||
|
||||
- /* Temporary until RPM clocks supported */
|
||||
- clk = clk_register_fixed_rate(dev, "xo", NULL, CLK_IS_ROOT, 19200000);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
-
|
||||
- /* Should move to DT node? */
|
||||
- clk = clk_register_fixed_rate(dev, "sleep_clk_src", NULL,
|
||||
- CLK_IS_ROOT, 32768);
|
||||
- if (IS_ERR(clk))
|
||||
- return PTR_ERR(clk);
|
||||
+ ret = qcom_cc_register_board_clk(dev, "xo_board", "xo", 19200000);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = qcom_cc_register_sleep_clk(dev);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
return qcom_cc_probe(pdev, &gcc_msm8974_desc);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
From 349290fc9e761aaef6d6882721189f668ec5ff49 Mon Sep 17 00:00:00 2001
|
||||
From: Peter Chen <peter.chen@nxp.com>
|
||||
Date: Fri, 15 Jul 2016 17:38:46 +0800
|
||||
Subject: mfd: qcom_rpm: Add missing of_node_put after calling of_parse_phandle
|
||||
|
||||
of_node_put needs to be called when the device node which is got
|
||||
from of_parse_phandle has finished using.
|
||||
|
||||
Signed-off-by: Peter Chen <peter.chen@nxp.com>
|
||||
Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>
|
||||
Signed-off-by: Lee Jones <lee.jones@linaro.org>
|
||||
---
|
||||
drivers/mfd/qcom_rpm.c | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
--- a/drivers/mfd/qcom_rpm.c
|
||||
+++ b/drivers/mfd/qcom_rpm.c
|
||||
@@ -538,6 +538,7 @@ static int qcom_rpm_probe(struct platfor
|
||||
}
|
||||
|
||||
rpm->ipc_regmap = syscon_node_to_regmap(syscon_np);
|
||||
+ of_node_put(syscon_np);
|
||||
if (IS_ERR(rpm->ipc_regmap))
|
||||
return PTR_ERR(rpm->ipc_regmap);
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
From 3526403353c2a1b94c3181f900582626d23c339b Mon Sep 17 00:00:00 2001
|
||||
From: Linus Walleij <linus.walleij@linaro.org>
|
||||
Date: Thu, 18 Aug 2016 20:40:45 +0200
|
||||
Subject: mfd: qcom_rpm: Handle message RAM clock
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The MSM8660, APQ8060, IPQ806x and MSM8960 have a GCC clock
|
||||
to the message RAM used by the RPM. This needs to be enabled
|
||||
for messages to pass through. This is a crude solution that
|
||||
simply prepare/enable at probe() and disable/unprepare
|
||||
at remove(). More elaborate PM is probably possible to
|
||||
add later.
|
||||
|
||||
The construction uses IS_ERR() to gracefully handle the
|
||||
platforms that do not provide a message RAM clock. It will
|
||||
bail out of probe only if the clock is hitting a probe
|
||||
deferral situation.
|
||||
|
||||
Of course this requires the proper device tree set-up:
|
||||
|
||||
rpm: rpm@104000 {
|
||||
compatible = "qcom,rpm-msm8660";
|
||||
clocks = <&gcc RPM_MSG_RAM_H_CLK>;
|
||||
clock-names = "ram";
|
||||
...
|
||||
};
|
||||
|
||||
I have provided this in the MSM8660 device tree, and will
|
||||
provide patches for the other targets.
|
||||
|
||||
Cc: Björn Andersson <bjorn.andersson@linaro.org>
|
||||
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
|
||||
Signed-off-by: Lee Jones <lee.jones@linaro.org>
|
||||
---
|
||||
drivers/mfd/qcom_rpm.c | 20 ++++++++++++++++++++
|
||||
1 file changed, 20 insertions(+)
|
||||
|
||||
--- a/drivers/mfd/qcom_rpm.c
|
||||
+++ b/drivers/mfd/qcom_rpm.c
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <linux/mfd/qcom_rpm.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
+#include <linux/clk.h>
|
||||
|
||||
#include <dt-bindings/mfd/qcom-rpm.h>
|
||||
|
||||
@@ -48,6 +49,7 @@ struct qcom_rpm {
|
||||
struct regmap *ipc_regmap;
|
||||
unsigned ipc_offset;
|
||||
unsigned ipc_bit;
|
||||
+ struct clk *ramclk;
|
||||
|
||||
struct completion ack;
|
||||
struct mutex lock;
|
||||
@@ -503,6 +505,20 @@ static int qcom_rpm_probe(struct platfor
|
||||
mutex_init(&rpm->lock);
|
||||
init_completion(&rpm->ack);
|
||||
|
||||
+ /* Enable message RAM clock */
|
||||
+ rpm->ramclk = devm_clk_get(&pdev->dev, "ram");
|
||||
+ if (IS_ERR(rpm->ramclk)) {
|
||||
+ ret = PTR_ERR(rpm->ramclk);
|
||||
+ if (ret == -EPROBE_DEFER)
|
||||
+ return ret;
|
||||
+ /*
|
||||
+ * Fall through in all other cases, as the clock is
|
||||
+ * optional. (Does not exist on all platforms.)
|
||||
+ */
|
||||
+ rpm->ramclk = NULL;
|
||||
+ }
|
||||
+ clk_prepare_enable(rpm->ramclk); /* Accepts NULL */
|
||||
+
|
||||
irq_ack = platform_get_irq_byname(pdev, "ack");
|
||||
if (irq_ack < 0) {
|
||||
dev_err(&pdev->dev, "required ack interrupt missing\n");
|
||||
@@ -621,7 +637,11 @@ static int qcom_rpm_probe(struct platfor
|
||||
|
||||
static int qcom_rpm_remove(struct platform_device *pdev)
|
||||
{
|
||||
+ struct qcom_rpm *rpm = dev_get_drvdata(&pdev->dev);
|
||||
+
|
||||
of_platform_depopulate(&pdev->dev);
|
||||
+ clk_disable_unprepare(rpm->ramclk);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
From 2165bf524da5f5e496d1cdb8c5afae1345ecce1e Mon Sep 17 00:00:00 2001
|
||||
From: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Date: Mon, 16 Nov 2015 12:27:59 -0500
|
||||
Subject: watchdog: core: add restart handler support
|
||||
|
||||
Many watchdog drivers implement the same code to register a restart
|
||||
handler. This patch provides a generic way to set such a function.
|
||||
|
||||
The patch adds a new restart watchdog operation. If a restart priority
|
||||
greater than 0 is needed, the driver can call
|
||||
watchdog_set_restart_priority to set it.
|
||||
|
||||
Suggested-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
Signed-off-by: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
Documentation/watchdog/watchdog-kernel-api.txt | 19 ++++++++++
|
||||
drivers/watchdog/watchdog_core.c | 48 ++++++++++++++++++++++++++
|
||||
include/linux/watchdog.h | 6 ++++
|
||||
3 files changed, 73 insertions(+)
|
||||
|
||||
--- a/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
+++ b/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
@@ -53,6 +53,7 @@ struct watchdog_device {
|
||||
unsigned int timeout;
|
||||
unsigned int min_timeout;
|
||||
unsigned int max_timeout;
|
||||
+ struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
struct mutex lock;
|
||||
unsigned long status;
|
||||
@@ -75,6 +76,10 @@ It contains following fields:
|
||||
* timeout: the watchdog timer's timeout value (in seconds).
|
||||
* min_timeout: the watchdog timer's minimum timeout value (in seconds).
|
||||
* max_timeout: the watchdog timer's maximum timeout value (in seconds).
|
||||
+* restart_nb: notifier block that is registered for machine restart, for
|
||||
+ internal use only. If a watchdog is capable of restarting the machine, it
|
||||
+ should define ops->restart. Priority can be changed through
|
||||
+ watchdog_set_restart_priority.
|
||||
* bootstatus: status of the device after booting (reported with watchdog
|
||||
WDIOF_* status bits).
|
||||
* driver_data: a pointer to the drivers private data of a watchdog device.
|
||||
@@ -100,6 +105,7 @@ struct watchdog_ops {
|
||||
unsigned int (*status)(struct watchdog_device *);
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
+ int (*restart)(struct watchdog_device *);
|
||||
void (*ref)(struct watchdog_device *);
|
||||
void (*unref)(struct watchdog_device *);
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
@@ -164,6 +170,8 @@ they are supported. These optional routi
|
||||
(Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the
|
||||
watchdog's info structure).
|
||||
* get_timeleft: this routines returns the time that's left before a reset.
|
||||
+* restart: this routine restarts the machine. It returns 0 on success or a
|
||||
+ negative errno code for failure.
|
||||
* ref: the operation that calls kref_get on the kref of a dynamically
|
||||
allocated watchdog_device struct.
|
||||
* unref: the operation that calls kref_put on the kref of a dynamically
|
||||
@@ -231,3 +239,14 @@ the device tree (if the module timeout p
|
||||
to set the default timeout value as timeout value in the watchdog_device and
|
||||
then use this function to set the user "preferred" timeout value.
|
||||
This routine returns zero on success and a negative errno code for failure.
|
||||
+
|
||||
+To change the priority of the restart handler the following helper should be
|
||||
+used:
|
||||
+
|
||||
+void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority);
|
||||
+
|
||||
+User should follow the following guidelines for setting the priority:
|
||||
+* 0: should be called in last resort, has limited restart capabilities
|
||||
+* 128: default restart handler, use if no other handler is expected to be
|
||||
+ available, and/or if restart is sufficient to restart the entire system
|
||||
+* 255: highest priority, will preempt all other restart handlers
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <linux/types.h> /* For standard types */
|
||||
#include <linux/errno.h> /* For the -ENODEV/... values */
|
||||
#include <linux/kernel.h> /* For printk/panic/... */
|
||||
+#include <linux/reboot.h> /* For restart handler */
|
||||
#include <linux/watchdog.h> /* For watchdog specific items */
|
||||
#include <linux/init.h> /* For __init/__exit/... */
|
||||
#include <linux/idr.h> /* For ida_* macros */
|
||||
@@ -137,6 +138,41 @@ int watchdog_init_timeout(struct watchdo
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(watchdog_init_timeout);
|
||||
|
||||
+static int watchdog_restart_notifier(struct notifier_block *nb,
|
||||
+ unsigned long action, void *data)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = container_of(nb, struct watchdog_device,
|
||||
+ restart_nb);
|
||||
+
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = wdd->ops->restart(wdd);
|
||||
+ if (ret)
|
||||
+ return NOTIFY_BAD;
|
||||
+
|
||||
+ return NOTIFY_DONE;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * watchdog_set_restart_priority - Change priority of restart handler
|
||||
+ * @wdd: watchdog device
|
||||
+ * @priority: priority of the restart handler, should follow these guidelines:
|
||||
+ * 0: use watchdog's restart function as last resort, has limited restart
|
||||
+ * capabilies
|
||||
+ * 128: default restart handler, use if no other handler is expected to be
|
||||
+ * available and/or if restart is sufficient to restart the entire system
|
||||
+ * 255: preempt all other handlers
|
||||
+ *
|
||||
+ * If a wdd->ops->restart function is provided when watchdog_register_device is
|
||||
+ * called, it will be registered as a restart handler with the priority given
|
||||
+ * here.
|
||||
+ */
|
||||
+void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority)
|
||||
+{
|
||||
+ wdd->restart_nb.priority = priority;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(watchdog_set_restart_priority);
|
||||
+
|
||||
static int __watchdog_register_device(struct watchdog_device *wdd)
|
||||
{
|
||||
int ret, id = -1, devno;
|
||||
@@ -202,6 +238,15 @@ static int __watchdog_register_device(st
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ if (wdd->ops->restart) {
|
||||
+ wdd->restart_nb.notifier_call = watchdog_restart_notifier;
|
||||
+
|
||||
+ ret = register_restart_handler(&wdd->restart_nb);
|
||||
+ if (ret)
|
||||
+ dev_warn(wdd->dev, "Cannot register restart handler (%d)\n",
|
||||
+ ret);
|
||||
+ }
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -238,6 +283,9 @@ static void __watchdog_unregister_device
|
||||
if (wdd == NULL)
|
||||
return;
|
||||
|
||||
+ if (wdd->ops->restart)
|
||||
+ unregister_restart_handler(&wdd->restart_nb);
|
||||
+
|
||||
devno = wdd->cdev.dev;
|
||||
ret = watchdog_dev_unregister(wdd);
|
||||
if (ret)
|
||||
--- a/include/linux/watchdog.h
|
||||
+++ b/include/linux/watchdog.h
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/cdev.h>
|
||||
+#include <linux/notifier.h>
|
||||
#include <uapi/linux/watchdog.h>
|
||||
|
||||
struct watchdog_ops;
|
||||
@@ -26,6 +27,7 @@ struct watchdog_device;
|
||||
* @status: The routine that shows the status of the watchdog device.
|
||||
* @set_timeout:The routine for setting the watchdog devices timeout value (in seconds).
|
||||
* @get_timeleft:The routine that gets the time left before a reset (in seconds).
|
||||
+ * @restart: The routine for restarting the machine.
|
||||
* @ref: The ref operation for dyn. allocated watchdog_device structs
|
||||
* @unref: The unref operation for dyn. allocated watchdog_device structs
|
||||
* @ioctl: The routines that handles extra ioctl calls.
|
||||
@@ -45,6 +47,7 @@ struct watchdog_ops {
|
||||
unsigned int (*status)(struct watchdog_device *);
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
+ int (*restart)(struct watchdog_device *);
|
||||
void (*ref)(struct watchdog_device *);
|
||||
void (*unref)(struct watchdog_device *);
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
@@ -62,6 +65,7 @@ struct watchdog_ops {
|
||||
* @timeout: The watchdog devices timeout value (in seconds).
|
||||
* @min_timeout:The watchdog devices minimum timeout value (in seconds).
|
||||
* @max_timeout:The watchdog devices maximum timeout value (in seconds).
|
||||
+ * @restart_nb: The notifier block to register a restart function.
|
||||
* @driver-data:Pointer to the drivers private data.
|
||||
* @lock: Lock for watchdog core internal use only.
|
||||
* @status: Field that contains the devices internal status bits.
|
||||
@@ -88,6 +92,7 @@ struct watchdog_device {
|
||||
unsigned int timeout;
|
||||
unsigned int min_timeout;
|
||||
unsigned int max_timeout;
|
||||
+ struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
struct mutex lock;
|
||||
unsigned long status;
|
||||
@@ -142,6 +147,7 @@ static inline void *watchdog_get_drvdata
|
||||
}
|
||||
|
||||
/* drivers/watchdog/watchdog_core.c */
|
||||
+void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority);
|
||||
extern int watchdog_init_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout_parm, struct device *dev);
|
||||
extern int watchdog_register_device(struct watchdog_device *);
|
|
@ -1,160 +0,0 @@
|
|||
From e131319669e0ef5e6fcd75174daeffa40492135c Mon Sep 17 00:00:00 2001
|
||||
From: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Date: Fri, 20 Nov 2015 16:54:51 -0500
|
||||
Subject: watchdog: core: add reboot notifier support
|
||||
|
||||
Many watchdog drivers register a reboot notifier in order to stop the
|
||||
watchdog on system reboot. Thus we can factorize this code in the
|
||||
watchdog core.
|
||||
|
||||
For that purpose, a new notifier block is added in watchdog_device for
|
||||
internal use only, as well as a new watchdog_stop_on_reboot helper
|
||||
function.
|
||||
|
||||
If this helper is called, watchdog core registers the related notifier
|
||||
block and will stop the watchdog when SYS_HALT or SYS_DOWN is received.
|
||||
|
||||
Since this operation can be critical on some platforms, abort the device
|
||||
registration if the reboot notifier registration fails.
|
||||
|
||||
Suggested-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
Signed-off-by: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
Documentation/watchdog/watchdog-kernel-api.txt | 8 ++++++
|
||||
drivers/watchdog/watchdog_core.c | 37 ++++++++++++++++++++++++++
|
||||
include/linux/watchdog.h | 9 +++++++
|
||||
3 files changed, 54 insertions(+)
|
||||
|
||||
--- a/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
+++ b/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
@@ -53,6 +53,7 @@ struct watchdog_device {
|
||||
unsigned int timeout;
|
||||
unsigned int min_timeout;
|
||||
unsigned int max_timeout;
|
||||
+ struct notifier_block reboot_nb;
|
||||
struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
struct mutex lock;
|
||||
@@ -76,6 +77,9 @@ It contains following fields:
|
||||
* timeout: the watchdog timer's timeout value (in seconds).
|
||||
* min_timeout: the watchdog timer's minimum timeout value (in seconds).
|
||||
* max_timeout: the watchdog timer's maximum timeout value (in seconds).
|
||||
+* reboot_nb: notifier block that is registered for reboot notifications, for
|
||||
+ internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core
|
||||
+ will stop the watchdog on such notifications.
|
||||
* restart_nb: notifier block that is registered for machine restart, for
|
||||
internal use only. If a watchdog is capable of restarting the machine, it
|
||||
should define ops->restart. Priority can be changed through
|
||||
@@ -240,6 +244,10 @@ to set the default timeout value as time
|
||||
then use this function to set the user "preferred" timeout value.
|
||||
This routine returns zero on success and a negative errno code for failure.
|
||||
|
||||
+To disable the watchdog on reboot, the user must call the following helper:
|
||||
+
|
||||
+static inline void watchdog_stop_on_reboot(struct watchdog_device *wdd);
|
||||
+
|
||||
To change the priority of the restart handler the following helper should be
|
||||
used:
|
||||
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -138,6 +138,25 @@ int watchdog_init_timeout(struct watchdo
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(watchdog_init_timeout);
|
||||
|
||||
+static int watchdog_reboot_notifier(struct notifier_block *nb,
|
||||
+ unsigned long code, void *data)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = container_of(nb, struct watchdog_device,
|
||||
+ reboot_nb);
|
||||
+
|
||||
+ if (code == SYS_DOWN || code == SYS_HALT) {
|
||||
+ if (watchdog_active(wdd)) {
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = wdd->ops->stop(wdd);
|
||||
+ if (ret)
|
||||
+ return NOTIFY_BAD;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return NOTIFY_DONE;
|
||||
+}
|
||||
+
|
||||
static int watchdog_restart_notifier(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
@@ -238,6 +257,21 @@ static int __watchdog_register_device(st
|
||||
return ret;
|
||||
}
|
||||
|
||||
+ if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) {
|
||||
+ wdd->reboot_nb.notifier_call = watchdog_reboot_notifier;
|
||||
+
|
||||
+ ret = register_reboot_notifier(&wdd->reboot_nb);
|
||||
+ if (ret) {
|
||||
+ dev_err(wdd->dev, "Cannot register reboot notifier (%d)\n",
|
||||
+ ret);
|
||||
+ watchdog_dev_unregister(wdd);
|
||||
+ device_destroy(watchdog_class, devno);
|
||||
+ ida_simple_remove(&watchdog_ida, wdd->id);
|
||||
+ wdd->dev = NULL;
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if (wdd->ops->restart) {
|
||||
wdd->restart_nb.notifier_call = watchdog_restart_notifier;
|
||||
|
||||
@@ -286,6 +320,9 @@ static void __watchdog_unregister_device
|
||||
if (wdd->ops->restart)
|
||||
unregister_restart_handler(&wdd->restart_nb);
|
||||
|
||||
+ if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status))
|
||||
+ unregister_reboot_notifier(&wdd->reboot_nb);
|
||||
+
|
||||
devno = wdd->cdev.dev;
|
||||
ret = watchdog_dev_unregister(wdd);
|
||||
if (ret)
|
||||
--- a/include/linux/watchdog.h
|
||||
+++ b/include/linux/watchdog.h
|
||||
@@ -65,6 +65,7 @@ struct watchdog_ops {
|
||||
* @timeout: The watchdog devices timeout value (in seconds).
|
||||
* @min_timeout:The watchdog devices minimum timeout value (in seconds).
|
||||
* @max_timeout:The watchdog devices maximum timeout value (in seconds).
|
||||
+ * @reboot_nb: The notifier block to stop watchdog on reboot.
|
||||
* @restart_nb: The notifier block to register a restart function.
|
||||
* @driver-data:Pointer to the drivers private data.
|
||||
* @lock: Lock for watchdog core internal use only.
|
||||
@@ -92,6 +93,7 @@ struct watchdog_device {
|
||||
unsigned int timeout;
|
||||
unsigned int min_timeout;
|
||||
unsigned int max_timeout;
|
||||
+ struct notifier_block reboot_nb;
|
||||
struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
struct mutex lock;
|
||||
@@ -102,6 +104,7 @@ struct watchdog_device {
|
||||
#define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */
|
||||
#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
|
||||
#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */
|
||||
+#define WDOG_STOP_ON_REBOOT 5 /* Should be stopped on reboot */
|
||||
struct list_head deferred;
|
||||
};
|
||||
|
||||
@@ -121,6 +124,12 @@ static inline void watchdog_set_nowayout
|
||||
set_bit(WDOG_NO_WAY_OUT, &wdd->status);
|
||||
}
|
||||
|
||||
+/* Use the following function to stop the watchdog on reboot */
|
||||
+static inline void watchdog_stop_on_reboot(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ set_bit(WDOG_STOP_ON_REBOOT, &wdd->status);
|
||||
+}
|
||||
+
|
||||
/* Use the following function to check if a timeout value is invalid */
|
||||
static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigned int t)
|
||||
{
|
|
@ -1,111 +0,0 @@
|
|||
From 906d7a5cfeda508e7361f021605579a00cd82815 Mon Sep 17 00:00:00 2001
|
||||
From: Pratyush Anand <panand@redhat.com>
|
||||
Date: Thu, 17 Dec 2015 17:53:58 +0530
|
||||
Subject: watchdog: Use static struct class watchdog_class in stead of pointer
|
||||
|
||||
We need few sysfs attributes to know different status of a watchdog device.
|
||||
To do that, we need to associate .dev_groups with watchdog_class. So
|
||||
convert it from pointer to static.
|
||||
Putting this static struct in watchdog_dev.c, so that static device
|
||||
attributes defined in that file can be attached to it.
|
||||
|
||||
Signed-off-by: Pratyush Anand <panand@redhat.com>
|
||||
Suggested-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/watchdog_core.c | 15 ++-------------
|
||||
drivers/watchdog/watchdog_core.h | 2 +-
|
||||
drivers/watchdog/watchdog_dev.c | 26 ++++++++++++++++++++++----
|
||||
3 files changed, 25 insertions(+), 18 deletions(-)
|
||||
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -370,19 +370,9 @@ static int __init watchdog_deferred_regi
|
||||
|
||||
static int __init watchdog_init(void)
|
||||
{
|
||||
- int err;
|
||||
-
|
||||
- watchdog_class = class_create(THIS_MODULE, "watchdog");
|
||||
- if (IS_ERR(watchdog_class)) {
|
||||
- pr_err("couldn't create class\n");
|
||||
+ watchdog_class = watchdog_dev_init();
|
||||
+ if (IS_ERR(watchdog_class))
|
||||
return PTR_ERR(watchdog_class);
|
||||
- }
|
||||
-
|
||||
- err = watchdog_dev_init();
|
||||
- if (err < 0) {
|
||||
- class_destroy(watchdog_class);
|
||||
- return err;
|
||||
- }
|
||||
|
||||
watchdog_deferred_registration();
|
||||
return 0;
|
||||
@@ -391,7 +381,6 @@ static int __init watchdog_init(void)
|
||||
static void __exit watchdog_exit(void)
|
||||
{
|
||||
watchdog_dev_exit();
|
||||
- class_destroy(watchdog_class);
|
||||
ida_destroy(&watchdog_ida);
|
||||
}
|
||||
|
||||
--- a/drivers/watchdog/watchdog_core.h
|
||||
+++ b/drivers/watchdog/watchdog_core.h
|
||||
@@ -33,5 +33,5 @@
|
||||
*/
|
||||
extern int watchdog_dev_register(struct watchdog_device *);
|
||||
extern int watchdog_dev_unregister(struct watchdog_device *);
|
||||
-extern int __init watchdog_dev_init(void);
|
||||
+extern struct class * __init watchdog_dev_init(void);
|
||||
extern void __exit watchdog_dev_exit(void);
|
||||
--- a/drivers/watchdog/watchdog_dev.c
|
||||
+++ b/drivers/watchdog/watchdog_dev.c
|
||||
@@ -581,18 +581,35 @@ int watchdog_dev_unregister(struct watch
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static struct class watchdog_class = {
|
||||
+ .name = "watchdog",
|
||||
+ .owner = THIS_MODULE,
|
||||
+};
|
||||
+
|
||||
/*
|
||||
* watchdog_dev_init: init dev part of watchdog core
|
||||
*
|
||||
* Allocate a range of chardev nodes to use for watchdog devices
|
||||
*/
|
||||
|
||||
-int __init watchdog_dev_init(void)
|
||||
+struct class * __init watchdog_dev_init(void)
|
||||
{
|
||||
- int err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
|
||||
- if (err < 0)
|
||||
+ int err;
|
||||
+
|
||||
+ err = class_register(&watchdog_class);
|
||||
+ if (err < 0) {
|
||||
+ pr_err("couldn't register class\n");
|
||||
+ return ERR_PTR(err);
|
||||
+ }
|
||||
+
|
||||
+ err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
|
||||
+ if (err < 0) {
|
||||
pr_err("watchdog: unable to allocate char dev region\n");
|
||||
- return err;
|
||||
+ class_unregister(&watchdog_class);
|
||||
+ return ERR_PTR(err);
|
||||
+ }
|
||||
+
|
||||
+ return &watchdog_class;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -604,4 +621,5 @@ int __init watchdog_dev_init(void)
|
||||
void __exit watchdog_dev_exit(void)
|
||||
{
|
||||
unregister_chrdev_region(watchdog_devt, MAX_DOGS);
|
||||
+ class_unregister(&watchdog_class);
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
From 33b711269ade3f6bc9d9d15e4343e6fa922d999b Mon Sep 17 00:00:00 2001
|
||||
From: Pratyush Anand <panand@redhat.com>
|
||||
Date: Thu, 17 Dec 2015 17:53:59 +0530
|
||||
Subject: watchdog: Read device status through sysfs attributes
|
||||
|
||||
This patch adds following attributes to watchdog device's sysfs interface
|
||||
to read its different status.
|
||||
|
||||
* state - reads whether device is active or not
|
||||
* identity - reads Watchdog device's identity string.
|
||||
* timeout - reads current timeout.
|
||||
* timeleft - reads timeleft before watchdog generates a reset
|
||||
* bootstatus - reads status of the watchdog device at boot
|
||||
* status - reads watchdog device's internal status bits
|
||||
* nowayout - reads whether nowayout feature was set or not
|
||||
|
||||
Testing with iTCO_wdt:
|
||||
# cd /sys/class/watchdog/watchdog1/
|
||||
# ls
|
||||
bootstatus dev device identity nowayout power state
|
||||
subsystem timeleft timeout uevent
|
||||
# cat identity
|
||||
iTCO_wdt
|
||||
# cat timeout
|
||||
30
|
||||
# cat state
|
||||
inactive
|
||||
# echo > /dev/watchdog1
|
||||
# cat timeleft
|
||||
26
|
||||
# cat state
|
||||
active
|
||||
# cat bootstatus
|
||||
0
|
||||
# cat nowayout
|
||||
0
|
||||
|
||||
Signed-off-by: Pratyush Anand <panand@redhat.com>
|
||||
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
Documentation/ABI/testing/sysfs-class-watchdog | 51 +++++++++++
|
||||
drivers/watchdog/Kconfig | 7 ++
|
||||
drivers/watchdog/watchdog_core.c | 2 +-
|
||||
drivers/watchdog/watchdog_dev.c | 114 +++++++++++++++++++++++++
|
||||
4 files changed, 173 insertions(+), 1 deletion(-)
|
||||
create mode 100644 Documentation/ABI/testing/sysfs-class-watchdog
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/ABI/testing/sysfs-class-watchdog
|
||||
@@ -0,0 +1,51 @@
|
||||
+What: /sys/class/watchdog/watchdogn/bootstatus
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. It contains status of the watchdog
|
||||
+ device at boot. It is equivalent to WDIOC_GETBOOTSTATUS of
|
||||
+ ioctl interface.
|
||||
+
|
||||
+What: /sys/class/watchdog/watchdogn/identity
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. It contains identity string of
|
||||
+ watchdog device.
|
||||
+
|
||||
+What: /sys/class/watchdog/watchdogn/nowayout
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. While reading, it gives '1' if that
|
||||
+ device supports nowayout feature else, it gives '0'.
|
||||
+
|
||||
+What: /sys/class/watchdog/watchdogn/state
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. It gives active/inactive status of
|
||||
+ watchdog device.
|
||||
+
|
||||
+What: /sys/class/watchdog/watchdogn/status
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. It contains watchdog device's
|
||||
+ internal status bits. It is equivalent to WDIOC_GETSTATUS
|
||||
+ of ioctl interface.
|
||||
+
|
||||
+What: /sys/class/watchdog/watchdogn/timeleft
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. It contains value of time left for
|
||||
+ reset generation. It is equivalent to WDIOC_GETTIMELEFT of
|
||||
+ ioctl interface.
|
||||
+
|
||||
+What: /sys/class/watchdog/watchdogn/timeout
|
||||
+Date: August 2015
|
||||
+Contact: Wim Van Sebroeck <wim@iguana.be>
|
||||
+Description:
|
||||
+ It is a read only file. It is read to know about current
|
||||
+ value of timeout programmed.
|
||||
--- a/drivers/watchdog/Kconfig
|
||||
+++ b/drivers/watchdog/Kconfig
|
||||
@@ -46,6 +46,13 @@ config WATCHDOG_NOWAYOUT
|
||||
get killed. If you say Y here, the watchdog cannot be stopped once
|
||||
it has been started.
|
||||
|
||||
+config WATCHDOG_SYSFS
|
||||
+ bool "Read different watchdog information through sysfs"
|
||||
+ default n
|
||||
+ help
|
||||
+ Say Y here if you want to enable watchdog device status read through
|
||||
+ sysfs attributes.
|
||||
+
|
||||
#
|
||||
# General Watchdog drivers
|
||||
#
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -249,7 +249,7 @@ static int __watchdog_register_device(st
|
||||
|
||||
devno = wdd->cdev.dev;
|
||||
wdd->dev = device_create(watchdog_class, wdd->parent, devno,
|
||||
- NULL, "watchdog%d", wdd->id);
|
||||
+ wdd, "watchdog%d", wdd->id);
|
||||
if (IS_ERR(wdd->dev)) {
|
||||
watchdog_dev_unregister(wdd);
|
||||
ida_simple_remove(&watchdog_ida, id);
|
||||
--- a/drivers/watchdog/watchdog_dev.c
|
||||
+++ b/drivers/watchdog/watchdog_dev.c
|
||||
@@ -247,6 +247,119 @@ out_timeleft:
|
||||
return err;
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_WATCHDOG_SYSFS
|
||||
+static ssize_t nowayout_show(struct device *dev, struct device_attribute *attr,
|
||||
+ char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return sprintf(buf, "%d\n", !!test_bit(WDOG_NO_WAY_OUT, &wdd->status));
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(nowayout);
|
||||
+
|
||||
+static ssize_t status_show(struct device *dev, struct device_attribute *attr,
|
||||
+ char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+ ssize_t status;
|
||||
+ unsigned int val;
|
||||
+
|
||||
+ status = watchdog_get_status(wdd, &val);
|
||||
+ if (!status)
|
||||
+ status = sprintf(buf, "%u\n", val);
|
||||
+
|
||||
+ return status;
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(status);
|
||||
+
|
||||
+static ssize_t bootstatus_show(struct device *dev,
|
||||
+ struct device_attribute *attr, char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return sprintf(buf, "%u\n", wdd->bootstatus);
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(bootstatus);
|
||||
+
|
||||
+static ssize_t timeleft_show(struct device *dev, struct device_attribute *attr,
|
||||
+ char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+ ssize_t status;
|
||||
+ unsigned int val;
|
||||
+
|
||||
+ status = watchdog_get_timeleft(wdd, &val);
|
||||
+ if (!status)
|
||||
+ status = sprintf(buf, "%u\n", val);
|
||||
+
|
||||
+ return status;
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(timeleft);
|
||||
+
|
||||
+static ssize_t timeout_show(struct device *dev, struct device_attribute *attr,
|
||||
+ char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return sprintf(buf, "%u\n", wdd->timeout);
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(timeout);
|
||||
+
|
||||
+static ssize_t identity_show(struct device *dev, struct device_attribute *attr,
|
||||
+ char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+
|
||||
+ return sprintf(buf, "%s\n", wdd->info->identity);
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(identity);
|
||||
+
|
||||
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
||||
+ char *buf)
|
||||
+{
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+
|
||||
+ if (watchdog_active(wdd))
|
||||
+ return sprintf(buf, "active\n");
|
||||
+
|
||||
+ return sprintf(buf, "inactive\n");
|
||||
+}
|
||||
+static DEVICE_ATTR_RO(state);
|
||||
+
|
||||
+static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||
+ int n)
|
||||
+{
|
||||
+ struct device *dev = container_of(kobj, struct device, kobj);
|
||||
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+ umode_t mode = attr->mode;
|
||||
+
|
||||
+ if (attr == &dev_attr_status.attr && !wdd->ops->status)
|
||||
+ mode = 0;
|
||||
+ else if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft)
|
||||
+ mode = 0;
|
||||
+
|
||||
+ return mode;
|
||||
+}
|
||||
+static struct attribute *wdt_attrs[] = {
|
||||
+ &dev_attr_state.attr,
|
||||
+ &dev_attr_identity.attr,
|
||||
+ &dev_attr_timeout.attr,
|
||||
+ &dev_attr_timeleft.attr,
|
||||
+ &dev_attr_bootstatus.attr,
|
||||
+ &dev_attr_status.attr,
|
||||
+ &dev_attr_nowayout.attr,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group wdt_group = {
|
||||
+ .attrs = wdt_attrs,
|
||||
+ .is_visible = wdt_is_visible,
|
||||
+};
|
||||
+__ATTRIBUTE_GROUPS(wdt);
|
||||
+#else
|
||||
+#define wdt_groups NULL
|
||||
+#endif
|
||||
+
|
||||
/*
|
||||
* watchdog_ioctl_op: call the watchdog drivers ioctl op if defined
|
||||
* @wdd: the watchdog device to do the ioctl on
|
||||
@@ -584,6 +697,7 @@ int watchdog_dev_unregister(struct watch
|
||||
static struct class watchdog_class = {
|
||||
.name = "watchdog",
|
||||
.owner = THIS_MODULE,
|
||||
+ .dev_groups = wdt_groups,
|
||||
};
|
||||
|
||||
/*
|
|
@ -1,261 +0,0 @@
|
|||
From 32ecc6392654a0db34b310e8924b5b2c3b8bf503 Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Fri, 25 Dec 2015 16:01:40 -0800
|
||||
Subject: watchdog: Create watchdog device in watchdog_dev.c
|
||||
|
||||
The watchdog character device is currently created in watchdog_dev.c,
|
||||
and the watchdog device in watchdog_core.c. This results in
|
||||
cross-dependencies, since device creation needs to know the watchdog
|
||||
character device number as well as the watchdog class, both of which
|
||||
reside in watchdog_dev.c.
|
||||
|
||||
Create the watchdog device in watchdog_dev.c to simplify the code.
|
||||
|
||||
Inspired by earlier patch set from Damien Riegel.
|
||||
|
||||
Cc: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/watchdog_core.c | 33 ++++--------------
|
||||
drivers/watchdog/watchdog_core.h | 4 +--
|
||||
drivers/watchdog/watchdog_dev.c | 73 +++++++++++++++++++++++++++++++++-------
|
||||
3 files changed, 69 insertions(+), 41 deletions(-)
|
||||
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -42,7 +42,6 @@
|
||||
#include "watchdog_core.h" /* For watchdog_dev_register/... */
|
||||
|
||||
static DEFINE_IDA(watchdog_ida);
|
||||
-static struct class *watchdog_class;
|
||||
|
||||
/*
|
||||
* Deferred Registration infrastructure.
|
||||
@@ -194,7 +193,7 @@ EXPORT_SYMBOL_GPL(watchdog_set_restart_p
|
||||
|
||||
static int __watchdog_register_device(struct watchdog_device *wdd)
|
||||
{
|
||||
- int ret, id = -1, devno;
|
||||
+ int ret, id = -1;
|
||||
|
||||
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
|
||||
return -EINVAL;
|
||||
@@ -247,16 +246,6 @@ static int __watchdog_register_device(st
|
||||
}
|
||||
}
|
||||
|
||||
- devno = wdd->cdev.dev;
|
||||
- wdd->dev = device_create(watchdog_class, wdd->parent, devno,
|
||||
- wdd, "watchdog%d", wdd->id);
|
||||
- if (IS_ERR(wdd->dev)) {
|
||||
- watchdog_dev_unregister(wdd);
|
||||
- ida_simple_remove(&watchdog_ida, id);
|
||||
- ret = PTR_ERR(wdd->dev);
|
||||
- return ret;
|
||||
- }
|
||||
-
|
||||
if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) {
|
||||
wdd->reboot_nb.notifier_call = watchdog_reboot_notifier;
|
||||
|
||||
@@ -265,9 +254,7 @@ static int __watchdog_register_device(st
|
||||
dev_err(wdd->dev, "Cannot register reboot notifier (%d)\n",
|
||||
ret);
|
||||
watchdog_dev_unregister(wdd);
|
||||
- device_destroy(watchdog_class, devno);
|
||||
ida_simple_remove(&watchdog_ida, wdd->id);
|
||||
- wdd->dev = NULL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -311,9 +298,6 @@ EXPORT_SYMBOL_GPL(watchdog_register_devi
|
||||
|
||||
static void __watchdog_unregister_device(struct watchdog_device *wdd)
|
||||
{
|
||||
- int ret;
|
||||
- int devno;
|
||||
-
|
||||
if (wdd == NULL)
|
||||
return;
|
||||
|
||||
@@ -323,13 +307,8 @@ static void __watchdog_unregister_device
|
||||
if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status))
|
||||
unregister_reboot_notifier(&wdd->reboot_nb);
|
||||
|
||||
- devno = wdd->cdev.dev;
|
||||
- ret = watchdog_dev_unregister(wdd);
|
||||
- if (ret)
|
||||
- pr_err("error unregistering /dev/watchdog (err=%d)\n", ret);
|
||||
- device_destroy(watchdog_class, devno);
|
||||
+ watchdog_dev_unregister(wdd);
|
||||
ida_simple_remove(&watchdog_ida, wdd->id);
|
||||
- wdd->dev = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,9 +349,11 @@ static int __init watchdog_deferred_regi
|
||||
|
||||
static int __init watchdog_init(void)
|
||||
{
|
||||
- watchdog_class = watchdog_dev_init();
|
||||
- if (IS_ERR(watchdog_class))
|
||||
- return PTR_ERR(watchdog_class);
|
||||
+ int err;
|
||||
+
|
||||
+ err = watchdog_dev_init();
|
||||
+ if (err < 0)
|
||||
+ return err;
|
||||
|
||||
watchdog_deferred_registration();
|
||||
return 0;
|
||||
--- a/drivers/watchdog/watchdog_core.h
|
||||
+++ b/drivers/watchdog/watchdog_core.h
|
||||
@@ -32,6 +32,6 @@
|
||||
* Functions/procedures to be called by the core
|
||||
*/
|
||||
extern int watchdog_dev_register(struct watchdog_device *);
|
||||
-extern int watchdog_dev_unregister(struct watchdog_device *);
|
||||
-extern struct class * __init watchdog_dev_init(void);
|
||||
+extern void watchdog_dev_unregister(struct watchdog_device *);
|
||||
+extern int __init watchdog_dev_init(void);
|
||||
extern void __exit watchdog_dev_exit(void);
|
||||
--- a/drivers/watchdog/watchdog_dev.c
|
||||
+++ b/drivers/watchdog/watchdog_dev.c
|
||||
@@ -628,17 +628,18 @@ static struct miscdevice watchdog_miscde
|
||||
};
|
||||
|
||||
/*
|
||||
- * watchdog_dev_register: register a watchdog device
|
||||
+ * watchdog_cdev_register: register watchdog character device
|
||||
* @wdd: watchdog device
|
||||
+ * @devno: character device number
|
||||
*
|
||||
- * Register a watchdog device including handling the legacy
|
||||
+ * Register a watchdog character device including handling the legacy
|
||||
* /dev/watchdog node. /dev/watchdog is actually a miscdevice and
|
||||
* thus we set it up like that.
|
||||
*/
|
||||
|
||||
-int watchdog_dev_register(struct watchdog_device *wdd)
|
||||
+static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||
{
|
||||
- int err, devno;
|
||||
+ int err;
|
||||
|
||||
if (wdd->id == 0) {
|
||||
old_wdd = wdd;
|
||||
@@ -656,7 +657,6 @@ int watchdog_dev_register(struct watchdo
|
||||
}
|
||||
|
||||
/* Fill in the data structures */
|
||||
- devno = MKDEV(MAJOR(watchdog_devt), wdd->id);
|
||||
cdev_init(&wdd->cdev, &watchdog_fops);
|
||||
wdd->cdev.owner = wdd->ops->owner;
|
||||
|
||||
@@ -674,13 +674,14 @@ int watchdog_dev_register(struct watchdo
|
||||
}
|
||||
|
||||
/*
|
||||
- * watchdog_dev_unregister: unregister a watchdog device
|
||||
+ * watchdog_cdev_unregister: unregister watchdog character device
|
||||
* @watchdog: watchdog device
|
||||
*
|
||||
- * Unregister the watchdog and if needed the legacy /dev/watchdog device.
|
||||
+ * Unregister watchdog character device and if needed the legacy
|
||||
+ * /dev/watchdog device.
|
||||
*/
|
||||
|
||||
-int watchdog_dev_unregister(struct watchdog_device *wdd)
|
||||
+static void watchdog_cdev_unregister(struct watchdog_device *wdd)
|
||||
{
|
||||
mutex_lock(&wdd->lock);
|
||||
set_bit(WDOG_UNREGISTERED, &wdd->status);
|
||||
@@ -691,7 +692,6 @@ int watchdog_dev_unregister(struct watch
|
||||
misc_deregister(&watchdog_miscdev);
|
||||
old_wdd = NULL;
|
||||
}
|
||||
- return 0;
|
||||
}
|
||||
|
||||
static struct class watchdog_class = {
|
||||
@@ -701,29 +701,76 @@ static struct class watchdog_class = {
|
||||
};
|
||||
|
||||
/*
|
||||
+ * watchdog_dev_register: register a watchdog device
|
||||
+ * @wdd: watchdog device
|
||||
+ *
|
||||
+ * Register a watchdog device including handling the legacy
|
||||
+ * /dev/watchdog node. /dev/watchdog is actually a miscdevice and
|
||||
+ * thus we set it up like that.
|
||||
+ */
|
||||
+
|
||||
+int watchdog_dev_register(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ struct device *dev;
|
||||
+ dev_t devno;
|
||||
+ int ret;
|
||||
+
|
||||
+ devno = MKDEV(MAJOR(watchdog_devt), wdd->id);
|
||||
+
|
||||
+ ret = watchdog_cdev_register(wdd, devno);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ dev = device_create(&watchdog_class, wdd->parent, devno, wdd,
|
||||
+ "watchdog%d", wdd->id);
|
||||
+ if (IS_ERR(dev)) {
|
||||
+ watchdog_cdev_unregister(wdd);
|
||||
+ return PTR_ERR(dev);
|
||||
+ }
|
||||
+ wdd->dev = dev;
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * watchdog_dev_unregister: unregister a watchdog device
|
||||
+ * @watchdog: watchdog device
|
||||
+ *
|
||||
+ * Unregister watchdog device and if needed the legacy
|
||||
+ * /dev/watchdog device.
|
||||
+ */
|
||||
+
|
||||
+void watchdog_dev_unregister(struct watchdog_device *wdd)
|
||||
+{
|
||||
+ watchdog_cdev_unregister(wdd);
|
||||
+ device_destroy(&watchdog_class, wdd->dev->devt);
|
||||
+ wdd->dev = NULL;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
* watchdog_dev_init: init dev part of watchdog core
|
||||
*
|
||||
* Allocate a range of chardev nodes to use for watchdog devices
|
||||
*/
|
||||
|
||||
-struct class * __init watchdog_dev_init(void)
|
||||
+int __init watchdog_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = class_register(&watchdog_class);
|
||||
if (err < 0) {
|
||||
pr_err("couldn't register class\n");
|
||||
- return ERR_PTR(err);
|
||||
+ return err;
|
||||
}
|
||||
|
||||
err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
|
||||
if (err < 0) {
|
||||
pr_err("watchdog: unable to allocate char dev region\n");
|
||||
class_unregister(&watchdog_class);
|
||||
- return ERR_PTR(err);
|
||||
+ return err;
|
||||
}
|
||||
|
||||
- return &watchdog_class;
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
/*
|
|
@ -1,969 +0,0 @@
|
|||
From b4ffb1909843b28f3b1b60197d517b123b7a9b66 Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Fri, 25 Dec 2015 16:01:42 -0800
|
||||
Subject: watchdog: Separate and maintain variables based on variable lifetime
|
||||
|
||||
All variables required by the watchdog core to manage a watchdog are
|
||||
currently stored in struct watchdog_device. The lifetime of those
|
||||
variables is determined by the watchdog driver. However, the lifetime
|
||||
of variables used by the watchdog core differs from the lifetime of
|
||||
struct watchdog_device. To remedy this situation, watchdog drivers
|
||||
can implement ref and unref callbacks, to be used by the watchdog
|
||||
core to lock struct watchdog_device in memory.
|
||||
|
||||
While this solves the immediate problem, it depends on watchdog drivers
|
||||
to actually implement the ref/unref callbacks. This is error prone,
|
||||
often not implemented in the first place, or not implemented correctly.
|
||||
|
||||
To solve the problem without requiring driver support, split the variables
|
||||
in struct watchdog_device into two data structures - one for variables
|
||||
associated with the watchdog driver, one for variables associated with
|
||||
the watchdog core. With this approach, the watchdog core can keep track
|
||||
of its variable lifetime and no longer depends on ref/unref callbacks
|
||||
in the driver. As a side effect, some of the variables originally in
|
||||
struct watchdog_driver are now private to the watchdog core and no longer
|
||||
visible in watchdog drivers.
|
||||
|
||||
As a side effect of the changes made, an ioctl will now always fail
|
||||
with -ENODEV after a watchdog device was unregistered with the character
|
||||
device still open. Previously, it would only fail with -ENODEV in some
|
||||
situations. Also, ioctl operations are now atomic from driver perspective.
|
||||
With this change, it is now guaranteed that the driver will not unregister
|
||||
a watchdog between a timeout change and the subsequent ping.
|
||||
|
||||
The 'ref' and 'unref' callbacks in struct watchdog_driver are no longer
|
||||
used and marked as deprecated.
|
||||
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
Documentation/watchdog/watchdog-kernel-api.txt | 45 +--
|
||||
drivers/watchdog/watchdog_core.c | 2 -
|
||||
drivers/watchdog/watchdog_dev.c | 383 +++++++++++++------------
|
||||
include/linux/watchdog.h | 22 +-
|
||||
4 files changed, 218 insertions(+), 234 deletions(-)
|
||||
|
||||
--- a/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
+++ b/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
@@ -44,7 +44,6 @@ The watchdog device structure looks like
|
||||
|
||||
struct watchdog_device {
|
||||
int id;
|
||||
- struct cdev cdev;
|
||||
struct device *dev;
|
||||
struct device *parent;
|
||||
const struct watchdog_info *info;
|
||||
@@ -56,7 +55,7 @@ struct watchdog_device {
|
||||
struct notifier_block reboot_nb;
|
||||
struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
- struct mutex lock;
|
||||
+ struct watchdog_core_data *wd_data;
|
||||
unsigned long status;
|
||||
struct list_head deferred;
|
||||
};
|
||||
@@ -66,8 +65,6 @@ It contains following fields:
|
||||
/dev/watchdog0 cdev (dynamic major, minor 0) as well as the old
|
||||
/dev/watchdog miscdev. The id is set automatically when calling
|
||||
watchdog_register_device.
|
||||
-* cdev: cdev for the dynamic /dev/watchdog<id> device nodes. This
|
||||
- field is also populated by watchdog_register_device.
|
||||
* dev: device under the watchdog class (created by watchdog_register_device).
|
||||
* parent: set this to the parent device (or NULL) before calling
|
||||
watchdog_register_device.
|
||||
@@ -89,11 +86,10 @@ It contains following fields:
|
||||
* driver_data: a pointer to the drivers private data of a watchdog device.
|
||||
This data should only be accessed via the watchdog_set_drvdata and
|
||||
watchdog_get_drvdata routines.
|
||||
-* lock: Mutex for WatchDog Timer Driver Core internal use only.
|
||||
+* wd_data: a pointer to watchdog core internal data.
|
||||
* status: this field contains a number of status bits that give extra
|
||||
information about the status of the device (Like: is the watchdog timer
|
||||
- running/active, is the nowayout bit set, is the device opened via
|
||||
- the /dev/watchdog interface or not, ...).
|
||||
+ running/active, or is the nowayout bit set).
|
||||
* deferred: entry in wtd_deferred_reg_list which is used to
|
||||
register early initialized watchdogs.
|
||||
|
||||
@@ -110,8 +106,8 @@ struct watchdog_ops {
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
int (*restart)(struct watchdog_device *);
|
||||
- void (*ref)(struct watchdog_device *);
|
||||
- void (*unref)(struct watchdog_device *);
|
||||
+ void (*ref)(struct watchdog_device *) __deprecated;
|
||||
+ void (*unref)(struct watchdog_device *) __deprecated;
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
};
|
||||
|
||||
@@ -120,20 +116,6 @@ driver's operations. This module owner w
|
||||
the watchdog is active. (This to avoid a system crash when you unload the
|
||||
module and /dev/watchdog is still open).
|
||||
|
||||
-If the watchdog_device struct is dynamically allocated, just locking the module
|
||||
-is not enough and a driver also needs to define the ref and unref operations to
|
||||
-ensure the structure holding the watchdog_device does not go away.
|
||||
-
|
||||
-The simplest (and usually sufficient) implementation of this is to:
|
||||
-1) Add a kref struct to the same structure which is holding the watchdog_device
|
||||
-2) Define a release callback for the kref which frees the struct holding both
|
||||
-3) Call kref_init on this kref *before* calling watchdog_register_device()
|
||||
-4) Define a ref operation calling kref_get on this kref
|
||||
-5) Define a unref operation calling kref_put on this kref
|
||||
-6) When it is time to cleanup:
|
||||
- * Do not kfree() the struct holding both, the last kref_put will do this!
|
||||
- * *After* calling watchdog_unregister_device() call kref_put on the kref
|
||||
-
|
||||
Some operations are mandatory and some are optional. The mandatory operations
|
||||
are:
|
||||
* start: this is a pointer to the routine that starts the watchdog timer
|
||||
@@ -176,34 +158,21 @@ they are supported. These optional routi
|
||||
* get_timeleft: this routines returns the time that's left before a reset.
|
||||
* restart: this routine restarts the machine. It returns 0 on success or a
|
||||
negative errno code for failure.
|
||||
-* ref: the operation that calls kref_get on the kref of a dynamically
|
||||
- allocated watchdog_device struct.
|
||||
-* unref: the operation that calls kref_put on the kref of a dynamically
|
||||
- allocated watchdog_device struct.
|
||||
* ioctl: if this routine is present then it will be called first before we do
|
||||
our own internal ioctl call handling. This routine should return -ENOIOCTLCMD
|
||||
if a command is not supported. The parameters that are passed to the ioctl
|
||||
call are: watchdog_device, cmd and arg.
|
||||
|
||||
+The 'ref' and 'unref' operations are no longer used and deprecated.
|
||||
+
|
||||
The status bits should (preferably) be set with the set_bit and clear_bit alike
|
||||
bit-operations. The status bits that are defined are:
|
||||
* WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device
|
||||
is active or not. When the watchdog is active after booting, then you should
|
||||
set this status bit (Note: when you register the watchdog timer device with
|
||||
this bit set, then opening /dev/watchdog will skip the start operation)
|
||||
-* WDOG_DEV_OPEN: this status bit shows whether or not the watchdog device
|
||||
- was opened via /dev/watchdog.
|
||||
- (This bit should only be used by the WatchDog Timer Driver Core).
|
||||
-* WDOG_ALLOW_RELEASE: this bit stores whether or not the magic close character
|
||||
- has been sent (so that we can support the magic close feature).
|
||||
- (This bit should only be used by the WatchDog Timer Driver Core).
|
||||
* WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog.
|
||||
If this bit is set then the watchdog timer will not be able to stop.
|
||||
-* WDOG_UNREGISTERED: this bit gets set by the WatchDog Timer Driver Core
|
||||
- after calling watchdog_unregister_device, and then checked before calling
|
||||
- any watchdog_ops, so that you can be sure that no operations (other then
|
||||
- unref) will get called after unregister, even if userspace still holds a
|
||||
- reference to /dev/watchdog
|
||||
|
||||
To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog
|
||||
timer device) you can either:
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -210,8 +210,6 @@ static int __watchdog_register_device(st
|
||||
* corrupted in a later stage then we expect a kernel panic!
|
||||
*/
|
||||
|
||||
- mutex_init(&wdd->lock);
|
||||
-
|
||||
/* Use alias for watchdog id if possible */
|
||||
if (wdd->parent) {
|
||||
ret = of_alias_get_id(wdd->parent->of_node, "watchdog");
|
||||
--- a/drivers/watchdog/watchdog_dev.c
|
||||
+++ b/drivers/watchdog/watchdog_dev.c
|
||||
@@ -32,27 +32,51 @@
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
-#include <linux/module.h> /* For module stuff/... */
|
||||
-#include <linux/types.h> /* For standard types (like size_t) */
|
||||
+#include <linux/cdev.h> /* For character device */
|
||||
#include <linux/errno.h> /* For the -ENODEV/... values */
|
||||
-#include <linux/kernel.h> /* For printk/panic/... */
|
||||
#include <linux/fs.h> /* For file operations */
|
||||
-#include <linux/watchdog.h> /* For watchdog specific items */
|
||||
-#include <linux/miscdevice.h> /* For handling misc devices */
|
||||
#include <linux/init.h> /* For __init/__exit/... */
|
||||
+#include <linux/kernel.h> /* For printk/panic/... */
|
||||
+#include <linux/kref.h> /* For data references */
|
||||
+#include <linux/miscdevice.h> /* For handling misc devices */
|
||||
+#include <linux/module.h> /* For module stuff/... */
|
||||
+#include <linux/mutex.h> /* For mutexes */
|
||||
+#include <linux/slab.h> /* For memory functions */
|
||||
+#include <linux/types.h> /* For standard types (like size_t) */
|
||||
+#include <linux/watchdog.h> /* For watchdog specific items */
|
||||
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
|
||||
|
||||
#include "watchdog_core.h"
|
||||
|
||||
+/*
|
||||
+ * struct watchdog_core_data - watchdog core internal data
|
||||
+ * @kref: Reference count.
|
||||
+ * @cdev: The watchdog's Character device.
|
||||
+ * @wdd: Pointer to watchdog device.
|
||||
+ * @lock: Lock for watchdog core.
|
||||
+ * @status: Watchdog core internal status bits.
|
||||
+ */
|
||||
+struct watchdog_core_data {
|
||||
+ struct kref kref;
|
||||
+ struct cdev cdev;
|
||||
+ struct watchdog_device *wdd;
|
||||
+ struct mutex lock;
|
||||
+ unsigned long status; /* Internal status bits */
|
||||
+#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
||||
+#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
|
||||
+};
|
||||
+
|
||||
/* the dev_t structure to store the dynamically allocated watchdog devices */
|
||||
static dev_t watchdog_devt;
|
||||
-/* the watchdog device behind /dev/watchdog */
|
||||
-static struct watchdog_device *old_wdd;
|
||||
+/* Reference to watchdog device behind /dev/watchdog */
|
||||
+static struct watchdog_core_data *old_wd_data;
|
||||
|
||||
/*
|
||||
* watchdog_ping: ping the watchdog.
|
||||
* @wdd: the watchdog device to ping
|
||||
*
|
||||
+ * The caller must hold wd_data->lock.
|
||||
+ *
|
||||
* If the watchdog has no own ping operation then it needs to be
|
||||
* restarted via the start operation. This wrapper function does
|
||||
* exactly that.
|
||||
@@ -61,25 +85,16 @@ static struct watchdog_device *old_wdd;
|
||||
|
||||
static int watchdog_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
- int err = 0;
|
||||
-
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_ping;
|
||||
- }
|
||||
+ int err;
|
||||
|
||||
if (!watchdog_active(wdd))
|
||||
- goto out_ping;
|
||||
+ return 0;
|
||||
|
||||
if (wdd->ops->ping)
|
||||
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
||||
else
|
||||
err = wdd->ops->start(wdd); /* restart watchdog */
|
||||
|
||||
-out_ping:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -87,6 +102,8 @@ out_ping:
|
||||
* watchdog_start: wrapper to start the watchdog.
|
||||
* @wdd: the watchdog device to start
|
||||
*
|
||||
+ * The caller must hold wd_data->lock.
|
||||
+ *
|
||||
* Start the watchdog if it is not active and mark it active.
|
||||
* This function returns zero on success or a negative errno code for
|
||||
* failure.
|
||||
@@ -94,24 +111,15 @@ out_ping:
|
||||
|
||||
static int watchdog_start(struct watchdog_device *wdd)
|
||||
{
|
||||
- int err = 0;
|
||||
-
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_start;
|
||||
- }
|
||||
+ int err;
|
||||
|
||||
if (watchdog_active(wdd))
|
||||
- goto out_start;
|
||||
+ return 0;
|
||||
|
||||
err = wdd->ops->start(wdd);
|
||||
if (err == 0)
|
||||
set_bit(WDOG_ACTIVE, &wdd->status);
|
||||
|
||||
-out_start:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -119,6 +127,8 @@ out_start:
|
||||
* watchdog_stop: wrapper to stop the watchdog.
|
||||
* @wdd: the watchdog device to stop
|
||||
*
|
||||
+ * The caller must hold wd_data->lock.
|
||||
+ *
|
||||
* Stop the watchdog if it is still active and unmark it active.
|
||||
* This function returns zero on success or a negative errno code for
|
||||
* failure.
|
||||
@@ -127,93 +137,58 @@ out_start:
|
||||
|
||||
static int watchdog_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
- int err = 0;
|
||||
-
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_stop;
|
||||
- }
|
||||
+ int err;
|
||||
|
||||
if (!watchdog_active(wdd))
|
||||
- goto out_stop;
|
||||
+ return 0;
|
||||
|
||||
if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) {
|
||||
dev_info(wdd->dev, "nowayout prevents watchdog being stopped!\n");
|
||||
- err = -EBUSY;
|
||||
- goto out_stop;
|
||||
+ return -EBUSY;
|
||||
}
|
||||
|
||||
err = wdd->ops->stop(wdd);
|
||||
if (err == 0)
|
||||
clear_bit(WDOG_ACTIVE, &wdd->status);
|
||||
|
||||
-out_stop:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* watchdog_get_status: wrapper to get the watchdog status
|
||||
* @wdd: the watchdog device to get the status from
|
||||
- * @status: the status of the watchdog device
|
||||
+ *
|
||||
+ * The caller must hold wd_data->lock.
|
||||
*
|
||||
* Get the watchdog's status flags.
|
||||
*/
|
||||
|
||||
-static int watchdog_get_status(struct watchdog_device *wdd,
|
||||
- unsigned int *status)
|
||||
+static unsigned int watchdog_get_status(struct watchdog_device *wdd)
|
||||
{
|
||||
- int err = 0;
|
||||
-
|
||||
- *status = 0;
|
||||
if (!wdd->ops->status)
|
||||
- return -EOPNOTSUPP;
|
||||
-
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_status;
|
||||
- }
|
||||
-
|
||||
- *status = wdd->ops->status(wdd);
|
||||
+ return 0;
|
||||
|
||||
-out_status:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
- return err;
|
||||
+ return wdd->ops->status(wdd);
|
||||
}
|
||||
|
||||
/*
|
||||
* watchdog_set_timeout: set the watchdog timer timeout
|
||||
* @wdd: the watchdog device to set the timeout for
|
||||
* @timeout: timeout to set in seconds
|
||||
+ *
|
||||
+ * The caller must hold wd_data->lock.
|
||||
*/
|
||||
|
||||
static int watchdog_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
- int err;
|
||||
-
|
||||
if (!wdd->ops->set_timeout || !(wdd->info->options & WDIOF_SETTIMEOUT))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (watchdog_timeout_invalid(wdd, timeout))
|
||||
return -EINVAL;
|
||||
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_timeout;
|
||||
- }
|
||||
-
|
||||
- err = wdd->ops->set_timeout(wdd, timeout);
|
||||
-
|
||||
-out_timeout:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
- return err;
|
||||
+ return wdd->ops->set_timeout(wdd, timeout);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -221,30 +196,22 @@ out_timeout:
|
||||
* @wdd: the watchdog device to get the remaining time from
|
||||
* @timeleft: the time that's left
|
||||
*
|
||||
+ * The caller must hold wd_data->lock.
|
||||
+ *
|
||||
* Get the time before a watchdog will reboot (if not pinged).
|
||||
*/
|
||||
|
||||
static int watchdog_get_timeleft(struct watchdog_device *wdd,
|
||||
unsigned int *timeleft)
|
||||
{
|
||||
- int err = 0;
|
||||
-
|
||||
*timeleft = 0;
|
||||
+
|
||||
if (!wdd->ops->get_timeleft)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_timeleft;
|
||||
- }
|
||||
-
|
||||
*timeleft = wdd->ops->get_timeleft(wdd);
|
||||
|
||||
-out_timeleft:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
- return err;
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_WATCHDOG_SYSFS
|
||||
@@ -261,14 +228,14 @@ static ssize_t status_show(struct device
|
||||
char *buf)
|
||||
{
|
||||
struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
- ssize_t status;
|
||||
- unsigned int val;
|
||||
+ struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
+ unsigned int status;
|
||||
|
||||
- status = watchdog_get_status(wdd, &val);
|
||||
- if (!status)
|
||||
- status = sprintf(buf, "%u\n", val);
|
||||
+ mutex_lock(&wd_data->lock);
|
||||
+ status = watchdog_get_status(wdd);
|
||||
+ mutex_unlock(&wd_data->lock);
|
||||
|
||||
- return status;
|
||||
+ return sprintf(buf, "%u\n", status);
|
||||
}
|
||||
static DEVICE_ATTR_RO(status);
|
||||
|
||||
@@ -285,10 +252,13 @@ static ssize_t timeleft_show(struct devi
|
||||
char *buf)
|
||||
{
|
||||
struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
+ struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
ssize_t status;
|
||||
unsigned int val;
|
||||
|
||||
+ mutex_lock(&wd_data->lock);
|
||||
status = watchdog_get_timeleft(wdd, &val);
|
||||
+ mutex_unlock(&wd_data->lock);
|
||||
if (!status)
|
||||
status = sprintf(buf, "%u\n", val);
|
||||
|
||||
@@ -365,28 +335,17 @@ __ATTRIBUTE_GROUPS(wdt);
|
||||
* @wdd: the watchdog device to do the ioctl on
|
||||
* @cmd: watchdog command
|
||||
* @arg: argument pointer
|
||||
+ *
|
||||
+ * The caller must hold wd_data->lock.
|
||||
*/
|
||||
|
||||
static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
- int err;
|
||||
-
|
||||
if (!wdd->ops->ioctl)
|
||||
return -ENOIOCTLCMD;
|
||||
|
||||
- mutex_lock(&wdd->lock);
|
||||
-
|
||||
- if (test_bit(WDOG_UNREGISTERED, &wdd->status)) {
|
||||
- err = -ENODEV;
|
||||
- goto out_ioctl;
|
||||
- }
|
||||
-
|
||||
- err = wdd->ops->ioctl(wdd, cmd, arg);
|
||||
-
|
||||
-out_ioctl:
|
||||
- mutex_unlock(&wdd->lock);
|
||||
- return err;
|
||||
+ return wdd->ops->ioctl(wdd, cmd, arg);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -404,10 +363,11 @@ out_ioctl:
|
||||
static ssize_t watchdog_write(struct file *file, const char __user *data,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
- struct watchdog_device *wdd = file->private_data;
|
||||
+ struct watchdog_core_data *wd_data = file->private_data;
|
||||
+ struct watchdog_device *wdd;
|
||||
+ int err;
|
||||
size_t i;
|
||||
char c;
|
||||
- int err;
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
@@ -416,18 +376,25 @@ static ssize_t watchdog_write(struct fil
|
||||
* Note: just in case someone wrote the magic character
|
||||
* five months ago...
|
||||
*/
|
||||
- clear_bit(WDOG_ALLOW_RELEASE, &wdd->status);
|
||||
+ clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);
|
||||
|
||||
/* scan to see whether or not we got the magic character */
|
||||
for (i = 0; i != len; i++) {
|
||||
if (get_user(c, data + i))
|
||||
return -EFAULT;
|
||||
if (c == 'V')
|
||||
- set_bit(WDOG_ALLOW_RELEASE, &wdd->status);
|
||||
+ set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);
|
||||
}
|
||||
|
||||
/* someone wrote to us, so we send the watchdog a keepalive ping */
|
||||
- err = watchdog_ping(wdd);
|
||||
+
|
||||
+ err = -ENODEV;
|
||||
+ mutex_lock(&wd_data->lock);
|
||||
+ wdd = wd_data->wdd;
|
||||
+ if (wdd)
|
||||
+ err = watchdog_ping(wdd);
|
||||
+ mutex_unlock(&wd_data->lock);
|
||||
+
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
@@ -447,71 +414,94 @@ static ssize_t watchdog_write(struct fil
|
||||
static long watchdog_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
- struct watchdog_device *wdd = file->private_data;
|
||||
+ struct watchdog_core_data *wd_data = file->private_data;
|
||||
void __user *argp = (void __user *)arg;
|
||||
+ struct watchdog_device *wdd;
|
||||
int __user *p = argp;
|
||||
unsigned int val;
|
||||
int err;
|
||||
|
||||
+ mutex_lock(&wd_data->lock);
|
||||
+
|
||||
+ wdd = wd_data->wdd;
|
||||
+ if (!wdd) {
|
||||
+ err = -ENODEV;
|
||||
+ goto out_ioctl;
|
||||
+ }
|
||||
+
|
||||
err = watchdog_ioctl_op(wdd, cmd, arg);
|
||||
if (err != -ENOIOCTLCMD)
|
||||
- return err;
|
||||
+ goto out_ioctl;
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
- return copy_to_user(argp, wdd->info,
|
||||
+ err = copy_to_user(argp, wdd->info,
|
||||
sizeof(struct watchdog_info)) ? -EFAULT : 0;
|
||||
+ break;
|
||||
case WDIOC_GETSTATUS:
|
||||
- err = watchdog_get_status(wdd, &val);
|
||||
- if (err == -ENODEV)
|
||||
- return err;
|
||||
- return put_user(val, p);
|
||||
+ val = watchdog_get_status(wdd);
|
||||
+ err = put_user(val, p);
|
||||
+ break;
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
- return put_user(wdd->bootstatus, p);
|
||||
+ err = put_user(wdd->bootstatus, p);
|
||||
+ break;
|
||||
case WDIOC_SETOPTIONS:
|
||||
- if (get_user(val, p))
|
||||
- return -EFAULT;
|
||||
+ if (get_user(val, p)) {
|
||||
+ err = -EFAULT;
|
||||
+ break;
|
||||
+ }
|
||||
if (val & WDIOS_DISABLECARD) {
|
||||
err = watchdog_stop(wdd);
|
||||
if (err < 0)
|
||||
- return err;
|
||||
+ break;
|
||||
}
|
||||
- if (val & WDIOS_ENABLECARD) {
|
||||
+ if (val & WDIOS_ENABLECARD)
|
||||
err = watchdog_start(wdd);
|
||||
- if (err < 0)
|
||||
- return err;
|
||||
- }
|
||||
- return 0;
|
||||
+ break;
|
||||
case WDIOC_KEEPALIVE:
|
||||
- if (!(wdd->info->options & WDIOF_KEEPALIVEPING))
|
||||
- return -EOPNOTSUPP;
|
||||
- return watchdog_ping(wdd);
|
||||
+ if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) {
|
||||
+ err = -EOPNOTSUPP;
|
||||
+ break;
|
||||
+ }
|
||||
+ err = watchdog_ping(wdd);
|
||||
+ break;
|
||||
case WDIOC_SETTIMEOUT:
|
||||
- if (get_user(val, p))
|
||||
- return -EFAULT;
|
||||
+ if (get_user(val, p)) {
|
||||
+ err = -EFAULT;
|
||||
+ break;
|
||||
+ }
|
||||
err = watchdog_set_timeout(wdd, val);
|
||||
if (err < 0)
|
||||
- return err;
|
||||
+ break;
|
||||
/* If the watchdog is active then we send a keepalive ping
|
||||
* to make sure that the watchdog keep's running (and if
|
||||
* possible that it takes the new timeout) */
|
||||
err = watchdog_ping(wdd);
|
||||
if (err < 0)
|
||||
- return err;
|
||||
+ break;
|
||||
/* Fall */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
/* timeout == 0 means that we don't know the timeout */
|
||||
- if (wdd->timeout == 0)
|
||||
- return -EOPNOTSUPP;
|
||||
- return put_user(wdd->timeout, p);
|
||||
+ if (wdd->timeout == 0) {
|
||||
+ err = -EOPNOTSUPP;
|
||||
+ break;
|
||||
+ }
|
||||
+ err = put_user(wdd->timeout, p);
|
||||
+ break;
|
||||
case WDIOC_GETTIMELEFT:
|
||||
err = watchdog_get_timeleft(wdd, &val);
|
||||
- if (err)
|
||||
- return err;
|
||||
- return put_user(val, p);
|
||||
+ if (err < 0)
|
||||
+ break;
|
||||
+ err = put_user(val, p);
|
||||
+ break;
|
||||
default:
|
||||
- return -ENOTTY;
|
||||
+ err = -ENOTTY;
|
||||
+ break;
|
||||
}
|
||||
+
|
||||
+out_ioctl:
|
||||
+ mutex_unlock(&wd_data->lock);
|
||||
+ return err;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -526,45 +516,59 @@ static long watchdog_ioctl(struct file *
|
||||
|
||||
static int watchdog_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
- int err = -EBUSY;
|
||||
+ struct watchdog_core_data *wd_data;
|
||||
struct watchdog_device *wdd;
|
||||
+ int err;
|
||||
|
||||
/* Get the corresponding watchdog device */
|
||||
if (imajor(inode) == MISC_MAJOR)
|
||||
- wdd = old_wdd;
|
||||
+ wd_data = old_wd_data;
|
||||
else
|
||||
- wdd = container_of(inode->i_cdev, struct watchdog_device, cdev);
|
||||
+ wd_data = container_of(inode->i_cdev, struct watchdog_core_data,
|
||||
+ cdev);
|
||||
|
||||
/* the watchdog is single open! */
|
||||
- if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status))
|
||||
+ if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status))
|
||||
return -EBUSY;
|
||||
|
||||
+ wdd = wd_data->wdd;
|
||||
+
|
||||
/*
|
||||
* If the /dev/watchdog device is open, we don't want the module
|
||||
* to be unloaded.
|
||||
*/
|
||||
- if (!try_module_get(wdd->ops->owner))
|
||||
- goto out;
|
||||
+ if (!try_module_get(wdd->ops->owner)) {
|
||||
+ err = -EBUSY;
|
||||
+ goto out_clear;
|
||||
+ }
|
||||
|
||||
err = watchdog_start(wdd);
|
||||
if (err < 0)
|
||||
goto out_mod;
|
||||
|
||||
- file->private_data = wdd;
|
||||
+ file->private_data = wd_data;
|
||||
|
||||
- if (wdd->ops->ref)
|
||||
- wdd->ops->ref(wdd);
|
||||
+ kref_get(&wd_data->kref);
|
||||
|
||||
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
|
||||
return nonseekable_open(inode, file);
|
||||
|
||||
out_mod:
|
||||
- module_put(wdd->ops->owner);
|
||||
-out:
|
||||
- clear_bit(WDOG_DEV_OPEN, &wdd->status);
|
||||
+ module_put(wd_data->wdd->ops->owner);
|
||||
+out_clear:
|
||||
+ clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
|
||||
return err;
|
||||
}
|
||||
|
||||
+static void watchdog_core_data_release(struct kref *kref)
|
||||
+{
|
||||
+ struct watchdog_core_data *wd_data;
|
||||
+
|
||||
+ wd_data = container_of(kref, struct watchdog_core_data, kref);
|
||||
+
|
||||
+ kfree(wd_data);
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* watchdog_release: release the watchdog device.
|
||||
* @inode: inode of device
|
||||
@@ -577,9 +581,16 @@ out:
|
||||
|
||||
static int watchdog_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
- struct watchdog_device *wdd = file->private_data;
|
||||
+ struct watchdog_core_data *wd_data = file->private_data;
|
||||
+ struct watchdog_device *wdd;
|
||||
int err = -EBUSY;
|
||||
|
||||
+ mutex_lock(&wd_data->lock);
|
||||
+
|
||||
+ wdd = wd_data->wdd;
|
||||
+ if (!wdd)
|
||||
+ goto done;
|
||||
+
|
||||
/*
|
||||
* We only stop the watchdog if we received the magic character
|
||||
* or if WDIOF_MAGICCLOSE is not set. If nowayout was set then
|
||||
@@ -587,29 +598,24 @@ static int watchdog_release(struct inode
|
||||
*/
|
||||
if (!test_bit(WDOG_ACTIVE, &wdd->status))
|
||||
err = 0;
|
||||
- else if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) ||
|
||||
+ else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) ||
|
||||
!(wdd->info->options & WDIOF_MAGICCLOSE))
|
||||
err = watchdog_stop(wdd);
|
||||
|
||||
/* If the watchdog was not stopped, send a keepalive ping */
|
||||
if (err < 0) {
|
||||
- mutex_lock(&wdd->lock);
|
||||
- if (!test_bit(WDOG_UNREGISTERED, &wdd->status))
|
||||
- dev_crit(wdd->dev, "watchdog did not stop!\n");
|
||||
- mutex_unlock(&wdd->lock);
|
||||
+ dev_crit(wdd->dev, "watchdog did not stop!\n");
|
||||
watchdog_ping(wdd);
|
||||
}
|
||||
|
||||
- /* Allow the owner module to be unloaded again */
|
||||
- module_put(wdd->ops->owner);
|
||||
-
|
||||
/* make sure that /dev/watchdog can be re-opened */
|
||||
- clear_bit(WDOG_DEV_OPEN, &wdd->status);
|
||||
-
|
||||
- /* Note wdd may be gone after this, do not use after this! */
|
||||
- if (wdd->ops->unref)
|
||||
- wdd->ops->unref(wdd);
|
||||
+ clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
|
||||
|
||||
+done:
|
||||
+ mutex_unlock(&wd_data->lock);
|
||||
+ /* Allow the owner module to be unloaded again */
|
||||
+ module_put(wd_data->cdev.owner);
|
||||
+ kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -639,10 +645,20 @@ static struct miscdevice watchdog_miscde
|
||||
|
||||
static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||
{
|
||||
+ struct watchdog_core_data *wd_data;
|
||||
int err;
|
||||
|
||||
+ wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL);
|
||||
+ if (!wd_data)
|
||||
+ return -ENOMEM;
|
||||
+ kref_init(&wd_data->kref);
|
||||
+ mutex_init(&wd_data->lock);
|
||||
+
|
||||
+ wd_data->wdd = wdd;
|
||||
+ wdd->wd_data = wd_data;
|
||||
+
|
||||
if (wdd->id == 0) {
|
||||
- old_wdd = wdd;
|
||||
+ old_wd_data = wd_data;
|
||||
watchdog_miscdev.parent = wdd->parent;
|
||||
err = misc_register(&watchdog_miscdev);
|
||||
if (err != 0) {
|
||||
@@ -651,23 +667,25 @@ static int watchdog_cdev_register(struct
|
||||
if (err == -EBUSY)
|
||||
pr_err("%s: a legacy watchdog module is probably present.\n",
|
||||
wdd->info->identity);
|
||||
- old_wdd = NULL;
|
||||
+ old_wd_data = NULL;
|
||||
+ kfree(wd_data);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill in the data structures */
|
||||
- cdev_init(&wdd->cdev, &watchdog_fops);
|
||||
- wdd->cdev.owner = wdd->ops->owner;
|
||||
+ cdev_init(&wd_data->cdev, &watchdog_fops);
|
||||
+ wd_data->cdev.owner = wdd->ops->owner;
|
||||
|
||||
/* Add the device */
|
||||
- err = cdev_add(&wdd->cdev, devno, 1);
|
||||
+ err = cdev_add(&wd_data->cdev, devno, 1);
|
||||
if (err) {
|
||||
pr_err("watchdog%d unable to add device %d:%d\n",
|
||||
wdd->id, MAJOR(watchdog_devt), wdd->id);
|
||||
if (wdd->id == 0) {
|
||||
misc_deregister(&watchdog_miscdev);
|
||||
- old_wdd = NULL;
|
||||
+ old_wd_data = NULL;
|
||||
+ kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
@@ -683,15 +701,20 @@ static int watchdog_cdev_register(struct
|
||||
|
||||
static void watchdog_cdev_unregister(struct watchdog_device *wdd)
|
||||
{
|
||||
- mutex_lock(&wdd->lock);
|
||||
- set_bit(WDOG_UNREGISTERED, &wdd->status);
|
||||
- mutex_unlock(&wdd->lock);
|
||||
+ struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
|
||||
- cdev_del(&wdd->cdev);
|
||||
+ cdev_del(&wd_data->cdev);
|
||||
if (wdd->id == 0) {
|
||||
misc_deregister(&watchdog_miscdev);
|
||||
- old_wdd = NULL;
|
||||
+ old_wd_data = NULL;
|
||||
}
|
||||
+
|
||||
+ mutex_lock(&wd_data->lock);
|
||||
+ wd_data->wdd = NULL;
|
||||
+ wdd->wd_data = NULL;
|
||||
+ mutex_unlock(&wd_data->lock);
|
||||
+
|
||||
+ kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
}
|
||||
|
||||
static struct class watchdog_class = {
|
||||
@@ -742,9 +765,9 @@ int watchdog_dev_register(struct watchdo
|
||||
|
||||
void watchdog_dev_unregister(struct watchdog_device *wdd)
|
||||
{
|
||||
- watchdog_cdev_unregister(wdd);
|
||||
device_destroy(&watchdog_class, wdd->dev->devt);
|
||||
wdd->dev = NULL;
|
||||
+ watchdog_cdev_unregister(wdd);
|
||||
}
|
||||
|
||||
/*
|
||||
--- a/include/linux/watchdog.h
|
||||
+++ b/include/linux/watchdog.h
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
struct watchdog_ops;
|
||||
struct watchdog_device;
|
||||
+struct watchdog_core_data;
|
||||
|
||||
/** struct watchdog_ops - The watchdog-devices operations
|
||||
*
|
||||
@@ -28,8 +29,6 @@ struct watchdog_device;
|
||||
* @set_timeout:The routine for setting the watchdog devices timeout value (in seconds).
|
||||
* @get_timeleft:The routine that gets the time left before a reset (in seconds).
|
||||
* @restart: The routine for restarting the machine.
|
||||
- * @ref: The ref operation for dyn. allocated watchdog_device structs
|
||||
- * @unref: The unref operation for dyn. allocated watchdog_device structs
|
||||
* @ioctl: The routines that handles extra ioctl calls.
|
||||
*
|
||||
* The watchdog_ops structure contains a list of low-level operations
|
||||
@@ -48,15 +47,14 @@ struct watchdog_ops {
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
int (*restart)(struct watchdog_device *);
|
||||
- void (*ref)(struct watchdog_device *);
|
||||
- void (*unref)(struct watchdog_device *);
|
||||
+ void (*ref)(struct watchdog_device *) __deprecated;
|
||||
+ void (*unref)(struct watchdog_device *) __deprecated;
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
};
|
||||
|
||||
/** struct watchdog_device - The structure that defines a watchdog device
|
||||
*
|
||||
* @id: The watchdog's ID. (Allocated by watchdog_register_device)
|
||||
- * @cdev: The watchdog's Character device.
|
||||
* @dev: The device for our watchdog
|
||||
* @parent: The parent bus device
|
||||
* @info: Pointer to a watchdog_info structure.
|
||||
@@ -67,8 +65,8 @@ struct watchdog_ops {
|
||||
* @max_timeout:The watchdog devices maximum timeout value (in seconds).
|
||||
* @reboot_nb: The notifier block to stop watchdog on reboot.
|
||||
* @restart_nb: The notifier block to register a restart function.
|
||||
- * @driver-data:Pointer to the drivers private data.
|
||||
- * @lock: Lock for watchdog core internal use only.
|
||||
+ * @driver_data:Pointer to the drivers private data.
|
||||
+ * @wd_data: Pointer to watchdog core internal data.
|
||||
* @status: Field that contains the devices internal status bits.
|
||||
* @deferred: entry in wtd_deferred_reg_list which is used to
|
||||
* register early initialized watchdogs.
|
||||
@@ -84,7 +82,6 @@ struct watchdog_ops {
|
||||
*/
|
||||
struct watchdog_device {
|
||||
int id;
|
||||
- struct cdev cdev;
|
||||
struct device *dev;
|
||||
struct device *parent;
|
||||
const struct watchdog_info *info;
|
||||
@@ -96,15 +93,12 @@ struct watchdog_device {
|
||||
struct notifier_block reboot_nb;
|
||||
struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
- struct mutex lock;
|
||||
+ struct watchdog_core_data *wd_data;
|
||||
unsigned long status;
|
||||
/* Bit numbers for status flags */
|
||||
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
|
||||
-#define WDOG_DEV_OPEN 1 /* Opened via /dev/watchdog ? */
|
||||
-#define WDOG_ALLOW_RELEASE 2 /* Did we receive the magic char ? */
|
||||
-#define WDOG_NO_WAY_OUT 3 /* Is 'nowayout' feature set ? */
|
||||
-#define WDOG_UNREGISTERED 4 /* Has the device been unregistered */
|
||||
-#define WDOG_STOP_ON_REBOOT 5 /* Should be stopped on reboot */
|
||||
+#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */
|
||||
+#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */
|
||||
struct list_head deferred;
|
||||
};
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
From 0254e953537c92df3e7d0176f401a211e944fd61 Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Sun, 3 Jan 2016 15:11:58 -0800
|
||||
Subject: watchdog: Drop pointer to watchdog device from struct watchdog_device
|
||||
|
||||
The lifetime of the watchdog device pointer is different from the lifetime
|
||||
of its character device. Remove it entirely to avoid race conditions.
|
||||
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
Documentation/watchdog/watchdog-kernel-api.txt | 2 --
|
||||
drivers/watchdog/watchdog_core.c | 8 ++++----
|
||||
drivers/watchdog/watchdog_dev.c | 9 ++++-----
|
||||
include/linux/watchdog.h | 2 --
|
||||
4 files changed, 8 insertions(+), 13 deletions(-)
|
||||
|
||||
--- a/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
+++ b/Documentation/watchdog/watchdog-kernel-api.txt
|
||||
@@ -44,7 +44,6 @@ The watchdog device structure looks like
|
||||
|
||||
struct watchdog_device {
|
||||
int id;
|
||||
- struct device *dev;
|
||||
struct device *parent;
|
||||
const struct watchdog_info *info;
|
||||
const struct watchdog_ops *ops;
|
||||
@@ -65,7 +64,6 @@ It contains following fields:
|
||||
/dev/watchdog0 cdev (dynamic major, minor 0) as well as the old
|
||||
/dev/watchdog miscdev. The id is set automatically when calling
|
||||
watchdog_register_device.
|
||||
-* dev: device under the watchdog class (created by watchdog_register_device).
|
||||
* parent: set this to the parent device (or NULL) before calling
|
||||
watchdog_register_device.
|
||||
* info: a pointer to a watchdog_info structure. This structure gives some
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -249,8 +249,8 @@ static int __watchdog_register_device(st
|
||||
|
||||
ret = register_reboot_notifier(&wdd->reboot_nb);
|
||||
if (ret) {
|
||||
- dev_err(wdd->dev, "Cannot register reboot notifier (%d)\n",
|
||||
- ret);
|
||||
+ pr_err("watchdog%d: Cannot register reboot notifier (%d)\n",
|
||||
+ wdd->id, ret);
|
||||
watchdog_dev_unregister(wdd);
|
||||
ida_simple_remove(&watchdog_ida, wdd->id);
|
||||
return ret;
|
||||
@@ -262,8 +262,8 @@ static int __watchdog_register_device(st
|
||||
|
||||
ret = register_restart_handler(&wdd->restart_nb);
|
||||
if (ret)
|
||||
- dev_warn(wdd->dev, "Cannot register restart handler (%d)\n",
|
||||
- ret);
|
||||
+ pr_warn("watchog%d: Cannot register restart handler (%d)\n",
|
||||
+ wdd->id, ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
--- a/drivers/watchdog/watchdog_dev.c
|
||||
+++ b/drivers/watchdog/watchdog_dev.c
|
||||
@@ -143,7 +143,8 @@ static int watchdog_stop(struct watchdog
|
||||
return 0;
|
||||
|
||||
if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) {
|
||||
- dev_info(wdd->dev, "nowayout prevents watchdog being stopped!\n");
|
||||
+ pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n",
|
||||
+ wdd->id);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
@@ -604,7 +605,7 @@ static int watchdog_release(struct inode
|
||||
|
||||
/* If the watchdog was not stopped, send a keepalive ping */
|
||||
if (err < 0) {
|
||||
- dev_crit(wdd->dev, "watchdog did not stop!\n");
|
||||
+ pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id);
|
||||
watchdog_ping(wdd);
|
||||
}
|
||||
|
||||
@@ -750,7 +751,6 @@ int watchdog_dev_register(struct watchdo
|
||||
watchdog_cdev_unregister(wdd);
|
||||
return PTR_ERR(dev);
|
||||
}
|
||||
- wdd->dev = dev;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -765,8 +765,7 @@ int watchdog_dev_register(struct watchdo
|
||||
|
||||
void watchdog_dev_unregister(struct watchdog_device *wdd)
|
||||
{
|
||||
- device_destroy(&watchdog_class, wdd->dev->devt);
|
||||
- wdd->dev = NULL;
|
||||
+ device_destroy(&watchdog_class, wdd->wd_data->cdev.dev);
|
||||
watchdog_cdev_unregister(wdd);
|
||||
}
|
||||
|
||||
--- a/include/linux/watchdog.h
|
||||
+++ b/include/linux/watchdog.h
|
||||
@@ -55,7 +55,6 @@ struct watchdog_ops {
|
||||
/** struct watchdog_device - The structure that defines a watchdog device
|
||||
*
|
||||
* @id: The watchdog's ID. (Allocated by watchdog_register_device)
|
||||
- * @dev: The device for our watchdog
|
||||
* @parent: The parent bus device
|
||||
* @info: Pointer to a watchdog_info structure.
|
||||
* @ops: Pointer to the list of watchdog operations.
|
||||
@@ -82,7 +81,6 @@ struct watchdog_ops {
|
||||
*/
|
||||
struct watchdog_device {
|
||||
int id;
|
||||
- struct device *dev;
|
||||
struct device *parent;
|
||||
const struct watchdog_info *info;
|
||||
const struct watchdog_ops *ops;
|
|
@ -1,26 +0,0 @@
|
|||
From 62cd1c40ce1c7c16835b599751c7a002eb5bbdf5 Mon Sep 17 00:00:00 2001
|
||||
From: Tomas Winkler <tomas.winkler@intel.com>
|
||||
Date: Sun, 3 Jan 2016 13:32:37 +0200
|
||||
Subject: watchdog: kill unref/ref ops
|
||||
|
||||
ref/unref ops are not called at all so even marked them as deprecated
|
||||
is misleading, we need to just drop the API.
|
||||
|
||||
Signed-off-by: Tomas Winkler <tomas.winkler@intel.com>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
include/linux/watchdog.h | 2 --
|
||||
1 file changed, 2 deletions(-)
|
||||
|
||||
--- a/include/linux/watchdog.h
|
||||
+++ b/include/linux/watchdog.h
|
||||
@@ -47,8 +47,6 @@ struct watchdog_ops {
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
int (*restart)(struct watchdog_device *);
|
||||
- void (*ref)(struct watchdog_device *) __deprecated;
|
||||
- void (*unref)(struct watchdog_device *) __deprecated;
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
};
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
From 80969a68ffed12f82e2a29908306ff43a6861a61 Mon Sep 17 00:00:00 2001
|
||||
From: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Date: Mon, 16 Nov 2015 12:28:09 -0500
|
||||
Subject: watchdog: qcom-wdt: use core restart handler
|
||||
|
||||
Get rid of the custom restart handler by using the one provided by the
|
||||
watchdog core.
|
||||
|
||||
Signed-off-by: Damien Riegel <damien.riegel@savoirfairelinux.com>
|
||||
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/qcom-wdt.c | 49 ++++++++++++++++++---------------------------
|
||||
1 file changed, 19 insertions(+), 30 deletions(-)
|
||||
|
||||
--- a/drivers/watchdog/qcom-wdt.c
|
||||
+++ b/drivers/watchdog/qcom-wdt.c
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
-#include <linux/reboot.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define WDT_RST 0x38
|
||||
@@ -28,7 +27,6 @@ struct qcom_wdt {
|
||||
struct watchdog_device wdd;
|
||||
struct clk *clk;
|
||||
unsigned long rate;
|
||||
- struct notifier_block restart_nb;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
@@ -72,25 +70,9 @@ static int qcom_wdt_set_timeout(struct w
|
||||
return qcom_wdt_start(wdd);
|
||||
}
|
||||
|
||||
-static const struct watchdog_ops qcom_wdt_ops = {
|
||||
- .start = qcom_wdt_start,
|
||||
- .stop = qcom_wdt_stop,
|
||||
- .ping = qcom_wdt_ping,
|
||||
- .set_timeout = qcom_wdt_set_timeout,
|
||||
- .owner = THIS_MODULE,
|
||||
-};
|
||||
-
|
||||
-static const struct watchdog_info qcom_wdt_info = {
|
||||
- .options = WDIOF_KEEPALIVEPING
|
||||
- | WDIOF_MAGICCLOSE
|
||||
- | WDIOF_SETTIMEOUT,
|
||||
- .identity = KBUILD_MODNAME,
|
||||
-};
|
||||
-
|
||||
-static int qcom_wdt_restart(struct notifier_block *nb, unsigned long action,
|
||||
- void *data)
|
||||
+static int qcom_wdt_restart(struct watchdog_device *wdd)
|
||||
{
|
||||
- struct qcom_wdt *wdt = container_of(nb, struct qcom_wdt, restart_nb);
|
||||
+ struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||
u32 timeout;
|
||||
|
||||
/*
|
||||
@@ -110,9 +92,25 @@ static int qcom_wdt_restart(struct notif
|
||||
wmb();
|
||||
|
||||
msleep(150);
|
||||
- return NOTIFY_DONE;
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
+static const struct watchdog_ops qcom_wdt_ops = {
|
||||
+ .start = qcom_wdt_start,
|
||||
+ .stop = qcom_wdt_stop,
|
||||
+ .ping = qcom_wdt_ping,
|
||||
+ .set_timeout = qcom_wdt_set_timeout,
|
||||
+ .restart = qcom_wdt_restart,
|
||||
+ .owner = THIS_MODULE,
|
||||
+};
|
||||
+
|
||||
+static const struct watchdog_info qcom_wdt_info = {
|
||||
+ .options = WDIOF_KEEPALIVEPING
|
||||
+ | WDIOF_MAGICCLOSE
|
||||
+ | WDIOF_SETTIMEOUT,
|
||||
+ .identity = KBUILD_MODNAME,
|
||||
+};
|
||||
+
|
||||
static int qcom_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qcom_wdt *wdt;
|
||||
@@ -187,14 +185,6 @@ static int qcom_wdt_probe(struct platfor
|
||||
goto err_clk_unprepare;
|
||||
}
|
||||
|
||||
- /*
|
||||
- * WDT restart notifier has priority 0 (use as a last resort)
|
||||
- */
|
||||
- wdt->restart_nb.notifier_call = qcom_wdt_restart;
|
||||
- ret = register_restart_handler(&wdt->restart_nb);
|
||||
- if (ret)
|
||||
- dev_err(&pdev->dev, "failed to setup restart handler\n");
|
||||
-
|
||||
platform_set_drvdata(pdev, wdt);
|
||||
return 0;
|
||||
|
||||
@@ -207,7 +197,6 @@ static int qcom_wdt_remove(struct platfo
|
||||
{
|
||||
struct qcom_wdt *wdt = platform_get_drvdata(pdev);
|
||||
|
||||
- unregister_restart_handler(&wdt->restart_nb);
|
||||
watchdog_unregister_device(&wdt->wdd);
|
||||
clk_disable_unprepare(wdt->clk);
|
||||
return 0;
|
|
@ -1,25 +0,0 @@
|
|||
From 0933b453f1c7104d873aacf8524f8ac380a7ed08 Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Thu, 24 Dec 2015 14:22:04 -0800
|
||||
Subject: watchdog: qcom-wdt: Do not set 'dev' in struct watchdog_device
|
||||
|
||||
The 'dev' pointer in struct watchdog_device is set by the watchdog core
|
||||
when registering the watchdog device and not by the driver. It points to
|
||||
the watchdog device, not its parent.
|
||||
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/qcom-wdt.c | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
--- a/drivers/watchdog/qcom-wdt.c
|
||||
+++ b/drivers/watchdog/qcom-wdt.c
|
||||
@@ -164,7 +164,6 @@ static int qcom_wdt_probe(struct platfor
|
||||
goto err_clk_unprepare;
|
||||
}
|
||||
|
||||
- wdt->wdd.dev = &pdev->dev;
|
||||
wdt->wdd.info = &qcom_wdt_info;
|
||||
wdt->wdd.ops = &qcom_wdt_ops;
|
||||
wdt->wdd.min_timeout = 1;
|
|
@ -1,51 +0,0 @@
|
|||
rom 4d8b229d5ea610affe672e919021e9d02cd877da Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Fri, 26 Feb 2016 17:32:49 -0800
|
||||
Subject: watchdog: Add 'action' and 'data' parameters to restart handler
|
||||
callback
|
||||
|
||||
The 'action' (or restart mode) and data parameters may be used by restart
|
||||
handlers, so they should be passed to the restart callback functions.
|
||||
|
||||
Cc: Sylvain Lemieux <slemieux@tycoint.com>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/qcom-wdt.c | 3 ++-
|
||||
drivers/watchdog/watchdog_core.c | 2 +-
|
||||
include/linux/watchdog.h | 2 +-
|
||||
|
||||
--- a/drivers/watchdog/qcom-wdt.c
|
||||
+++ b/drivers/watchdog/qcom-wdt.c
|
||||
@@ -70,7 +70,8 @@ static int qcom_wdt_set_timeout(struct w
|
||||
return qcom_wdt_start(wdd);
|
||||
}
|
||||
|
||||
-static int qcom_wdt_restart(struct watchdog_device *wdd)
|
||||
+static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,
|
||||
+ void *data)
|
||||
{
|
||||
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||
u32 timeout;
|
||||
--- a/drivers/watchdog/watchdog_core.c
|
||||
+++ b/drivers/watchdog/watchdog_core.c
|
||||
@@ -164,7 +164,7 @@ static int watchdog_restart_notifier(str
|
||||
|
||||
int ret;
|
||||
|
||||
- ret = wdd->ops->restart(wdd);
|
||||
+ ret = wdd->ops->restart(wdd, action, data);
|
||||
if (ret)
|
||||
return NOTIFY_BAD;
|
||||
|
||||
--- a/include/linux/watchdog.h
|
||||
+++ b/include/linux/watchdog.h
|
||||
@@ -46,7 +46,7 @@ struct watchdog_ops {
|
||||
unsigned int (*status)(struct watchdog_device *);
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
- int (*restart)(struct watchdog_device *);
|
||||
+ int (*restart)(struct watchdog_device *, unsigned long, void *);
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
};
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
From b6ef36d2c1e391adc1fe1b2dd2a0f887a9f3052b Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <groeck@chromium.org>
|
||||
Date: Mon, 4 Apr 2016 17:37:46 -0700
|
||||
Subject: watchdog: qcom: Report reboot reason
|
||||
|
||||
The Qualcom watchdog timer block reports if the system was reset by the
|
||||
watchdog. Pass the information to user space.
|
||||
|
||||
Reviewed-by: Grant Grundler <grundler@chromium.org>
|
||||
Tested-by: Grant Grundler <grundler@chromium.org>
|
||||
Signed-off-by: Guenter Roeck <groeck@chromium.org>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/qcom-wdt.c | 7 ++++++-
|
||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/watchdog/qcom-wdt.c
|
||||
+++ b/drivers/watchdog/qcom-wdt.c
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#define WDT_RST 0x38
|
||||
#define WDT_EN 0x40
|
||||
+#define WDT_STS 0x44
|
||||
#define WDT_BITE_TIME 0x5C
|
||||
|
||||
struct qcom_wdt {
|
||||
@@ -108,7 +109,8 @@ static const struct watchdog_ops qcom_wd
|
||||
static const struct watchdog_info qcom_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING
|
||||
| WDIOF_MAGICCLOSE
|
||||
- | WDIOF_SETTIMEOUT,
|
||||
+ | WDIOF_SETTIMEOUT
|
||||
+ | WDIOF_CARDRESET,
|
||||
.identity = KBUILD_MODNAME,
|
||||
};
|
||||
|
||||
@@ -171,6 +173,9 @@ static int qcom_wdt_probe(struct platfor
|
||||
wdt->wdd.max_timeout = 0x10000000U / wdt->rate;
|
||||
wdt->wdd.parent = &pdev->dev;
|
||||
|
||||
+ if (readl(wdt->base + WDT_STS) & 1)
|
||||
+ wdt->wdd.bootstatus = WDIOF_CARDRESET;
|
||||
+
|
||||
/*
|
||||
* If 'timeout-sec' unspecified in devicetree, assume a 30 second
|
||||
* default, unless the max timeout is less than 30 seconds, then use
|
|
@ -1,162 +0,0 @@
|
|||
From f0d9d0f4b44ae5503ea368e7f066b20f12ca1d37 Mon Sep 17 00:00:00 2001
|
||||
From: Matthew McClintock <mmcclint@codeaurora.org>
|
||||
Date: Wed, 29 Jun 2016 10:50:01 -0700
|
||||
Subject: watchdog: qcom: add option for standalone watchdog not in timer block
|
||||
|
||||
Commit 0dfd582e026a ("watchdog: qcom: use timer devicetree
|
||||
binding") moved to use the watchdog as a subset timer
|
||||
register block. Some devices have the watchdog completely
|
||||
standalone with slightly different register offsets as
|
||||
well so let's account for the differences here.
|
||||
|
||||
The existing "kpss-standalone" compatible string doesn't
|
||||
make it entirely clear exactly what the device is so
|
||||
rename to "kpss-wdt" to reflect watchdog timer
|
||||
functionality. Also update ipq4019 DTS with an SoC
|
||||
specific compatible.
|
||||
|
||||
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
|
||||
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
|
||||
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
.../devicetree/bindings/watchdog/qcom-wdt.txt | 2 +
|
||||
arch/arm/boot/dts/qcom-ipq4019.dtsi | 2 +-
|
||||
drivers/watchdog/qcom-wdt.c | 64 ++++++++++++++++------
|
||||
3 files changed, 51 insertions(+), 17 deletions(-)
|
||||
|
||||
--- a/drivers/watchdog/qcom-wdt.c
|
||||
+++ b/drivers/watchdog/qcom-wdt.c
|
||||
@@ -18,19 +18,42 @@
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/watchdog.h>
|
||||
+#include <linux/of_device.h>
|
||||
|
||||
-#define WDT_RST 0x38
|
||||
-#define WDT_EN 0x40
|
||||
-#define WDT_STS 0x44
|
||||
-#define WDT_BITE_TIME 0x5C
|
||||
+enum wdt_reg {
|
||||
+ WDT_RST,
|
||||
+ WDT_EN,
|
||||
+ WDT_STS,
|
||||
+ WDT_BITE_TIME,
|
||||
+};
|
||||
+
|
||||
+static const u32 reg_offset_data_apcs_tmr[] = {
|
||||
+ [WDT_RST] = 0x38,
|
||||
+ [WDT_EN] = 0x40,
|
||||
+ [WDT_STS] = 0x44,
|
||||
+ [WDT_BITE_TIME] = 0x5C,
|
||||
+};
|
||||
+
|
||||
+static const u32 reg_offset_data_kpss[] = {
|
||||
+ [WDT_RST] = 0x4,
|
||||
+ [WDT_EN] = 0x8,
|
||||
+ [WDT_STS] = 0xC,
|
||||
+ [WDT_BITE_TIME] = 0x14,
|
||||
+};
|
||||
|
||||
struct qcom_wdt {
|
||||
struct watchdog_device wdd;
|
||||
struct clk *clk;
|
||||
unsigned long rate;
|
||||
void __iomem *base;
|
||||
+ const u32 *layout;
|
||||
};
|
||||
|
||||
+static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg)
|
||||
+{
|
||||
+ return wdt->base + wdt->layout[reg];
|
||||
+}
|
||||
+
|
||||
static inline
|
||||
struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd)
|
||||
{
|
||||
@@ -41,10 +64,10 @@ static int qcom_wdt_start(struct watchdo
|
||||
{
|
||||
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||
|
||||
- writel(0, wdt->base + WDT_EN);
|
||||
- writel(1, wdt->base + WDT_RST);
|
||||
- writel(wdd->timeout * wdt->rate, wdt->base + WDT_BITE_TIME);
|
||||
- writel(1, wdt->base + WDT_EN);
|
||||
+ writel(0, wdt_addr(wdt, WDT_EN));
|
||||
+ writel(1, wdt_addr(wdt, WDT_RST));
|
||||
+ writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));
|
||||
+ writel(1, wdt_addr(wdt, WDT_EN));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -52,7 +75,7 @@ static int qcom_wdt_stop(struct watchdog
|
||||
{
|
||||
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||
|
||||
- writel(0, wdt->base + WDT_EN);
|
||||
+ writel(0, wdt_addr(wdt, WDT_EN));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -60,7 +83,7 @@ static int qcom_wdt_ping(struct watchdog
|
||||
{
|
||||
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||
|
||||
- writel(1, wdt->base + WDT_RST);
|
||||
+ writel(1, wdt_addr(wdt, WDT_RST));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -83,10 +106,10 @@ static int qcom_wdt_restart(struct watch
|
||||
*/
|
||||
timeout = 128 * wdt->rate / 1000;
|
||||
|
||||
- writel(0, wdt->base + WDT_EN);
|
||||
- writel(1, wdt->base + WDT_RST);
|
||||
- writel(timeout, wdt->base + WDT_BITE_TIME);
|
||||
- writel(1, wdt->base + WDT_EN);
|
||||
+ writel(0, wdt_addr(wdt, WDT_EN));
|
||||
+ writel(1, wdt_addr(wdt, WDT_RST));
|
||||
+ writel(timeout, wdt_addr(wdt, WDT_BITE_TIME));
|
||||
+ writel(1, wdt_addr(wdt, WDT_EN));
|
||||
|
||||
/*
|
||||
* Actually make sure the above sequence hits hardware before sleeping.
|
||||
@@ -119,9 +142,16 @@ static int qcom_wdt_probe(struct platfor
|
||||
struct qcom_wdt *wdt;
|
||||
struct resource *res;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
+ const u32 *regs;
|
||||
u32 percpu_offset;
|
||||
int ret;
|
||||
|
||||
+ regs = of_device_get_match_data(&pdev->dev);
|
||||
+ if (!regs) {
|
||||
+ dev_err(&pdev->dev, "Unsupported QCOM WDT module\n");
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
|
||||
if (!wdt)
|
||||
return -ENOMEM;
|
||||
@@ -172,6 +202,7 @@ static int qcom_wdt_probe(struct platfor
|
||||
wdt->wdd.min_timeout = 1;
|
||||
wdt->wdd.max_timeout = 0x10000000U / wdt->rate;
|
||||
wdt->wdd.parent = &pdev->dev;
|
||||
+ wdt->layout = regs;
|
||||
|
||||
if (readl(wdt->base + WDT_STS) & 1)
|
||||
wdt->wdd.bootstatus = WDIOF_CARDRESET;
|
||||
@@ -208,8 +239,9 @@ static int qcom_wdt_remove(struct platfo
|
||||
}
|
||||
|
||||
static const struct of_device_id qcom_wdt_of_table[] = {
|
||||
- { .compatible = "qcom,kpss-timer" },
|
||||
- { .compatible = "qcom,scss-timer" },
|
||||
+ { .compatible = "qcom,kpss-timer", .data = reg_offset_data_apcs_tmr },
|
||||
+ { .compatible = "qcom,scss-timer", .data = reg_offset_data_apcs_tmr },
|
||||
+ { .compatible = "qcom,kpss-wdt", .data = reg_offset_data_kpss },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qcom_wdt_of_table);
|
|
@ -1,60 +0,0 @@
|
|||
From 10073a205df269abcbd9c3fbc690a813827107ef Mon Sep 17 00:00:00 2001
|
||||
From: Matthew McClintock <mmcclint@codeaurora.org>
|
||||
Date: Tue, 28 Jun 2016 11:35:21 -0700
|
||||
Subject: watchdog: qcom: configure BARK time in addition to BITE time
|
||||
|
||||
For certain parts and some versions of TZ, TZ will reset the chip
|
||||
when a BARK is triggered even though it was not configured here. So
|
||||
by default let's configure this BARK time as well.
|
||||
|
||||
Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org>
|
||||
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Thomas Pedersen <twp@codeaurora.org>
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/qcom-wdt.c | 5 +++++
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
--- a/drivers/watchdog/qcom-wdt.c
|
||||
+++ b/drivers/watchdog/qcom-wdt.c
|
||||
@@ -24,6 +24,7 @@ enum wdt_reg {
|
||||
WDT_RST,
|
||||
WDT_EN,
|
||||
WDT_STS,
|
||||
+ WDT_BARK_TIME,
|
||||
WDT_BITE_TIME,
|
||||
};
|
||||
|
||||
@@ -31,6 +32,7 @@ static const u32 reg_offset_data_apcs_tm
|
||||
[WDT_RST] = 0x38,
|
||||
[WDT_EN] = 0x40,
|
||||
[WDT_STS] = 0x44,
|
||||
+ [WDT_BARK_TIME] = 0x4C,
|
||||
[WDT_BITE_TIME] = 0x5C,
|
||||
};
|
||||
|
||||
@@ -38,6 +40,7 @@ static const u32 reg_offset_data_kpss[]
|
||||
[WDT_RST] = 0x4,
|
||||
[WDT_EN] = 0x8,
|
||||
[WDT_STS] = 0xC,
|
||||
+ [WDT_BARK_TIME] = 0x10,
|
||||
[WDT_BITE_TIME] = 0x14,
|
||||
};
|
||||
|
||||
@@ -66,6 +69,7 @@ static int qcom_wdt_start(struct watchdo
|
||||
|
||||
writel(0, wdt_addr(wdt, WDT_EN));
|
||||
writel(1, wdt_addr(wdt, WDT_RST));
|
||||
+ writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));
|
||||
writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));
|
||||
writel(1, wdt_addr(wdt, WDT_EN));
|
||||
return 0;
|
||||
@@ -108,6 +112,7 @@ static int qcom_wdt_restart(struct watch
|
||||
|
||||
writel(0, wdt_addr(wdt, WDT_EN));
|
||||
writel(1, wdt_addr(wdt, WDT_RST));
|
||||
+ writel(timeout, wdt_addr(wdt, WDT_BARK_TIME));
|
||||
writel(timeout, wdt_addr(wdt, WDT_BITE_TIME));
|
||||
writel(1, wdt_addr(wdt, WDT_EN));
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
From 3b8d058cfe6a3b14abee324f4c4b33e64bf61aeb Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Fri, 25 Dec 2015 16:01:45 -0800
|
||||
Subject: hwmon: (sch56xx) Drop watchdog driver data reference count callbacks
|
||||
|
||||
Reference counting is now implemented in the watchdog core and no longer
|
||||
required in watchdog drivers.
|
||||
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/hwmon/sch56xx-common.c | 31 +------------------------------
|
||||
1 file changed, 1 insertion(+), 30 deletions(-)
|
||||
|
||||
--- a/drivers/hwmon/sch56xx-common.c
|
||||
+++ b/drivers/hwmon/sch56xx-common.c
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/uaccess.h>
|
||||
-#include <linux/kref.h>
|
||||
#include <linux/slab.h>
|
||||
#include "sch56xx-common.h"
|
||||
|
||||
@@ -67,7 +66,6 @@ MODULE_PARM_DESC(nowayout, "Watchdog can
|
||||
struct sch56xx_watchdog_data {
|
||||
u16 addr;
|
||||
struct mutex *io_lock;
|
||||
- struct kref kref;
|
||||
struct watchdog_info wdinfo;
|
||||
struct watchdog_device wddev;
|
||||
u8 watchdog_preset;
|
||||
@@ -258,15 +256,6 @@ EXPORT_SYMBOL(sch56xx_read_virtual_reg12
|
||||
* Watchdog routines
|
||||
*/
|
||||
|
||||
-/* Release our data struct when we're unregistered *and*
|
||||
- all references to our watchdog device are released */
|
||||
-static void watchdog_release_resources(struct kref *r)
|
||||
-{
|
||||
- struct sch56xx_watchdog_data *data =
|
||||
- container_of(r, struct sch56xx_watchdog_data, kref);
|
||||
- kfree(data);
|
||||
-}
|
||||
-
|
||||
static int watchdog_set_timeout(struct watchdog_device *wddev,
|
||||
unsigned int timeout)
|
||||
{
|
||||
@@ -395,28 +384,12 @@ static int watchdog_stop(struct watchdog
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static void watchdog_ref(struct watchdog_device *wddev)
|
||||
-{
|
||||
- struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
|
||||
-
|
||||
- kref_get(&data->kref);
|
||||
-}
|
||||
-
|
||||
-static void watchdog_unref(struct watchdog_device *wddev)
|
||||
-{
|
||||
- struct sch56xx_watchdog_data *data = watchdog_get_drvdata(wddev);
|
||||
-
|
||||
- kref_put(&data->kref, watchdog_release_resources);
|
||||
-}
|
||||
-
|
||||
static const struct watchdog_ops watchdog_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = watchdog_start,
|
||||
.stop = watchdog_stop,
|
||||
.ping = watchdog_trigger,
|
||||
.set_timeout = watchdog_set_timeout,
|
||||
- .ref = watchdog_ref,
|
||||
- .unref = watchdog_unref,
|
||||
};
|
||||
|
||||
struct sch56xx_watchdog_data *sch56xx_watchdog_register(struct device *parent,
|
||||
@@ -448,7 +421,6 @@ struct sch56xx_watchdog_data *sch56xx_wa
|
||||
|
||||
data->addr = addr;
|
||||
data->io_lock = io_lock;
|
||||
- kref_init(&data->kref);
|
||||
|
||||
strlcpy(data->wdinfo.identity, "sch56xx watchdog",
|
||||
sizeof(data->wdinfo.identity));
|
||||
@@ -494,8 +466,7 @@ EXPORT_SYMBOL(sch56xx_watchdog_register)
|
||||
void sch56xx_watchdog_unregister(struct sch56xx_watchdog_data *data)
|
||||
{
|
||||
watchdog_unregister_device(&data->wddev);
|
||||
- kref_put(&data->kref, watchdog_release_resources);
|
||||
- /* Don't touch data after this it may have been free-ed! */
|
||||
+ kfree(data);
|
||||
}
|
||||
EXPORT_SYMBOL(sch56xx_watchdog_unregister);
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
From 756d1e9247dff6d416b0c9e073247f5e808bb5fa Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Fri, 25 Dec 2015 16:01:43 -0800
|
||||
Subject: watchdog: da9052_wdt: Drop reference counting
|
||||
|
||||
Reference counting is now implemented in the watchdog core and no longer
|
||||
required in watchdog drivers.
|
||||
|
||||
Since it was implememented a no-op, and since the local memory is allocated
|
||||
with devm_kzalloc(), the reference counting code in the driver really did
|
||||
not really work anyway, and this patch effectively fixes a bug which could
|
||||
cause a crash on unloading if the watchdog device was still open.
|
||||
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/da9052_wdt.c | 24 ------------------------
|
||||
1 file changed, 24 deletions(-)
|
||||
|
||||
--- a/drivers/watchdog/da9052_wdt.c
|
||||
+++ b/drivers/watchdog/da9052_wdt.c
|
||||
@@ -31,7 +31,6 @@
|
||||
struct da9052_wdt_data {
|
||||
struct watchdog_device wdt;
|
||||
struct da9052 *da9052;
|
||||
- struct kref kref;
|
||||
unsigned long jpast;
|
||||
};
|
||||
|
||||
@@ -51,10 +50,6 @@ static const struct {
|
||||
};
|
||||
|
||||
|
||||
-static void da9052_wdt_release_resources(struct kref *r)
|
||||
-{
|
||||
-}
|
||||
-
|
||||
static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev,
|
||||
unsigned int timeout)
|
||||
{
|
||||
@@ -104,20 +99,6 @@ static int da9052_wdt_set_timeout(struct
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static void da9052_wdt_ref(struct watchdog_device *wdt_dev)
|
||||
-{
|
||||
- struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
|
||||
-
|
||||
- kref_get(&driver_data->kref);
|
||||
-}
|
||||
-
|
||||
-static void da9052_wdt_unref(struct watchdog_device *wdt_dev)
|
||||
-{
|
||||
- struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
|
||||
-
|
||||
- kref_put(&driver_data->kref, da9052_wdt_release_resources);
|
||||
-}
|
||||
-
|
||||
static int da9052_wdt_start(struct watchdog_device *wdt_dev)
|
||||
{
|
||||
return da9052_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
|
||||
@@ -170,8 +151,6 @@ static const struct watchdog_ops da9052_
|
||||
.stop = da9052_wdt_stop,
|
||||
.ping = da9052_wdt_ping,
|
||||
.set_timeout = da9052_wdt_set_timeout,
|
||||
- .ref = da9052_wdt_ref,
|
||||
- .unref = da9052_wdt_unref,
|
||||
};
|
||||
|
||||
|
||||
@@ -198,8 +177,6 @@ static int da9052_wdt_probe(struct platf
|
||||
da9052_wdt->parent = &pdev->dev;
|
||||
watchdog_set_drvdata(da9052_wdt, driver_data);
|
||||
|
||||
- kref_init(&driver_data->kref);
|
||||
-
|
||||
ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG,
|
||||
DA9052_CONTROLD_TWDSCALE, 0);
|
||||
if (ret < 0) {
|
||||
@@ -225,7 +202,6 @@ static int da9052_wdt_remove(struct plat
|
||||
struct da9052_wdt_data *driver_data = platform_get_drvdata(pdev);
|
||||
|
||||
watchdog_unregister_device(&driver_data->wdt);
|
||||
- kref_put(&driver_data->kref, da9052_wdt_release_resources);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
From 43f676ace2e0591718ff493d290bc49b35ec2ffc Mon Sep 17 00:00:00 2001
|
||||
From: Guenter Roeck <linux@roeck-us.net>
|
||||
Date: Fri, 25 Dec 2015 16:01:44 -0800
|
||||
Subject: watchdog: da9055_wdt: Drop reference counting
|
||||
|
||||
Reference counting is now implemented in the watchdog core and no longer
|
||||
required in watchdog drivers.
|
||||
|
||||
Since it was implememented a no-op, and since the local memory is allocated
|
||||
with devm_kzalloc(), the reference counting code in the driver really did
|
||||
not really work anyway, and this patch effectively fixes a bug which could
|
||||
cause a crash on unloading if the watchdog device was still open.
|
||||
|
||||
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
|
||||
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
|
||||
---
|
||||
drivers/watchdog/da9055_wdt.c | 24 ------------------------
|
||||
1 file changed, 24 deletions(-)
|
||||
|
||||
--- a/drivers/watchdog/da9055_wdt.c
|
||||
+++ b/drivers/watchdog/da9055_wdt.c
|
||||
@@ -35,7 +35,6 @@ MODULE_PARM_DESC(nowayout,
|
||||
struct da9055_wdt_data {
|
||||
struct watchdog_device wdt;
|
||||
struct da9055 *da9055;
|
||||
- struct kref kref;
|
||||
};
|
||||
|
||||
static const struct {
|
||||
@@ -99,24 +98,6 @@ static int da9055_wdt_ping(struct watchd
|
||||
DA9055_WATCHDOG_MASK, 1);
|
||||
}
|
||||
|
||||
-static void da9055_wdt_release_resources(struct kref *r)
|
||||
-{
|
||||
-}
|
||||
-
|
||||
-static void da9055_wdt_ref(struct watchdog_device *wdt_dev)
|
||||
-{
|
||||
- struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
|
||||
-
|
||||
- kref_get(&driver_data->kref);
|
||||
-}
|
||||
-
|
||||
-static void da9055_wdt_unref(struct watchdog_device *wdt_dev)
|
||||
-{
|
||||
- struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
|
||||
-
|
||||
- kref_put(&driver_data->kref, da9055_wdt_release_resources);
|
||||
-}
|
||||
-
|
||||
static int da9055_wdt_start(struct watchdog_device *wdt_dev)
|
||||
{
|
||||
return da9055_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
|
||||
@@ -138,8 +119,6 @@ static const struct watchdog_ops da9055_
|
||||
.stop = da9055_wdt_stop,
|
||||
.ping = da9055_wdt_ping,
|
||||
.set_timeout = da9055_wdt_set_timeout,
|
||||
- .ref = da9055_wdt_ref,
|
||||
- .unref = da9055_wdt_unref,
|
||||
};
|
||||
|
||||
static int da9055_wdt_probe(struct platform_device *pdev)
|
||||
@@ -165,8 +144,6 @@ static int da9055_wdt_probe(struct platf
|
||||
watchdog_set_nowayout(da9055_wdt, nowayout);
|
||||
watchdog_set_drvdata(da9055_wdt, driver_data);
|
||||
|
||||
- kref_init(&driver_data->kref);
|
||||
-
|
||||
ret = da9055_wdt_stop(da9055_wdt);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to stop watchdog, %d\n", ret);
|
||||
@@ -189,7 +166,6 @@ static int da9055_wdt_remove(struct plat
|
||||
struct da9055_wdt_data *driver_data = platform_get_drvdata(pdev);
|
||||
|
||||
watchdog_unregister_device(&driver_data->wdt);
|
||||
- kref_put(&driver_data->kref, da9055_wdt_release_resources);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,746 +0,0 @@
|
|||
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 @@ config QCOM_GDSC
|
||||
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 @@ config COMMON_CLK_QCOM
|
||||
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
|
||||
@@ -22,3 +22,4 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm896
|
||||
obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
|
||||
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
|
||||
--- /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
|
|
@ -1,586 +0,0 @@
|
|||
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
|
|
@ -1,94 +0,0 @@
|
|||
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;
|
||||
|
|
@ -1,536 +0,0 @@
|
|||
From 9066073c6c27994a30187abf3b674770b4088348 Mon Sep 17 00:00:00 2001
|
||||
From: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Date: Thu, 5 May 2016 14:21:39 +0530
|
||||
Subject: thermal: qcom: tsens: Add a skeletal TSENS drivers
|
||||
|
||||
TSENS is Qualcomms' thermal temperature sensor device. It
|
||||
supports reading temperatures from multiple thermal sensors
|
||||
present on various QCOM SoCs.
|
||||
Calibration data is generally read from a non-volatile memory
|
||||
(eeprom) device.
|
||||
|
||||
Add a skeleton driver with all the necessary abstractions so
|
||||
a variety of qcom device families which support TSENS can
|
||||
add driver extensions.
|
||||
|
||||
Also add the required device tree bindings which can be used
|
||||
to describe the TSENS device in DT.
|
||||
|
||||
Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Reviewed-by: Lina Iyer <lina.iyer@linaro.org>
|
||||
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
|
||||
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
|
||||
---
|
||||
.../devicetree/bindings/thermal/qcom-tsens.txt | 21 +++
|
||||
drivers/thermal/Kconfig | 5 +
|
||||
drivers/thermal/Makefile | 1 +
|
||||
drivers/thermal/qcom/Kconfig | 11 ++
|
||||
drivers/thermal/qcom/Makefile | 2 +
|
||||
drivers/thermal/qcom/tsens-common.c | 141 +++++++++++++++
|
||||
drivers/thermal/qcom/tsens.c | 195 +++++++++++++++++++++
|
||||
drivers/thermal/qcom/tsens.h | 90 ++++++++++
|
||||
8 files changed, 466 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/thermal/qcom-tsens.txt
|
||||
create mode 100644 drivers/thermal/qcom/Kconfig
|
||||
create mode 100644 drivers/thermal/qcom/Makefile
|
||||
create mode 100644 drivers/thermal/qcom/tsens-common.c
|
||||
create mode 100644 drivers/thermal/qcom/tsens.c
|
||||
create mode 100644 drivers/thermal/qcom/tsens.h
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.txt
|
||||
@@ -0,0 +1,21 @@
|
||||
+* QCOM SoC Temperature Sensor (TSENS)
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible :
|
||||
+ - "qcom,msm8916-tsens" : For 8916 Family of SoCs
|
||||
+ - "qcom,msm8974-tsens" : For 8974 Family of SoCs
|
||||
+ - "qcom,msm8996-tsens" : For 8996 Family of SoCs
|
||||
+
|
||||
+- reg: Address range of the thermal registers
|
||||
+- #thermal-sensor-cells : Should be 1. See ./thermal.txt for a description.
|
||||
+- Refer to Documentation/devicetree/bindings/nvmem/nvmem.txt to know how to specify
|
||||
+nvmem cells
|
||||
+
|
||||
+Example:
|
||||
+tsens: thermal-sensor@900000 {
|
||||
+ compatible = "qcom,msm8916-tsens";
|
||||
+ reg = <0x4a8000 0x2000>;
|
||||
+ nvmem-cells = <&tsens_caldata>, <&tsens_calsel>;
|
||||
+ nvmem-cell-names = "caldata", "calsel";
|
||||
+ #thermal-sensor-cells = <1>;
|
||||
+ };
|
||||
--- a/drivers/thermal/Kconfig
|
||||
+++ b/drivers/thermal/Kconfig
|
||||
@@ -391,4 +391,9 @@ config QCOM_SPMI_TEMP_ALARM
|
||||
real time die temperature if an ADC is present or an estimate of the
|
||||
temperature based upon the over temperature stage value.
|
||||
|
||||
+menu "Qualcomm thermal drivers"
|
||||
+depends on (ARCH_QCOM && OF) || COMPILE_TEST
|
||||
+source "drivers/thermal/qcom/Kconfig"
|
||||
+endmenu
|
||||
+
|
||||
endif
|
||||
--- a/drivers/thermal/Makefile
|
||||
+++ b/drivers/thermal/Makefile
|
||||
@@ -48,3 +48,4 @@ obj-$(CONFIG_INTEL_PCH_THERMAL) += intel
|
||||
obj-$(CONFIG_ST_THERMAL) += st/
|
||||
obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o
|
||||
obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
|
||||
+obj-$(CONFIG_QCOM_TSENS) += qcom/
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/Kconfig
|
||||
@@ -0,0 +1,11 @@
|
||||
+config QCOM_TSENS
|
||||
+ tristate "Qualcomm TSENS Temperature Alarm"
|
||||
+ depends on THERMAL
|
||||
+ depends on QCOM_QFPROM
|
||||
+ depends on ARCH_QCOM || COMPILE_TEST
|
||||
+ help
|
||||
+ This enables the thermal sysfs driver for the TSENS device. It shows
|
||||
+ up in Sysfs as a thermal zone with multiple trip points. Disabling the
|
||||
+ thermal zone device via the mode file results in disabling the sensor.
|
||||
+ Also able to set threshold temperature for both hot and cold and update
|
||||
+ when a threshold is reached.
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/Makefile
|
||||
@@ -0,0 +1,2 @@
|
||||
+obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
|
||||
+qcom_tsens-y += tsens.o tsens-common.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/tsens-common.c
|
||||
@@ -0,0 +1,141 @@
|
||||
+/*
|
||||
+ * Copyright (c) 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/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/nvmem-consumer.h>
|
||||
+#include <linux/of_address.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include "tsens.h"
|
||||
+
|
||||
+#define S0_ST_ADDR 0x1030
|
||||
+#define SN_ADDR_OFFSET 0x4
|
||||
+#define SN_ST_TEMP_MASK 0x3ff
|
||||
+#define CAL_DEGC_PT1 30
|
||||
+#define CAL_DEGC_PT2 120
|
||||
+#define SLOPE_FACTOR 1000
|
||||
+#define SLOPE_DEFAULT 3200
|
||||
+
|
||||
+char *qfprom_read(struct device *dev, const char *cname)
|
||||
+{
|
||||
+ struct nvmem_cell *cell;
|
||||
+ ssize_t data;
|
||||
+ char *ret;
|
||||
+
|
||||
+ cell = nvmem_cell_get(dev, cname);
|
||||
+ if (IS_ERR(cell))
|
||||
+ return ERR_CAST(cell);
|
||||
+
|
||||
+ ret = nvmem_cell_read(cell, &data);
|
||||
+ nvmem_cell_put(cell);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Use this function on devices where slope and offset calculations
|
||||
+ * depend on calibration data read from qfprom. On others the slope
|
||||
+ * and offset values are derived from tz->tzp->slope and tz->tzp->offset
|
||||
+ * resp.
|
||||
+ */
|
||||
+void compute_intercept_slope(struct tsens_device *tmdev, u32 *p1,
|
||||
+ u32 *p2, u32 mode)
|
||||
+{
|
||||
+ int i;
|
||||
+ int num, den;
|
||||
+
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ dev_dbg(tmdev->dev,
|
||||
+ "sensor%d - data_point1:%#x data_point2:%#x\n",
|
||||
+ i, p1[i], p2[i]);
|
||||
+
|
||||
+ tmdev->sensor[i].slope = SLOPE_DEFAULT;
|
||||
+ if (mode == TWO_PT_CALIB) {
|
||||
+ /*
|
||||
+ * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
|
||||
+ * temp_120_degc - temp_30_degc (x2 - x1)
|
||||
+ */
|
||||
+ num = p2[i] - p1[i];
|
||||
+ num *= SLOPE_FACTOR;
|
||||
+ den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
|
||||
+ tmdev->sensor[i].slope = num / den;
|
||||
+ }
|
||||
+
|
||||
+ tmdev->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
|
||||
+ (CAL_DEGC_PT1 *
|
||||
+ tmdev->sensor[i].slope);
|
||||
+ dev_dbg(tmdev->dev, "offset:%d\n", tmdev->sensor[i].offset);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
|
||||
+{
|
||||
+ int degc, num, den;
|
||||
+
|
||||
+ num = (adc_code * SLOPE_FACTOR) - s->offset;
|
||||
+ den = s->slope;
|
||||
+
|
||||
+ if (num > 0)
|
||||
+ degc = num + (den / 2);
|
||||
+ else if (num < 0)
|
||||
+ degc = num - (den / 2);
|
||||
+ else
|
||||
+ degc = num;
|
||||
+
|
||||
+ degc /= den;
|
||||
+
|
||||
+ return degc;
|
||||
+}
|
||||
+
|
||||
+int get_temp_common(struct tsens_device *tmdev, int id, int *temp)
|
||||
+{
|
||||
+ struct tsens_sensor *s = &tmdev->sensor[id];
|
||||
+ u32 code;
|
||||
+ unsigned int sensor_addr;
|
||||
+ int last_temp = 0, ret;
|
||||
+
|
||||
+ sensor_addr = S0_ST_ADDR + s->hw_id * SN_ADDR_OFFSET;
|
||||
+ ret = regmap_read(tmdev->map, sensor_addr, &code);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ last_temp = code & SN_ST_TEMP_MASK;
|
||||
+
|
||||
+ *temp = code_to_degc(last_temp, s) * 1000;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct regmap_config tsens_config = {
|
||||
+ .reg_bits = 32,
|
||||
+ .val_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+};
|
||||
+
|
||||
+int __init init_common(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ void __iomem *base;
|
||||
+
|
||||
+ base = of_iomap(tmdev->dev->of_node, 0);
|
||||
+ if (IS_ERR(base))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ tmdev->map = devm_regmap_init_mmio(tmdev->dev, base, &tsens_config);
|
||||
+ if (!tmdev->map) {
|
||||
+ iounmap(base);
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/tsens.c
|
||||
@@ -0,0 +1,195 @@
|
||||
+/*
|
||||
+ * Copyright (c) 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/err.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pm.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/thermal.h>
|
||||
+#include "tsens.h"
|
||||
+
|
||||
+static int tsens_get_temp(void *data, int *temp)
|
||||
+{
|
||||
+ const struct tsens_sensor *s = data;
|
||||
+ struct tsens_device *tmdev = s->tmdev;
|
||||
+
|
||||
+ return tmdev->ops->get_temp(tmdev, s->id, temp);
|
||||
+}
|
||||
+
|
||||
+static int tsens_get_trend(void *data, long *temp)
|
||||
+{
|
||||
+ const struct tsens_sensor *s = data;
|
||||
+ struct tsens_device *tmdev = s->tmdev;
|
||||
+
|
||||
+ if (tmdev->ops->get_trend)
|
||||
+ return tmdev->ops->get_trend(tmdev, s->id, temp);
|
||||
+
|
||||
+ return -ENOTSUPP;
|
||||
+}
|
||||
+
|
||||
+static int tsens_suspend(struct device *dev)
|
||||
+{
|
||||
+ struct tsens_device *tmdev = dev_get_drvdata(dev);
|
||||
+
|
||||
+ if (tmdev->ops && tmdev->ops->suspend)
|
||||
+ return tmdev->ops->suspend(tmdev);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int tsens_resume(struct device *dev)
|
||||
+{
|
||||
+ struct tsens_device *tmdev = dev_get_drvdata(dev);
|
||||
+
|
||||
+ if (tmdev->ops && tmdev->ops->resume)
|
||||
+ return tmdev->ops->resume(tmdev);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
|
||||
+
|
||||
+static const struct of_device_id tsens_table[] = {
|
||||
+ {
|
||||
+ .compatible = "qcom,msm8916-tsens",
|
||||
+ }, {
|
||||
+ .compatible = "qcom,msm8974-tsens",
|
||||
+ },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, tsens_table);
|
||||
+
|
||||
+static const struct thermal_zone_of_device_ops tsens_of_ops = {
|
||||
+ .get_temp = tsens_get_temp,
|
||||
+ .get_trend = tsens_get_trend,
|
||||
+};
|
||||
+
|
||||
+static int tsens_register(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int i;
|
||||
+ struct thermal_zone_device *tzd;
|
||||
+ u32 *hw_id, n = tmdev->num_sensors;
|
||||
+
|
||||
+ hw_id = devm_kcalloc(tmdev->dev, n, sizeof(u32), GFP_KERNEL);
|
||||
+ if (!hw_id)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ tmdev->sensor[i].tmdev = tmdev;
|
||||
+ tmdev->sensor[i].id = i;
|
||||
+ tzd = devm_thermal_zone_of_sensor_register(tmdev->dev, i,
|
||||
+ &tmdev->sensor[i],
|
||||
+ &tsens_of_ops);
|
||||
+ if (IS_ERR(tzd))
|
||||
+ continue;
|
||||
+ tmdev->sensor[i].tzd = tzd;
|
||||
+ if (tmdev->ops->enable)
|
||||
+ tmdev->ops->enable(tmdev, i);
|
||||
+ }
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int tsens_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ int ret, i;
|
||||
+ struct device *dev;
|
||||
+ struct device_node *np;
|
||||
+ struct tsens_sensor *s;
|
||||
+ struct tsens_device *tmdev;
|
||||
+ const struct tsens_data *data;
|
||||
+ const struct of_device_id *id;
|
||||
+
|
||||
+ if (pdev->dev.of_node)
|
||||
+ dev = &pdev->dev;
|
||||
+ else
|
||||
+ dev = pdev->dev.parent;
|
||||
+
|
||||
+ np = dev->of_node;
|
||||
+
|
||||
+ id = of_match_node(tsens_table, np);
|
||||
+ if (!id)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ data = id->data;
|
||||
+
|
||||
+ if (data->num_sensors <= 0) {
|
||||
+ dev_err(dev, "invalid number of sensors\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ tmdev = devm_kzalloc(dev, sizeof(*tmdev) +
|
||||
+ data->num_sensors * sizeof(*s), GFP_KERNEL);
|
||||
+ if (!tmdev)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ tmdev->dev = dev;
|
||||
+ tmdev->num_sensors = data->num_sensors;
|
||||
+ tmdev->ops = data->ops;
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ if (data->hw_ids)
|
||||
+ tmdev->sensor[i].hw_id = data->hw_ids[i];
|
||||
+ else
|
||||
+ tmdev->sensor[i].hw_id = i;
|
||||
+ }
|
||||
+
|
||||
+ if (!tmdev->ops || !tmdev->ops->init || !tmdev->ops->get_temp)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ ret = tmdev->ops->init(tmdev);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "tsens init failed\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (tmdev->ops->calibrate) {
|
||||
+ ret = tmdev->ops->calibrate(tmdev);
|
||||
+ if (ret < 0) {
|
||||
+ dev_err(dev, "tsens calibration failed\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ ret = tsens_register(tmdev);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, tmdev);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int tsens_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct tsens_device *tmdev = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ if (tmdev->ops->disable)
|
||||
+ tmdev->ops->disable(tmdev);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver tsens_driver = {
|
||||
+ .probe = tsens_probe,
|
||||
+ .remove = tsens_remove,
|
||||
+ .driver = {
|
||||
+ .name = "qcom-tsens",
|
||||
+ .pm = &tsens_pm_ops,
|
||||
+ .of_match_table = tsens_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(tsens_driver);
|
||||
+
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
|
||||
+MODULE_ALIAS("platform:qcom-tsens");
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/tsens.h
|
||||
@@ -0,0 +1,90 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2015, 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.
|
||||
+ */
|
||||
+#ifndef __QCOM_TSENS_H__
|
||||
+#define __QCOM_TSENS_H__
|
||||
+
|
||||
+#define ONE_PT_CALIB 0x1
|
||||
+#define ONE_PT_CALIB2 0x2
|
||||
+#define TWO_PT_CALIB 0x3
|
||||
+
|
||||
+struct tsens_device;
|
||||
+
|
||||
+struct tsens_sensor {
|
||||
+ struct tsens_device *tmdev;
|
||||
+ struct thermal_zone_device *tzd;
|
||||
+ int offset;
|
||||
+ int id;
|
||||
+ int hw_id;
|
||||
+ int slope;
|
||||
+ u32 status;
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * struct tsens_ops - operations as supported by the tsens device
|
||||
+ * @init: Function to initialize the tsens device
|
||||
+ * @calibrate: Function to calibrate the tsens device
|
||||
+ * @get_temp: Function which returns the temp in millidegC
|
||||
+ * @enable: Function to enable (clocks/power) tsens device
|
||||
+ * @disable: Function to disable the tsens device
|
||||
+ * @suspend: Function to suspend the tsens device
|
||||
+ * @resume: Function to resume the tsens device
|
||||
+ * @get_trend: Function to get the thermal/temp trend
|
||||
+ */
|
||||
+struct tsens_ops {
|
||||
+ /* mandatory callbacks */
|
||||
+ int (*init)(struct tsens_device *);
|
||||
+ int (*calibrate)(struct tsens_device *);
|
||||
+ int (*get_temp)(struct tsens_device *, int, int *);
|
||||
+ /* optional callbacks */
|
||||
+ int (*enable)(struct tsens_device *, int);
|
||||
+ void (*disable)(struct tsens_device *);
|
||||
+ int (*suspend)(struct tsens_device *);
|
||||
+ int (*resume)(struct tsens_device *);
|
||||
+ int (*get_trend)(struct tsens_device *, int, long *);
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * struct tsens_data - tsens instance specific data
|
||||
+ * @num_sensors: Max number of sensors supported by platform
|
||||
+ * @ops: operations the tsens instance supports
|
||||
+ * @hw_ids: Subset of sensors ids supported by platform, if not the first n
|
||||
+ */
|
||||
+struct tsens_data {
|
||||
+ const u32 num_sensors;
|
||||
+ const struct tsens_ops *ops;
|
||||
+ unsigned int *hw_ids;
|
||||
+};
|
||||
+
|
||||
+/* Registers to be saved/restored across a context loss */
|
||||
+struct tsens_context {
|
||||
+ int threshold;
|
||||
+ int control;
|
||||
+};
|
||||
+
|
||||
+struct tsens_device {
|
||||
+ struct device *dev;
|
||||
+ u32 num_sensors;
|
||||
+ struct regmap *map;
|
||||
+ struct regmap_field *status_field;
|
||||
+ struct tsens_context ctx;
|
||||
+ bool trdy;
|
||||
+ const struct tsens_ops *ops;
|
||||
+ struct tsens_sensor sensor[0];
|
||||
+};
|
||||
+
|
||||
+char *qfprom_read(struct device *, const char *);
|
||||
+void compute_intercept_slope(struct tsens_device *, u32 *, u32 *, u32);
|
||||
+int init_common(struct tsens_device *);
|
||||
+int get_temp_common(struct tsens_device *, int, int *);
|
||||
+
|
||||
+#endif /* __QCOM_TSENS_H__ */
|
|
@ -1,165 +0,0 @@
|
|||
From 840a5bd3ed3fdd62456d4d26c3128ec10496555b Mon Sep 17 00:00:00 2001
|
||||
From: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Date: Thu, 5 May 2016 14:21:40 +0530
|
||||
Subject: thermal: qcom: tsens-8916: Add support for 8916 family of SoCs
|
||||
|
||||
Add support to calibrate sensors on 8916 family and also add common
|
||||
functions to read temperature from sensors (This can be reused on
|
||||
other SoCs having similar TSENS device)
|
||||
The calibration data is read from eeprom using the generic nvmem
|
||||
framework apis.
|
||||
|
||||
Based on the original code by Siddartha Mohanadoss and Stephen Boyd.
|
||||
|
||||
Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
|
||||
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
|
||||
---
|
||||
drivers/thermal/qcom/Makefile | 2 +-
|
||||
drivers/thermal/qcom/tsens-8916.c | 113 ++++++++++++++++++++++++++++++++++++++
|
||||
drivers/thermal/qcom/tsens.c | 1 +
|
||||
drivers/thermal/qcom/tsens.h | 2 +
|
||||
4 files changed, 117 insertions(+), 1 deletion(-)
|
||||
create mode 100644 drivers/thermal/qcom/tsens-8916.c
|
||||
|
||||
--- a/drivers/thermal/qcom/Makefile
|
||||
+++ b/drivers/thermal/qcom/Makefile
|
||||
@@ -1,2 +1,2 @@
|
||||
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
|
||||
-qcom_tsens-y += tsens.o tsens-common.o
|
||||
+qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/tsens-8916.c
|
||||
@@ -0,0 +1,113 @@
|
||||
+/*
|
||||
+ * Copyright (c) 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/platform_device.h>
|
||||
+#include "tsens.h"
|
||||
+
|
||||
+/* eeprom layout data for 8916 */
|
||||
+#define BASE0_MASK 0x0000007f
|
||||
+#define BASE1_MASK 0xfe000000
|
||||
+#define BASE0_SHIFT 0
|
||||
+#define BASE1_SHIFT 25
|
||||
+
|
||||
+#define S0_P1_MASK 0x00000f80
|
||||
+#define S1_P1_MASK 0x003e0000
|
||||
+#define S2_P1_MASK 0xf8000000
|
||||
+#define S3_P1_MASK 0x000003e0
|
||||
+#define S4_P1_MASK 0x000f8000
|
||||
+
|
||||
+#define S0_P2_MASK 0x0001f000
|
||||
+#define S1_P2_MASK 0x07c00000
|
||||
+#define S2_P2_MASK 0x0000001f
|
||||
+#define S3_P2_MASK 0x00007c00
|
||||
+#define S4_P2_MASK 0x01f00000
|
||||
+
|
||||
+#define S0_P1_SHIFT 7
|
||||
+#define S1_P1_SHIFT 17
|
||||
+#define S2_P1_SHIFT 27
|
||||
+#define S3_P1_SHIFT 5
|
||||
+#define S4_P1_SHIFT 15
|
||||
+
|
||||
+#define S0_P2_SHIFT 12
|
||||
+#define S1_P2_SHIFT 22
|
||||
+#define S2_P2_SHIFT 0
|
||||
+#define S3_P2_SHIFT 10
|
||||
+#define S4_P2_SHIFT 20
|
||||
+
|
||||
+#define CAL_SEL_MASK 0xe0000000
|
||||
+#define CAL_SEL_SHIFT 29
|
||||
+
|
||||
+static int calibrate_8916(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int base0 = 0, base1 = 0, i;
|
||||
+ u32 p1[5], p2[5];
|
||||
+ int mode = 0;
|
||||
+ u32 *qfprom_cdata, *qfprom_csel;
|
||||
+
|
||||
+ qfprom_cdata = (u32 *)qfprom_read(tmdev->dev, "calib");
|
||||
+ if (IS_ERR(qfprom_cdata))
|
||||
+ return PTR_ERR(qfprom_cdata);
|
||||
+
|
||||
+ qfprom_csel = (u32 *)qfprom_read(tmdev->dev, "calib_sel");
|
||||
+ if (IS_ERR(qfprom_csel))
|
||||
+ return PTR_ERR(qfprom_csel);
|
||||
+
|
||||
+ mode = (qfprom_csel[0] & CAL_SEL_MASK) >> CAL_SEL_SHIFT;
|
||||
+ dev_dbg(tmdev->dev, "calibration mode is %d\n", mode);
|
||||
+
|
||||
+ switch (mode) {
|
||||
+ case TWO_PT_CALIB:
|
||||
+ base1 = (qfprom_cdata[1] & BASE1_MASK) >> BASE1_SHIFT;
|
||||
+ p2[0] = (qfprom_cdata[0] & S0_P2_MASK) >> S0_P2_SHIFT;
|
||||
+ p2[1] = (qfprom_cdata[0] & S1_P2_MASK) >> S1_P2_SHIFT;
|
||||
+ p2[2] = (qfprom_cdata[1] & S2_P2_MASK) >> S2_P2_SHIFT;
|
||||
+ p2[3] = (qfprom_cdata[1] & S3_P2_MASK) >> S3_P2_SHIFT;
|
||||
+ p2[4] = (qfprom_cdata[1] & S4_P2_MASK) >> S4_P2_SHIFT;
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++)
|
||||
+ p2[i] = ((base1 + p2[i]) << 3);
|
||||
+ /* Fall through */
|
||||
+ case ONE_PT_CALIB2:
|
||||
+ base0 = (qfprom_cdata[0] & BASE0_MASK);
|
||||
+ p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT;
|
||||
+ p1[1] = (qfprom_cdata[0] & S1_P1_MASK) >> S1_P1_SHIFT;
|
||||
+ p1[2] = (qfprom_cdata[0] & S2_P1_MASK) >> S2_P1_SHIFT;
|
||||
+ p1[3] = (qfprom_cdata[1] & S3_P1_MASK) >> S3_P1_SHIFT;
|
||||
+ p1[4] = (qfprom_cdata[1] & S4_P1_MASK) >> S4_P1_SHIFT;
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++)
|
||||
+ p1[i] = (((base0) + p1[i]) << 3);
|
||||
+ break;
|
||||
+ default:
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ p1[i] = 500;
|
||||
+ p2[i] = 780;
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ compute_intercept_slope(tmdev, p1, p2, mode);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+const struct tsens_ops ops_8916 = {
|
||||
+ .init = init_common,
|
||||
+ .calibrate = calibrate_8916,
|
||||
+ .get_temp = get_temp_common,
|
||||
+};
|
||||
+
|
||||
+const struct tsens_data data_8916 = {
|
||||
+ .num_sensors = 5,
|
||||
+ .ops = &ops_8916,
|
||||
+ .hw_ids = (unsigned int []){0, 1, 2, 4, 5 },
|
||||
+};
|
||||
--- a/drivers/thermal/qcom/tsens.c
|
||||
+++ b/drivers/thermal/qcom/tsens.c
|
||||
@@ -65,6 +65,7 @@ static SIMPLE_DEV_PM_OPS(tsens_pm_ops, t
|
||||
static const struct of_device_id tsens_table[] = {
|
||||
{
|
||||
.compatible = "qcom,msm8916-tsens",
|
||||
+ .data = &data_8916,
|
||||
}, {
|
||||
.compatible = "qcom,msm8974-tsens",
|
||||
},
|
||||
--- a/drivers/thermal/qcom/tsens.h
|
||||
+++ b/drivers/thermal/qcom/tsens.h
|
||||
@@ -87,4 +87,6 @@ void compute_intercept_slope(struct tsen
|
||||
int init_common(struct tsens_device *);
|
||||
int get_temp_common(struct tsens_device *, int, int *);
|
||||
|
||||
+extern const struct tsens_data data_8916;
|
||||
+
|
||||
#endif /* __QCOM_TSENS_H__ */
|
|
@ -1,293 +0,0 @@
|
|||
From 5e6703bd2d83548998848865cb9a9a795f31a311 Mon Sep 17 00:00:00 2001
|
||||
From: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Date: Thu, 5 May 2016 14:21:41 +0530
|
||||
Subject: thermal: qcom: tsens-8974: Add support for 8974 family of SoCs
|
||||
|
||||
Add .calibrate support for 8974 family as part of tsens_ops.
|
||||
|
||||
Based on the original code by Siddartha Mohanadoss and Stephen Boyd.
|
||||
|
||||
Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
|
||||
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
|
||||
---
|
||||
drivers/thermal/qcom/Makefile | 2 +-
|
||||
drivers/thermal/qcom/tsens-8974.c | 244 ++++++++++++++++++++++++++++++++++++++
|
||||
drivers/thermal/qcom/tsens.c | 1 +
|
||||
drivers/thermal/qcom/tsens.h | 2 +-
|
||||
4 files changed, 247 insertions(+), 2 deletions(-)
|
||||
create mode 100644 drivers/thermal/qcom/tsens-8974.c
|
||||
|
||||
--- a/drivers/thermal/qcom/Makefile
|
||||
+++ b/drivers/thermal/qcom/Makefile
|
||||
@@ -1,2 +1,2 @@
|
||||
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
|
||||
-qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o
|
||||
+qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/tsens-8974.c
|
||||
@@ -0,0 +1,244 @@
|
||||
+/*
|
||||
+ * Copyright (c) 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/platform_device.h>
|
||||
+#include "tsens.h"
|
||||
+
|
||||
+/* eeprom layout data for 8974 */
|
||||
+#define BASE1_MASK 0xff
|
||||
+#define S0_P1_MASK 0x3f00
|
||||
+#define S1_P1_MASK 0xfc000
|
||||
+#define S2_P1_MASK 0x3f00000
|
||||
+#define S3_P1_MASK 0xfc000000
|
||||
+#define S4_P1_MASK 0x3f
|
||||
+#define S5_P1_MASK 0xfc0
|
||||
+#define S6_P1_MASK 0x3f000
|
||||
+#define S7_P1_MASK 0xfc0000
|
||||
+#define S8_P1_MASK 0x3f000000
|
||||
+#define S8_P1_MASK_BKP 0x3f
|
||||
+#define S9_P1_MASK 0x3f
|
||||
+#define S9_P1_MASK_BKP 0xfc0
|
||||
+#define S10_P1_MASK 0xfc0
|
||||
+#define S10_P1_MASK_BKP 0x3f000
|
||||
+#define CAL_SEL_0_1 0xc0000000
|
||||
+#define CAL_SEL_2 0x40000000
|
||||
+#define CAL_SEL_SHIFT 30
|
||||
+#define CAL_SEL_SHIFT_2 28
|
||||
+
|
||||
+#define S0_P1_SHIFT 8
|
||||
+#define S1_P1_SHIFT 14
|
||||
+#define S2_P1_SHIFT 20
|
||||
+#define S3_P1_SHIFT 26
|
||||
+#define S5_P1_SHIFT 6
|
||||
+#define S6_P1_SHIFT 12
|
||||
+#define S7_P1_SHIFT 18
|
||||
+#define S8_P1_SHIFT 24
|
||||
+#define S9_P1_BKP_SHIFT 6
|
||||
+#define S10_P1_SHIFT 6
|
||||
+#define S10_P1_BKP_SHIFT 12
|
||||
+
|
||||
+#define BASE2_SHIFT 12
|
||||
+#define BASE2_BKP_SHIFT 18
|
||||
+#define S0_P2_SHIFT 20
|
||||
+#define S0_P2_BKP_SHIFT 26
|
||||
+#define S1_P2_SHIFT 26
|
||||
+#define S2_P2_BKP_SHIFT 6
|
||||
+#define S3_P2_SHIFT 6
|
||||
+#define S3_P2_BKP_SHIFT 12
|
||||
+#define S4_P2_SHIFT 12
|
||||
+#define S4_P2_BKP_SHIFT 18
|
||||
+#define S5_P2_SHIFT 18
|
||||
+#define S5_P2_BKP_SHIFT 24
|
||||
+#define S6_P2_SHIFT 24
|
||||
+#define S7_P2_BKP_SHIFT 6
|
||||
+#define S8_P2_SHIFT 6
|
||||
+#define S8_P2_BKP_SHIFT 12
|
||||
+#define S9_P2_SHIFT 12
|
||||
+#define S9_P2_BKP_SHIFT 18
|
||||
+#define S10_P2_SHIFT 18
|
||||
+#define S10_P2_BKP_SHIFT 24
|
||||
+
|
||||
+#define BASE2_MASK 0xff000
|
||||
+#define BASE2_BKP_MASK 0xfc0000
|
||||
+#define S0_P2_MASK 0x3f00000
|
||||
+#define S0_P2_BKP_MASK 0xfc000000
|
||||
+#define S1_P2_MASK 0xfc000000
|
||||
+#define S1_P2_BKP_MASK 0x3f
|
||||
+#define S2_P2_MASK 0x3f
|
||||
+#define S2_P2_BKP_MASK 0xfc0
|
||||
+#define S3_P2_MASK 0xfc0
|
||||
+#define S3_P2_BKP_MASK 0x3f000
|
||||
+#define S4_P2_MASK 0x3f000
|
||||
+#define S4_P2_BKP_MASK 0xfc0000
|
||||
+#define S5_P2_MASK 0xfc0000
|
||||
+#define S5_P2_BKP_MASK 0x3f000000
|
||||
+#define S6_P2_MASK 0x3f000000
|
||||
+#define S6_P2_BKP_MASK 0x3f
|
||||
+#define S7_P2_MASK 0x3f
|
||||
+#define S7_P2_BKP_MASK 0xfc0
|
||||
+#define S8_P2_MASK 0xfc0
|
||||
+#define S8_P2_BKP_MASK 0x3f000
|
||||
+#define S9_P2_MASK 0x3f000
|
||||
+#define S9_P2_BKP_MASK 0xfc0000
|
||||
+#define S10_P2_MASK 0xfc0000
|
||||
+#define S10_P2_BKP_MASK 0x3f000000
|
||||
+
|
||||
+#define BKP_SEL 0x3
|
||||
+#define BKP_REDUN_SEL 0xe0000000
|
||||
+#define BKP_REDUN_SHIFT 29
|
||||
+
|
||||
+#define BIT_APPEND 0x3
|
||||
+
|
||||
+static int calibrate_8974(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int base1 = 0, base2 = 0, i;
|
||||
+ u32 p1[11], p2[11];
|
||||
+ int mode = 0;
|
||||
+ u32 *calib, *bkp;
|
||||
+ u32 calib_redun_sel;
|
||||
+
|
||||
+ calib = (u32 *)qfprom_read(tmdev->dev, "calib");
|
||||
+ if (IS_ERR(calib))
|
||||
+ return PTR_ERR(calib);
|
||||
+
|
||||
+ bkp = (u32 *)qfprom_read(tmdev->dev, "calib_backup");
|
||||
+ if (IS_ERR(bkp))
|
||||
+ return PTR_ERR(bkp);
|
||||
+
|
||||
+ calib_redun_sel = bkp[1] & BKP_REDUN_SEL;
|
||||
+ calib_redun_sel >>= BKP_REDUN_SHIFT;
|
||||
+
|
||||
+ if (calib_redun_sel == BKP_SEL) {
|
||||
+ mode = (calib[4] & CAL_SEL_0_1) >> CAL_SEL_SHIFT;
|
||||
+ mode |= (calib[5] & CAL_SEL_2) >> CAL_SEL_SHIFT_2;
|
||||
+
|
||||
+ switch (mode) {
|
||||
+ case TWO_PT_CALIB:
|
||||
+ base2 = (bkp[2] & BASE2_BKP_MASK) >> BASE2_BKP_SHIFT;
|
||||
+ p2[0] = (bkp[2] & S0_P2_BKP_MASK) >> S0_P2_BKP_SHIFT;
|
||||
+ p2[1] = (bkp[3] & S1_P2_BKP_MASK);
|
||||
+ p2[2] = (bkp[3] & S2_P2_BKP_MASK) >> S2_P2_BKP_SHIFT;
|
||||
+ p2[3] = (bkp[3] & S3_P2_BKP_MASK) >> S3_P2_BKP_SHIFT;
|
||||
+ p2[4] = (bkp[3] & S4_P2_BKP_MASK) >> S4_P2_BKP_SHIFT;
|
||||
+ p2[5] = (calib[4] & S5_P2_BKP_MASK) >> S5_P2_BKP_SHIFT;
|
||||
+ p2[6] = (calib[5] & S6_P2_BKP_MASK);
|
||||
+ p2[7] = (calib[5] & S7_P2_BKP_MASK) >> S7_P2_BKP_SHIFT;
|
||||
+ p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT;
|
||||
+ p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT;
|
||||
+ p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT;
|
||||
+ /* Fall through */
|
||||
+ case ONE_PT_CALIB:
|
||||
+ case ONE_PT_CALIB2:
|
||||
+ base1 = bkp[0] & BASE1_MASK;
|
||||
+ p1[0] = (bkp[0] & S0_P1_MASK) >> S0_P1_SHIFT;
|
||||
+ p1[1] = (bkp[0] & S1_P1_MASK) >> S1_P1_SHIFT;
|
||||
+ p1[2] = (bkp[0] & S2_P1_MASK) >> S2_P1_SHIFT;
|
||||
+ p1[3] = (bkp[0] & S3_P1_MASK) >> S3_P1_SHIFT;
|
||||
+ p1[4] = (bkp[1] & S4_P1_MASK);
|
||||
+ p1[5] = (bkp[1] & S5_P1_MASK) >> S5_P1_SHIFT;
|
||||
+ p1[6] = (bkp[1] & S6_P1_MASK) >> S6_P1_SHIFT;
|
||||
+ p1[7] = (bkp[1] & S7_P1_MASK) >> S7_P1_SHIFT;
|
||||
+ p1[8] = (bkp[2] & S8_P1_MASK_BKP) >> S8_P1_SHIFT;
|
||||
+ p1[9] = (bkp[2] & S9_P1_MASK_BKP) >> S9_P1_BKP_SHIFT;
|
||||
+ p1[10] = (bkp[2] & S10_P1_MASK_BKP) >> S10_P1_BKP_SHIFT;
|
||||
+ break;
|
||||
+ }
|
||||
+ } else {
|
||||
+ mode = (calib[1] & CAL_SEL_0_1) >> CAL_SEL_SHIFT;
|
||||
+ mode |= (calib[3] & CAL_SEL_2) >> CAL_SEL_SHIFT_2;
|
||||
+
|
||||
+ switch (mode) {
|
||||
+ case TWO_PT_CALIB:
|
||||
+ base2 = (calib[2] & BASE2_MASK) >> BASE2_SHIFT;
|
||||
+ p2[0] = (calib[2] & S0_P2_MASK) >> S0_P2_SHIFT;
|
||||
+ p2[1] = (calib[2] & S1_P2_MASK) >> S1_P2_SHIFT;
|
||||
+ p2[2] = (calib[3] & S2_P2_MASK);
|
||||
+ p2[3] = (calib[3] & S3_P2_MASK) >> S3_P2_SHIFT;
|
||||
+ p2[4] = (calib[3] & S4_P2_MASK) >> S4_P2_SHIFT;
|
||||
+ p2[5] = (calib[3] & S5_P2_MASK) >> S5_P2_SHIFT;
|
||||
+ p2[6] = (calib[3] & S6_P2_MASK) >> S6_P2_SHIFT;
|
||||
+ p2[7] = (calib[4] & S7_P2_MASK);
|
||||
+ p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT;
|
||||
+ p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT;
|
||||
+ p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT;
|
||||
+ /* Fall through */
|
||||
+ case ONE_PT_CALIB:
|
||||
+ case ONE_PT_CALIB2:
|
||||
+ base1 = calib[0] & BASE1_MASK;
|
||||
+ p1[0] = (calib[0] & S0_P1_MASK) >> S0_P1_SHIFT;
|
||||
+ p1[1] = (calib[0] & S1_P1_MASK) >> S1_P1_SHIFT;
|
||||
+ p1[2] = (calib[0] & S2_P1_MASK) >> S2_P1_SHIFT;
|
||||
+ p1[3] = (calib[0] & S3_P1_MASK) >> S3_P1_SHIFT;
|
||||
+ p1[4] = (calib[1] & S4_P1_MASK);
|
||||
+ p1[5] = (calib[1] & S5_P1_MASK) >> S5_P1_SHIFT;
|
||||
+ p1[6] = (calib[1] & S6_P1_MASK) >> S6_P1_SHIFT;
|
||||
+ p1[7] = (calib[1] & S7_P1_MASK) >> S7_P1_SHIFT;
|
||||
+ p1[8] = (calib[1] & S8_P1_MASK) >> S8_P1_SHIFT;
|
||||
+ p1[9] = (calib[2] & S9_P1_MASK);
|
||||
+ p1[10] = (calib[2] & S10_P1_MASK) >> S10_P1_SHIFT;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ switch (mode) {
|
||||
+ case ONE_PT_CALIB:
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++)
|
||||
+ p1[i] += (base1 << 2) | BIT_APPEND;
|
||||
+ break;
|
||||
+ case TWO_PT_CALIB:
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ p2[i] += base2;
|
||||
+ p2[i] <<= 2;
|
||||
+ p2[i] |= BIT_APPEND;
|
||||
+ }
|
||||
+ /* Fall through */
|
||||
+ case ONE_PT_CALIB2:
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ p1[i] += base1;
|
||||
+ p1[i] <<= 2;
|
||||
+ p1[i] |= BIT_APPEND;
|
||||
+ }
|
||||
+ break;
|
||||
+ default:
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++)
|
||||
+ p2[i] = 780;
|
||||
+ p1[0] = 502;
|
||||
+ p1[1] = 509;
|
||||
+ p1[2] = 503;
|
||||
+ p1[3] = 509;
|
||||
+ p1[4] = 505;
|
||||
+ p1[5] = 509;
|
||||
+ p1[6] = 507;
|
||||
+ p1[7] = 510;
|
||||
+ p1[8] = 508;
|
||||
+ p1[9] = 509;
|
||||
+ p1[10] = 508;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ compute_intercept_slope(tmdev, p1, p2, mode);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+const struct tsens_ops ops_8974 = {
|
||||
+ .init = init_common,
|
||||
+ .calibrate = calibrate_8974,
|
||||
+ .get_temp = get_temp_common,
|
||||
+};
|
||||
+
|
||||
+const struct tsens_data data_8974 = {
|
||||
+ .num_sensors = 11,
|
||||
+ .ops = &ops_8974,
|
||||
+};
|
||||
--- a/drivers/thermal/qcom/tsens.c
|
||||
+++ b/drivers/thermal/qcom/tsens.c
|
||||
@@ -68,6 +68,7 @@ static const struct of_device_id tsens_t
|
||||
.data = &data_8916,
|
||||
}, {
|
||||
.compatible = "qcom,msm8974-tsens",
|
||||
+ .data = &data_8974,
|
||||
},
|
||||
{}
|
||||
};
|
||||
--- a/drivers/thermal/qcom/tsens.h
|
||||
+++ b/drivers/thermal/qcom/tsens.h
|
||||
@@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsen
|
||||
int init_common(struct tsens_device *);
|
||||
int get_temp_common(struct tsens_device *, int, int *);
|
||||
|
||||
-extern const struct tsens_data data_8916;
|
||||
+extern const struct tsens_data data_8916, data_8974;
|
||||
|
||||
#endif /* __QCOM_TSENS_H__ */
|
|
@ -1,364 +0,0 @@
|
|||
From 20d4fd84bf524ad91e2cc3e4ab4020c27cfc0081 Mon Sep 17 00:00:00 2001
|
||||
From: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Date: Thu, 5 May 2016 14:21:43 +0530
|
||||
Subject: thermal: qcom: tsens-8960: Add support for 8960 family of SoCs
|
||||
|
||||
8960 family of SoCs have the TSENS device as part of GCC, hence
|
||||
the driver probes the virtual child device created by GCC and
|
||||
uses the parent to extract all DT properties and reuses the GCC
|
||||
regmap.
|
||||
|
||||
Also GCC/TSENS are part of a domain thats not always ON.
|
||||
Hence add .suspend and .resume hooks to save and restore some of
|
||||
the inited register context.
|
||||
|
||||
Also 8960 family have some of the TSENS init sequence thats
|
||||
required to be done by the HLOS driver (some later versions of TSENS
|
||||
do not export these registers to non-secure world, and hence need
|
||||
these initializations to be done by secure bootloaders)
|
||||
|
||||
8660 from the same family has just one sensor and hence some register
|
||||
offset/layout differences which need special handling in the driver.
|
||||
|
||||
Based on the original code from Siddartha Mohanadoss, Stephen Boyd and
|
||||
Narendran Rajan.
|
||||
|
||||
Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
|
||||
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
|
||||
---
|
||||
drivers/thermal/qcom/Makefile | 2 +-
|
||||
drivers/thermal/qcom/tsens-8960.c | 292 ++++++++++++++++++++++++++++++++++++++
|
||||
drivers/thermal/qcom/tsens.c | 8 +-
|
||||
drivers/thermal/qcom/tsens.h | 2 +-
|
||||
4 files changed, 298 insertions(+), 6 deletions(-)
|
||||
create mode 100644 drivers/thermal/qcom/tsens-8960.c
|
||||
|
||||
--- a/drivers/thermal/qcom/Makefile
|
||||
+++ b/drivers/thermal/qcom/Makefile
|
||||
@@ -1,2 +1,2 @@
|
||||
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
|
||||
-qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o
|
||||
+qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/thermal/qcom/tsens-8960.c
|
||||
@@ -0,0 +1,292 @@
|
||||
+/*
|
||||
+ * Copyright (c) 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/platform_device.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/bitops.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/thermal.h>
|
||||
+#include "tsens.h"
|
||||
+
|
||||
+#define CAL_MDEGC 30000
|
||||
+
|
||||
+#define CONFIG_ADDR 0x3640
|
||||
+#define CONFIG_ADDR_8660 0x3620
|
||||
+/* CONFIG_ADDR bitmasks */
|
||||
+#define CONFIG 0x9b
|
||||
+#define CONFIG_MASK 0xf
|
||||
+#define CONFIG_8660 1
|
||||
+#define CONFIG_SHIFT_8660 28
|
||||
+#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660)
|
||||
+
|
||||
+#define STATUS_CNTL_ADDR_8064 0x3660
|
||||
+#define CNTL_ADDR 0x3620
|
||||
+/* CNTL_ADDR bitmasks */
|
||||
+#define EN BIT(0)
|
||||
+#define SW_RST BIT(1)
|
||||
+#define SENSOR0_EN BIT(3)
|
||||
+#define SLP_CLK_ENA BIT(26)
|
||||
+#define SLP_CLK_ENA_8660 BIT(24)
|
||||
+#define MEASURE_PERIOD 1
|
||||
+#define SENSOR0_SHIFT 3
|
||||
+
|
||||
+/* INT_STATUS_ADDR bitmasks */
|
||||
+#define MIN_STATUS_MASK BIT(0)
|
||||
+#define LOWER_STATUS_CLR BIT(1)
|
||||
+#define UPPER_STATUS_CLR BIT(2)
|
||||
+#define MAX_STATUS_MASK BIT(3)
|
||||
+
|
||||
+#define THRESHOLD_ADDR 0x3624
|
||||
+/* THRESHOLD_ADDR bitmasks */
|
||||
+#define THRESHOLD_MAX_LIMIT_SHIFT 24
|
||||
+#define THRESHOLD_MIN_LIMIT_SHIFT 16
|
||||
+#define THRESHOLD_UPPER_LIMIT_SHIFT 8
|
||||
+#define THRESHOLD_LOWER_LIMIT_SHIFT 0
|
||||
+
|
||||
+/* Initial temperature threshold values */
|
||||
+#define LOWER_LIMIT_TH 0x50
|
||||
+#define UPPER_LIMIT_TH 0xdf
|
||||
+#define MIN_LIMIT_TH 0x0
|
||||
+#define MAX_LIMIT_TH 0xff
|
||||
+
|
||||
+#define S0_STATUS_ADDR 0x3628
|
||||
+#define INT_STATUS_ADDR 0x363c
|
||||
+#define TRDY_MASK BIT(7)
|
||||
+#define TIMEOUT_US 100
|
||||
+
|
||||
+static int suspend_8960(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int ret;
|
||||
+ unsigned int mask;
|
||||
+ struct regmap *map = tmdev->map;
|
||||
+
|
||||
+ ret = regmap_read(map, THRESHOLD_ADDR, &tmdev->ctx.threshold);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_read(map, CNTL_ADDR, &tmdev->ctx.control);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (tmdev->num_sensors > 1)
|
||||
+ mask = SLP_CLK_ENA | EN;
|
||||
+ else
|
||||
+ mask = SLP_CLK_ENA_8660 | EN;
|
||||
+
|
||||
+ ret = regmap_update_bits(map, CNTL_ADDR, mask, 0);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int resume_8960(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int ret;
|
||||
+ struct regmap *map = tmdev->map;
|
||||
+
|
||||
+ ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ /*
|
||||
+ * Separate CONFIG restore is not needed only for 8660 as
|
||||
+ * config is part of CTRL Addr and its restored as such
|
||||
+ */
|
||||
+ if (tmdev->num_sensors > 1) {
|
||||
+ ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ ret = regmap_write(map, THRESHOLD_ADDR, tmdev->ctx.threshold);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = regmap_write(map, CNTL_ADDR, tmdev->ctx.control);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int enable_8960(struct tsens_device *tmdev, int id)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 reg, mask;
|
||||
+
|
||||
+ ret = regmap_read(tmdev->map, CNTL_ADDR, ®);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ mask = BIT(id + SENSOR0_SHIFT);
|
||||
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg | SW_RST);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (tmdev->num_sensors > 1)
|
||||
+ reg |= mask | SLP_CLK_ENA | EN;
|
||||
+ else
|
||||
+ reg |= mask | SLP_CLK_ENA_8660 | EN;
|
||||
+
|
||||
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void disable_8960(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 reg_cntl;
|
||||
+ u32 mask;
|
||||
+
|
||||
+ mask = GENMASK(tmdev->num_sensors - 1, 0);
|
||||
+ mask <<= SENSOR0_SHIFT;
|
||||
+ mask |= EN;
|
||||
+
|
||||
+ ret = regmap_read(tmdev->map, CNTL_ADDR, ®_cntl);
|
||||
+ if (ret)
|
||||
+ return;
|
||||
+
|
||||
+ reg_cntl &= ~mask;
|
||||
+
|
||||
+ if (tmdev->num_sensors > 1)
|
||||
+ reg_cntl &= ~SLP_CLK_ENA;
|
||||
+ else
|
||||
+ reg_cntl &= ~SLP_CLK_ENA_8660;
|
||||
+
|
||||
+ regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
|
||||
+}
|
||||
+
|
||||
+static int init_8960(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int ret, i;
|
||||
+ u32 reg_cntl;
|
||||
+
|
||||
+ tmdev->map = dev_get_regmap(tmdev->dev, NULL);
|
||||
+ if (!tmdev->map)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ /*
|
||||
+ * The status registers for each sensor are discontiguous
|
||||
+ * because some SoCs have 5 sensors while others have more
|
||||
+ * but the control registers stay in the same place, i.e
|
||||
+ * directly after the first 5 status registers.
|
||||
+ */
|
||||
+ for (i = 0; i < tmdev->num_sensors; i++) {
|
||||
+ if (i >= 5)
|
||||
+ tmdev->sensor[i].status = S0_STATUS_ADDR + 40;
|
||||
+ tmdev->sensor[i].status += i * 4;
|
||||
+ }
|
||||
+
|
||||
+ reg_cntl = SW_RST;
|
||||
+ ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (tmdev->num_sensors > 1) {
|
||||
+ reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18);
|
||||
+ reg_cntl &= ~SW_RST;
|
||||
+ ret = regmap_update_bits(tmdev->map, CONFIG_ADDR,
|
||||
+ CONFIG_MASK, CONFIG);
|
||||
+ } else {
|
||||
+ reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16);
|
||||
+ reg_cntl &= ~CONFIG_MASK_8660;
|
||||
+ reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660;
|
||||
+ }
|
||||
+
|
||||
+ reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << SENSOR0_SHIFT;
|
||||
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ reg_cntl |= EN;
|
||||
+ ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int calibrate_8960(struct tsens_device *tmdev)
|
||||
+{
|
||||
+ int i;
|
||||
+ char *data;
|
||||
+
|
||||
+ ssize_t num_read = tmdev->num_sensors;
|
||||
+ struct tsens_sensor *s = tmdev->sensor;
|
||||
+
|
||||
+ data = qfprom_read(tmdev->dev, "calib");
|
||||
+ if (IS_ERR(data))
|
||||
+ data = qfprom_read(tmdev->dev, "calib_backup");
|
||||
+ if (IS_ERR(data))
|
||||
+ return PTR_ERR(data);
|
||||
+
|
||||
+ for (i = 0; i < num_read; i++, s++)
|
||||
+ s->offset = data[i];
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/* Temperature on y axis and ADC-code on x-axis */
|
||||
+static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
|
||||
+{
|
||||
+ int slope, offset;
|
||||
+
|
||||
+ slope = thermal_zone_get_slope(s->tzd);
|
||||
+ offset = CAL_MDEGC - slope * s->offset;
|
||||
+
|
||||
+ return adc_code * slope + offset;
|
||||
+}
|
||||
+
|
||||
+static int get_temp_8960(struct tsens_device *tmdev, int id, int *temp)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 code, trdy;
|
||||
+ const struct tsens_sensor *s = &tmdev->sensor[id];
|
||||
+ unsigned long timeout;
|
||||
+
|
||||
+ timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
|
||||
+ do {
|
||||
+ ret = regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ if (!(trdy & TRDY_MASK))
|
||||
+ continue;
|
||||
+ ret = regmap_read(tmdev->map, s->status, &code);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ *temp = code_to_mdegC(code, s);
|
||||
+ return 0;
|
||||
+ } while (time_before(jiffies, timeout));
|
||||
+
|
||||
+ return -ETIMEDOUT;
|
||||
+}
|
||||
+
|
||||
+const struct tsens_ops ops_8960 = {
|
||||
+ .init = init_8960,
|
||||
+ .calibrate = calibrate_8960,
|
||||
+ .get_temp = get_temp_8960,
|
||||
+ .enable = enable_8960,
|
||||
+ .disable = disable_8960,
|
||||
+ .suspend = suspend_8960,
|
||||
+ .resume = resume_8960,
|
||||
+};
|
||||
+
|
||||
+const struct tsens_data data_8960 = {
|
||||
+ .num_sensors = 11,
|
||||
+ .ops = &ops_8960,
|
||||
+};
|
||||
--- a/drivers/thermal/qcom/tsens.c
|
||||
+++ b/drivers/thermal/qcom/tsens.c
|
||||
@@ -122,10 +122,10 @@ static int tsens_probe(struct platform_d
|
||||
np = dev->of_node;
|
||||
|
||||
id = of_match_node(tsens_table, np);
|
||||
- if (!id)
|
||||
- return -EINVAL;
|
||||
-
|
||||
- data = id->data;
|
||||
+ if (id)
|
||||
+ data = id->data;
|
||||
+ else
|
||||
+ data = &data_8960;
|
||||
|
||||
if (data->num_sensors <= 0) {
|
||||
dev_err(dev, "invalid number of sensors\n");
|
||||
--- a/drivers/thermal/qcom/tsens.h
|
||||
+++ b/drivers/thermal/qcom/tsens.h
|
||||
@@ -87,6 +87,6 @@ void compute_intercept_slope(struct tsen
|
||||
int init_common(struct tsens_device *);
|
||||
int get_temp_common(struct tsens_device *, int, int *);
|
||||
|
||||
-extern const struct tsens_data data_8916, data_8974;
|
||||
+extern const struct tsens_data data_8916, data_8974, data_8960;
|
||||
|
||||
#endif /* __QCOM_TSENS_H__ */
|
|
@ -1,46 +0,0 @@
|
|||
From 5b97469a55872a30a0d53a1279a8ae8b1c68b52c Mon Sep 17 00:00:00 2001
|
||||
From: Arnd Bergmann <arnd@arndb.de>
|
||||
Date: Mon, 4 Jul 2016 15:12:28 +0200
|
||||
Subject: thermal: qcom: tsens-8916: mark PM functions __maybe_unused
|
||||
|
||||
The newly added tsens-8916 driver produces warnings when CONFIG_PM
|
||||
is disabled:
|
||||
|
||||
drivers/thermal/qcom/tsens.c:53:12: error: 'tsens_resume' defined but not used [-Werror=unused-function]
|
||||
static int tsens_resume(struct device *dev)
|
||||
^~~~~~~~~~~~
|
||||
drivers/thermal/qcom/tsens.c:43:12: error: 'tsens_suspend' defined but not used [-Werror=unused-function]
|
||||
static int tsens_suspend(struct device *dev)
|
||||
^~~~~~~~~~~~~
|
||||
|
||||
This marks both functions __maybe_unused to let the compiler
|
||||
know that they might be used in other configurations, without
|
||||
adding ugly #ifdef logic.
|
||||
|
||||
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
|
||||
Reviewed-by: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
|
||||
---
|
||||
drivers/thermal/qcom/tsens.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/thermal/qcom/tsens.c
|
||||
+++ b/drivers/thermal/qcom/tsens.c
|
||||
@@ -40,7 +40,7 @@ static int tsens_get_trend(void *data, l
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
-static int tsens_suspend(struct device *dev)
|
||||
+static int __maybe_unused tsens_suspend(struct device *dev)
|
||||
{
|
||||
struct tsens_device *tmdev = dev_get_drvdata(dev);
|
||||
|
||||
@@ -50,7 +50,7 @@ static int tsens_suspend(struct device *
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int tsens_resume(struct device *dev)
|
||||
+static int __maybe_unused tsens_resume(struct device *dev)
|
||||
{
|
||||
struct tsens_device *tmdev = dev_get_drvdata(dev);
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
From e498b4984db82b4ba3ceea7dba813222a31e9c2e Mon Sep 17 00:00:00 2001
|
||||
From: Laxman Dewangan <ldewangan@nvidia.com>
|
||||
Date: Wed, 9 Mar 2016 18:40:06 +0530
|
||||
Subject: thermal: of-thermal: Add devm version of
|
||||
thermal_zone_of_sensor_register
|
||||
|
||||
Add resource managed version of thermal_zone_of_sensor_register() and
|
||||
thermal_zone_of_sensor_unregister().
|
||||
|
||||
This helps in reducing the code size in error path, remove of
|
||||
driver remove callbacks and making proper sequence for deallocations.
|
||||
|
||||
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
|
||||
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
|
||||
---
|
||||
drivers/thermal/of-thermal.c | 81 ++++++++++++++++++++++++++++++++++++++++++++
|
||||
include/linux/thermal.h | 18 ++++++++++
|
||||
2 files changed, 99 insertions(+)
|
||||
|
||||
--- a/drivers/thermal/of-thermal.c
|
||||
+++ b/drivers/thermal/of-thermal.c
|
||||
@@ -559,6 +559,87 @@ void thermal_zone_of_sensor_unregister(s
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister);
|
||||
|
||||
+static void devm_thermal_zone_of_sensor_release(struct device *dev, void *res)
|
||||
+{
|
||||
+ thermal_zone_of_sensor_unregister(dev,
|
||||
+ *(struct thermal_zone_device **)res);
|
||||
+}
|
||||
+
|
||||
+static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res,
|
||||
+ void *data)
|
||||
+{
|
||||
+ struct thermal_zone_device **r = res;
|
||||
+
|
||||
+ if (WARN_ON(!r || !*r))
|
||||
+ return 0;
|
||||
+
|
||||
+ return *r == data;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * devm_thermal_zone_of_sensor_register - Resource managed version of
|
||||
+ * thermal_zone_of_sensor_register()
|
||||
+ * @dev: a valid struct device pointer of a sensor device. Must contain
|
||||
+ * a valid .of_node, for the sensor node.
|
||||
+ * @sensor_id: a sensor identifier, in case the sensor IP has more
|
||||
+ * than one sensors
|
||||
+ * @data: a private pointer (owned by the caller) that will be passed
|
||||
+ * back, when a temperature reading is needed.
|
||||
+ * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp.
|
||||
+ *
|
||||
+ * Refer thermal_zone_of_sensor_register() for more details.
|
||||
+ *
|
||||
+ * Return: On success returns a valid struct thermal_zone_device,
|
||||
+ * otherwise, it returns a corresponding ERR_PTR(). Caller must
|
||||
+ * check the return value with help of IS_ERR() helper.
|
||||
+ * Registered hermal_zone_device device will automatically be
|
||||
+ * released when device is unbounded.
|
||||
+ */
|
||||
+struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
|
||||
+ struct device *dev, int sensor_id,
|
||||
+ void *data, const struct thermal_zone_of_device_ops *ops)
|
||||
+{
|
||||
+ struct thermal_zone_device **ptr, *tzd;
|
||||
+
|
||||
+ ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr),
|
||||
+ GFP_KERNEL);
|
||||
+ if (!ptr)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops);
|
||||
+ if (IS_ERR(tzd)) {
|
||||
+ devres_free(ptr);
|
||||
+ return tzd;
|
||||
+ }
|
||||
+
|
||||
+ *ptr = tzd;
|
||||
+ devres_add(dev, ptr);
|
||||
+
|
||||
+ return tzd;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_register);
|
||||
+
|
||||
+/**
|
||||
+ * devm_thermal_zone_of_sensor_unregister - Resource managed version of
|
||||
+ * thermal_zone_of_sensor_unregister().
|
||||
+ * @dev: Device for which which resource was allocated.
|
||||
+ * @tzd: a pointer to struct thermal_zone_device where the sensor is registered.
|
||||
+ *
|
||||
+ * This function removes the sensor callbacks and private data from the
|
||||
+ * thermal zone device registered with devm_thermal_zone_of_sensor_register()
|
||||
+ * API. It will also silent the zone by remove the .get_temp() and .get_trend()
|
||||
+ * thermal zone device callbacks.
|
||||
+ * Normally this function will not need to be called and the resource
|
||||
+ * management code will ensure that the resource is freed.
|
||||
+ */
|
||||
+void devm_thermal_zone_of_sensor_unregister(struct device *dev,
|
||||
+ struct thermal_zone_device *tzd)
|
||||
+{
|
||||
+ WARN_ON(devres_release(dev, devm_thermal_zone_of_sensor_release,
|
||||
+ devm_thermal_zone_of_sensor_match, tzd));
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_unregister);
|
||||
+
|
||||
/*** functions parsing device tree nodes ***/
|
||||
|
||||
/**
|
||||
--- a/include/linux/thermal.h
|
||||
+++ b/include/linux/thermal.h
|
||||
@@ -364,6 +364,11 @@ thermal_zone_of_sensor_register(struct d
|
||||
const struct thermal_zone_of_device_ops *ops);
|
||||
void thermal_zone_of_sensor_unregister(struct device *dev,
|
||||
struct thermal_zone_device *tz);
|
||||
+struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
|
||||
+ struct device *dev, int id, void *data,
|
||||
+ const struct thermal_zone_of_device_ops *ops);
|
||||
+void devm_thermal_zone_of_sensor_unregister(struct device *dev,
|
||||
+ struct thermal_zone_device *tz);
|
||||
#else
|
||||
static inline struct thermal_zone_device *
|
||||
thermal_zone_of_sensor_register(struct device *dev, int id, void *data,
|
||||
@@ -378,6 +383,19 @@ void thermal_zone_of_sensor_unregister(s
|
||||
{
|
||||
}
|
||||
|
||||
+static inline struct thermal_zone_device *devm_thermal_zone_of_sensor_register(
|
||||
+ struct device *dev, int id, void *data,
|
||||
+ const struct thermal_zone_of_device_ops *ops)
|
||||
+{
|
||||
+ return ERR_PTR(-ENODEV);
|
||||
+}
|
||||
+
|
||||
+static inline
|
||||
+void devm_thermal_zone_of_sensor_unregister(struct device *dev,
|
||||
+ struct thermal_zone_device *tz)
|
||||
+{
|
||||
+}
|
||||
+
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_THERMAL)
|
|
@ -1,101 +0,0 @@
|
|||
From 4a7069a32c99a81950de035535b0a064dcceaeba Mon Sep 17 00:00:00 2001
|
||||
From: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Date: Thu, 5 May 2016 14:21:42 +0530
|
||||
Subject: [PATCH] thermal: core: export apis to get slope and offset
|
||||
|
||||
Add apis for platform thermal drivers to query for slope and offset
|
||||
attributes, which might be needed for temperature calculations.
|
||||
|
||||
Signed-off-by: Rajendra Nayak <rnayak@codeaurora.org>
|
||||
Signed-off-by: Eduardo Valentin <edubezval@gmail.com>
|
||||
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
|
||||
---
|
||||
Documentation/thermal/sysfs-api.txt | 12 ++++++++++++
|
||||
drivers/thermal/thermal_core.c | 30 ++++++++++++++++++++++++++++++
|
||||
include/linux/thermal.h | 8 ++++++++
|
||||
3 files changed, 50 insertions(+)
|
||||
|
||||
--- a/Documentation/thermal/sysfs-api.txt
|
||||
+++ b/Documentation/thermal/sysfs-api.txt
|
||||
@@ -72,6 +72,18 @@ temperature) and throttle appropriate de
|
||||
It deletes the corresponding entry form /sys/class/thermal folder and
|
||||
unbind all the thermal cooling devices it uses.
|
||||
|
||||
+1.1.7 int thermal_zone_get_slope(struct thermal_zone_device *tz)
|
||||
+
|
||||
+ This interface is used to read the slope attribute value
|
||||
+ for the thermal zone device, which might be useful for platform
|
||||
+ drivers for temperature calculations.
|
||||
+
|
||||
+1.1.8 int thermal_zone_get_offset(struct thermal_zone_device *tz)
|
||||
+
|
||||
+ This interface is used to read the offset attribute value
|
||||
+ for the thermal zone device, which might be useful for platform
|
||||
+ drivers for temperature calculations.
|
||||
+
|
||||
1.2 thermal cooling device interface
|
||||
1.2.1 struct thermal_cooling_device *thermal_cooling_device_register(char *name,
|
||||
void *devdata, struct thermal_cooling_device_ops *)
|
||||
--- a/drivers/thermal/thermal_core.c
|
||||
+++ b/drivers/thermal/thermal_core.c
|
||||
@@ -2061,6 +2061,36 @@ exit:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_zone_get_zone_by_name);
|
||||
|
||||
+/**
|
||||
+ * thermal_zone_get_slope - return the slope attribute of the thermal zone
|
||||
+ * @tz: thermal zone device with the slope attribute
|
||||
+ *
|
||||
+ * Return: If the thermal zone device has a slope attribute, return it, else
|
||||
+ * return 1.
|
||||
+ */
|
||||
+int thermal_zone_get_slope(struct thermal_zone_device *tz)
|
||||
+{
|
||||
+ if (tz && tz->tzp)
|
||||
+ return tz->tzp->slope;
|
||||
+ return 1;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(thermal_zone_get_slope);
|
||||
+
|
||||
+/**
|
||||
+ * thermal_zone_get_offset - return the offset attribute of the thermal zone
|
||||
+ * @tz: thermal zone device with the offset attribute
|
||||
+ *
|
||||
+ * Return: If the thermal zone device has a offset attribute, return it, else
|
||||
+ * return 0.
|
||||
+ */
|
||||
+int thermal_zone_get_offset(struct thermal_zone_device *tz)
|
||||
+{
|
||||
+ if (tz && tz->tzp)
|
||||
+ return tz->tzp->offset;
|
||||
+ return 0;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(thermal_zone_get_offset);
|
||||
+
|
||||
#ifdef CONFIG_NET
|
||||
static const struct genl_multicast_group thermal_event_mcgrps[] = {
|
||||
{ .name = THERMAL_GENL_MCAST_GROUP_NAME, },
|
||||
--- a/include/linux/thermal.h
|
||||
+++ b/include/linux/thermal.h
|
||||
@@ -432,6 +432,8 @@ thermal_of_cooling_device_register(struc
|
||||
void thermal_cooling_device_unregister(struct thermal_cooling_device *);
|
||||
struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name);
|
||||
int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp);
|
||||
+int thermal_zone_get_slope(struct thermal_zone_device *tz);
|
||||
+int thermal_zone_get_offset(struct thermal_zone_device *tz);
|
||||
|
||||
int get_tz_trend(struct thermal_zone_device *, int);
|
||||
struct thermal_instance *get_thermal_instance(struct thermal_zone_device *,
|
||||
@@ -489,6 +491,12 @@ static inline struct thermal_zone_device
|
||||
static inline int thermal_zone_get_temp(
|
||||
struct thermal_zone_device *tz, int *temp)
|
||||
{ return -ENODEV; }
|
||||
+static inline int thermal_zone_get_slope(
|
||||
+ struct thermal_zone_device *tz)
|
||||
+{ return -ENODEV; }
|
||||
+static inline int thermal_zone_get_offset(
|
||||
+ struct thermal_zone_device *tz)
|
||||
+{ return -ENODEV; }
|
||||
static inline int get_tz_trend(struct thermal_zone_device *tz, int trip)
|
||||
{ return -ENODEV; }
|
||||
static inline struct thermal_instance *
|
|
@ -1,42 +0,0 @@
|
|||
From 313a72ff983cc2e00ac4dcb791d40ebf2f9d5718 Mon Sep 17 00:00:00 2001
|
||||
From: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
||||
Date: Tue, 17 Nov 2015 09:12:41 +0000
|
||||
Subject: nvmem: core: return error for non word aligned access
|
||||
|
||||
nvmem providers have restrictions on register strides, so return error
|
||||
when users attempt to read/write buffers with sizes which are less
|
||||
than word size.
|
||||
|
||||
Without this patch the userspace would continue to try as it does not
|
||||
get any error from the nvmem core, resulting in a hang or endless loop
|
||||
in userspace.
|
||||
|
||||
Reported-by: Ariel D'Alessandro <ariel@vanguardiasur.com.ar>
|
||||
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
||||
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
---
|
||||
drivers/nvmem/core.c | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
--- a/drivers/nvmem/core.c
|
||||
+++ b/drivers/nvmem/core.c
|
||||
@@ -70,6 +70,9 @@ static ssize_t bin_attr_nvmem_read(struc
|
||||
if (pos >= nvmem->size)
|
||||
return 0;
|
||||
|
||||
+ if (count < nvmem->word_size)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
if (pos + count > nvmem->size)
|
||||
count = nvmem->size - pos;
|
||||
|
||||
@@ -95,6 +98,9 @@ static ssize_t bin_attr_nvmem_write(stru
|
||||
if (pos >= nvmem->size)
|
||||
return 0;
|
||||
|
||||
+ if (count < nvmem->word_size)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
if (pos + count > nvmem->size)
|
||||
count = nvmem->size - pos;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
From dfdf141429f0895b63c882facc42c86f225033cb Mon Sep 17 00:00:00 2001
|
||||
From: Rasmus Villemoes <linux@rasmusvillemoes.dk>
|
||||
Date: Mon, 8 Feb 2016 22:04:29 +0100
|
||||
Subject: nvmem: core: fix error path in nvmem_add_cells()
|
||||
|
||||
The current code fails to nvmem_cell_drop(cells[0]) - even worse, if
|
||||
the loop above fails already at i==0, we'll enter an essentially
|
||||
infinite loop doing nvmem_cell_drop on cells[-1], cells[-2], ... which
|
||||
is unlikely to end well.
|
||||
|
||||
Also, we're not freeing the temporary backing array cells on the error
|
||||
path.
|
||||
|
||||
Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
|
||||
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
---
|
||||
drivers/nvmem/core.c | 4 +++-
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/nvmem/core.c
|
||||
+++ b/drivers/nvmem/core.c
|
||||
@@ -294,9 +294,11 @@ static int nvmem_add_cells(struct nvmem_
|
||||
|
||||
return 0;
|
||||
err:
|
||||
- while (--i)
|
||||
+ while (i--)
|
||||
nvmem_cell_drop(cells[i]);
|
||||
|
||||
+ kfree(cells);
|
||||
+
|
||||
return rval;
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
From 811b0d6538b9f26f3eb0f90fe4e6118f2480ec6f Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Lunn <andrew@lunn.ch>
|
||||
Date: Fri, 26 Feb 2016 20:59:18 +0100
|
||||
Subject: nvmem: Add flag to export NVMEM to root only
|
||||
|
||||
Legacy AT24, AT25 EEPROMs are exported in sys so that only root can
|
||||
read the contents. The EEPROMs may contain sensitive information. Add
|
||||
a flag so the provide can indicate that NVMEM should also restrict
|
||||
access to root only.
|
||||
|
||||
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
|
||||
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
||||
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
---
|
||||
drivers/nvmem/core.c | 57 ++++++++++++++++++++++++++++++++++++++++--
|
||||
include/linux/nvmem-provider.h | 1 +
|
||||
2 files changed, 56 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/nvmem/core.c
|
||||
+++ b/drivers/nvmem/core.c
|
||||
@@ -161,6 +161,53 @@ static const struct attribute_group *nvm
|
||||
NULL,
|
||||
};
|
||||
|
||||
+/* default read/write permissions, root only */
|
||||
+static struct bin_attribute bin_attr_rw_root_nvmem = {
|
||||
+ .attr = {
|
||||
+ .name = "nvmem",
|
||||
+ .mode = S_IWUSR | S_IRUSR,
|
||||
+ },
|
||||
+ .read = bin_attr_nvmem_read,
|
||||
+ .write = bin_attr_nvmem_write,
|
||||
+};
|
||||
+
|
||||
+static struct bin_attribute *nvmem_bin_rw_root_attributes[] = {
|
||||
+ &bin_attr_rw_root_nvmem,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group nvmem_bin_rw_root_group = {
|
||||
+ .bin_attrs = nvmem_bin_rw_root_attributes,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group *nvmem_rw_root_dev_groups[] = {
|
||||
+ &nvmem_bin_rw_root_group,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+/* read only permission, root only */
|
||||
+static struct bin_attribute bin_attr_ro_root_nvmem = {
|
||||
+ .attr = {
|
||||
+ .name = "nvmem",
|
||||
+ .mode = S_IRUSR,
|
||||
+ },
|
||||
+ .read = bin_attr_nvmem_read,
|
||||
+};
|
||||
+
|
||||
+static struct bin_attribute *nvmem_bin_ro_root_attributes[] = {
|
||||
+ &bin_attr_ro_root_nvmem,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group nvmem_bin_ro_root_group = {
|
||||
+ .bin_attrs = nvmem_bin_ro_root_attributes,
|
||||
+};
|
||||
+
|
||||
+static const struct attribute_group *nvmem_ro_root_dev_groups[] = {
|
||||
+ &nvmem_bin_ro_root_group,
|
||||
+ NULL,
|
||||
+};
|
||||
+
|
||||
static void nvmem_release(struct device *dev)
|
||||
{
|
||||
struct nvmem_device *nvmem = to_nvmem_device(dev);
|
||||
@@ -355,8 +402,14 @@ struct nvmem_device *nvmem_register(cons
|
||||
nvmem->read_only = of_property_read_bool(np, "read-only") |
|
||||
config->read_only;
|
||||
|
||||
- nvmem->dev.groups = nvmem->read_only ? nvmem_ro_dev_groups :
|
||||
- nvmem_rw_dev_groups;
|
||||
+ if (config->root_only)
|
||||
+ nvmem->dev.groups = nvmem->read_only ?
|
||||
+ nvmem_ro_root_dev_groups :
|
||||
+ nvmem_rw_root_dev_groups;
|
||||
+ else
|
||||
+ nvmem->dev.groups = nvmem->read_only ?
|
||||
+ nvmem_ro_dev_groups :
|
||||
+ nvmem_rw_dev_groups;
|
||||
|
||||
device_initialize(&nvmem->dev);
|
||||
|
||||
--- a/include/linux/nvmem-provider.h
|
||||
+++ b/include/linux/nvmem-provider.h
|
||||
@@ -23,6 +23,7 @@ struct nvmem_config {
|
||||
const struct nvmem_cell_info *cells;
|
||||
int ncells;
|
||||
bool read_only;
|
||||
+ bool root_only;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_NVMEM)
|
|
@ -1,181 +0,0 @@
|
|||
From b6c217ab9be6895384cf0b284ace84ad79e5c53b Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Lunn <andrew@lunn.ch>
|
||||
Date: Fri, 26 Feb 2016 20:59:19 +0100
|
||||
Subject: nvmem: Add backwards compatibility support for older EEPROM drivers.
|
||||
|
||||
Older drivers made an 'eeprom' file available in the /sys device
|
||||
directory. Have the NVMEM core provide this to retain backwards
|
||||
compatibility.
|
||||
|
||||
Signed-off-by: Andrew Lunn <andrew@lunn.ch>
|
||||
Acked-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|
||||
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
|
||||
---
|
||||
drivers/nvmem/core.c | 84 ++++++++++++++++++++++++++++++++++++++----
|
||||
include/linux/nvmem-provider.h | 4 +-
|
||||
2 files changed, 79 insertions(+), 9 deletions(-)
|
||||
|
||||
--- a/drivers/nvmem/core.c
|
||||
+++ b/drivers/nvmem/core.c
|
||||
@@ -38,8 +38,13 @@ struct nvmem_device {
|
||||
int users;
|
||||
size_t size;
|
||||
bool read_only;
|
||||
+ int flags;
|
||||
+ struct bin_attribute eeprom;
|
||||
+ struct device *base_dev;
|
||||
};
|
||||
|
||||
+#define FLAG_COMPAT BIT(0)
|
||||
+
|
||||
struct nvmem_cell {
|
||||
const char *name;
|
||||
int offset;
|
||||
@@ -56,16 +61,26 @@ static DEFINE_IDA(nvmem_ida);
|
||||
static LIST_HEAD(nvmem_cells);
|
||||
static DEFINE_MUTEX(nvmem_cells_mutex);
|
||||
|
||||
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
+static struct lock_class_key eeprom_lock_key;
|
||||
+#endif
|
||||
+
|
||||
#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev)
|
||||
|
||||
static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
- struct device *dev = container_of(kobj, struct device, kobj);
|
||||
- struct nvmem_device *nvmem = to_nvmem_device(dev);
|
||||
+ struct device *dev;
|
||||
+ struct nvmem_device *nvmem;
|
||||
int rc;
|
||||
|
||||
+ if (attr->private)
|
||||
+ dev = attr->private;
|
||||
+ else
|
||||
+ dev = container_of(kobj, struct device, kobj);
|
||||
+ nvmem = to_nvmem_device(dev);
|
||||
+
|
||||
/* Stop the user from reading */
|
||||
if (pos >= nvmem->size)
|
||||
return 0;
|
||||
@@ -90,10 +105,16 @@ static ssize_t bin_attr_nvmem_write(stru
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t pos, size_t count)
|
||||
{
|
||||
- struct device *dev = container_of(kobj, struct device, kobj);
|
||||
- struct nvmem_device *nvmem = to_nvmem_device(dev);
|
||||
+ struct device *dev;
|
||||
+ struct nvmem_device *nvmem;
|
||||
int rc;
|
||||
|
||||
+ if (attr->private)
|
||||
+ dev = attr->private;
|
||||
+ else
|
||||
+ dev = container_of(kobj, struct device, kobj);
|
||||
+ nvmem = to_nvmem_device(dev);
|
||||
+
|
||||
/* Stop the user from writing */
|
||||
if (pos >= nvmem->size)
|
||||
return 0;
|
||||
@@ -349,6 +370,43 @@ err:
|
||||
return rval;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * nvmem_setup_compat() - Create an additional binary entry in
|
||||
+ * drivers sys directory, to be backwards compatible with the older
|
||||
+ * drivers/misc/eeprom drivers.
|
||||
+ */
|
||||
+static int nvmem_setup_compat(struct nvmem_device *nvmem,
|
||||
+ const struct nvmem_config *config)
|
||||
+{
|
||||
+ int rval;
|
||||
+
|
||||
+ if (!config->base_dev)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ if (nvmem->read_only)
|
||||
+ nvmem->eeprom = bin_attr_ro_root_nvmem;
|
||||
+ else
|
||||
+ nvmem->eeprom = bin_attr_rw_root_nvmem;
|
||||
+ nvmem->eeprom.attr.name = "eeprom";
|
||||
+ nvmem->eeprom.size = nvmem->size;
|
||||
+#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
+ nvmem->eeprom.attr.key = &eeprom_lock_key;
|
||||
+#endif
|
||||
+ nvmem->eeprom.private = &nvmem->dev;
|
||||
+ nvmem->base_dev = config->base_dev;
|
||||
+
|
||||
+ rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom);
|
||||
+ if (rval) {
|
||||
+ dev_err(&nvmem->dev,
|
||||
+ "Failed to create eeprom binary file %d\n", rval);
|
||||
+ return rval;
|
||||
+ }
|
||||
+
|
||||
+ nvmem->flags |= FLAG_COMPAT;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* nvmem_register() - Register a nvmem device for given nvmem_config.
|
||||
* Also creates an binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
|
||||
@@ -416,16 +474,23 @@ struct nvmem_device *nvmem_register(cons
|
||||
dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
|
||||
|
||||
rval = device_add(&nvmem->dev);
|
||||
- if (rval) {
|
||||
- ida_simple_remove(&nvmem_ida, nvmem->id);
|
||||
- kfree(nvmem);
|
||||
- return ERR_PTR(rval);
|
||||
+ if (rval)
|
||||
+ goto out;
|
||||
+
|
||||
+ if (config->compat) {
|
||||
+ rval = nvmem_setup_compat(nvmem, config);
|
||||
+ if (rval)
|
||||
+ goto out;
|
||||
}
|
||||
|
||||
if (config->cells)
|
||||
nvmem_add_cells(nvmem, config);
|
||||
|
||||
return nvmem;
|
||||
+out:
|
||||
+ ida_simple_remove(&nvmem_ida, nvmem->id);
|
||||
+ kfree(nvmem);
|
||||
+ return ERR_PTR(rval);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nvmem_register);
|
||||
|
||||
@@ -445,6 +510,9 @@ int nvmem_unregister(struct nvmem_device
|
||||
}
|
||||
mutex_unlock(&nvmem_mutex);
|
||||
|
||||
+ if (nvmem->flags & FLAG_COMPAT)
|
||||
+ device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom);
|
||||
+
|
||||
nvmem_device_remove_all_cells(nvmem);
|
||||
device_del(&nvmem->dev);
|
||||
|
||||
--- a/include/linux/nvmem-provider.h
|
||||
+++ b/include/linux/nvmem-provider.h
|
||||
@@ -24,6 +24,9 @@ struct nvmem_config {
|
||||
int ncells;
|
||||
bool read_only;
|
||||
bool root_only;
|
||||
+ /* To be only used by old driver/misc/eeprom drivers */
|
||||
+ bool compat;
|
||||
+ struct device *base_dev;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_NVMEM)
|
||||
@@ -44,5 +47,4 @@ static inline int nvmem_unregister(struc
|
||||
}
|
||||
|
||||
#endif /* CONFIG_NVMEM */
|
||||
-
|
||||
#endif /* ifndef _LINUX_NVMEM_PROVIDER_H */
|
|
@ -1,61 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -4,14 +4,6 @@
|
||||
model = "Qualcomm IPQ8064/AP148";
|
||||
compatible = "qcom,ipq8064-ap148", "qcom,ipq8064";
|
||||
|
||||
- aliases {
|
||||
- serial0 = &gsbi4_serial;
|
||||
- };
|
||||
-
|
||||
- chosen {
|
||||
- stdout-path = "serial0:115200n8";
|
||||
- };
|
||||
-
|
||||
reserved-memory {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
@@ -22,6 +14,14 @@
|
||||
};
|
||||
};
|
||||
|
||||
+ alias {
|
||||
+ serial0 = &uart4;
|
||||
+ };
|
||||
+
|
||||
+ chosen {
|
||||
+ linux,stdout-path = "serial0:115200n8";
|
||||
+ };
|
||||
+
|
||||
soc {
|
||||
pinmux@800000 {
|
||||
i2c4_pins: i2c4_pinmux {
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
syscon-tcsr = <&tcsr>;
|
||||
|
||||
- serial@12490000 {
|
||||
+ uart2: serial@12490000 {
|
||||
compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm";
|
||||
reg = <0x12490000 0x1000>,
|
||||
<0x12480000 0x1000>;
|
||||
@@ -197,7 +197,7 @@
|
||||
|
||||
syscon-tcsr = <&tcsr>;
|
||||
|
||||
- gsbi4_serial: serial@16340000 {
|
||||
+ uart4: serial@16340000 {
|
||||
compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm";
|
||||
reg = <0x16340000 0x1000>,
|
||||
<0x16300000 0x1000>;
|
||||
@@ -234,7 +234,7 @@
|
||||
|
||||
syscon-tcsr = <&tcsr>;
|
||||
|
||||
- serial@1a240000 {
|
||||
+ uart5: serial@1a240000 {
|
||||
compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm";
|
||||
reg = <0x1a240000 0x1000>,
|
||||
<0x1a200000 0x1000>;
|
|
@ -1,19 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -77,15 +77,7 @@
|
||||
spi-max-frequency = <50000000>;
|
||||
reg = <0>;
|
||||
|
||||
- partition@0 {
|
||||
- label = "rootfs";
|
||||
- reg = <0x0 0x1000000>;
|
||||
- };
|
||||
-
|
||||
- partition@1 {
|
||||
- label = "scratch";
|
||||
- reg = <0x1000000 0x1000000>;
|
||||
- };
|
||||
+ linux,part-probe = "qcom-smem";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,160 +0,0 @@
|
|||
From f26cc3733bdd697bd81ae505fc133fa7c9b6ea19 Mon Sep 17 00:00:00 2001
|
||||
From: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Date: Tue, 7 Apr 2015 19:58:58 -0700
|
||||
Subject: [PATCH] ARM: dts: qcom: add initial DB149 device-tree
|
||||
|
||||
Add basic DB149 (IPQ806x based platform) device-tree. It supports UART,
|
||||
SATA, USB2, USB3 and NOR flash.
|
||||
|
||||
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
---
|
||||
arch/arm/boot/dts/Makefile | 1 +
|
||||
arch/arm/boot/dts/qcom-ipq8064-db149.dts | 132 +++++++++++++++++++++++++++++++
|
||||
2 files changed, 133 insertions(+)
|
||||
create mode 100644 arch/arm/boot/dts/qcom-ipq8064-db149.dts
|
||||
|
||||
--- a/arch/arm/boot/dts/Makefile
|
||||
+++ b/arch/arm/boot/dts/Makefile
|
||||
@@ -506,6 +506,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \
|
||||
qcom-apq8084-ifc6540.dtb \
|
||||
qcom-apq8084-mtp.dtb \
|
||||
qcom-ipq8064-ap148.dtb \
|
||||
+ qcom-ipq8064-db149.dtb \
|
||||
qcom-msm8660-surf.dtb \
|
||||
qcom-msm8960-cdp.dtb \
|
||||
qcom-msm8974-sony-xperia-honami.dtb
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts
|
||||
@@ -0,0 +1,132 @@
|
||||
+#include "qcom-ipq8064-v1.0.dtsi"
|
||||
+
|
||||
+/ {
|
||||
+ model = "Qualcomm IPQ8064/DB149";
|
||||
+ compatible = "qcom,ipq8064-db149", "qcom,ipq8064";
|
||||
+
|
||||
+ reserved-memory {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <1>;
|
||||
+ ranges;
|
||||
+ rsvd@41200000 {
|
||||
+ reg = <0x41200000 0x300000>;
|
||||
+ no-map;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ alias {
|
||||
+ serial0 = &uart2;
|
||||
+ };
|
||||
+
|
||||
+ chosen {
|
||||
+ linux,stdout-path = "serial0:115200n8";
|
||||
+ };
|
||||
+
|
||||
+ soc {
|
||||
+ pinmux@800000 {
|
||||
+ i2c4_pins: i2c4_pinmux {
|
||||
+ pins = "gpio12", "gpio13";
|
||||
+ function = "gsbi4";
|
||||
+ bias-disable;
|
||||
+ };
|
||||
+
|
||||
+ spi_pins: spi_pins {
|
||||
+ mux {
|
||||
+ pins = "gpio18", "gpio19", "gpio21";
|
||||
+ function = "gsbi5";
|
||||
+ drive-strength = <10>;
|
||||
+ bias-none;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ gsbi2: gsbi@12480000 {
|
||||
+ qcom,mode = <GSBI_PROT_I2C_UART>;
|
||||
+ status = "ok";
|
||||
+ uart2: serial@12490000 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ gsbi5: gsbi@1a200000 {
|
||||
+ qcom,mode = <GSBI_PROT_SPI>;
|
||||
+ status = "ok";
|
||||
+
|
||||
+ spi4: spi@1a280000 {
|
||||
+ status = "ok";
|
||||
+ spi-max-frequency = <50000000>;
|
||||
+
|
||||
+ pinctrl-0 = <&spi_pins>;
|
||||
+ pinctrl-names = "default";
|
||||
+
|
||||
+ cs-gpios = <&qcom_pinmux 20 0>;
|
||||
+
|
||||
+ flash: m25p80@0 {
|
||||
+ compatible = "s25fl256s1";
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <1>;
|
||||
+ spi-max-frequency = <50000000>;
|
||||
+ reg = <0>;
|
||||
+ m25p,fast-read;
|
||||
+
|
||||
+ partition@0 {
|
||||
+ label = "lowlevel_init";
|
||||
+ reg = <0x0 0x1b0000>;
|
||||
+ };
|
||||
+
|
||||
+ partition@1 {
|
||||
+ label = "u-boot";
|
||||
+ reg = <0x1b0000 0x80000>;
|
||||
+ };
|
||||
+
|
||||
+ partition@2 {
|
||||
+ label = "u-boot-env";
|
||||
+ reg = <0x230000 0x40000>;
|
||||
+ };
|
||||
+
|
||||
+ partition@3 {
|
||||
+ label = "caldata";
|
||||
+ reg = <0x270000 0x40000>;
|
||||
+ };
|
||||
+
|
||||
+ partition@4 {
|
||||
+ label = "firmware";
|
||||
+ reg = <0x2b0000 0x1d50000>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ sata-phy@1b400000 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ sata@29000000 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@100f8800 { /* USB3 port 1 HS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@100f8830 { /* USB3 port 1 SS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@110f8800 { /* USB3 port 0 HS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@110f8830 { /* USB3 port 0 SS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ usb30@0 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ usb30@1 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+ };
|
||||
+};
|
|
@ -1,52 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -47,14 +47,12 @@
|
||||
status = "ok";
|
||||
};
|
||||
|
||||
- i2c4: i2c@16380000 {
|
||||
- status = "ok";
|
||||
-
|
||||
- clock-frequency = <200000>;
|
||||
-
|
||||
- pinctrl-0 = <&i2c4_pins>;
|
||||
- pinctrl-names = "default";
|
||||
- };
|
||||
+ /*
|
||||
+ * The i2c device on gsbi4 should not be enabled.
|
||||
+ * On ipq806x designs gsbi4 i2c is meant for exclusive
|
||||
+ * RPM usage. Turning this on in kernel manifests as
|
||||
+ * i2c failure for the RPM.
|
||||
+ */
|
||||
};
|
||||
|
||||
gsbi5: gsbi@1a200000 {
|
||||
--- a/drivers/clk/qcom/gcc-ipq806x.c
|
||||
+++ b/drivers/clk/qcom/gcc-ipq806x.c
|
||||
@@ -294,7 +294,7 @@ static struct clk_rcg gsbi1_uart_src = {
|
||||
.parent_names = gcc_pxo_pll8,
|
||||
.num_parents = 2,
|
||||
.ops = &clk_rcg_ops,
|
||||
- .flags = CLK_SET_PARENT_GATE,
|
||||
+ .flags = CLK_SET_PARENT_GATE | CLK_IGNORE_UNUSED,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -312,7 +312,7 @@ static struct clk_branch gsbi1_uart_clk
|
||||
},
|
||||
.num_parents = 1,
|
||||
.ops = &clk_branch_ops,
|
||||
- .flags = CLK_SET_RATE_PARENT,
|
||||
+ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -890,7 +890,7 @@ static struct clk_branch gsbi1_h_clk = {
|
||||
.hw.init = &(struct clk_init_data){
|
||||
.name = "gsbi1_h_clk",
|
||||
.ops = &clk_branch_ops,
|
||||
- .flags = CLK_IS_ROOT,
|
||||
+ .flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -4,6 +4,11 @@
|
||||
model = "Qualcomm IPQ8064/AP148";
|
||||
compatible = "qcom,ipq8064-ap148", "qcom,ipq8064";
|
||||
|
||||
+ memory@0 {
|
||||
+ reg = <0x42000000 0x1e000000>;
|
||||
+ device_type = "memory";
|
||||
+ };
|
||||
+
|
||||
reserved-memory {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
|
@ -1,30 +0,0 @@
|
|||
From: Arnd Bergmann <arnd@arndb.de>
|
||||
Date: Tue, 24 Nov 2015 23:13:09 +0100
|
||||
Subject: [PATCH] ARM: qcom: select ARM_CPU_SUSPEND for power management
|
||||
|
||||
The qcom spm driver uses cpu_resume_arm(), which is not included
|
||||
in the kernel in all configurations:
|
||||
|
||||
drivers/built-in.o: In function `qcom_cpu_spc':
|
||||
:(.text+0xbc022): undefined reference to `cpu_suspend'
|
||||
drivers/built-in.o: In function `qcom_cpuidle_init':
|
||||
:(.init.text+0x610c): undefined reference to `cpu_resume_arm'
|
||||
|
||||
This adds a 'select' Kconfig statement to ensure it's always
|
||||
enabled.
|
||||
|
||||
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
|
||||
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Signed-off-by: Andy Gross <agross@codeaurora.org>
|
||||
---
|
||||
|
||||
--- a/drivers/soc/qcom/Kconfig
|
||||
+++ b/drivers/soc/qcom/Kconfig
|
||||
@@ -13,6 +13,7 @@ config QCOM_GSBI
|
||||
config QCOM_PM
|
||||
bool "Qualcomm Power Management"
|
||||
depends on ARCH_QCOM && !ARM64
|
||||
+ select ARM_CPU_SUSPEND
|
||||
select QCOM_SCM
|
||||
help
|
||||
QCOM Platform specific power driver to manage cores and L2 low power
|
|
@ -1,33 +0,0 @@
|
|||
From c7c482da19a5e4ba7101198c21c2434056b0b2da Mon Sep 17 00:00:00 2001
|
||||
From: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Date: Thu, 13 Aug 2015 09:45:26 -0700
|
||||
Subject: [PATCH 1/3] ARM: qcom: add SFPB nodes to IPQ806x dts
|
||||
|
||||
Add one new node to the ipq806x.dtsi file to declare & register the
|
||||
hardware spinlock devices. This mechanism is required to be used by
|
||||
other drivers such as SMEM.
|
||||
|
||||
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
---
|
||||
arch/arm/boot/dts/qcom-ipq8064.dtsi | 11 +++++++++++
|
||||
1 file changed, 11 insertions(+)
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -329,5 +329,16 @@
|
||||
#reset-cells = <1>;
|
||||
};
|
||||
|
||||
+ sfpb_mutex_block: syscon@1200600 {
|
||||
+ compatible = "syscon";
|
||||
+ reg = <0x01200600 0x100>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ sfpb_mutex: sfpb-mutex {
|
||||
+ compatible = "qcom,sfpb-mutex";
|
||||
+ syscon = <&sfpb_mutex_block 4 4>;
|
||||
+
|
||||
+ #hwlock-cells = <1>;
|
||||
};
|
||||
};
|
|
@ -1,36 +0,0 @@
|
|||
From f212be3a6134db8dd7c5f6f0987536a669401fae Mon Sep 17 00:00:00 2001
|
||||
From: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Date: Fri, 14 Aug 2015 11:17:20 -0700
|
||||
Subject: [PATCH 2/3] ARM: qcom: add SMEM device node to IPQ806x dts
|
||||
|
||||
SMEM is used on IPQ806x to store various board related information such
|
||||
as boot device and flash partition layout. We'll declare it as a device
|
||||
so we can make use of it thanks to the new SMEM soc driver.
|
||||
|
||||
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
---
|
||||
arch/arm/boot/dts/qcom-ipq8064.dtsi | 8 +++++++-
|
||||
1 file changed, 7 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -55,7 +55,7 @@
|
||||
no-map;
|
||||
};
|
||||
|
||||
- smem@41000000 {
|
||||
+ smem: smem@41000000 {
|
||||
reg = <0x41000000 0x200000>;
|
||||
no-map;
|
||||
};
|
||||
@@ -341,4 +341,10 @@
|
||||
|
||||
#hwlock-cells = <1>;
|
||||
};
|
||||
+
|
||||
+ smem {
|
||||
+ compatible = "qcom,smem";
|
||||
+ memory-region = <&smem>;
|
||||
+ hwlocks = <&sfpb_mutex 3>;
|
||||
+ };
|
||||
};
|
|
@ -1,275 +0,0 @@
|
|||
From 61e8e1b1af77f24339da3f0822a76fa65ed635c6 Mon Sep 17 00:00:00 2001
|
||||
From: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Date: Thu, 13 Aug 2015 09:53:14 -0700
|
||||
Subject: [PATCH] mtd: add SMEM parser for QCOM platforms
|
||||
|
||||
On QCOM platforms using MTD devices storage (such as IPQ806x), SMEM is
|
||||
used to store partition layout. This new parser can now be used to read
|
||||
SMEM and use it to register an MTD layout according to its content.
|
||||
|
||||
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org>
|
||||
---
|
||||
drivers/mtd/Kconfig | 7 ++
|
||||
drivers/mtd/Makefile | 1 +
|
||||
drivers/mtd/qcom_smem_part.c | 228 +++++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 236 insertions(+)
|
||||
create mode 100644 drivers/mtd/qcom_smem_part.c
|
||||
|
||||
--- a/drivers/mtd/Kconfig
|
||||
+++ b/drivers/mtd/Kconfig
|
||||
@@ -190,6 +190,13 @@ config MTD_MYLOADER_PARTS
|
||||
You will still need the parsing functions to be called by the driver
|
||||
for your particular device. It won't happen automatically.
|
||||
|
||||
+config MTD_QCOM_SMEM_PARTS
|
||||
+ tristate "QCOM SMEM partitioning support"
|
||||
+ depends on QCOM_SMEM
|
||||
+ help
|
||||
+ This provides partitions parser for QCOM devices using SMEM
|
||||
+ such as IPQ806x.
|
||||
+
|
||||
comment "User Modules And Translation Layers"
|
||||
|
||||
#
|
||||
--- a/drivers/mtd/Makefile
|
||||
+++ b/drivers/mtd/Makefile
|
||||
@@ -16,6 +16,7 @@ obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o
|
||||
obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o
|
||||
obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o
|
||||
obj-$(CONFIG_MTD_MYLOADER_PARTS) += myloader.o
|
||||
+obj-$(CONFIG_MTD_QCOM_SMEM_PARTS) += qcom_smem_part.o
|
||||
|
||||
# 'Users' - code which presents functionality to userspace.
|
||||
obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/mtd/qcom_smem_part.c
|
||||
@@ -0,0 +1,228 @@
|
||||
+/*
|
||||
+ * Copyright (c) 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/device.h>
|
||||
+#include <linux/slab.h>
|
||||
+
|
||||
+#include <linux/mtd/mtd.h>
|
||||
+#include <linux/mtd/partitions.h>
|
||||
+#include <linux/spi/spi.h>
|
||||
+#include <linux/module.h>
|
||||
+
|
||||
+#include <linux/soc/qcom/smem.h>
|
||||
+
|
||||
+/* Processor/host identifier for the application processor */
|
||||
+#define SMEM_HOST_APPS 0
|
||||
+
|
||||
+/* SMEM items index */
|
||||
+#define SMEM_AARM_PARTITION_TABLE 9
|
||||
+#define SMEM_BOOT_FLASH_TYPE 421
|
||||
+#define SMEM_BOOT_FLASH_BLOCK_SIZE 424
|
||||
+
|
||||
+/* SMEM Flash types */
|
||||
+#define SMEM_FLASH_NAND 2
|
||||
+#define SMEM_FLASH_SPI 6
|
||||
+
|
||||
+#define SMEM_PART_NAME_SZ 16
|
||||
+#define SMEM_PARTS_MAX 32
|
||||
+
|
||||
+struct smem_partition {
|
||||
+ char name[SMEM_PART_NAME_SZ];
|
||||
+ __le32 start;
|
||||
+ __le32 size;
|
||||
+ __le32 attr;
|
||||
+};
|
||||
+
|
||||
+struct smem_partition_table {
|
||||
+ u8 magic[8];
|
||||
+ __le32 version;
|
||||
+ __le32 len;
|
||||
+ struct smem_partition parts[SMEM_PARTS_MAX];
|
||||
+};
|
||||
+
|
||||
+/* SMEM Magic values in partition table */
|
||||
+static const u8 SMEM_PTABLE_MAGIC[] = {
|
||||
+ 0xaa, 0x73, 0xee, 0x55,
|
||||
+ 0xdb, 0xbd, 0x5e, 0xe3,
|
||||
+};
|
||||
+
|
||||
+static int qcom_smem_get_flash_blksz(u64 **smem_blksz)
|
||||
+{
|
||||
+ size_t size;
|
||||
+
|
||||
+ *smem_blksz = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_BLOCK_SIZE,
|
||||
+ &size);
|
||||
+
|
||||
+ if (IS_ERR(*smem_blksz)) {
|
||||
+ pr_err("Unable to read flash blksz from SMEM\n");
|
||||
+ return -ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ if (size != sizeof(**smem_blksz)) {
|
||||
+ pr_err("Invalid flash blksz size in SMEM\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_smem_get_flash_type(u64 **smem_flash_type)
|
||||
+{
|
||||
+ size_t size;
|
||||
+
|
||||
+ *smem_flash_type = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_TYPE,
|
||||
+ &size);
|
||||
+
|
||||
+ if (IS_ERR(*smem_flash_type)) {
|
||||
+ pr_err("Unable to read flash type from SMEM\n");
|
||||
+ return -ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ if (size != sizeof(**smem_flash_type)) {
|
||||
+ pr_err("Invalid flash type size in SMEM\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_smem_get_flash_partitions(struct smem_partition_table **pparts)
|
||||
+{
|
||||
+ size_t size;
|
||||
+
|
||||
+ *pparts = qcom_smem_get(SMEM_HOST_APPS, SMEM_AARM_PARTITION_TABLE,
|
||||
+ &size);
|
||||
+
|
||||
+ if (IS_ERR(*pparts)) {
|
||||
+ pr_err("Unable to read partition table from SMEM\n");
|
||||
+ return -ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int of_dev_node_match(struct device *dev, void *data)
|
||||
+{
|
||||
+ return dev->of_node == data;
|
||||
+}
|
||||
+
|
||||
+static bool is_spi_device(struct device_node *np)
|
||||
+{
|
||||
+ struct device *dev;
|
||||
+
|
||||
+ dev = bus_find_device(&spi_bus_type, NULL, np, of_dev_node_match);
|
||||
+ if (!dev)
|
||||
+ return false;
|
||||
+
|
||||
+ put_device(dev);
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+static int parse_qcom_smem_partitions(struct mtd_info *master,
|
||||
+ struct mtd_partition **pparts,
|
||||
+ struct mtd_part_parser_data *data)
|
||||
+{
|
||||
+ struct smem_partition_table *smem_parts;
|
||||
+ u64 *smem_flash_type, *smem_blksz;
|
||||
+ struct mtd_partition *mtd_parts;
|
||||
+ struct device_node *of_node = data->of_node;
|
||||
+ int i, ret;
|
||||
+
|
||||
+ /*
|
||||
+ * SMEM will only store the partition table of the boot device.
|
||||
+ * If this is not the boot device, do not return any partition.
|
||||
+ */
|
||||
+ ret = qcom_smem_get_flash_type(&smem_flash_type);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if ((*smem_flash_type == SMEM_FLASH_NAND && !mtd_type_is_nand(master))
|
||||
+ || (*smem_flash_type == SMEM_FLASH_SPI && !is_spi_device(of_node)))
|
||||
+ return 0;
|
||||
+
|
||||
+ /*
|
||||
+ * Just for sanity purpose, make sure the block size in SMEM matches the
|
||||
+ * block size of the MTD device
|
||||
+ */
|
||||
+ ret = qcom_smem_get_flash_blksz(&smem_blksz);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (*smem_blksz != master->erasesize) {
|
||||
+ pr_err("SMEM block size differs from MTD block size\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ /* Get partition pointer from SMEM */
|
||||
+ ret = qcom_smem_get_flash_partitions(&smem_parts);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (memcmp(SMEM_PTABLE_MAGIC, smem_parts->magic,
|
||||
+ sizeof(SMEM_PTABLE_MAGIC))) {
|
||||
+ pr_err("SMEM partition magic invalid\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ /* Allocate and populate the mtd structures */
|
||||
+ mtd_parts = kcalloc(le32_to_cpu(smem_parts->len), sizeof(*mtd_parts),
|
||||
+ GFP_KERNEL);
|
||||
+ if (!mtd_parts)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for (i = 0; i < smem_parts->len; i++) {
|
||||
+ struct smem_partition *s_part = &smem_parts->parts[i];
|
||||
+ struct mtd_partition *m_part = &mtd_parts[i];
|
||||
+
|
||||
+ m_part->name = s_part->name;
|
||||
+ m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz);
|
||||
+ m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz);
|
||||
+
|
||||
+ /*
|
||||
+ * The last SMEM partition may have its size marked as
|
||||
+ * something like 0xffffffff, which means "until the end of the
|
||||
+ * flash device". In this case, truncate it.
|
||||
+ */
|
||||
+ if (m_part->offset + m_part->size > master->size)
|
||||
+ m_part->size = master->size - m_part->offset;
|
||||
+ }
|
||||
+
|
||||
+ *pparts = mtd_parts;
|
||||
+
|
||||
+ return smem_parts->len;
|
||||
+}
|
||||
+
|
||||
+static struct mtd_part_parser qcom_smem_parser = {
|
||||
+ .owner = THIS_MODULE,
|
||||
+ .parse_fn = parse_qcom_smem_partitions,
|
||||
+ .name = "qcom-smem",
|
||||
+};
|
||||
+
|
||||
+static int __init qcom_smem_parser_init(void)
|
||||
+{
|
||||
+ register_mtd_parser(&qcom_smem_parser);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void __exit qcom_smem_parser_exit(void)
|
||||
+{
|
||||
+ deregister_mtd_parser(&qcom_smem_parser);
|
||||
+}
|
||||
+
|
||||
+module_init(qcom_smem_parser_init);
|
||||
+module_exit(qcom_smem_parser_exit);
|
||||
+
|
||||
+MODULE_LICENSE("GPL");
|
||||
+MODULE_AUTHOR("Mathieu Olivari <mathieu@codeaurora.org>");
|
||||
+MODULE_DESCRIPTION("Parsing code for SMEM based partition tables");
|
|
@ -1,42 +0,0 @@
|
|||
From 1407bf13e3bf5f1168484c3e68b6ef9d8cf2bc72 Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <balbi@ti.com>
|
||||
Date: Mon, 16 Nov 2015 16:06:37 -0600
|
||||
Subject: usb: dwc3: core: purge dev_dbg() calls
|
||||
|
||||
The last few dev_dbg() messages are converted to
|
||||
tracepoints and we can finally ignore dev_dbg()
|
||||
messages during debug sessions.
|
||||
|
||||
Signed-off-by: Felipe Balbi <balbi@ti.com>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 8 +++++---
|
||||
1 file changed, 5 insertions(+), 3 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -272,7 +272,8 @@ static int dwc3_event_buffers_setup(stru
|
||||
|
||||
for (n = 0; n < dwc->num_event_buffers; n++) {
|
||||
evt = dwc->ev_buffs[n];
|
||||
- dev_dbg(dwc->dev, "Event buf %p dma %08llx length %d\n",
|
||||
+ dwc3_trace(trace_dwc3_core,
|
||||
+ "Event buf %p dma %08llx length %d\n",
|
||||
evt->buf, (unsigned long long) evt->dma,
|
||||
evt->length);
|
||||
|
||||
@@ -608,12 +609,13 @@ static int dwc3_core_init(struct dwc3 *d
|
||||
reg |= DWC3_GCTL_GBLHIBERNATIONEN;
|
||||
break;
|
||||
default:
|
||||
- dev_dbg(dwc->dev, "No power optimization available\n");
|
||||
+ dwc3_trace(trace_dwc3_core, "No power optimization available\n");
|
||||
}
|
||||
|
||||
/* check if current dwc3 is on simulation board */
|
||||
if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) {
|
||||
- dev_dbg(dwc->dev, "it is on FPGA board\n");
|
||||
+ dwc3_trace(trace_dwc3_core,
|
||||
+ "running on FPGA platform\n");
|
||||
dwc->is_fpga = true;
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
From 2c7f1bd9127a1a49ee25d9c2b2ce17b11c7fb05f Mon Sep 17 00:00:00 2001
|
||||
From: John Youn <John.Youn@synopsys.com>
|
||||
Date: Fri, 5 Feb 2016 17:08:59 -0800
|
||||
Subject: usb: dwc3: Update maximum_speed for SuperSpeedPlus
|
||||
|
||||
If the maximum_speed is not set, set it to a known value, either
|
||||
SuperSpeed or SuperSpeedPlus based on the type of controller we are
|
||||
using. If we are on DWC_usb31 controller, check the PHY interface to see
|
||||
if it is capable of SuperSpeedPlus.
|
||||
|
||||
Also this check is moved after dwc3_core_init() so that we can check
|
||||
dwc->revision.
|
||||
|
||||
Signed-off-by: John Youn <johnyoun@synopsys.com>
|
||||
Signed-off-by: Felipe Balbi <balbi@kernel.org>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 17 +++++++++++++----
|
||||
1 file changed, 13 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -962,10 +962,6 @@ static int dwc3_probe(struct platform_de
|
||||
fladj = pdata->fladj_value;
|
||||
}
|
||||
|
||||
- /* default to superspeed if no maximum_speed passed */
|
||||
- if (dwc->maximum_speed == USB_SPEED_UNKNOWN)
|
||||
- dwc->maximum_speed = USB_SPEED_SUPER;
|
||||
-
|
||||
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
|
||||
dwc->tx_de_emphasis = tx_de_emphasis;
|
||||
|
||||
@@ -1016,6 +1012,19 @@ static int dwc3_probe(struct platform_de
|
||||
goto err1;
|
||||
}
|
||||
|
||||
+ /* default to superspeed if no maximum_speed passed */
|
||||
+ if (dwc->maximum_speed == USB_SPEED_UNKNOWN) {
|
||||
+ dwc->maximum_speed = USB_SPEED_SUPER;
|
||||
+
|
||||
+ /*
|
||||
+ * default to superspeed plus if we are capable.
|
||||
+ */
|
||||
+ if (dwc3_is_usb31(dwc) &&
|
||||
+ (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) ==
|
||||
+ DWC3_GHWPARAMS3_SSPHY_IFC_GEN2))
|
||||
+ dwc->maximum_speed = USB_SPEED_SUPER_PLUS;
|
||||
+ }
|
||||
+
|
||||
/* Adjust Frame Length */
|
||||
dwc3_frame_length_adjustment(dwc, fladj);
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
From 77966eb85e6d988a6daaf8ac14ac33026ceb3ab7 Mon Sep 17 00:00:00 2001
|
||||
From: John Youn <John.Youn@synopsys.com>
|
||||
Date: Fri, 19 Feb 2016 17:31:01 -0800
|
||||
Subject: usb: dwc3: Validate the maximum_speed parameter
|
||||
|
||||
Check that dwc->maximum_speed is set to a valid value. Also add an error
|
||||
when we use it later if we encounter an invalid value.
|
||||
|
||||
Signed-off-by: John Youn <johnyoun@synopsys.com>
|
||||
Signed-off-by: Felipe Balbi <balbi@kernel.org>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 18 ++++++++++++++++--
|
||||
drivers/usb/dwc3/gadget.c | 9 ++++++---
|
||||
2 files changed, 22 insertions(+), 5 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -1012,8 +1012,20 @@ static int dwc3_probe(struct platform_de
|
||||
goto err1;
|
||||
}
|
||||
|
||||
- /* default to superspeed if no maximum_speed passed */
|
||||
- if (dwc->maximum_speed == USB_SPEED_UNKNOWN) {
|
||||
+ /* Check the maximum_speed parameter */
|
||||
+ switch (dwc->maximum_speed) {
|
||||
+ case USB_SPEED_LOW:
|
||||
+ case USB_SPEED_FULL:
|
||||
+ case USB_SPEED_HIGH:
|
||||
+ case USB_SPEED_SUPER:
|
||||
+ case USB_SPEED_SUPER_PLUS:
|
||||
+ break;
|
||||
+ default:
|
||||
+ dev_err(dev, "invalid maximum_speed parameter %d\n",
|
||||
+ dwc->maximum_speed);
|
||||
+ /* fall through */
|
||||
+ case USB_SPEED_UNKNOWN:
|
||||
+ /* default to superspeed */
|
||||
dwc->maximum_speed = USB_SPEED_SUPER;
|
||||
|
||||
/*
|
||||
@@ -1023,6 +1035,8 @@ static int dwc3_probe(struct platform_de
|
||||
(DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) ==
|
||||
DWC3_GHWPARAMS3_SSPHY_IFC_GEN2))
|
||||
dwc->maximum_speed = USB_SPEED_SUPER_PLUS;
|
||||
+
|
||||
+ break;
|
||||
}
|
||||
|
||||
/* Adjust Frame Length */
|
||||
--- a/drivers/usb/dwc3/gadget.c
|
||||
+++ b/drivers/usb/dwc3/gadget.c
|
||||
@@ -1634,10 +1634,13 @@ static int dwc3_gadget_start(struct usb_
|
||||
case USB_SPEED_HIGH:
|
||||
reg |= DWC3_DSTS_HIGHSPEED;
|
||||
break;
|
||||
- case USB_SPEED_SUPER: /* FALLTHROUGH */
|
||||
- case USB_SPEED_UNKNOWN: /* FALTHROUGH */
|
||||
default:
|
||||
- reg |= DWC3_DSTS_SUPERSPEED;
|
||||
+ dev_err(dwc->dev, "invalid dwc->maximum_speed (%d)\n",
|
||||
+ dwc->maximum_speed);
|
||||
+ /* fall through */
|
||||
+ case USB_SPEED_SUPER:
|
||||
+ reg |= DWC3_DCFG_SUPERSPEED;
|
||||
+ break;
|
||||
}
|
||||
}
|
||||
dwc3_writel(dwc->regs, DWC3_DCFG, reg);
|
|
@ -1,28 +0,0 @@
|
|||
From c4137a9c841ec7fb300782d211f2d6907f4d6e20 Mon Sep 17 00:00:00 2001
|
||||
From: John Youn <John.Youn@synopsys.com>
|
||||
Date: Fri, 5 Feb 2016 17:08:18 -0800
|
||||
Subject: usb: dwc3: DWC_usb31 controller check
|
||||
|
||||
Add a convenience function to check if the controller is DWC_usb31.
|
||||
|
||||
Signed-off-by: John Youn <johnyoun@synopsys.com>
|
||||
Signed-off-by: Felipe Balbi <balbi@kernel.org>
|
||||
---
|
||||
drivers/usb/dwc3/core.h | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.h
|
||||
+++ b/drivers/usb/dwc3/core.h
|
||||
@@ -1019,6 +1019,12 @@ struct dwc3_gadget_ep_cmd_params {
|
||||
void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
|
||||
int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc);
|
||||
|
||||
+/* check whether we are on the DWC_usb31 core */
|
||||
+static inline bool dwc3_is_usb31(struct dwc3 *dwc)
|
||||
+{
|
||||
+ return !!(dwc->revision & DWC3_REVISION_IS_DWC31);
|
||||
+}
|
||||
+
|
||||
#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
|
||||
int dwc3_host_init(struct dwc3 *dwc);
|
||||
void dwc3_host_exit(struct dwc3 *dwc);
|
|
@ -1,42 +0,0 @@
|
|||
From 1f38f88a24c86d46cf47782ffabd5457f231f8ca Mon Sep 17 00:00:00 2001
|
||||
From: John Youn <John.Youn@synopsys.com>
|
||||
Date: Fri, 5 Feb 2016 17:08:31 -0800
|
||||
Subject: usb: dwc3: Update register fields for SuperSpeedPlus
|
||||
|
||||
Update various registers fields definitions for the DWC_usb31 controller
|
||||
for SuperSpeedPlus support.
|
||||
|
||||
Signed-off-by: John Youn <johnyoun@synopsys.com>
|
||||
Signed-off-by: Felipe Balbi <balbi@kernel.org>
|
||||
---
|
||||
drivers/usb/dwc3/core.h | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.h
|
||||
+++ b/drivers/usb/dwc3/core.h
|
||||
@@ -220,7 +220,8 @@
|
||||
/* Global HWPARAMS3 Register */
|
||||
#define DWC3_GHWPARAMS3_SSPHY_IFC(n) ((n) & 3)
|
||||
#define DWC3_GHWPARAMS3_SSPHY_IFC_DIS 0
|
||||
-#define DWC3_GHWPARAMS3_SSPHY_IFC_ENA 1
|
||||
+#define DWC3_GHWPARAMS3_SSPHY_IFC_GEN1 1
|
||||
+#define DWC3_GHWPARAMS3_SSPHY_IFC_GEN2 2 /* DWC_usb31 only */
|
||||
#define DWC3_GHWPARAMS3_HSPHY_IFC(n) (((n) & (3 << 2)) >> 2)
|
||||
#define DWC3_GHWPARAMS3_HSPHY_IFC_DIS 0
|
||||
#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI 1
|
||||
@@ -246,6 +247,7 @@
|
||||
#define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f)
|
||||
|
||||
#define DWC3_DCFG_SPEED_MASK (7 << 0)
|
||||
+#define DWC3_DCFG_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */
|
||||
#define DWC3_DCFG_SUPERSPEED (4 << 0)
|
||||
#define DWC3_DCFG_HIGHSPEED (0 << 0)
|
||||
#define DWC3_DCFG_FULLSPEED2 (1 << 0)
|
||||
@@ -336,6 +338,7 @@
|
||||
|
||||
#define DWC3_DSTS_CONNECTSPD (7 << 0)
|
||||
|
||||
+#define DWC3_DSTS_SUPERSPEED_PLUS (5 << 0) /* DWC_usb31 only */
|
||||
#define DWC3_DSTS_SUPERSPEED (4 << 0)
|
||||
#define DWC3_DSTS_HIGHSPEED (0 << 0)
|
||||
#define DWC3_DSTS_FULLSPEED2 (1 << 0)
|
|
@ -1,100 +0,0 @@
|
|||
From f59dcab176293b646e1358144c93c58c3cda2813 Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
Date: Fri, 11 Mar 2016 10:51:52 +0200
|
||||
Subject: usb: dwc3: core: improve reset sequence
|
||||
|
||||
According to Synopsys Databook, we shouldn't be
|
||||
relying on GCTL.CORESOFTRESET bit as that's only for
|
||||
debugging purposes. Instead, let's use DCTL.CSFTRST
|
||||
if we're OTG or PERIPHERAL mode.
|
||||
|
||||
Host side block will be reset by XHCI driver if
|
||||
necessary. Note that this reduces amount of time
|
||||
spent on dwc3_probe() by a long margin.
|
||||
|
||||
We're still gonna wait for reset to finish for a
|
||||
long time (default to 1ms max), but tests show that
|
||||
the reset polling loop executed at most 19 times
|
||||
(modprobe dwc3 && modprobe -r dwc3 executed 1000
|
||||
times in a row).
|
||||
|
||||
Suggested-by: Mian Yousaf Kaukab <yousaf.kaukab@intel.com>
|
||||
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 48 ++++++++++++++++++------------------------------
|
||||
1 file changed, 18 insertions(+), 30 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -67,23 +67,9 @@ void dwc3_set_mode(struct dwc3 *dwc, u32
|
||||
static int dwc3_core_soft_reset(struct dwc3 *dwc)
|
||||
{
|
||||
u32 reg;
|
||||
+ int retries = 1000;
|
||||
int ret;
|
||||
|
||||
- /* Before Resetting PHY, put Core in Reset */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
||||
- reg |= DWC3_GCTL_CORESOFTRESET;
|
||||
- dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
||||
-
|
||||
- /* Assert USB3 PHY reset */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
|
||||
- reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
|
||||
- dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
|
||||
-
|
||||
- /* Assert USB2 PHY reset */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
||||
- reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
|
||||
- dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
||||
-
|
||||
usb_phy_init(dwc->usb2_phy);
|
||||
usb_phy_init(dwc->usb3_phy);
|
||||
ret = phy_init(dwc->usb2_generic_phy);
|
||||
@@ -95,26 +81,28 @@ static int dwc3_core_soft_reset(struct d
|
||||
phy_exit(dwc->usb2_generic_phy);
|
||||
return ret;
|
||||
}
|
||||
- mdelay(100);
|
||||
|
||||
- /* Clear USB3 PHY reset */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
|
||||
- reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
|
||||
- dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
|
||||
-
|
||||
- /* Clear USB2 PHY reset */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
||||
- reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
|
||||
- dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
||||
-
|
||||
- mdelay(100);
|
||||
-
|
||||
- /* After PHYs are stable we can take Core out of reset state */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
||||
- reg &= ~DWC3_GCTL_CORESOFTRESET;
|
||||
- dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
||||
+ /*
|
||||
+ * We're resetting only the device side because, if we're in host mode,
|
||||
+ * XHCI driver will reset the host block. If dwc3 was configured for
|
||||
+ * host-only mode, then we can return early.
|
||||
+ */
|
||||
+ if (dwc->dr_mode == USB_DR_MODE_HOST)
|
||||
+ return 0;
|
||||
+
|
||||
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
||||
+ reg |= DWC3_DCTL_CSFTRST;
|
||||
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
|
||||
+
|
||||
+ do {
|
||||
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
||||
+ if (!(reg & DWC3_DCTL_CSFTRST))
|
||||
+ return 0;
|
||||
+
|
||||
+ udelay(1);
|
||||
+ } while (--retries);
|
||||
|
||||
- return 0;
|
||||
+ return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,242 +0,0 @@
|
|||
From bc5081617faeb3b2f0c126dc37264b87af7da47f Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
Date: Thu, 4 Feb 2016 14:18:01 +0200
|
||||
Subject: usb: dwc3: drop FIFO resizing logic
|
||||
|
||||
That FIFO resizing logic was added to support OMAP5
|
||||
ES1.0 which had a bogus default FIFO size. I can't
|
||||
remember the exact size of default FIFO, but it was
|
||||
less than one bulk superspeed packet (<1024) which
|
||||
would prevent USB3 from ever working on OMAP5 ES1.0.
|
||||
|
||||
However, OMAP5 ES1.0 support has been dropped by
|
||||
commit aa2f4b16f830 ("ARM: OMAP5: id: Remove ES1.0
|
||||
support") which renders FIFO resizing unnecessary.
|
||||
|
||||
Tested-by: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
---
|
||||
Documentation/devicetree/bindings/usb/dwc3.txt | 4 +-
|
||||
.../devicetree/bindings/usb/qcom,dwc3.txt | 1 -
|
||||
drivers/usb/dwc3/core.c | 4 -
|
||||
drivers/usb/dwc3/core.h | 5 --
|
||||
drivers/usb/dwc3/ep0.c | 9 ---
|
||||
drivers/usb/dwc3/gadget.c | 86 ----------------------
|
||||
drivers/usb/dwc3/platform_data.h | 1 -
|
||||
7 files changed, 2 insertions(+), 108 deletions(-)
|
||||
|
||||
--- a/Documentation/devicetree/bindings/usb/dwc3.txt
|
||||
+++ b/Documentation/devicetree/bindings/usb/dwc3.txt
|
||||
@@ -14,7 +14,6 @@ Optional properties:
|
||||
the second element is expected to be a handle to the USB3/SS PHY
|
||||
- phys: from the *Generic PHY* bindings
|
||||
- phy-names: from the *Generic PHY* bindings
|
||||
- - tx-fifo-resize: determines if the FIFO *has* to be reallocated.
|
||||
- snps,usb3_lpm_capable: determines if platform is USB3 LPM capable
|
||||
- snps,disable_scramble_quirk: true when SW should disable data scrambling.
|
||||
Only really useful for FPGA builds.
|
||||
@@ -47,6 +46,8 @@ Optional properties:
|
||||
register for post-silicon frame length adjustment when the
|
||||
fladj_30mhz_sdbnd signal is invalid or incorrect.
|
||||
|
||||
+ - <DEPRECATED> tx-fifo-resize: determines if the FIFO *has* to be reallocated.
|
||||
+
|
||||
This is usually a subnode to DWC3 glue to which it is connected.
|
||||
|
||||
dwc3@4a030000 {
|
||||
@@ -54,5 +55,4 @@ dwc3@4a030000 {
|
||||
reg = <0x4a030000 0xcfff>;
|
||||
interrupts = <0 92 4>
|
||||
usb-phy = <&usb2_phy>, <&usb3,phy>;
|
||||
- tx-fifo-resize;
|
||||
};
|
||||
--- a/Documentation/devicetree/bindings/usb/qcom,dwc3.txt
|
||||
+++ b/Documentation/devicetree/bindings/usb/qcom,dwc3.txt
|
||||
@@ -59,7 +59,6 @@ Example device nodes:
|
||||
interrupts = <0 205 0x4>;
|
||||
phys = <&hs_phy>, <&ss_phy>;
|
||||
phy-names = "usb2-phy", "usb3-phy";
|
||||
- tx-fifo-resize;
|
||||
dr_mode = "host";
|
||||
};
|
||||
};
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -882,9 +882,6 @@ static int dwc3_probe(struct platform_de
|
||||
dwc->usb3_lpm_capable = device_property_read_bool(dev,
|
||||
"snps,usb3_lpm_capable");
|
||||
|
||||
- dwc->needs_fifo_resize = device_property_read_bool(dev,
|
||||
- "tx-fifo-resize");
|
||||
-
|
||||
dwc->disable_scramble_quirk = device_property_read_bool(dev,
|
||||
"snps,disable_scramble_quirk");
|
||||
dwc->u2exit_lfps_quirk = device_property_read_bool(dev,
|
||||
@@ -926,7 +923,6 @@ static int dwc3_probe(struct platform_de
|
||||
if (pdata->hird_threshold)
|
||||
hird_threshold = pdata->hird_threshold;
|
||||
|
||||
- dwc->needs_fifo_resize = pdata->tx_fifo_resize;
|
||||
dwc->usb3_lpm_capable = pdata->usb3_lpm_capable;
|
||||
dwc->dr_mode = pdata->dr_mode;
|
||||
|
||||
--- a/drivers/usb/dwc3/core.h
|
||||
+++ b/drivers/usb/dwc3/core.h
|
||||
@@ -705,9 +705,7 @@ struct dwc3_scratchpad_array {
|
||||
* 0 - utmi_sleep_n
|
||||
* 1 - utmi_l1_suspend_n
|
||||
* @is_fpga: true when we are using the FPGA board
|
||||
- * @needs_fifo_resize: not all users might want fifo resizing, flag it
|
||||
* @pullups_connected: true when Run/Stop bit is set
|
||||
- * @resize_fifos: tells us it's ok to reconfigure our TxFIFO sizes.
|
||||
* @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
|
||||
* @start_config_issued: true when StartConfig command has been issued
|
||||
* @three_stage_setup: set if we perform a three phase setup
|
||||
@@ -850,9 +848,7 @@ struct dwc3 {
|
||||
unsigned has_lpm_erratum:1;
|
||||
unsigned is_utmi_l1_suspend:1;
|
||||
unsigned is_fpga:1;
|
||||
- unsigned needs_fifo_resize:1;
|
||||
unsigned pullups_connected:1;
|
||||
- unsigned resize_fifos:1;
|
||||
unsigned setup_packet_pending:1;
|
||||
unsigned three_stage_setup:1;
|
||||
unsigned usb3_lpm_capable:1;
|
||||
@@ -1020,7 +1016,6 @@ struct dwc3_gadget_ep_cmd_params {
|
||||
|
||||
/* prototypes */
|
||||
void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
|
||||
-int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc);
|
||||
|
||||
/* check whether we are on the DWC_usb31 core */
|
||||
static inline bool dwc3_is_usb31(struct dwc3 *dwc)
|
||||
--- a/drivers/usb/dwc3/ep0.c
|
||||
+++ b/drivers/usb/dwc3/ep0.c
|
||||
@@ -587,9 +587,6 @@ static int dwc3_ep0_set_config(struct dw
|
||||
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
||||
reg |= (DWC3_DCTL_ACCEPTU1ENA | DWC3_DCTL_ACCEPTU2ENA);
|
||||
dwc3_writel(dwc->regs, DWC3_DCTL, reg);
|
||||
-
|
||||
- dwc->resize_fifos = true;
|
||||
- dwc3_trace(trace_dwc3_ep0, "resize FIFOs flag SET");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1028,12 +1025,6 @@ static int dwc3_ep0_start_control_status
|
||||
|
||||
static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep)
|
||||
{
|
||||
- if (dwc->resize_fifos) {
|
||||
- dwc3_trace(trace_dwc3_ep0, "Resizing FIFOs");
|
||||
- dwc3_gadget_resize_tx_fifos(dwc);
|
||||
- dwc->resize_fifos = 0;
|
||||
- }
|
||||
-
|
||||
WARN_ON(dwc3_ep0_start_control_status(dep));
|
||||
}
|
||||
|
||||
--- a/drivers/usb/dwc3/gadget.c
|
||||
+++ b/drivers/usb/dwc3/gadget.c
|
||||
@@ -145,92 +145,6 @@ int dwc3_gadget_set_link_state(struct dw
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
-/**
|
||||
- * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case
|
||||
- * @dwc: pointer to our context structure
|
||||
- *
|
||||
- * This function will a best effort FIFO allocation in order
|
||||
- * to improve FIFO usage and throughput, while still allowing
|
||||
- * us to enable as many endpoints as possible.
|
||||
- *
|
||||
- * Keep in mind that this operation will be highly dependent
|
||||
- * on the configured size for RAM1 - which contains TxFifo -,
|
||||
- * the amount of endpoints enabled on coreConsultant tool, and
|
||||
- * the width of the Master Bus.
|
||||
- *
|
||||
- * In the ideal world, we would always be able to satisfy the
|
||||
- * following equation:
|
||||
- *
|
||||
- * ((512 + 2 * MDWIDTH-Bytes) + (Number of IN Endpoints - 1) * \
|
||||
- * (3 * (1024 + MDWIDTH-Bytes) + MDWIDTH-Bytes)) / MDWIDTH-Bytes
|
||||
- *
|
||||
- * Unfortunately, due to many variables that's not always the case.
|
||||
- */
|
||||
-int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc)
|
||||
-{
|
||||
- int last_fifo_depth = 0;
|
||||
- int ram1_depth;
|
||||
- int fifo_size;
|
||||
- int mdwidth;
|
||||
- int num;
|
||||
-
|
||||
- if (!dwc->needs_fifo_resize)
|
||||
- return 0;
|
||||
-
|
||||
- ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7);
|
||||
- mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0);
|
||||
-
|
||||
- /* MDWIDTH is represented in bits, we need it in bytes */
|
||||
- mdwidth >>= 3;
|
||||
-
|
||||
- /*
|
||||
- * FIXME For now we will only allocate 1 wMaxPacketSize space
|
||||
- * for each enabled endpoint, later patches will come to
|
||||
- * improve this algorithm so that we better use the internal
|
||||
- * FIFO space
|
||||
- */
|
||||
- for (num = 0; num < dwc->num_in_eps; num++) {
|
||||
- /* bit0 indicates direction; 1 means IN ep */
|
||||
- struct dwc3_ep *dep = dwc->eps[(num << 1) | 1];
|
||||
- int mult = 1;
|
||||
- int tmp;
|
||||
-
|
||||
- if (!(dep->flags & DWC3_EP_ENABLED))
|
||||
- continue;
|
||||
-
|
||||
- if (usb_endpoint_xfer_bulk(dep->endpoint.desc)
|
||||
- || usb_endpoint_xfer_isoc(dep->endpoint.desc))
|
||||
- mult = 3;
|
||||
-
|
||||
- /*
|
||||
- * REVISIT: the following assumes we will always have enough
|
||||
- * space available on the FIFO RAM for all possible use cases.
|
||||
- * Make sure that's true somehow and change FIFO allocation
|
||||
- * accordingly.
|
||||
- *
|
||||
- * If we have Bulk or Isochronous endpoints, we want
|
||||
- * them to be able to be very, very fast. So we're giving
|
||||
- * those endpoints a fifo_size which is enough for 3 full
|
||||
- * packets
|
||||
- */
|
||||
- tmp = mult * (dep->endpoint.maxpacket + mdwidth);
|
||||
- tmp += mdwidth;
|
||||
-
|
||||
- fifo_size = DIV_ROUND_UP(tmp, mdwidth);
|
||||
-
|
||||
- fifo_size |= (last_fifo_depth << 16);
|
||||
-
|
||||
- dwc3_trace(trace_dwc3_gadget, "%s: Fifo Addr %04x Size %d",
|
||||
- dep->name, last_fifo_depth, fifo_size & 0xffff);
|
||||
-
|
||||
- dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(num), fifo_size);
|
||||
-
|
||||
- last_fifo_depth += (fifo_size & 0xffff);
|
||||
- }
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
|
||||
int status)
|
||||
{
|
||||
--- a/drivers/usb/dwc3/platform_data.h
|
||||
+++ b/drivers/usb/dwc3/platform_data.h
|
||||
@@ -23,7 +23,6 @@
|
||||
struct dwc3_platform_data {
|
||||
enum usb_device_speed maximum_speed;
|
||||
enum usb_dr_mode dr_mode;
|
||||
- bool tx_fifo_resize;
|
||||
bool usb3_lpm_capable;
|
||||
|
||||
unsigned is_utmi_l1_suspend:1;
|
|
@ -1,265 +0,0 @@
|
|||
From 660e9bde74d6915227d7ee3485b11e5f52637b26 Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
Date: Wed, 30 Mar 2016 09:26:24 +0300
|
||||
Subject: usb: dwc3: remove num_event_buffers
|
||||
|
||||
We never, ever route any of the other event buffers
|
||||
so we might as well drop support for them.
|
||||
|
||||
Until someone has a real, proper benefit for
|
||||
multiple event buffers, we will rely on a single
|
||||
one. This also helps reduce memory footprint of
|
||||
dwc3.ko which won't allocate memory for the extra
|
||||
event buffers.
|
||||
|
||||
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 81 +++++++++++++++++++----------------------------
|
||||
drivers/usb/dwc3/core.h | 2 --
|
||||
drivers/usb/dwc3/gadget.c | 38 +++++++---------------
|
||||
3 files changed, 44 insertions(+), 77 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -203,13 +203,10 @@ static struct dwc3_event_buffer *dwc3_al
|
||||
static void dwc3_free_event_buffers(struct dwc3 *dwc)
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
- int i;
|
||||
|
||||
- for (i = 0; i < dwc->num_event_buffers; i++) {
|
||||
- evt = dwc->ev_buffs[i];
|
||||
- if (evt)
|
||||
- dwc3_free_one_event_buffer(dwc, evt);
|
||||
- }
|
||||
+ evt = dwc->ev_buffs[0];
|
||||
+ if (evt)
|
||||
+ dwc3_free_one_event_buffer(dwc, evt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,27 +219,19 @@ static void dwc3_free_event_buffers(stru
|
||||
*/
|
||||
static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
|
||||
{
|
||||
- int num;
|
||||
- int i;
|
||||
-
|
||||
- num = DWC3_NUM_INT(dwc->hwparams.hwparams1);
|
||||
- dwc->num_event_buffers = num;
|
||||
+ struct dwc3_event_buffer *evt;
|
||||
|
||||
- dwc->ev_buffs = devm_kzalloc(dwc->dev, sizeof(*dwc->ev_buffs) * num,
|
||||
+ dwc->ev_buffs = devm_kzalloc(dwc->dev, sizeof(*dwc->ev_buffs),
|
||||
GFP_KERNEL);
|
||||
if (!dwc->ev_buffs)
|
||||
return -ENOMEM;
|
||||
|
||||
- for (i = 0; i < num; i++) {
|
||||
- struct dwc3_event_buffer *evt;
|
||||
-
|
||||
- evt = dwc3_alloc_one_event_buffer(dwc, length);
|
||||
- if (IS_ERR(evt)) {
|
||||
- dev_err(dwc->dev, "can't allocate event buffer\n");
|
||||
- return PTR_ERR(evt);
|
||||
- }
|
||||
- dwc->ev_buffs[i] = evt;
|
||||
+ evt = dwc3_alloc_one_event_buffer(dwc, length);
|
||||
+ if (IS_ERR(evt)) {
|
||||
+ dev_err(dwc->dev, "can't allocate event buffer\n");
|
||||
+ return PTR_ERR(evt);
|
||||
}
|
||||
+ dwc->ev_buffs[0] = evt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -256,25 +245,22 @@ static int dwc3_alloc_event_buffers(stru
|
||||
static int dwc3_event_buffers_setup(struct dwc3 *dwc)
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
- int n;
|
||||
|
||||
- for (n = 0; n < dwc->num_event_buffers; n++) {
|
||||
- evt = dwc->ev_buffs[n];
|
||||
- dwc3_trace(trace_dwc3_core,
|
||||
- "Event buf %p dma %08llx length %d\n",
|
||||
- evt->buf, (unsigned long long) evt->dma,
|
||||
- evt->length);
|
||||
-
|
||||
- evt->lpos = 0;
|
||||
-
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n),
|
||||
- lower_32_bits(evt->dma));
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n),
|
||||
- upper_32_bits(evt->dma));
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n),
|
||||
- DWC3_GEVNTSIZ_SIZE(evt->length));
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0);
|
||||
- }
|
||||
+ evt = dwc->ev_buffs[0];
|
||||
+ dwc3_trace(trace_dwc3_core,
|
||||
+ "Event buf %p dma %08llx length %d\n",
|
||||
+ evt->buf, (unsigned long long) evt->dma,
|
||||
+ evt->length);
|
||||
+
|
||||
+ evt->lpos = 0;
|
||||
+
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0),
|
||||
+ lower_32_bits(evt->dma));
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0),
|
||||
+ upper_32_bits(evt->dma));
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0),
|
||||
+ DWC3_GEVNTSIZ_SIZE(evt->length));
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -282,19 +268,16 @@ static int dwc3_event_buffers_setup(stru
|
||||
static void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
- int n;
|
||||
|
||||
- for (n = 0; n < dwc->num_event_buffers; n++) {
|
||||
- evt = dwc->ev_buffs[n];
|
||||
+ evt = dwc->ev_buffs[0];
|
||||
|
||||
- evt->lpos = 0;
|
||||
+ evt->lpos = 0;
|
||||
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n), 0);
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), 0);
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), DWC3_GEVNTSIZ_INTMASK
|
||||
- | DWC3_GEVNTSIZ_SIZE(0));
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0);
|
||||
- }
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0), 0);
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0), 0);
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), DWC3_GEVNTSIZ_INTMASK
|
||||
+ | DWC3_GEVNTSIZ_SIZE(0));
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
|
||||
}
|
||||
|
||||
static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc)
|
||||
--- a/drivers/usb/dwc3/core.h
|
||||
+++ b/drivers/usb/dwc3/core.h
|
||||
@@ -663,7 +663,6 @@ struct dwc3_scratchpad_array {
|
||||
* @regs: base address for our registers
|
||||
* @regs_size: address space size
|
||||
* @nr_scratch: number of scratch buffers
|
||||
- * @num_event_buffers: calculated number of event buffers
|
||||
* @u1u2: only used on revisions <1.83a for workaround
|
||||
* @maximum_speed: maximum speed requested (mainly for testing purposes)
|
||||
* @revision: revision register contents
|
||||
@@ -773,7 +772,6 @@ struct dwc3 {
|
||||
u32 gctl;
|
||||
|
||||
u32 nr_scratch;
|
||||
- u32 num_event_buffers;
|
||||
u32 u1u2;
|
||||
u32 maximum_speed;
|
||||
|
||||
--- a/drivers/usb/dwc3/gadget.c
|
||||
+++ b/drivers/usb/dwc3/gadget.c
|
||||
@@ -2556,14 +2556,14 @@ static void dwc3_process_event_entry(str
|
||||
}
|
||||
}
|
||||
|
||||
-static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf)
|
||||
+static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc)
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
int left;
|
||||
u32 reg;
|
||||
|
||||
- evt = dwc->ev_buffs[buf];
|
||||
+ evt = dwc->ev_buffs[0];
|
||||
left = evt->count;
|
||||
|
||||
if (!(evt->flags & DWC3_EVENT_PENDING))
|
||||
@@ -2588,7 +2588,7 @@ static irqreturn_t dwc3_process_event_bu
|
||||
evt->lpos = (evt->lpos + 4) % DWC3_EVENT_BUFFERS_SIZE;
|
||||
left -= 4;
|
||||
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), 4);
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 4);
|
||||
}
|
||||
|
||||
evt->count = 0;
|
||||
@@ -2596,9 +2596,9 @@ static irqreturn_t dwc3_process_event_bu
|
||||
ret = IRQ_HANDLED;
|
||||
|
||||
/* Unmask interrupt */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(buf));
|
||||
+ reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(0));
|
||||
reg &= ~DWC3_GEVNTSIZ_INTMASK;
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(buf), reg);
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -2608,27 +2608,23 @@ static irqreturn_t dwc3_thread_interrupt
|
||||
struct dwc3 *dwc = _dwc;
|
||||
unsigned long flags;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
- int i;
|
||||
|
||||
spin_lock_irqsave(&dwc->lock, flags);
|
||||
-
|
||||
- for (i = 0; i < dwc->num_event_buffers; i++)
|
||||
- ret |= dwc3_process_event_buf(dwc, i);
|
||||
-
|
||||
+ ret = dwc3_process_event_buf(dwc);
|
||||
spin_unlock_irqrestore(&dwc->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
-static irqreturn_t dwc3_check_event_buf(struct dwc3 *dwc, u32 buf)
|
||||
+static irqreturn_t dwc3_check_event_buf(struct dwc3 *dwc)
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
u32 count;
|
||||
u32 reg;
|
||||
|
||||
- evt = dwc->ev_buffs[buf];
|
||||
+ evt = dwc->ev_buffs[0];
|
||||
|
||||
- count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(buf));
|
||||
+ count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0));
|
||||
count &= DWC3_GEVNTCOUNT_MASK;
|
||||
if (!count)
|
||||
return IRQ_NONE;
|
||||
@@ -2637,9 +2633,9 @@ static irqreturn_t dwc3_check_event_buf(
|
||||
evt->flags |= DWC3_EVENT_PENDING;
|
||||
|
||||
/* Mask interrupt */
|
||||
- reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(buf));
|
||||
+ reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(0));
|
||||
reg |= DWC3_GEVNTSIZ_INTMASK;
|
||||
- dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(buf), reg);
|
||||
+ dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), reg);
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
@@ -2647,18 +2643,8 @@ static irqreturn_t dwc3_check_event_buf(
|
||||
static irqreturn_t dwc3_interrupt(int irq, void *_dwc)
|
||||
{
|
||||
struct dwc3 *dwc = _dwc;
|
||||
- int i;
|
||||
- irqreturn_t ret = IRQ_NONE;
|
||||
-
|
||||
- for (i = 0; i < dwc->num_event_buffers; i++) {
|
||||
- irqreturn_t status;
|
||||
|
||||
- status = dwc3_check_event_buf(dwc, i);
|
||||
- if (status == IRQ_WAKE_THREAD)
|
||||
- ret = status;
|
||||
- }
|
||||
-
|
||||
- return ret;
|
||||
+ return dwc3_check_event_buf(dwc);
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,96 +0,0 @@
|
|||
From 696c8b1282205caa5206264449f80ef756f14ef7 Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
Date: Wed, 30 Mar 2016 09:37:03 +0300
|
||||
Subject: usb: dwc3: drop ev_buffs array
|
||||
|
||||
we will be using a single event buffer and that
|
||||
renders ev_buffs array unnecessary. Let's remove it
|
||||
in favor of a single pointer to a single event
|
||||
buffer.
|
||||
|
||||
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 13 ++++---------
|
||||
drivers/usb/dwc3/core.h | 2 +-
|
||||
drivers/usb/dwc3/gadget.c | 4 ++--
|
||||
3 files changed, 7 insertions(+), 12 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -204,7 +204,7 @@ static void dwc3_free_event_buffers(stru
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
|
||||
- evt = dwc->ev_buffs[0];
|
||||
+ evt = dwc->ev_buf;
|
||||
if (evt)
|
||||
dwc3_free_one_event_buffer(dwc, evt);
|
||||
}
|
||||
@@ -221,17 +221,12 @@ static int dwc3_alloc_event_buffers(stru
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
|
||||
- dwc->ev_buffs = devm_kzalloc(dwc->dev, sizeof(*dwc->ev_buffs),
|
||||
- GFP_KERNEL);
|
||||
- if (!dwc->ev_buffs)
|
||||
- return -ENOMEM;
|
||||
-
|
||||
evt = dwc3_alloc_one_event_buffer(dwc, length);
|
||||
if (IS_ERR(evt)) {
|
||||
dev_err(dwc->dev, "can't allocate event buffer\n");
|
||||
return PTR_ERR(evt);
|
||||
}
|
||||
- dwc->ev_buffs[0] = evt;
|
||||
+ dwc->ev_buf = evt;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -246,7 +241,7 @@ static int dwc3_event_buffers_setup(stru
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
|
||||
- evt = dwc->ev_buffs[0];
|
||||
+ evt = dwc->ev_buf;
|
||||
dwc3_trace(trace_dwc3_core,
|
||||
"Event buf %p dma %08llx length %d\n",
|
||||
evt->buf, (unsigned long long) evt->dma,
|
||||
@@ -269,7 +264,7 @@ static void dwc3_event_buffers_cleanup(s
|
||||
{
|
||||
struct dwc3_event_buffer *evt;
|
||||
|
||||
- evt = dwc->ev_buffs[0];
|
||||
+ evt = dwc->ev_buf;
|
||||
|
||||
evt->lpos = 0;
|
||||
|
||||
--- a/drivers/usb/dwc3/core.h
|
||||
+++ b/drivers/usb/dwc3/core.h
|
||||
@@ -748,7 +748,7 @@ struct dwc3 {
|
||||
struct platform_device *xhci;
|
||||
struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM];
|
||||
|
||||
- struct dwc3_event_buffer **ev_buffs;
|
||||
+ struct dwc3_event_buffer *ev_buf;
|
||||
struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM];
|
||||
|
||||
struct usb_gadget gadget;
|
||||
--- a/drivers/usb/dwc3/gadget.c
|
||||
+++ b/drivers/usb/dwc3/gadget.c
|
||||
@@ -2563,7 +2563,7 @@ static irqreturn_t dwc3_process_event_bu
|
||||
int left;
|
||||
u32 reg;
|
||||
|
||||
- evt = dwc->ev_buffs[0];
|
||||
+ evt = dwc->ev_buf;
|
||||
left = evt->count;
|
||||
|
||||
if (!(evt->flags & DWC3_EVENT_PENDING))
|
||||
@@ -2622,7 +2622,7 @@ static irqreturn_t dwc3_check_event_buf(
|
||||
u32 count;
|
||||
u32 reg;
|
||||
|
||||
- evt = dwc->ev_buffs[0];
|
||||
+ evt = dwc->ev_buf;
|
||||
|
||||
count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0));
|
||||
count &= DWC3_GEVNTCOUNT_MASK;
|
|
@ -1,67 +0,0 @@
|
|||
From 5c4ad318de3b8e8680d654c82a254c4b65243739 Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <balbi@kernel.org>
|
||||
Date: Mon, 11 Apr 2016 17:12:34 +0300
|
||||
Subject: usb: dwc3: core: fix PHY handling during suspend
|
||||
|
||||
we need to power off the PHY during suspend and
|
||||
power it back on during resume.
|
||||
|
||||
Signed-off-by: Felipe Balbi <balbi@kernel.org>
|
||||
[nsekhar@ti.com: fix call to usb_phy_set_suspend() in dwc3_suspend()]
|
||||
Signed-off-by: Sekhar Nori <nsekhar@ti.com>
|
||||
Signed-off-by: Roger Quadros <rogerq@ti.com>
|
||||
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
---
|
||||
drivers/usb/dwc3/core.c | 23 ++++++++++++++++++++++-
|
||||
1 file changed, 22 insertions(+), 1 deletion(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/core.c
|
||||
+++ b/drivers/usb/dwc3/core.c
|
||||
@@ -1124,6 +1124,11 @@ static int dwc3_suspend(struct device *d
|
||||
phy_exit(dwc->usb2_generic_phy);
|
||||
phy_exit(dwc->usb3_generic_phy);
|
||||
|
||||
+ usb_phy_set_suspend(dwc->usb2_phy, 1);
|
||||
+ usb_phy_set_suspend(dwc->usb3_phy, 1);
|
||||
+ WARN_ON(phy_power_off(dwc->usb2_generic_phy) < 0);
|
||||
+ WARN_ON(phy_power_off(dwc->usb3_generic_phy) < 0);
|
||||
+
|
||||
pinctrl_pm_select_sleep_state(dev);
|
||||
|
||||
return 0;
|
||||
@@ -1137,11 +1142,21 @@ static int dwc3_resume(struct device *de
|
||||
|
||||
pinctrl_pm_select_default_state(dev);
|
||||
|
||||
+ usb_phy_set_suspend(dwc->usb2_phy, 0);
|
||||
+ usb_phy_set_suspend(dwc->usb3_phy, 0);
|
||||
+ ret = phy_power_on(dwc->usb2_generic_phy);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = phy_power_on(dwc->usb3_generic_phy);
|
||||
+ if (ret < 0)
|
||||
+ goto err_usb2phy_power;
|
||||
+
|
||||
usb_phy_init(dwc->usb3_phy);
|
||||
usb_phy_init(dwc->usb2_phy);
|
||||
ret = phy_init(dwc->usb2_generic_phy);
|
||||
if (ret < 0)
|
||||
- return ret;
|
||||
+ goto err_usb3phy_power;
|
||||
|
||||
ret = phy_init(dwc->usb3_generic_phy);
|
||||
if (ret < 0)
|
||||
@@ -1174,6 +1189,12 @@ static int dwc3_resume(struct device *de
|
||||
err_usb2phy_init:
|
||||
phy_exit(dwc->usb2_generic_phy);
|
||||
|
||||
+err_usb3phy_power:
|
||||
+ phy_power_off(dwc->usb3_generic_phy);
|
||||
+
|
||||
+err_usb2phy_power:
|
||||
+ phy_power_off(dwc->usb2_generic_phy);
|
||||
+
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1,234 +0,0 @@
|
|||
From 41c2b5280cd2fa3e198c422cdf223ba6e48f857a Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <balbi@ti.com>
|
||||
Date: Wed, 18 Nov 2015 13:15:20 -0600
|
||||
Subject: [PATCH] usb: dwc3: add generic OF glue layer
|
||||
|
||||
For simple platforms which merely enable some clocks
|
||||
and populate its children, we can use this generic
|
||||
glue layer to avoid boilerplate code duplication.
|
||||
|
||||
For now this supports Qcom and Xilinx, but if we
|
||||
find a way to add generic handling of regulators and
|
||||
optional PHYs, we can absorb exynos as well.
|
||||
|
||||
Tested-by: Subbaraya Sundeep Bhatta <subbaraya.sundeep.bhatta@xilinx.com>
|
||||
Signed-off-by: Felipe Balbi <balbi@ti.com>
|
||||
(cherry picked from commit 16adc674d0d68a50dfc725574738d7ae11cf5d7e)
|
||||
|
||||
Change-Id: I6fd260442997b198dc12ca726814b7a9518e6353
|
||||
Signed-off-by: Nitheesh Sekar <nsekar@codeaurora.org>
|
||||
---
|
||||
drivers/usb/dwc3/Kconfig | 9 ++
|
||||
drivers/usb/dwc3/Makefile | 1 +
|
||||
drivers/usb/dwc3/dwc3-of-simple.c | 178 ++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 188 insertions(+)
|
||||
create mode 100644 drivers/usb/dwc3/dwc3-of-simple.c
|
||||
|
||||
--- a/drivers/usb/dwc3/Kconfig
|
||||
+++ b/drivers/usb/dwc3/Kconfig
|
||||
@@ -87,6 +87,15 @@ config USB_DWC3_KEYSTONE
|
||||
Support of USB2/3 functionality in TI Keystone2 platforms.
|
||||
Say 'Y' or 'M' here if you have one such device
|
||||
|
||||
+config USB_DWC3_OF_SIMPLE
|
||||
+ tristate "Generic OF Simple Glue Layer"
|
||||
+ depends on OF && COMMON_CLK
|
||||
+ default USB_DWC3
|
||||
+ help
|
||||
+ Support USB2/3 functionality in simple SoC integrations.
|
||||
+ Currently supports Xilinx and Qualcomm DWC USB3 IP.
|
||||
+ Say 'Y' or 'M' if you have one such device.
|
||||
+
|
||||
config USB_DWC3_ST
|
||||
tristate "STMicroelectronics Platforms"
|
||||
depends on ARCH_STI && OF
|
||||
--- a/drivers/usb/dwc3/Makefile
|
||||
+++ b/drivers/usb/dwc3/Makefile
|
||||
@@ -37,5 +37,6 @@ obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-oma
|
||||
obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o
|
||||
obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
|
||||
obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o
|
||||
+obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o
|
||||
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
|
||||
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
@@ -0,0 +1,178 @@
|
||||
+/**
|
||||
+ * dwc3-of-simple.c - OF glue layer for simple integrations
|
||||
+ *
|
||||
+ * Copyright (c) 2015 Texas Instruments Incorporated - http://www.ti.com
|
||||
+ *
|
||||
+ * Author: Felipe Balbi <balbi@ti.com>
|
||||
+ *
|
||||
+ * This program is free software: you can redistribute it and/or modify
|
||||
+ * it under the terms of the GNU General Public License version 2 of
|
||||
+ * the License as published by the Free Software Foundation.
|
||||
+ *
|
||||
+ * This program is distributed in the hope that it will be useful,
|
||||
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
+ * GNU General Public License for more details.
|
||||
+ *
|
||||
+ * This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov
|
||||
+ * <iivanov@mm-sol.com> and the original patch adding support for Xilinx' SoC
|
||||
+ * by Subbaraya Sundeep Bhatta <subbaraya.sundeep.bhatta@xilinx.com>
|
||||
+ */
|
||||
+
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/dma-mapping.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_platform.h>
|
||||
+#include <linux/pm_runtime.h>
|
||||
+
|
||||
+struct dwc3_of_simple {
|
||||
+ struct device *dev;
|
||||
+ struct clk **clks;
|
||||
+ int num_clocks;
|
||||
+};
|
||||
+
|
||||
+static int dwc3_of_simple_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct dwc3_of_simple *simple;
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ struct device_node *np = dev->of_node;
|
||||
+
|
||||
+ int ret;
|
||||
+ int i;
|
||||
+
|
||||
+ simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL);
|
||||
+ if (!simple)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ ret = of_clk_get_parent_count(np);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ simple->num_clocks = ret;
|
||||
+
|
||||
+ simple->clks = devm_kcalloc(dev, simple->num_clocks,
|
||||
+ sizeof(struct clk *), GFP_KERNEL);
|
||||
+ if (!simple->clks)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ simple->dev = dev;
|
||||
+
|
||||
+ for (i = 0; i < simple->num_clocks; i++) {
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ clk = of_clk_get(np, i);
|
||||
+ if (IS_ERR(clk)) {
|
||||
+ while (--i >= 0)
|
||||
+ clk_put(simple->clks[i]);
|
||||
+ return PTR_ERR(clk);
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(clk);
|
||||
+ if (ret < 0) {
|
||||
+ while (--i >= 0) {
|
||||
+ clk_disable_unprepare(simple->clks[i]);
|
||||
+ clk_put(simple->clks[i]);
|
||||
+ }
|
||||
+ clk_put(clk);
|
||||
+
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ simple->clks[i] = clk;
|
||||
+ }
|
||||
+
|
||||
+ ret = of_platform_populate(np, NULL, NULL, dev);
|
||||
+ if (ret) {
|
||||
+ for (i = 0; i < simple->num_clocks; i++) {
|
||||
+ clk_disable_unprepare(simple->clks[i]);
|
||||
+ clk_put(simple->clks[i]);
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ pm_runtime_set_active(dev);
|
||||
+ pm_runtime_enable(dev);
|
||||
+ pm_runtime_get_sync(dev);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int dwc3_of_simple_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct dwc3_of_simple *simple = platform_get_drvdata(pdev);
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < simple->num_clocks; i++) {
|
||||
+ clk_unprepare(simple->clks[i]);
|
||||
+ clk_put(simple->clks[i]);
|
||||
+ }
|
||||
+
|
||||
+ of_platform_depopulate(dev);
|
||||
+
|
||||
+ pm_runtime_put_sync(dev);
|
||||
+ pm_runtime_disable(dev);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int dwc3_of_simple_runtime_suspend(struct device *dev)
|
||||
+{
|
||||
+ struct dwc3_of_simple *simple = dev_get_drvdata(dev);
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < simple->num_clocks; i++)
|
||||
+ clk_disable(simple->clks[i]);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int dwc3_of_simple_runtime_resume(struct device *dev)
|
||||
+{
|
||||
+ struct dwc3_of_simple *simple = dev_get_drvdata(dev);
|
||||
+ int ret;
|
||||
+ int i;
|
||||
+
|
||||
+ for (i = 0; i < simple->num_clocks; i++) {
|
||||
+ ret = clk_enable(simple->clks[i]);
|
||||
+ if (ret < 0) {
|
||||
+ while (--i >= 0)
|
||||
+ clk_disable(simple->clks[i]);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
|
||||
+ SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend,
|
||||
+ dwc3_of_simple_runtime_resume, NULL)
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id of_dwc3_simple_match[] = {
|
||||
+ { .compatible = "qcom,dwc3" },
|
||||
+ { .compatible = "xlnx,zynqmp-dwc3" },
|
||||
+ { /* Sentinel */ }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
|
||||
+
|
||||
+static struct platform_driver dwc3_of_simple_driver = {
|
||||
+ .probe = dwc3_of_simple_probe,
|
||||
+ .remove = dwc3_of_simple_remove,
|
||||
+ .driver = {
|
||||
+ .name = "dwc3-of-simple",
|
||||
+ .of_match_table = of_dwc3_simple_match,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(dwc3_of_simple_driver);
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer");
|
||||
+MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
|
|
@ -1,36 +0,0 @@
|
|||
From 131386d63ca3177d471aa93808c69b85fdac520d Mon Sep 17 00:00:00 2001
|
||||
From: Felipe Balbi <balbi@ti.com>
|
||||
Date: Tue, 22 Dec 2015 21:56:10 -0600
|
||||
Subject: [PATCH] usb: dwc3: of-simple: fix build warning on !PM
|
||||
|
||||
if we have a !PM kernel build, our runtime
|
||||
suspend/resume callbacks will be left defined but
|
||||
unused. Add a ifdef CONFIG_PM guard.
|
||||
|
||||
Signed-off-by: Felipe Balbi <balbi@ti.com>
|
||||
(cherry picked from commit 5072cfc40a80cea3749fd3413b3896630d8c787e)
|
||||
|
||||
Change-Id: I088186c33aa917ec8da2985372ceefc289b24242
|
||||
Signed-off-by: Nitheesh Sekar <nsekar@codeaurora.org>
|
||||
---
|
||||
drivers/usb/dwc3/dwc3-of-simple.c | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
--- a/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
@@ -122,6 +122,7 @@ static int dwc3_of_simple_remove(struct
|
||||
return 0;
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_PM
|
||||
static int dwc3_of_simple_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
|
||||
@@ -150,6 +151,7 @@ static int dwc3_of_simple_runtime_resume
|
||||
|
||||
return 0;
|
||||
}
|
||||
+#endif
|
||||
|
||||
static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend,
|
|
@ -1,47 +0,0 @@
|
|||
From 07c8b15688055d81ac8e1c8c964b9e4c302287f1 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 22 Feb 2016 11:12:47 -0800
|
||||
Subject: [PATCH] usb: dwc3: Remove impossible check for
|
||||
of_clk_get_parent_count() < 0
|
||||
|
||||
The check for < 0 is impossible now that
|
||||
of_clk_get_parent_count() returns an unsigned int. Simplify the
|
||||
code and update the types.
|
||||
|
||||
Acked-by: Felipe Balbi <balbi@kernel.org>
|
||||
Cc: <linux-usb@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
(cherry picked from commit 3d755dcc20dd452b52532eca17da40ebbd12aee9)
|
||||
|
||||
Change-Id: Iaa38e064d801fb36c855fea51c0443840368e0d3
|
||||
Signed-off-by: Nitheesh Sekar <nsekar@codeaurora.org>
|
||||
---
|
||||
drivers/usb/dwc3/dwc3-of-simple.c | 9 +++++----
|
||||
1 file changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
--- a/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
@@ -42,6 +42,7 @@ static int dwc3_of_simple_probe(struct p
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
|
||||
+ unsigned int count;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
@@ -49,11 +50,11 @@ static int dwc3_of_simple_probe(struct p
|
||||
if (!simple)
|
||||
return -ENOMEM;
|
||||
|
||||
- ret = of_clk_get_parent_count(np);
|
||||
- if (ret < 0)
|
||||
- return ret;
|
||||
+ count = of_clk_get_parent_count(np);
|
||||
+ if (!count)
|
||||
+ return -ENOENT;
|
||||
|
||||
- simple->num_clocks = ret;
|
||||
+ simple->num_clocks = count;
|
||||
|
||||
simple->clks = devm_kcalloc(dev, simple->num_clocks,
|
||||
sizeof(struct clk *), GFP_KERNEL);
|
|
@ -1,27 +0,0 @@
|
|||
From 4c4f106c032ff32b89c98a4d819e68e6e643c14e Mon Sep 17 00:00:00 2001
|
||||
From: Wei Yongjun <weiyj.lk@gmail.com>
|
||||
Date: Tue, 26 Jul 2016 14:47:00 +0000
|
||||
Subject: usb: dwc3: fix missing platform_set_drvdata() in
|
||||
dwc3_of_simple_probe()
|
||||
|
||||
Add missing platform_set_drvdata() in dwc3_of_simple_probe(), otherwise
|
||||
calling platform_get_drvdata() in remove returns NULL.
|
||||
|
||||
This is detected by Coccinelle semantic patch.
|
||||
|
||||
Signed-off-by: Wei Yongjun <weiyj.lk@gmail.com>
|
||||
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
|
||||
---
|
||||
drivers/usb/dwc3/dwc3-of-simple.c | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
--- a/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
|
||||
@@ -61,6 +61,7 @@ static int dwc3_of_simple_probe(struct p
|
||||
if (!simple->clks)
|
||||
return -ENOMEM;
|
||||
|
||||
+ platform_set_drvdata(pdev, simple);
|
||||
simple->dev = dev;
|
||||
|
||||
for (i = 0; i < simple->num_clocks; i++) {
|
|
@ -1,512 +0,0 @@
|
|||
--- a/drivers/phy/Kconfig
|
||||
+++ b/drivers/phy/Kconfig
|
||||
@@ -390,4 +390,15 @@ config PHY_CYGNUS_PCIE
|
||||
Enable this to support the Broadcom Cygnus PCIe PHY.
|
||||
If unsure, say N.
|
||||
|
||||
+config PHY_QCOM_DWC3
|
||||
+ tristate "QCOM DWC3 USB PHY support"
|
||||
+ depends on ARCH_QCOM
|
||||
+ depends on HAS_IOMEM
|
||||
+ depends on OF
|
||||
+ select GENERIC_PHY
|
||||
+ help
|
||||
+ This option enables support for the Synopsis PHYs present inside the
|
||||
+ Qualcomm USB3.0 DWC3 controller. This driver supports both HS and SS
|
||||
+ PHY controllers.
|
||||
+
|
||||
endmenu
|
||||
--- a/drivers/phy/Makefile
|
||||
+++ b/drivers/phy/Makefile
|
||||
@@ -48,3 +48,4 @@ obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1
|
||||
obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o
|
||||
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
|
||||
obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o
|
||||
+obj-$(CONFIG_PHY_QCOM_DWC3) += phy-qcom-dwc3.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/phy/phy-qcom-dwc3.c
|
||||
@@ -0,0 +1,484 @@
|
||||
+/* Copyright (c) 2014-2015, Code Aurora Forum. 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/clk.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/phy/phy.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/delay.h>
|
||||
+
|
||||
+/**
|
||||
+ * USB QSCRATCH Hardware registers
|
||||
+ */
|
||||
+#define QSCRATCH_GENERAL_CFG (0x08)
|
||||
+#define HSUSB_PHY_CTRL_REG (0x10)
|
||||
+
|
||||
+/* PHY_CTRL_REG */
|
||||
+#define HSUSB_CTRL_DMSEHV_CLAMP BIT(24)
|
||||
+#define HSUSB_CTRL_USB2_SUSPEND BIT(23)
|
||||
+#define HSUSB_CTRL_UTMI_CLK_EN BIT(21)
|
||||
+#define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20)
|
||||
+#define HSUSB_CTRL_USE_CLKCORE BIT(18)
|
||||
+#define HSUSB_CTRL_DPSEHV_CLAMP BIT(17)
|
||||
+#define HSUSB_CTRL_COMMONONN BIT(11)
|
||||
+#define HSUSB_CTRL_ID_HV_CLAMP BIT(9)
|
||||
+#define HSUSB_CTRL_OTGSESSVLD_CLAMP BIT(8)
|
||||
+#define HSUSB_CTRL_CLAMP_EN BIT(7)
|
||||
+#define HSUSB_CTRL_RETENABLEN BIT(1)
|
||||
+#define HSUSB_CTRL_POR BIT(0)
|
||||
+
|
||||
+/* QSCRATCH_GENERAL_CFG */
|
||||
+#define HSUSB_GCFG_XHCI_REV BIT(2)
|
||||
+
|
||||
+/**
|
||||
+ * USB QSCRATCH Hardware registers
|
||||
+ */
|
||||
+#define SSUSB_PHY_CTRL_REG (0x00)
|
||||
+#define SSUSB_PHY_PARAM_CTRL_1 (0x04)
|
||||
+#define SSUSB_PHY_PARAM_CTRL_2 (0x08)
|
||||
+#define CR_PROTOCOL_DATA_IN_REG (0x0c)
|
||||
+#define CR_PROTOCOL_DATA_OUT_REG (0x10)
|
||||
+#define CR_PROTOCOL_CAP_ADDR_REG (0x14)
|
||||
+#define CR_PROTOCOL_CAP_DATA_REG (0x18)
|
||||
+#define CR_PROTOCOL_READ_REG (0x1c)
|
||||
+#define CR_PROTOCOL_WRITE_REG (0x20)
|
||||
+
|
||||
+/* PHY_CTRL_REG */
|
||||
+#define SSUSB_CTRL_REF_USE_PAD BIT(28)
|
||||
+#define SSUSB_CTRL_TEST_POWERDOWN BIT(27)
|
||||
+#define SSUSB_CTRL_LANE0_PWR_PRESENT BIT(24)
|
||||
+#define SSUSB_CTRL_SS_PHY_EN BIT(8)
|
||||
+#define SSUSB_CTRL_SS_PHY_RESET BIT(7)
|
||||
+
|
||||
+/* SSPHY control registers */
|
||||
+#define SSPHY_CTRL_RX_OVRD_IN_HI(lane) (0x1006 + 0x100 * lane)
|
||||
+#define SSPHY_CTRL_TX_OVRD_DRV_LO(lane) (0x1002 + 0x100 * lane)
|
||||
+
|
||||
+/* RX OVRD IN HI bits */
|
||||
+#define RX_OVRD_IN_HI_RX_RESET_OVRD BIT(13)
|
||||
+#define RX_OVRD_IN_HI_RX_RX_RESET BIT(12)
|
||||
+#define RX_OVRD_IN_HI_RX_EQ_OVRD BIT(11)
|
||||
+#define RX_OVRD_IN_HI_RX_EQ_MASK 0x0700
|
||||
+#define RX_OVRD_IN_HI_RX_EQ_SHIFT 8
|
||||
+#define RX_OVRD_IN_HI_RX_EQ_EN_OVRD BIT(7)
|
||||
+#define RX_OVRD_IN_HI_RX_EQ_EN BIT(6)
|
||||
+#define RX_OVRD_IN_HI_RX_LOS_FILTER_OVRD BIT(5)
|
||||
+#define RX_OVRD_IN_HI_RX_LOS_FILTER_MASK 0x0018
|
||||
+#define RX_OVRD_IN_HI_RX_RATE_OVRD BIT(2)
|
||||
+#define RX_OVRD_IN_HI_RX_RATE_MASK 0x0003
|
||||
+
|
||||
+/* TX OVRD DRV LO register bits */
|
||||
+#define TX_OVRD_DRV_LO_AMPLITUDE_MASK 0x007F
|
||||
+#define TX_OVRD_DRV_LO_PREEMPH_MASK 0x3F80
|
||||
+#define TX_OVRD_DRV_LO_PREEMPH_SHIFT 7
|
||||
+#define TX_OVRD_DRV_LO_EN BIT(14)
|
||||
+
|
||||
+/* SS CAP register bits */
|
||||
+#define SS_CR_CAP_ADDR_REG BIT(0)
|
||||
+#define SS_CR_CAP_DATA_REG BIT(0)
|
||||
+#define SS_CR_READ_REG BIT(0)
|
||||
+#define SS_CR_WRITE_REG BIT(0)
|
||||
+
|
||||
+struct qcom_dwc3_usb_phy {
|
||||
+ void __iomem *base;
|
||||
+ struct device *dev;
|
||||
+ struct clk *xo_clk;
|
||||
+ struct clk *ref_clk;
|
||||
+};
|
||||
+
|
||||
+struct qcom_dwc3_phy_drvdata {
|
||||
+ struct phy_ops ops;
|
||||
+ u32 clk_rate;
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * Write register and read back masked value to confirm it is written
|
||||
+ *
|
||||
+ * @base - QCOM DWC3 PHY base virtual address.
|
||||
+ * @offset - register offset.
|
||||
+ * @mask - register bitmask specifying what should be updated
|
||||
+ * @val - value to write.
|
||||
+ */
|
||||
+static inline void qcom_dwc3_phy_write_readback(
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3, u32 offset,
|
||||
+ const u32 mask, u32 val)
|
||||
+{
|
||||
+ u32 write_val, tmp = readl(phy_dwc3->base + offset);
|
||||
+
|
||||
+ tmp &= ~mask; /* retain other bits */
|
||||
+ write_val = tmp | val;
|
||||
+
|
||||
+ writel(write_val, phy_dwc3->base + offset);
|
||||
+
|
||||
+ /* Read back to see if val was written */
|
||||
+ tmp = readl(phy_dwc3->base + offset);
|
||||
+ tmp &= mask; /* clear other bits */
|
||||
+
|
||||
+ if (tmp != val)
|
||||
+ dev_err(phy_dwc3->dev, "write: %x to QSCRATCH: %x FAILED\n",
|
||||
+ val, offset);
|
||||
+}
|
||||
+
|
||||
+static int wait_for_latch(void __iomem *addr)
|
||||
+{
|
||||
+ u32 retry = 10;
|
||||
+
|
||||
+ while (true) {
|
||||
+ if (!readl(addr))
|
||||
+ break;
|
||||
+
|
||||
+ if (--retry == 0)
|
||||
+ return -ETIMEDOUT;
|
||||
+
|
||||
+ usleep_range(10, 20);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Write SSPHY register
|
||||
+ *
|
||||
+ * @base - QCOM DWC3 PHY base virtual address.
|
||||
+ * @addr - SSPHY address to write.
|
||||
+ * @val - value to write.
|
||||
+ */
|
||||
+static int qcom_dwc3_ss_write_phycreg(struct qcom_dwc3_usb_phy *phy_dwc3,
|
||||
+ u32 addr, u32 val)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ writel(addr, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
|
||||
+ writel(SS_CR_CAP_ADDR_REG, phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
|
||||
+
|
||||
+ ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG);
|
||||
+ if (ret)
|
||||
+ goto err_wait;
|
||||
+
|
||||
+ writel(val, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG);
|
||||
+ writel(SS_CR_CAP_DATA_REG, phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG);
|
||||
+
|
||||
+ ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG);
|
||||
+ if (ret)
|
||||
+ goto err_wait;
|
||||
+
|
||||
+ writel(SS_CR_WRITE_REG, phy_dwc3->base + CR_PROTOCOL_WRITE_REG);
|
||||
+
|
||||
+ ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_WRITE_REG);
|
||||
+
|
||||
+err_wait:
|
||||
+ if (ret)
|
||||
+ dev_err(phy_dwc3->dev, "timeout waiting for latch\n");
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Read SSPHY register.
|
||||
+ *
|
||||
+ * @base - QCOM DWC3 PHY base virtual address.
|
||||
+ * @addr - SSPHY address to read.
|
||||
+ */
|
||||
+static int qcom_dwc3_ss_read_phycreg(void __iomem *base, u32 addr, u32 *val)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ writel(addr, base + CR_PROTOCOL_DATA_IN_REG);
|
||||
+ writel(SS_CR_CAP_ADDR_REG, base + CR_PROTOCOL_CAP_ADDR_REG);
|
||||
+
|
||||
+ ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG);
|
||||
+ if (ret)
|
||||
+ goto err_wait;
|
||||
+
|
||||
+ /*
|
||||
+ * Due to hardware bug, first read of SSPHY register might be
|
||||
+ * incorrect. Hence as workaround, SW should perform SSPHY register
|
||||
+ * read twice, but use only second read and ignore first read.
|
||||
+ */
|
||||
+ writel(SS_CR_READ_REG, base + CR_PROTOCOL_READ_REG);
|
||||
+
|
||||
+ ret = wait_for_latch(base + CR_PROTOCOL_READ_REG);
|
||||
+ if (ret)
|
||||
+ goto err_wait;
|
||||
+
|
||||
+ /* throwaway read */
|
||||
+ readl(base + CR_PROTOCOL_DATA_OUT_REG);
|
||||
+
|
||||
+ writel(SS_CR_READ_REG, base + CR_PROTOCOL_READ_REG);
|
||||
+
|
||||
+ ret = wait_for_latch(base + CR_PROTOCOL_READ_REG);
|
||||
+ if (ret)
|
||||
+ goto err_wait;
|
||||
+
|
||||
+ *val = readl(base + CR_PROTOCOL_DATA_OUT_REG);
|
||||
+
|
||||
+err_wait:
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int qcom_dwc3_phy_power_on(struct phy *phy)
|
||||
+{
|
||||
+ int ret;
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy);
|
||||
+
|
||||
+ ret = clk_prepare_enable(phy_dwc3->xo_clk);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ ret = clk_prepare_enable(phy_dwc3->ref_clk);
|
||||
+ if (ret)
|
||||
+ clk_disable_unprepare(phy_dwc3->xo_clk);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int qcom_dwc3_phy_power_off(struct phy *phy)
|
||||
+{
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy);
|
||||
+
|
||||
+ clk_disable_unprepare(phy_dwc3->ref_clk);
|
||||
+ clk_disable_unprepare(phy_dwc3->xo_clk);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_dwc3_hs_phy_init(struct phy *phy)
|
||||
+{
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy);
|
||||
+ u32 val;
|
||||
+
|
||||
+ /*
|
||||
+ * HSPHY Initialization: Enable UTMI clock, select 19.2MHz fsel
|
||||
+ * enable clamping, and disable RETENTION (power-on default is ENABLED)
|
||||
+ */
|
||||
+ val = HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_DMSEHV_CLAMP |
|
||||
+ HSUSB_CTRL_RETENABLEN | HSUSB_CTRL_COMMONONN |
|
||||
+ HSUSB_CTRL_OTGSESSVLD_CLAMP | HSUSB_CTRL_ID_HV_CLAMP |
|
||||
+ HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_UTMI_OTG_VBUS_VALID |
|
||||
+ HSUSB_CTRL_UTMI_CLK_EN | HSUSB_CTRL_CLAMP_EN | 0x70;
|
||||
+
|
||||
+ /* use core clock if external reference is not present */
|
||||
+ if (!phy_dwc3->xo_clk)
|
||||
+ val |= HSUSB_CTRL_USE_CLKCORE;
|
||||
+
|
||||
+ writel(val, phy_dwc3->base + HSUSB_PHY_CTRL_REG);
|
||||
+ usleep_range(2000, 2200);
|
||||
+
|
||||
+ /* Disable (bypass) VBUS and ID filters */
|
||||
+ writel(HSUSB_GCFG_XHCI_REV, phy_dwc3->base + QSCRATCH_GENERAL_CFG);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_dwc3_ss_phy_init(struct phy *phy)
|
||||
+{
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy);
|
||||
+ int ret;
|
||||
+ u32 data = 0;
|
||||
+
|
||||
+ /* reset phy */
|
||||
+ data = readl(phy_dwc3->base + SSUSB_PHY_CTRL_REG);
|
||||
+ writel(data | SSUSB_CTRL_SS_PHY_RESET,
|
||||
+ phy_dwc3->base + SSUSB_PHY_CTRL_REG);
|
||||
+ usleep_range(2000, 2200);
|
||||
+ writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
|
||||
+
|
||||
+ /* clear REF_PAD if we don't have XO clk */
|
||||
+ if (!phy_dwc3->xo_clk)
|
||||
+ data &= ~SSUSB_CTRL_REF_USE_PAD;
|
||||
+ else
|
||||
+ data |= SSUSB_CTRL_REF_USE_PAD;
|
||||
+
|
||||
+ writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
|
||||
+
|
||||
+ /* wait for ref clk to become stable, this can take up to 30ms */
|
||||
+ msleep(30);
|
||||
+
|
||||
+ data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT;
|
||||
+ writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG);
|
||||
+
|
||||
+ /*
|
||||
+ * Fix RX Equalization setting as follows
|
||||
+ * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0
|
||||
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1
|
||||
+ * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3
|
||||
+ * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1
|
||||
+ */
|
||||
+ ret = qcom_dwc3_ss_read_phycreg(phy_dwc3->base,
|
||||
+ SSPHY_CTRL_RX_OVRD_IN_HI(0), &data);
|
||||
+ if (ret)
|
||||
+ goto err_phy_trans;
|
||||
+
|
||||
+ data &= ~RX_OVRD_IN_HI_RX_EQ_EN;
|
||||
+ data |= RX_OVRD_IN_HI_RX_EQ_EN_OVRD;
|
||||
+ data &= ~RX_OVRD_IN_HI_RX_EQ_MASK;
|
||||
+ data |= 0x3 << RX_OVRD_IN_HI_RX_EQ_SHIFT;
|
||||
+ data |= RX_OVRD_IN_HI_RX_EQ_OVRD;
|
||||
+ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3,
|
||||
+ SSPHY_CTRL_RX_OVRD_IN_HI(0), data);
|
||||
+ if (ret)
|
||||
+ goto err_phy_trans;
|
||||
+
|
||||
+ /*
|
||||
+ * Set EQ and TX launch amplitudes as follows
|
||||
+ * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22
|
||||
+ * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127
|
||||
+ * LANE0.TX_OVRD_DRV_LO.EN set to 1.
|
||||
+ */
|
||||
+ ret = qcom_dwc3_ss_read_phycreg(phy_dwc3->base,
|
||||
+ SSPHY_CTRL_TX_OVRD_DRV_LO(0), &data);
|
||||
+ if (ret)
|
||||
+ goto err_phy_trans;
|
||||
+
|
||||
+ data &= ~TX_OVRD_DRV_LO_PREEMPH_MASK;
|
||||
+ data |= 0x16 << TX_OVRD_DRV_LO_PREEMPH_SHIFT;
|
||||
+ data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK;
|
||||
+ data |= 0x7f;
|
||||
+ data |= TX_OVRD_DRV_LO_EN;
|
||||
+ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3,
|
||||
+ SSPHY_CTRL_TX_OVRD_DRV_LO(0), data);
|
||||
+ if (ret)
|
||||
+ goto err_phy_trans;
|
||||
+
|
||||
+ /*
|
||||
+ * Set the QSCRATCH PHY_PARAM_CTRL1 parameters as follows
|
||||
+ * TX_FULL_SWING [26:20] amplitude to 127
|
||||
+ * TX_DEEMPH_3_5DB [13:8] to 22
|
||||
+ * LOS_BIAS [2:0] to 0x5
|
||||
+ */
|
||||
+ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_PARAM_CTRL_1,
|
||||
+ 0x07f03f07, 0x07f01605);
|
||||
+
|
||||
+err_phy_trans:
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int qcom_dwc3_ss_phy_exit(struct phy *phy)
|
||||
+{
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy);
|
||||
+
|
||||
+ /* Sequence to put SSPHY in low power state:
|
||||
+ * 1. Clear REF_PHY_EN in PHY_CTRL_REG
|
||||
+ * 2. Clear REF_USE_PAD in PHY_CTRL_REG
|
||||
+ * 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention
|
||||
+ */
|
||||
+ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
|
||||
+ SSUSB_CTRL_SS_PHY_EN, 0x0);
|
||||
+ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
|
||||
+ SSUSB_CTRL_REF_USE_PAD, 0x0);
|
||||
+ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG,
|
||||
+ 0x0, SSUSB_CTRL_TEST_POWERDOWN);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct qcom_dwc3_phy_drvdata qcom_dwc3_hs_drvdata = {
|
||||
+ .ops = {
|
||||
+ .init = qcom_dwc3_hs_phy_init,
|
||||
+ .power_on = qcom_dwc3_phy_power_on,
|
||||
+ .power_off = qcom_dwc3_phy_power_off,
|
||||
+ .owner = THIS_MODULE,
|
||||
+ },
|
||||
+ .clk_rate = 60000000,
|
||||
+};
|
||||
+
|
||||
+static const struct qcom_dwc3_phy_drvdata qcom_dwc3_ss_drvdata = {
|
||||
+ .ops = {
|
||||
+ .init = qcom_dwc3_ss_phy_init,
|
||||
+ .exit = qcom_dwc3_ss_phy_exit,
|
||||
+ .power_on = qcom_dwc3_phy_power_on,
|
||||
+ .power_off = qcom_dwc3_phy_power_off,
|
||||
+ .owner = THIS_MODULE,
|
||||
+ },
|
||||
+ .clk_rate = 125000000,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id qcom_dwc3_phy_table[] = {
|
||||
+ { .compatible = "qcom,dwc3-hs-usb-phy", .data = &qcom_dwc3_hs_drvdata },
|
||||
+ { .compatible = "qcom,dwc3-ss-usb-phy", .data = &qcom_dwc3_ss_drvdata },
|
||||
+ { /* Sentinel */ }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, qcom_dwc3_phy_table);
|
||||
+
|
||||
+static int qcom_dwc3_phy_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct qcom_dwc3_usb_phy *phy_dwc3;
|
||||
+ struct phy_provider *phy_provider;
|
||||
+ struct phy *generic_phy;
|
||||
+ struct resource *res;
|
||||
+ const struct of_device_id *match;
|
||||
+ const struct qcom_dwc3_phy_drvdata *data;
|
||||
+
|
||||
+ phy_dwc3 = devm_kzalloc(&pdev->dev, sizeof(*phy_dwc3), GFP_KERNEL);
|
||||
+ if (!phy_dwc3)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ match = of_match_node(qcom_dwc3_phy_table, pdev->dev.of_node);
|
||||
+ data = match->data;
|
||||
+
|
||||
+ phy_dwc3->dev = &pdev->dev;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ phy_dwc3->base = devm_ioremap_resource(phy_dwc3->dev, res);
|
||||
+ if (IS_ERR(phy_dwc3->base))
|
||||
+ return PTR_ERR(phy_dwc3->base);
|
||||
+
|
||||
+ phy_dwc3->ref_clk = devm_clk_get(phy_dwc3->dev, "ref");
|
||||
+ if (IS_ERR(phy_dwc3->ref_clk)) {
|
||||
+ dev_dbg(phy_dwc3->dev, "cannot get reference clock\n");
|
||||
+ return PTR_ERR(phy_dwc3->ref_clk);
|
||||
+ }
|
||||
+
|
||||
+ clk_set_rate(phy_dwc3->ref_clk, data->clk_rate);
|
||||
+
|
||||
+ phy_dwc3->xo_clk = devm_clk_get(phy_dwc3->dev, "xo");
|
||||
+ if (IS_ERR(phy_dwc3->xo_clk)) {
|
||||
+ dev_dbg(phy_dwc3->dev, "cannot get TCXO clock\n");
|
||||
+ phy_dwc3->xo_clk = NULL;
|
||||
+ }
|
||||
+
|
||||
+ generic_phy = devm_phy_create(phy_dwc3->dev, pdev->dev.of_node,
|
||||
+ &data->ops);
|
||||
+
|
||||
+ if (IS_ERR(generic_phy))
|
||||
+ return PTR_ERR(generic_phy);
|
||||
+
|
||||
+ phy_set_drvdata(generic_phy, phy_dwc3);
|
||||
+ platform_set_drvdata(pdev, phy_dwc3);
|
||||
+
|
||||
+ phy_provider = devm_of_phy_provider_register(phy_dwc3->dev,
|
||||
+ of_phy_simple_xlate);
|
||||
+
|
||||
+ if (IS_ERR(phy_provider))
|
||||
+ return PTR_ERR(phy_provider);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver qcom_dwc3_phy_driver = {
|
||||
+ .probe = qcom_dwc3_phy_probe,
|
||||
+ .driver = {
|
||||
+ .name = "qcom-dwc3-usb-phy",
|
||||
+ .owner = THIS_MODULE,
|
||||
+ .of_match_table = qcom_dwc3_phy_table,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(qcom_dwc3_phy_driver);
|
||||
+
|
||||
+MODULE_ALIAS("platform:phy-qcom-dwc3");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_AUTHOR("Andy Gross <agross@codeaurora.org>");
|
||||
+MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>");
|
||||
+MODULE_DESCRIPTION("DesignWare USB3 QCOM PHY driver");
|
|
@ -1,123 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -92,5 +92,29 @@
|
||||
sata@29000000 {
|
||||
status = "ok";
|
||||
};
|
||||
+
|
||||
+ phy@100f8800 { /* USB3 port 1 HS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@100f8830 { /* USB3 port 1 SS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@110f8800 { /* USB3 port 0 HS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ phy@110f8830 { /* USB3 port 0 SS phy */
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ usb30@0 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ usb30@1 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
};
|
||||
};
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -333,6 +333,88 @@
|
||||
compatible = "syscon";
|
||||
reg = <0x01200600 0x100>;
|
||||
};
|
||||
+
|
||||
+ hs_phy_1: phy@100f8800 {
|
||||
+ compatible = "qcom,dwc3-hs-usb-phy";
|
||||
+ reg = <0x100f8800 0x30>;
|
||||
+ clocks = <&gcc USB30_1_UTMI_CLK>;
|
||||
+ clock-names = "ref";
|
||||
+ #phy-cells = <0>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ ss_phy_1: phy@100f8830 {
|
||||
+ compatible = "qcom,dwc3-ss-usb-phy";
|
||||
+ reg = <0x100f8830 0x30>;
|
||||
+ clocks = <&gcc USB30_1_MASTER_CLK>;
|
||||
+ clock-names = "ref";
|
||||
+ #phy-cells = <0>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ hs_phy_0: phy@110f8800 {
|
||||
+ compatible = "qcom,dwc3-hs-usb-phy";
|
||||
+ reg = <0x110f8800 0x30>;
|
||||
+ clocks = <&gcc USB30_0_UTMI_CLK>;
|
||||
+ clock-names = "ref";
|
||||
+ #phy-cells = <0>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ ss_phy_0: phy@110f8830 {
|
||||
+ compatible = "qcom,dwc3-ss-usb-phy";
|
||||
+ reg = <0x110f8830 0x30>;
|
||||
+ clocks = <&gcc USB30_0_MASTER_CLK>;
|
||||
+ clock-names = "ref";
|
||||
+ #phy-cells = <0>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ usb3_0: usb30@0 {
|
||||
+ compatible = "qcom,dwc3";
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <1>;
|
||||
+ clocks = <&gcc USB30_0_MASTER_CLK>;
|
||||
+ clock-names = "core";
|
||||
+
|
||||
+ ranges;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+
|
||||
+ dwc3@11000000 {
|
||||
+ compatible = "snps,dwc3";
|
||||
+ reg = <0x11000000 0xcd00>;
|
||||
+ interrupts = <0 110 0x4>;
|
||||
+ phys = <&hs_phy_0>, <&ss_phy_0>;
|
||||
+ phy-names = "usb2-phy", "usb3-phy";
|
||||
+ dr_mode = "host";
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ usb3_1: usb30@1 {
|
||||
+ compatible = "qcom,dwc3";
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <1>;
|
||||
+ clocks = <&gcc USB30_1_MASTER_CLK>;
|
||||
+ clock-names = "core";
|
||||
+
|
||||
+ ranges;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+
|
||||
+ dwc3@10000000 {
|
||||
+ compatible = "snps,dwc3";
|
||||
+ reg = <0x10000000 0xcd00>;
|
||||
+ interrupts = <0 205 0x4>;
|
||||
+ phys = <&hs_phy_1>, <&ss_phy_1>;
|
||||
+ phy-names = "usb2-phy", "usb3-phy";
|
||||
+ dr_mode = "host";
|
||||
+ };
|
||||
+ };
|
||||
};
|
||||
|
||||
sfpb_mutex: sfpb-mutex {
|
|
@ -1,263 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v2,3/5] DT: PCI: qcom: Document PCIe devicetree bindings
|
||||
From: Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
X-Patchwork-Id: 6326181
|
||||
Message-Id: <1430743338-10441-4-git-send-email-svarbanov@mm-sol.com>
|
||||
To: Rob Herring <robh+dt@kernel.org>, Kumar Gala <galak@codeaurora.org>,
|
||||
Mark Rutland <mark.rutland@arm.com>,
|
||||
Grant Likely <grant.likely@linaro.org>,
|
||||
Bjorn Helgaas <bhelgaas@google.com>,
|
||||
Kishon Vijay Abraham I <kishon@ti.com>,
|
||||
Russell King <linux@arm.linux.org.uk>, Arnd Bergmann <arnd@arndb.de>
|
||||
Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org,
|
||||
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
|
||||
linux-pci@vger.kernel.org, Mathieu Olivari <mathieu@codeaurora.org>,
|
||||
Srinivas Kandagatla <srinivas.kandagatla@linaro.org>,
|
||||
Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
Date: Mon, 4 May 2015 15:42:16 +0300
|
||||
|
||||
Document Qualcomm PCIe driver devicetree bindings.
|
||||
|
||||
Signed-off-by: Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/pci/qcom,pcie.txt | 231 ++++++++++++++++++++
|
||||
1 files changed, 231 insertions(+), 0 deletions(-)
|
||||
create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie.txt
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt
|
||||
@@ -0,0 +1,231 @@
|
||||
+* Qualcomm PCI express root complex
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: Value shall include
|
||||
+ - "qcom,pcie-v0" for apq/ipq8064
|
||||
+ - "qcom,pcie-v1" for apq8084
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: Register ranges as listed in the reg-names property
|
||||
+
|
||||
+- reg-names:
|
||||
+ Usage: required
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: Must include the following entries
|
||||
+ - "parf" Qualcomm specific registers
|
||||
+ - "dbi" Designware PCIe registers
|
||||
+ - "elbi" External local bus interface registers
|
||||
+ - "config" PCIe configuration space
|
||||
+
|
||||
+- device_type:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Should be "pci". As specified in designware-pcie.txt
|
||||
+
|
||||
+- #address-cells:
|
||||
+ Usage: required
|
||||
+ Value type: <u32>
|
||||
+ Definition: Should be set to 3. As specified in designware-pcie.txt
|
||||
+
|
||||
+- #size-cells:
|
||||
+ Usage: required
|
||||
+ Value type: <u32>
|
||||
+ Definition: Should be set 2. As specified in designware-pcie.txt
|
||||
+
|
||||
+- ranges:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: As specified in designware-pcie.txt
|
||||
+
|
||||
+- interrupts:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: MSI interrupt
|
||||
+
|
||||
+- interrupt-names:
|
||||
+ Usage: required
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: Should contain "msi"
|
||||
+
|
||||
+- #interrupt-cells:
|
||||
+ Usage: required
|
||||
+ Value type: <u32>
|
||||
+ Definition: Should be 1. As specified in designware-pcie.txt
|
||||
+
|
||||
+- interrupt-map-mask:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: As specified in designware-pcie.txt
|
||||
+
|
||||
+- interrupt-map:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: As specified in designware-pcie.txt
|
||||
+
|
||||
+- clocks:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: List of phandle and clock specifier pairs as listed
|
||||
+ in clock-names property
|
||||
+
|
||||
+- clock-names:
|
||||
+ Usage: required
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: Should contain the following entries
|
||||
+ * should be populated for v0 and v1
|
||||
+ - "iface" Configuration AHB clock
|
||||
+
|
||||
+ * should be populated for v0
|
||||
+ - "core" Clocks the pcie hw block
|
||||
+ - "phy" Clocks the pcie PHY block
|
||||
+
|
||||
+ * should be populated for v1
|
||||
+ - "aux" Auxiliary (AUX) clock
|
||||
+ - "bus_master" Master AXI clock
|
||||
+ - "bus_slave" Slave AXI clock
|
||||
+
|
||||
+- resets:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: List of phandle and reset specifier pairs as listed
|
||||
+ in reset-names property
|
||||
+
|
||||
+- reset-names:
|
||||
+ Usage: required
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: Should contain the following entries
|
||||
+ * should be populated for v0
|
||||
+ - "axi" AXI reset
|
||||
+ - "ahb" AHB reset
|
||||
+ - "por" POR reset
|
||||
+ - "pci" PCI reset
|
||||
+ - "phy" PHY reset
|
||||
+
|
||||
+ * should be populated for v1
|
||||
+ - "core" Core reset
|
||||
+
|
||||
+- power-domains:
|
||||
+ Usage: required (for v1 only)
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: A phandle and power domain specifier pair to the
|
||||
+ power domain which is responsible for collapsing
|
||||
+ and restoring power to the peripheral
|
||||
+
|
||||
+- <name>-supply:
|
||||
+ Usage: required
|
||||
+ Value type: <phandle>
|
||||
+ Definition: List of phandles to the power supply regulator(s)
|
||||
+ * should be populated for v0 and v1
|
||||
+ - "vdda" core analog power supply
|
||||
+
|
||||
+ * should be populated for v0
|
||||
+ - "vdda_phy" analog power supply for PHY
|
||||
+ - "vdda_refclk" analog power supply for IC which generate
|
||||
+ reference clock
|
||||
+
|
||||
+- phys:
|
||||
+ Usage: required (for v1 only)
|
||||
+ Value type: <phandle>
|
||||
+ Definition: List of phandle(s) as listed in phy-names property
|
||||
+
|
||||
+- phy-names:
|
||||
+ Usage: required (for v1 only)
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: Should contain "pciephy"
|
||||
+
|
||||
+- <name>-gpio:
|
||||
+ Usage: optional
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: List of phandle and gpio specifier pairs. Should contain
|
||||
+ - "perst" PCIe endpoint reset signal line
|
||||
+ - "pewake" PCIe endpoint wake signal line
|
||||
+
|
||||
+- pinctrl-0:
|
||||
+ Usage: required
|
||||
+ Value type: <phandle>
|
||||
+ Definition: List of phandles pointing at a pin(s) configuration
|
||||
+
|
||||
+- pinctrl-names
|
||||
+ Usage: required
|
||||
+ Value type: <stringlist>
|
||||
+ Definition: List of names of pinctrl-0 state
|
||||
+
|
||||
+* Example for v0
|
||||
+ pcie0: pci@1b500000 {
|
||||
+ compatible = "qcom,pcie-v0";
|
||||
+ reg = <0x1b500000 0x1000
|
||||
+ 0x1b502000 0x80
|
||||
+ 0x1b600000 0x100
|
||||
+ 0x0ff00000 0x100000>;
|
||||
+ reg-names = "dbi", "elbi", "parf", "config";
|
||||
+ device_type = "pci";
|
||||
+ linux,pci-domain = <0>;
|
||||
+ bus-range = <0x00 0xff>;
|
||||
+ num-lanes = <1>;
|
||||
+ #address-cells = <3>;
|
||||
+ #size-cells = <2>;
|
||||
+ ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */
|
||||
+ 0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* memory */
|
||||
+ interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>;
|
||||
+ interrupt-names = "msi";
|
||||
+ #interrupt-cells = <1>;
|
||||
+ interrupt-map-mask = <0 0 0 0x7>;
|
||||
+ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
+ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
+ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
+ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
+ clocks = <&gcc PCIE_A_CLK>,
|
||||
+ <&gcc PCIE_H_CLK>,
|
||||
+ <&gcc PCIE_PHY_CLK>;
|
||||
+ clock-names = "core", "iface", "phy";
|
||||
+ resets = <&gcc PCIE_ACLK_RESET>,
|
||||
+ <&gcc PCIE_HCLK_RESET>,
|
||||
+ <&gcc PCIE_POR_RESET>,
|
||||
+ <&gcc PCIE_PCI_RESET>,
|
||||
+ <&gcc PCIE_PHY_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+ };
|
||||
+
|
||||
+* Example for v1
|
||||
+ pcie0@fc520000 {
|
||||
+ compatible = "qcom,pcie-v1";
|
||||
+ reg = <0xfc520000 0x2000>,
|
||||
+ <0xff000000 0x1000>,
|
||||
+ <0xff001000 0x1000>,
|
||||
+ <0xff002000 0x2000>;
|
||||
+ reg-names = "parf", "dbi", "elbi", "config";
|
||||
+ device_type = "pci";
|
||||
+ linux,pci-domain = <0>;
|
||||
+ bus-range = <0x00 0xff>;
|
||||
+ num-lanes = <1>;
|
||||
+ #address-cells = <3>;
|
||||
+ #size-cells = <2>;
|
||||
+ ranges = <0x81000000 0 0 0xff200000 0 0x00100000 /* I/O */
|
||||
+ 0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */
|
||||
+ interrupts = <GIC_SPI 243 IRQ_TYPE_NONE>;
|
||||
+ interrupt-names = "msi";
|
||||
+ #interrupt-cells = <1>;
|
||||
+ interrupt-map-mask = <0 0 0 0x7>;
|
||||
+ interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
+ <0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
+ <0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
+ <0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
+ clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>,
|
||||
+ <&gcc GCC_PCIE_0_MSTR_AXI_CLK>,
|
||||
+ <&gcc GCC_PCIE_0_SLV_AXI_CLK>,
|
||||
+ <&gcc GCC_PCIE_0_AUX_CLK>;
|
||||
+ clock-names = "iface", "master_bus", "slave_bus", "aux";
|
||||
+ resets = <&gcc GCC_PCIE_0_BCR>;
|
||||
+ reset-names = "core";
|
||||
+ power-domains = <&gcc PCIE0_GDSC>;
|
||||
+ vdda-supply = <&pma8084_l3>;
|
||||
+ phys = <&pciephy0>;
|
||||
+ phy-names = "pciephy";
|
||||
+ perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>;
|
||||
+ pinctrl-0 = <&pcie0_pins_default>;
|
||||
+ pinctrl-names = "default";
|
||||
+ };
|
|
@ -1,752 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v2,4/5] PCI: qcom: Add Qualcomm PCIe controller driver
|
||||
From: Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
X-Patchwork-Id: 6326161
|
||||
Message-Id: <1430743338-10441-5-git-send-email-svarbanov@mm-sol.com>
|
||||
To: Rob Herring <robh+dt@kernel.org>, Kumar Gala <galak@codeaurora.org>,
|
||||
Mark Rutland <mark.rutland@arm.com>,
|
||||
Grant Likely <grant.likely@linaro.org>,
|
||||
Bjorn Helgaas <bhelgaas@google.com>,
|
||||
Kishon Vijay Abraham I <kishon@ti.com>,
|
||||
Russell King <linux@arm.linux.org.uk>, Arnd Bergmann <arnd@arndb.de>
|
||||
Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org,
|
||||
linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org,
|
||||
linux-pci@vger.kernel.org, Mathieu Olivari <mathieu@codeaurora.org>,
|
||||
Srinivas Kandagatla <srinivas.kandagatla@linaro.org>,
|
||||
Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
Date: Mon, 4 May 2015 15:42:17 +0300
|
||||
|
||||
The PCIe driver reuse the Designware common code for host
|
||||
and MSI initialization, and also program the Qualcomm
|
||||
application specific registers.
|
||||
|
||||
Signed-off-by: Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
|
||||
---
|
||||
MAINTAINERS | 7 +
|
||||
drivers/pci/host/Kconfig | 9 +
|
||||
drivers/pci/host/Makefile | 1 +
|
||||
drivers/pci/host/pcie-qcom.c | 677 ++++++++++++++++++++++++++++++++++++++++++
|
||||
4 files changed, 694 insertions(+), 0 deletions(-)
|
||||
create mode 100644 drivers/pci/host/pcie-qcom.c
|
||||
|
||||
--- a/MAINTAINERS
|
||||
+++ b/MAINTAINERS
|
||||
@@ -8248,6 +8248,13 @@ S: Maintained
|
||||
F: Documentation/devicetree/bindings/pci/hisilicon-pcie.txt
|
||||
F: drivers/pci/host/pcie-hisi.c
|
||||
|
||||
+PCIE DRIVER FOR QUALCOMM MSM
|
||||
+M: Stanimir Varbanov <svarbanov@mm-sol.com>
|
||||
+L: linux-pci@vger.kernel.org
|
||||
+L: linux-arm-msm@vger.kernel.org
|
||||
+S: Maintained
|
||||
+F: drivers/pci/host/*qcom*
|
||||
+
|
||||
PCMCIA SUBSYSTEM
|
||||
P: Linux PCMCIA Team
|
||||
L: linux-pcmcia@lists.infradead.org
|
||||
--- a/drivers/pci/host/Kconfig
|
||||
+++ b/drivers/pci/host/Kconfig
|
||||
@@ -173,4 +173,13 @@ config PCI_HISI
|
||||
help
|
||||
Say Y here if you want PCIe controller support on HiSilicon HIP05 SoC
|
||||
|
||||
+config PCIE_QCOM
|
||||
+ bool "Qualcomm PCIe controller"
|
||||
+ depends on ARCH_QCOM && OF || (ARM && COMPILE_TEST)
|
||||
+ select PCIE_DW
|
||||
+ select PCIEPORTBUS
|
||||
+ help
|
||||
+ Say Y here to enable PCIe controller support on Qualcomm SoCs. The
|
||||
+ PCIe controller use Designware core plus Qualcomm specific hardware
|
||||
+ wrappers.
|
||||
endmenu
|
||||
--- /dev/null
|
||||
+++ b/drivers/pci/host/pcie-qcom.c
|
||||
@@ -0,0 +1,676 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2014, 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/clk.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/gpio.h>
|
||||
+#include <linux/interrupt.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of_gpio.h>
|
||||
+#include <linux/pci.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/phy/phy.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+#include <linux/reset.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/types.h>
|
||||
+
|
||||
+#include "pcie-designware.h"
|
||||
+
|
||||
+#define PCIE20_PARF_PHY_CTRL 0x40
|
||||
+#define PCIE20_PARF_PHY_REFCLK 0x4C
|
||||
+#define PCIE20_PARF_DBI_BASE_ADDR 0x168
|
||||
+#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c
|
||||
+#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178
|
||||
+
|
||||
+#define PCIE20_ELBI_SYS_CTRL 0x04
|
||||
+#define PCIE20_ELBI_SYS_STTS 0x08
|
||||
+#define XMLH_LINK_UP BIT(10)
|
||||
+
|
||||
+#define PCIE20_CAP 0x70
|
||||
+#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10)
|
||||
+
|
||||
+#define PERST_DELAY_MIN_US 1000
|
||||
+#define PERST_DELAY_MAX_US 1005
|
||||
+
|
||||
+#define LINKUP_DELAY_MIN_US 5000
|
||||
+#define LINKUP_DELAY_MAX_US 5100
|
||||
+#define LINKUP_RETRIES_COUNT 20
|
||||
+
|
||||
+#define PCIE_V0 0 /* apq8064 */
|
||||
+#define PCIE_V1 1 /* apq8084 */
|
||||
+
|
||||
+struct qcom_pcie_resources_v0 {
|
||||
+ struct clk *iface_clk;
|
||||
+ struct clk *core_clk;
|
||||
+ struct clk *phy_clk;
|
||||
+ struct reset_control *pci_reset;
|
||||
+ struct reset_control *axi_reset;
|
||||
+ struct reset_control *ahb_reset;
|
||||
+ struct reset_control *por_reset;
|
||||
+ struct reset_control *phy_reset;
|
||||
+ struct regulator *vdda;
|
||||
+ struct regulator *vdda_phy;
|
||||
+ struct regulator *vdda_refclk;
|
||||
+};
|
||||
+
|
||||
+struct qcom_pcie_resources_v1 {
|
||||
+ struct clk *iface;
|
||||
+ struct clk *aux;
|
||||
+ struct clk *master_bus;
|
||||
+ struct clk *slave_bus;
|
||||
+ struct reset_control *core;
|
||||
+ struct regulator *vdda;
|
||||
+};
|
||||
+
|
||||
+union pcie_resources {
|
||||
+ struct qcom_pcie_resources_v0 v0;
|
||||
+ struct qcom_pcie_resources_v1 v1;
|
||||
+};
|
||||
+
|
||||
+struct qcom_pcie {
|
||||
+ struct pcie_port pp;
|
||||
+ struct device *dev;
|
||||
+ union pcie_resources res;
|
||||
+ void __iomem *parf;
|
||||
+ void __iomem *dbi;
|
||||
+ void __iomem *elbi;
|
||||
+ struct phy *phy;
|
||||
+ struct gpio_desc *reset;
|
||||
+ unsigned int version;
|
||||
+};
|
||||
+
|
||||
+#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp)
|
||||
+
|
||||
+static inline void
|
||||
+writel_masked(void __iomem *addr, u32 clear_mask, u32 set_mask)
|
||||
+{
|
||||
+ u32 val = readl(addr);
|
||||
+
|
||||
+ val &= ~clear_mask;
|
||||
+ val |= set_mask;
|
||||
+ writel(val, addr);
|
||||
+}
|
||||
+
|
||||
+static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert)
|
||||
+{
|
||||
+ int val, active_low;
|
||||
+
|
||||
+ if (IS_ERR_OR_NULL(pcie->reset))
|
||||
+ return;
|
||||
+
|
||||
+ active_low = gpiod_is_active_low(pcie->reset);
|
||||
+
|
||||
+ if (assert)
|
||||
+ val = !!active_low;
|
||||
+ else
|
||||
+ val = !active_low;
|
||||
+
|
||||
+ gpiod_set_value(pcie->reset, val);
|
||||
+
|
||||
+ usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US);
|
||||
+}
|
||||
+
|
||||
+static void qcom_ep_reset_assert(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ qcom_ep_reset_assert_deassert(pcie, 1);
|
||||
+}
|
||||
+
|
||||
+static void qcom_ep_reset_deassert(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ qcom_ep_reset_assert_deassert(pcie, 0);
|
||||
+}
|
||||
+
|
||||
+static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg)
|
||||
+{
|
||||
+ struct pcie_port *pp = arg;
|
||||
+
|
||||
+ return dw_handle_msi_irq(pp);
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_link_up(struct pcie_port *pp)
|
||||
+{
|
||||
+ struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
+ u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS);
|
||||
+
|
||||
+ return val & BIT(29) ? 1 : 0;
|
||||
+}
|
||||
+
|
||||
+static void qcom_pcie_disable_resources_v0(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
+
|
||||
+ reset_control_assert(res->pci_reset);
|
||||
+ reset_control_assert(res->axi_reset);
|
||||
+ reset_control_assert(res->ahb_reset);
|
||||
+ reset_control_assert(res->por_reset);
|
||||
+ reset_control_assert(res->pci_reset);
|
||||
+ clk_disable_unprepare(res->iface_clk);
|
||||
+ clk_disable_unprepare(res->core_clk);
|
||||
+ clk_disable_unprepare(res->phy_clk);
|
||||
+ regulator_disable(res->vdda);
|
||||
+ regulator_disable(res->vdda_phy);
|
||||
+ regulator_disable(res->vdda_refclk);
|
||||
+}
|
||||
+
|
||||
+static void qcom_pcie_disable_resources_v1(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
|
||||
+
|
||||
+ reset_control_assert(res->core);
|
||||
+ clk_disable_unprepare(res->slave_bus);
|
||||
+ clk_disable_unprepare(res->master_bus);
|
||||
+ clk_disable_unprepare(res->iface);
|
||||
+ clk_disable_unprepare(res->aux);
|
||||
+ regulator_disable(res->vdda);
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_enable_resources_v0(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
+ struct device *dev = pcie->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = regulator_enable(res->vdda);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot enable vdda regulator\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ ret = regulator_enable(res->vdda_refclk);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot enable vdda_refclk regulator\n");
|
||||
+ goto err_refclk;
|
||||
+ }
|
||||
+
|
||||
+ ret = regulator_enable(res->vdda_phy);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot enable vdda_phy regulator\n");
|
||||
+ goto err_vdda_phy;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->iface_clk);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable iface clock\n");
|
||||
+ goto err_iface;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->core_clk);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable core clock\n");
|
||||
+ goto err_clk_core;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->phy_clk);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable phy clock\n");
|
||||
+ goto err_clk_phy;
|
||||
+ }
|
||||
+
|
||||
+ ret = reset_control_deassert(res->ahb_reset);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot deassert ahb reset\n");
|
||||
+ goto err_reset_ahb;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+err_reset_ahb:
|
||||
+ clk_disable_unprepare(res->phy_clk);
|
||||
+err_clk_phy:
|
||||
+ clk_disable_unprepare(res->core_clk);
|
||||
+err_clk_core:
|
||||
+ clk_disable_unprepare(res->iface_clk);
|
||||
+err_iface:
|
||||
+ regulator_disable(res->vdda_phy);
|
||||
+err_vdda_phy:
|
||||
+ regulator_disable(res->vdda_refclk);
|
||||
+err_refclk:
|
||||
+ regulator_disable(res->vdda);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_enable_resources_v1(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
|
||||
+ struct device *dev = pcie->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = reset_control_deassert(res->core);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot deassert core reset\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->aux);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable aux clock\n");
|
||||
+ goto err_res;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->iface);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable iface clock\n");
|
||||
+ goto err_aux;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->master_bus);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable master_bus clock\n");
|
||||
+ goto err_iface;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->slave_bus);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable slave_bus clock\n");
|
||||
+ goto err_master;
|
||||
+ }
|
||||
+
|
||||
+ ret = regulator_enable(res->vdda);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot enable vdda regulator\n");
|
||||
+ goto err_slave;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+err_slave:
|
||||
+ clk_disable_unprepare(res->slave_bus);
|
||||
+err_master:
|
||||
+ clk_disable_unprepare(res->master_bus);
|
||||
+err_iface:
|
||||
+ clk_disable_unprepare(res->iface);
|
||||
+err_aux:
|
||||
+ clk_disable_unprepare(res->aux);
|
||||
+err_res:
|
||||
+ reset_control_assert(res->core);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
+ struct device *dev = pcie->dev;
|
||||
+
|
||||
+ res->vdda = devm_regulator_get(dev, "vdda");
|
||||
+ if (IS_ERR(res->vdda))
|
||||
+ return PTR_ERR(res->vdda);
|
||||
+
|
||||
+ res->vdda_phy = devm_regulator_get(dev, "vdda_phy");
|
||||
+ if (IS_ERR(res->vdda_phy))
|
||||
+ return PTR_ERR(res->vdda_phy);
|
||||
+
|
||||
+ res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk");
|
||||
+ if (IS_ERR(res->vdda_refclk))
|
||||
+ return PTR_ERR(res->vdda_refclk);
|
||||
+
|
||||
+ res->iface_clk = devm_clk_get(dev, "iface");
|
||||
+ if (IS_ERR(res->iface_clk))
|
||||
+ return PTR_ERR(res->iface_clk);
|
||||
+
|
||||
+ res->core_clk = devm_clk_get(dev, "core");
|
||||
+ if (IS_ERR(res->core_clk))
|
||||
+ return PTR_ERR(res->core_clk);
|
||||
+
|
||||
+ res->phy_clk = devm_clk_get(dev, "phy");
|
||||
+ if (IS_ERR(res->phy_clk))
|
||||
+ return PTR_ERR(res->phy_clk);
|
||||
+
|
||||
+ res->pci_reset = devm_reset_control_get(dev, "pci");
|
||||
+ if (IS_ERR(res->pci_reset))
|
||||
+ return PTR_ERR(res->pci_reset);
|
||||
+
|
||||
+ res->axi_reset = devm_reset_control_get(dev, "axi");
|
||||
+ if (IS_ERR(res->axi_reset))
|
||||
+ return PTR_ERR(res->axi_reset);
|
||||
+
|
||||
+ res->ahb_reset = devm_reset_control_get(dev, "ahb");
|
||||
+ if (IS_ERR(res->ahb_reset))
|
||||
+ return PTR_ERR(res->ahb_reset);
|
||||
+
|
||||
+ res->por_reset = devm_reset_control_get(dev, "por");
|
||||
+ if (IS_ERR(res->por_reset))
|
||||
+ return PTR_ERR(res->por_reset);
|
||||
+
|
||||
+ res->phy_reset = devm_reset_control_get(dev, "phy");
|
||||
+ if (IS_ERR(res->phy_reset))
|
||||
+ return PTR_ERR(res->phy_reset);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct qcom_pcie_resources_v1 *res = &pcie->res.v1;
|
||||
+ struct device *dev = pcie->dev;
|
||||
+
|
||||
+ res->vdda = devm_regulator_get(dev, "vdda");
|
||||
+ if (IS_ERR(res->vdda))
|
||||
+ return PTR_ERR(res->vdda);
|
||||
+
|
||||
+ res->iface = devm_clk_get(dev, "iface");
|
||||
+ if (IS_ERR(res->iface))
|
||||
+ return PTR_ERR(res->iface);
|
||||
+
|
||||
+ res->aux = devm_clk_get(dev, "aux");
|
||||
+ if (IS_ERR(res->aux) && PTR_ERR(res->aux) == -EPROBE_DEFER)
|
||||
+ return -EPROBE_DEFER;
|
||||
+ else if (IS_ERR(res->aux))
|
||||
+ res->aux = NULL;
|
||||
+
|
||||
+ res->master_bus = devm_clk_get(dev, "master_bus");
|
||||
+ if (IS_ERR(res->master_bus))
|
||||
+ return PTR_ERR(res->master_bus);
|
||||
+
|
||||
+ res->slave_bus = devm_clk_get(dev, "slave_bus");
|
||||
+ if (IS_ERR(res->slave_bus))
|
||||
+ return PTR_ERR(res->slave_bus);
|
||||
+
|
||||
+ res->core = devm_reset_control_get(dev, "core");
|
||||
+ if (IS_ERR(res->core))
|
||||
+ return PTR_ERR(res->core);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_enable_link_training(struct pcie_port *pp)
|
||||
+{
|
||||
+ struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
+ struct device *dev = pp->dev;
|
||||
+ int retries;
|
||||
+ u32 val;
|
||||
+
|
||||
+ /* enable link training */
|
||||
+ writel_masked(pcie->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0));
|
||||
+
|
||||
+ /* wait for up to 100ms for the link to come up */
|
||||
+ retries = LINKUP_RETRIES_COUNT;
|
||||
+ do {
|
||||
+ val = readl(pcie->elbi + PCIE20_ELBI_SYS_STTS);
|
||||
+ if (val & XMLH_LINK_UP)
|
||||
+ break;
|
||||
+ usleep_range(LINKUP_DELAY_MIN_US, LINKUP_DELAY_MAX_US);
|
||||
+ } while (retries--);
|
||||
+
|
||||
+ if (retries < 0 || !dw_pcie_link_up(pp)) {
|
||||
+ dev_err(dev, "link initialization failed\n");
|
||||
+ return -ENXIO;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void qcom_pcie_host_init_v1(struct pcie_port *pp)
|
||||
+{
|
||||
+ struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
+ int ret;
|
||||
+
|
||||
+ qcom_ep_reset_assert(pcie);
|
||||
+
|
||||
+ ret = qcom_pcie_enable_resources_v1(pcie);
|
||||
+ if (ret)
|
||||
+ return;
|
||||
+
|
||||
+ /* change DBI base address */
|
||||
+ writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR);
|
||||
+
|
||||
+ if (IS_ENABLED(CONFIG_PCI_MSI))
|
||||
+ writel_masked(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT,
|
||||
+ 0, BIT(31));
|
||||
+
|
||||
+ ret = phy_init(pcie->phy);
|
||||
+ if (ret)
|
||||
+ goto err_res;
|
||||
+
|
||||
+ ret = phy_power_on(pcie->phy);
|
||||
+ if (ret)
|
||||
+ goto err_phy;
|
||||
+
|
||||
+ dw_pcie_setup_rc(pp);
|
||||
+
|
||||
+ if (IS_ENABLED(CONFIG_PCI_MSI))
|
||||
+ dw_pcie_msi_init(pp);
|
||||
+
|
||||
+ qcom_ep_reset_deassert(pcie);
|
||||
+
|
||||
+ ret = qcom_pcie_enable_link_training(pp);
|
||||
+ if (ret)
|
||||
+ goto err;
|
||||
+
|
||||
+ return;
|
||||
+
|
||||
+err:
|
||||
+ qcom_ep_reset_assert(pcie);
|
||||
+ phy_power_off(pcie->phy);
|
||||
+err_phy:
|
||||
+ phy_exit(pcie->phy);
|
||||
+err_res:
|
||||
+ qcom_pcie_disable_resources_v1(pcie);
|
||||
+}
|
||||
+
|
||||
+static void qcom_pcie_host_init_v0(struct pcie_port *pp)
|
||||
+{
|
||||
+ struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
+ struct qcom_pcie_resources_v0 *res = &pcie->res.v0;
|
||||
+ struct device *dev = pcie->dev;
|
||||
+ int ret;
|
||||
+
|
||||
+ qcom_ep_reset_assert(pcie);
|
||||
+
|
||||
+ ret = qcom_pcie_enable_resources_v0(pcie);
|
||||
+ if (ret)
|
||||
+ return;
|
||||
+
|
||||
+ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0);
|
||||
+
|
||||
+ /* enable external reference clock */
|
||||
+ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16));
|
||||
+
|
||||
+ ret = reset_control_deassert(res->phy_reset);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot deassert phy reset\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ ret = reset_control_deassert(res->pci_reset);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot deassert pci reset\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ ret = reset_control_deassert(res->por_reset);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot deassert por reset\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ ret = reset_control_deassert(res->axi_reset);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot deassert axi reset\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ /* wait 150ms for clock acquisition */
|
||||
+ usleep_range(10000, 15000);
|
||||
+
|
||||
+ dw_pcie_setup_rc(pp);
|
||||
+
|
||||
+ if (IS_ENABLED(CONFIG_PCI_MSI))
|
||||
+ dw_pcie_msi_init(pp);
|
||||
+
|
||||
+ qcom_ep_reset_deassert(pcie);
|
||||
+
|
||||
+ ret = qcom_pcie_enable_link_training(pp);
|
||||
+ if (ret)
|
||||
+ goto err;
|
||||
+
|
||||
+ return;
|
||||
+err:
|
||||
+ qcom_ep_reset_assert(pcie);
|
||||
+ qcom_pcie_disable_resources_v0(pcie);
|
||||
+}
|
||||
+
|
||||
+static void qcom_pcie_host_init(struct pcie_port *pp)
|
||||
+{
|
||||
+ struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
+
|
||||
+ if (pcie->version == PCIE_V0)
|
||||
+ return qcom_pcie_host_init_v0(pp);
|
||||
+ else
|
||||
+ return qcom_pcie_host_init_v1(pp);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val)
|
||||
+{
|
||||
+ /* the device class is not reported correctly from the register */
|
||||
+ if (where == PCI_CLASS_REVISION && size == 4) {
|
||||
+ *val = readl(pp->dbi_base + PCI_CLASS_REVISION);
|
||||
+ *val &= ~(0xffff << 16);
|
||||
+ *val |= PCI_CLASS_BRIDGE_PCI << 16;
|
||||
+ return PCIBIOS_SUCCESSFUL;
|
||||
+ }
|
||||
+
|
||||
+ return dw_pcie_cfg_read(pp->dbi_base + where, size, val);
|
||||
+}
|
||||
+
|
||||
+static struct pcie_host_ops qcom_pcie_ops = {
|
||||
+ .link_up = qcom_pcie_link_up,
|
||||
+ .host_init = qcom_pcie_host_init,
|
||||
+ .rd_own_conf = qcom_pcie_rd_own_conf,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id qcom_pcie_match[] = {
|
||||
+ { .compatible = "qcom,pcie-v0", .data = (void *)PCIE_V0 },
|
||||
+ { .compatible = "qcom,pcie-v1", .data = (void *)PCIE_V1 },
|
||||
+ { }
|
||||
+};
|
||||
+
|
||||
+static int qcom_pcie_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ const struct of_device_id *match;
|
||||
+ struct resource *res;
|
||||
+ struct qcom_pcie *pcie;
|
||||
+ struct pcie_port *pp;
|
||||
+ int ret;
|
||||
+
|
||||
+ match = of_match_node(qcom_pcie_match, dev->of_node);
|
||||
+ if (!match)
|
||||
+ return -ENXIO;
|
||||
+
|
||||
+ pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
|
||||
+ if (!pcie)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ pcie->version = (unsigned int)match->data;
|
||||
+
|
||||
+ pcie->reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_LOW);
|
||||
+ if (IS_ERR(pcie->reset) && PTR_ERR(pcie->reset) == -EPROBE_DEFER)
|
||||
+ return PTR_ERR(pcie->reset);
|
||||
+
|
||||
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf");
|
||||
+ pcie->parf = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(pcie->parf))
|
||||
+ return PTR_ERR(pcie->parf);
|
||||
+
|
||||
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
|
||||
+ pcie->dbi = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(pcie->dbi))
|
||||
+ return PTR_ERR(pcie->dbi);
|
||||
+
|
||||
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi");
|
||||
+ pcie->elbi = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(pcie->elbi))
|
||||
+ return PTR_ERR(pcie->elbi);
|
||||
+
|
||||
+ pcie->phy = devm_phy_optional_get(dev, "pciephy");
|
||||
+ if (IS_ERR(pcie->phy))
|
||||
+ return PTR_ERR(pcie->phy);
|
||||
+
|
||||
+ pcie->dev = dev;
|
||||
+
|
||||
+ if (pcie->version == PCIE_V0)
|
||||
+ ret = qcom_pcie_get_resources_v0(pcie);
|
||||
+ else
|
||||
+ ret = qcom_pcie_get_resources_v1(pcie);
|
||||
+
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ pp = &pcie->pp;
|
||||
+ pp->dev = dev;
|
||||
+ pp->dbi_base = pcie->dbi;
|
||||
+ pp->root_bus_nr = -1;
|
||||
+ pp->ops = &qcom_pcie_ops;
|
||||
+
|
||||
+ if (IS_ENABLED(CONFIG_PCI_MSI)) {
|
||||
+ pp->msi_irq = platform_get_irq_byname(pdev, "msi");
|
||||
+ if (pp->msi_irq < 0) {
|
||||
+ dev_err(dev, "cannot get msi irq\n");
|
||||
+ return pp->msi_irq;
|
||||
+ }
|
||||
+
|
||||
+ ret = devm_request_irq(dev, pp->msi_irq,
|
||||
+ qcom_pcie_msi_irq_handler,
|
||||
+ IRQF_SHARED, "qcom-pcie-msi", pp);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot request msi irq\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ ret = dw_pcie_host_init(pp);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot initialize host\n");
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ platform_set_drvdata(pdev, pcie);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int qcom_pcie_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct qcom_pcie *pcie = platform_get_drvdata(pdev);
|
||||
+
|
||||
+ qcom_ep_reset_assert(pcie);
|
||||
+ phy_power_off(pcie->phy);
|
||||
+ phy_exit(pcie->phy);
|
||||
+ if (pcie->version == PCIE_V0)
|
||||
+ qcom_pcie_disable_resources_v0(pcie);
|
||||
+ else
|
||||
+ qcom_pcie_disable_resources_v1(pcie);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver qcom_pcie_driver = {
|
||||
+ .probe = qcom_pcie_probe,
|
||||
+ .remove = qcom_pcie_remove,
|
||||
+ .driver = {
|
||||
+ .name = "qcom-pcie",
|
||||
+ .of_match_table = qcom_pcie_match,
|
||||
+ },
|
||||
+};
|
||||
+
|
||||
+module_platform_driver(qcom_pcie_driver);
|
||||
+
|
||||
+MODULE_AUTHOR("Stanimir Varbanov <svarbanov@mm-sol.com>");
|
||||
+MODULE_DESCRIPTION("Qualcomm PCIe root complex driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:qcom-pcie");
|
||||
--- a/drivers/pci/host/Makefile
|
||||
+++ b/drivers/pci/host/Makefile
|
||||
@@ -20,3 +20,4 @@ obj-$(CONFIG_PCIE_IPROC_BCMA) += pcie-ip
|
||||
obj-$(CONFIG_PCIE_ALTERA) += pcie-altera.o
|
||||
obj-$(CONFIG_PCIE_ALTERA_MSI) += pcie-altera-msi.o
|
||||
obj-$(CONFIG_PCI_HISI) += pcie-hisi.o
|
||||
+obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o
|
|
@ -1,245 +0,0 @@
|
|||
From 5b40516b2f5fb9b2a7d6d3e2e924f12ec9d183a8 Mon Sep 17 00:00:00 2001
|
||||
From: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Date: Tue, 21 Apr 2015 19:01:42 -0700
|
||||
Subject: [PATCH 8/9] ARM: dts: qcom: add pcie nodes to ipq806x platforms
|
||||
|
||||
qcom-pcie driver now supports version 0 of the controller. This change
|
||||
adds the corresponding entries to the IPQ806x dtsi file and
|
||||
corresponding platform (AP148).
|
||||
|
||||
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
---
|
||||
arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 30 ++++++++
|
||||
arch/arm/boot/dts/qcom-ipq8064.dtsi | 124 +++++++++++++++++++++++++++++++
|
||||
2 files changed, 154 insertions(+)
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -116,5 +116,15 @@
|
||||
usb30@1 {
|
||||
status = "ok";
|
||||
};
|
||||
+
|
||||
+ pcie0: pci@1b500000 {
|
||||
+ status = "ok";
|
||||
+ phy-tx0-term-offset = <7>;
|
||||
+ };
|
||||
+
|
||||
+ pcie1: pci@1b700000 {
|
||||
+ status = "ok";
|
||||
+ phy-tx0-term-offset = <7>;
|
||||
+ };
|
||||
};
|
||||
};
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts
|
||||
@@ -128,5 +128,17 @@
|
||||
usb30@1 {
|
||||
status = "ok";
|
||||
};
|
||||
+
|
||||
+ pcie0: pci@1b500000 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ pcie1: pci@1b700000 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
+
|
||||
+ pcie2: pci@1b900000 {
|
||||
+ status = "ok";
|
||||
+ };
|
||||
};
|
||||
};
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -4,6 +4,9 @@
|
||||
#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/reset/qcom,gcc-ipq806x.h>
|
||||
+#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
+#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
/ {
|
||||
model = "Qualcomm IPQ8064";
|
||||
@@ -99,6 +102,34 @@
|
||||
interrupt-controller;
|
||||
#interrupt-cells = <2>;
|
||||
interrupts = <0 16 0x4>;
|
||||
+
|
||||
+ pcie0_pins: pcie0_pinmux {
|
||||
+ mux {
|
||||
+ pins = "gpio3";
|
||||
+ function = "pcie1_rst";
|
||||
+ drive-strength = <2>;
|
||||
+ bias-disable;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ pcie1_pins: pcie1_pinmux {
|
||||
+ mux {
|
||||
+ pins = "gpio48";
|
||||
+ function = "pcie2_rst";
|
||||
+ drive-strength = <2>;
|
||||
+ bias-disable;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ pcie2_pins: pcie2_pinmux {
|
||||
+ mux {
|
||||
+ pins = "gpio63";
|
||||
+ function = "pcie3_rst";
|
||||
+ drive-strength = <2>;
|
||||
+ bias-disable;
|
||||
+ output-low;
|
||||
+ };
|
||||
+ };
|
||||
};
|
||||
|
||||
intc: interrupt-controller@2000000 {
|
||||
@@ -415,6 +446,144 @@
|
||||
dr_mode = "host";
|
||||
};
|
||||
};
|
||||
+
|
||||
+ pcie0: pci@1b500000 {
|
||||
+ compatible = "qcom,pcie-v0";
|
||||
+ reg = <0x1b500000 0x1000
|
||||
+ 0x1b502000 0x80
|
||||
+ 0x1b600000 0x100
|
||||
+ 0x0ff00000 0x100000>;
|
||||
+ reg-names = "dbi", "elbi", "parf", "config";
|
||||
+ device_type = "pci";
|
||||
+ linux,pci-domain = <0>;
|
||||
+ bus-range = <0x00 0xff>;
|
||||
+ num-lanes = <1>;
|
||||
+ #address-cells = <3>;
|
||||
+ #size-cells = <2>;
|
||||
+
|
||||
+ ranges = <0x81000000 0 0x0fe00000 0x0fe00000 0 0x00100000 /* downstream I/O */
|
||||
+ 0x82000000 0 0x08000000 0x08000000 0 0x07e00000>; /* non-prefetchable memory */
|
||||
+
|
||||
+ interrupts = <GIC_SPI 35 IRQ_TYPE_NONE>;
|
||||
+ interrupt-names = "msi";
|
||||
+ #interrupt-cells = <1>;
|
||||
+ interrupt-map-mask = <0 0 0 0x7>;
|
||||
+ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
+ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
+ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
+ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
+
|
||||
+ clocks = <&gcc PCIE_A_CLK>,
|
||||
+ <&gcc PCIE_H_CLK>,
|
||||
+ <&gcc PCIE_PHY_CLK>;
|
||||
+ clock-names = "core", "iface", "phy";
|
||||
+
|
||||
+ resets = <&gcc PCIE_ACLK_RESET>,
|
||||
+ <&gcc PCIE_HCLK_RESET>,
|
||||
+ <&gcc PCIE_POR_RESET>,
|
||||
+ <&gcc PCIE_PCI_RESET>,
|
||||
+ <&gcc PCIE_PHY_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+
|
||||
+ pinctrl-0 = <&pcie0_pins>;
|
||||
+ pinctrl-names = "default";
|
||||
+
|
||||
+ perst-gpios = <&qcom_pinmux 3 GPIO_ACTIVE_LOW>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ pcie1: pci@1b700000 {
|
||||
+ compatible = "qcom,pcie-v0";
|
||||
+ reg = <0x1b700000 0x1000
|
||||
+ 0x1b702000 0x80
|
||||
+ 0x1b800000 0x100
|
||||
+ 0x31f00000 0x100000>;
|
||||
+ reg-names = "dbi", "elbi", "parf", "config";
|
||||
+ device_type = "pci";
|
||||
+ linux,pci-domain = <1>;
|
||||
+ bus-range = <0x00 0xff>;
|
||||
+ num-lanes = <1>;
|
||||
+ #address-cells = <3>;
|
||||
+ #size-cells = <2>;
|
||||
+
|
||||
+ ranges = <0x81000000 0 0x31e00000 0x31e00000 0 0x00100000 /* downstream I/O */
|
||||
+ 0x82000000 0 0x2e000000 0x2e000000 0 0x03e00000>; /* non-prefetchable memory */
|
||||
+
|
||||
+ interrupts = <GIC_SPI 57 IRQ_TYPE_NONE>;
|
||||
+ interrupt-names = "msi";
|
||||
+ #interrupt-cells = <1>;
|
||||
+ interrupt-map-mask = <0 0 0 0x7>;
|
||||
+ interrupt-map = <0 0 0 1 &intc 0 58 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
+ <0 0 0 2 &intc 0 59 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
+ <0 0 0 3 &intc 0 60 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
+ <0 0 0 4 &intc 0 61 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
+
|
||||
+ clocks = <&gcc PCIE_1_A_CLK>,
|
||||
+ <&gcc PCIE_1_H_CLK>,
|
||||
+ <&gcc PCIE_1_PHY_CLK>;
|
||||
+ clock-names = "core", "iface", "phy";
|
||||
+
|
||||
+ resets = <&gcc PCIE_1_ACLK_RESET>,
|
||||
+ <&gcc PCIE_1_HCLK_RESET>,
|
||||
+ <&gcc PCIE_1_POR_RESET>,
|
||||
+ <&gcc PCIE_1_PCI_RESET>,
|
||||
+ <&gcc PCIE_1_PHY_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+
|
||||
+ pinctrl-0 = <&pcie1_pins>;
|
||||
+ pinctrl-names = "default";
|
||||
+
|
||||
+ perst-gpios = <&qcom_pinmux 48 GPIO_ACTIVE_LOW>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
+
|
||||
+ pcie2: pci@1b900000 {
|
||||
+ compatible = "qcom,pcie-v0";
|
||||
+ reg = <0x1b900000 0x1000
|
||||
+ 0x1b902000 0x80
|
||||
+ 0x1ba00000 0x100
|
||||
+ 0x35f00000 0x100000>;
|
||||
+ reg-names = "dbi", "elbi", "parf", "config";
|
||||
+ device_type = "pci";
|
||||
+ linux,pci-domain = <2>;
|
||||
+ bus-range = <0x00 0xff>;
|
||||
+ num-lanes = <1>;
|
||||
+ #address-cells = <3>;
|
||||
+ #size-cells = <2>;
|
||||
+
|
||||
+ ranges = <0x81000000 0 0x35e00000 0x35e00000 0 0x00100000 /* downstream I/O */
|
||||
+ 0x82000000 0 0x32000000 0x32000000 0 0x03e00000>; /* non-prefetchable memory */
|
||||
+
|
||||
+ interrupts = <GIC_SPI 71 IRQ_TYPE_NONE>;
|
||||
+ interrupt-names = "msi";
|
||||
+ #interrupt-cells = <1>;
|
||||
+ interrupt-map-mask = <0 0 0 0x7>;
|
||||
+ interrupt-map = <0 0 0 1 &intc 0 72 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
|
||||
+ <0 0 0 2 &intc 0 73 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
|
||||
+ <0 0 0 3 &intc 0 74 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
|
||||
+ <0 0 0 4 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
|
||||
+
|
||||
+ clocks = <&gcc PCIE_2_A_CLK>,
|
||||
+ <&gcc PCIE_2_H_CLK>,
|
||||
+ <&gcc PCIE_2_PHY_CLK>;
|
||||
+ clock-names = "core", "iface", "phy";
|
||||
+
|
||||
+ resets = <&gcc PCIE_2_ACLK_RESET>,
|
||||
+ <&gcc PCIE_2_HCLK_RESET>,
|
||||
+ <&gcc PCIE_2_POR_RESET>,
|
||||
+ <&gcc PCIE_2_PCI_RESET>,
|
||||
+ <&gcc PCIE_2_PHY_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+
|
||||
+ pinctrl-0 = <&pcie2_pins>;
|
||||
+ pinctrl-names = "default";
|
||||
+
|
||||
+ perst-gpios = <&qcom_pinmux 63 GPIO_ACTIVE_LOW>;
|
||||
+
|
||||
+ status = "disabled";
|
||||
+ };
|
||||
};
|
||||
|
||||
sfpb_mutex: sfpb-mutex {
|
|
@ -1,29 +0,0 @@
|
|||
From f004aa1dec6e2e206be025de15b115d60f2b21e3 Mon Sep 17 00:00:00 2001
|
||||
From: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
Date: Tue, 21 Apr 2015 19:09:07 -0700
|
||||
Subject: [PATCH 9/9] ARM: qcom: automatically select PCI_DOMAINS if PCI is
|
||||
enabled
|
||||
|
||||
If multiple PCIe devices are present in the system, the kernel will
|
||||
panic at boot time when trying to scan the PCI buses. This happens on
|
||||
IPQ806x based platforms, which has 3 PCIe ports.
|
||||
|
||||
Enabling this option allows the kernel to assign the pci-domains
|
||||
according to the device-tree content. This allows multiple PCIe
|
||||
controllers to coexist in the system.
|
||||
|
||||
Signed-off-by: Mathieu Olivari <mathieu@codeaurora.org>
|
||||
---
|
||||
arch/arm/mach-qcom/Kconfig | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
--- a/arch/arm/mach-qcom/Kconfig
|
||||
+++ b/arch/arm/mach-qcom/Kconfig
|
||||
@@ -5,6 +5,7 @@ menuconfig ARCH_QCOM
|
||||
select ARM_AMBA
|
||||
select PINCTRL
|
||||
select QCOM_SCM if SMP
|
||||
+ select PCI_DOMAINS if PCI
|
||||
help
|
||||
Support for Qualcomm's devicetree based systems.
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
--- a/drivers/pci/host/pcie-qcom.c
|
||||
+++ b/drivers/pci/host/pcie-qcom.c
|
||||
@@ -29,8 +29,53 @@
|
||||
|
||||
#include "pcie-designware.h"
|
||||
|
||||
+/* DBI registers */
|
||||
+#define PCIE20_CAP 0x70
|
||||
+#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10)
|
||||
+
|
||||
+#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818
|
||||
+#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c
|
||||
+
|
||||
+#define PCIE20_PLR_IATU_VIEWPORT 0x900
|
||||
+#define PCIE20_PLR_IATU_REGION_OUTBOUND (0x0 << 31)
|
||||
+#define PCIE20_PLR_IATU_REGION_INDEX(x) (x << 0)
|
||||
+
|
||||
+#define PCIE20_PLR_IATU_CTRL1 0x904
|
||||
+#define PCIE20_PLR_IATU_TYPE_CFG0 (0x4 << 0)
|
||||
+#define PCIE20_PLR_IATU_TYPE_MEM (0x0 << 0)
|
||||
+
|
||||
+#define PCIE20_PLR_IATU_CTRL2 0x908
|
||||
+#define PCIE20_PLR_IATU_ENABLE BIT(31)
|
||||
+
|
||||
+#define PCIE20_PLR_IATU_LBAR 0x90C
|
||||
+#define PCIE20_PLR_IATU_UBAR 0x910
|
||||
+#define PCIE20_PLR_IATU_LAR 0x914
|
||||
+#define PCIE20_PLR_IATU_LTAR 0x918
|
||||
+#define PCIE20_PLR_IATU_UTAR 0x91c
|
||||
+
|
||||
+#define MSM_PCIE_DEV_CFG_ADDR 0x01000000
|
||||
+
|
||||
+/* PARF registers */
|
||||
+#define PCIE20_PARF_PCS_DEEMPH 0x34
|
||||
+#define PCS_DEEMPH_TX_DEEMPH_GEN1(x) (x << 16)
|
||||
+#define PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) (x << 8)
|
||||
+#define PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) (x << 0)
|
||||
+
|
||||
+#define PCIE20_PARF_PCS_SWING 0x38
|
||||
+#define PCS_SWING_TX_SWING_FULL(x) (x << 8)
|
||||
+#define PCS_SWING_TX_SWING_LOW(x) (x << 0)
|
||||
+
|
||||
#define PCIE20_PARF_PHY_CTRL 0x40
|
||||
+#define PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK (0x1f << 16)
|
||||
+#define PHY_CTRL_PHY_TX0_TERM_OFFSET(x) (x << 16)
|
||||
+
|
||||
#define PCIE20_PARF_PHY_REFCLK 0x4C
|
||||
+#define REF_SSP_EN BIT(16)
|
||||
+#define REF_USE_PAD BIT(12)
|
||||
+
|
||||
+#define PCIE20_PARF_CONFIG_BITS 0x50
|
||||
+#define PHY_RX0_EQ(x) (x << 24)
|
||||
+
|
||||
#define PCIE20_PARF_DBI_BASE_ADDR 0x168
|
||||
#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c
|
||||
#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178
|
||||
@@ -39,9 +84,6 @@
|
||||
#define PCIE20_ELBI_SYS_STTS 0x08
|
||||
#define XMLH_LINK_UP BIT(10)
|
||||
|
||||
-#define PCIE20_CAP 0x70
|
||||
-#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10)
|
||||
-
|
||||
#define PERST_DELAY_MIN_US 1000
|
||||
#define PERST_DELAY_MAX_US 1005
|
||||
|
||||
@@ -56,14 +98,18 @@ struct qcom_pcie_resources_v0 {
|
||||
struct clk *iface_clk;
|
||||
struct clk *core_clk;
|
||||
struct clk *phy_clk;
|
||||
+ struct clk *aux_clk;
|
||||
+ struct clk *ref_clk;
|
||||
struct reset_control *pci_reset;
|
||||
struct reset_control *axi_reset;
|
||||
struct reset_control *ahb_reset;
|
||||
struct reset_control *por_reset;
|
||||
struct reset_control *phy_reset;
|
||||
+ struct reset_control *ext_reset;
|
||||
struct regulator *vdda;
|
||||
struct regulator *vdda_phy;
|
||||
struct regulator *vdda_refclk;
|
||||
+ uint8_t phy_tx0_term_offset;
|
||||
};
|
||||
|
||||
struct qcom_pcie_resources_v1 {
|
||||
@@ -106,20 +152,10 @@ writel_masked(void __iomem *addr, u32 cl
|
||||
|
||||
static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert)
|
||||
{
|
||||
- int val, active_low;
|
||||
-
|
||||
if (IS_ERR_OR_NULL(pcie->reset))
|
||||
return;
|
||||
|
||||
- active_low = gpiod_is_active_low(pcie->reset);
|
||||
-
|
||||
- if (assert)
|
||||
- val = !!active_low;
|
||||
- else
|
||||
- val = !active_low;
|
||||
-
|
||||
- gpiod_set_value(pcie->reset, val);
|
||||
-
|
||||
+ gpiod_set_value(pcie->reset, assert);
|
||||
usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US);
|
||||
}
|
||||
|
||||
@@ -156,10 +192,13 @@ static void qcom_pcie_disable_resources_
|
||||
reset_control_assert(res->axi_reset);
|
||||
reset_control_assert(res->ahb_reset);
|
||||
reset_control_assert(res->por_reset);
|
||||
- reset_control_assert(res->pci_reset);
|
||||
+ reset_control_assert(res->phy_reset);
|
||||
+ reset_control_assert(res->ext_reset);
|
||||
clk_disable_unprepare(res->iface_clk);
|
||||
clk_disable_unprepare(res->core_clk);
|
||||
clk_disable_unprepare(res->phy_clk);
|
||||
+ clk_disable_unprepare(res->aux_clk);
|
||||
+ clk_disable_unprepare(res->ref_clk);
|
||||
regulator_disable(res->vdda);
|
||||
regulator_disable(res->vdda_phy);
|
||||
regulator_disable(res->vdda_refclk);
|
||||
@@ -201,6 +240,12 @@ static int qcom_pcie_enable_resources_v0
|
||||
goto err_vdda_phy;
|
||||
}
|
||||
|
||||
+ ret = reset_control_deassert(res->ext_reset);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot assert ext reset\n");
|
||||
+ goto err_reset_ext;
|
||||
+ }
|
||||
+
|
||||
ret = clk_prepare_enable(res->iface_clk);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot prepare/enable iface clock\n");
|
||||
@@ -219,21 +264,40 @@ static int qcom_pcie_enable_resources_v0
|
||||
goto err_clk_phy;
|
||||
}
|
||||
|
||||
+ ret = clk_prepare_enable(res->aux_clk);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable aux clock\n");
|
||||
+ goto err_clk_aux;
|
||||
+ }
|
||||
+
|
||||
+ ret = clk_prepare_enable(res->ref_clk);
|
||||
+ if (ret) {
|
||||
+ dev_err(dev, "cannot prepare/enable ref clock\n");
|
||||
+ goto err_clk_ref;
|
||||
+ }
|
||||
+
|
||||
ret = reset_control_deassert(res->ahb_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert ahb reset\n");
|
||||
goto err_reset_ahb;
|
||||
}
|
||||
+ udelay(1);
|
||||
|
||||
return 0;
|
||||
|
||||
err_reset_ahb:
|
||||
+ clk_disable_unprepare(res->ref_clk);
|
||||
+err_clk_ref:
|
||||
+ clk_disable_unprepare(res->aux_clk);
|
||||
+err_clk_aux:
|
||||
clk_disable_unprepare(res->phy_clk);
|
||||
err_clk_phy:
|
||||
clk_disable_unprepare(res->core_clk);
|
||||
err_clk_core:
|
||||
clk_disable_unprepare(res->iface_clk);
|
||||
err_iface:
|
||||
+ reset_control_assert(res->ext_reset);
|
||||
+err_reset_ext:
|
||||
regulator_disable(res->vdda_phy);
|
||||
err_vdda_phy:
|
||||
regulator_disable(res->vdda_refclk);
|
||||
@@ -329,6 +393,14 @@ static int qcom_pcie_get_resources_v0(st
|
||||
if (IS_ERR(res->phy_clk))
|
||||
return PTR_ERR(res->phy_clk);
|
||||
|
||||
+ res->aux_clk = devm_clk_get(dev, "aux");
|
||||
+ if (IS_ERR(res->aux_clk))
|
||||
+ return PTR_ERR(res->aux_clk);
|
||||
+
|
||||
+ res->ref_clk = devm_clk_get(dev, "ref");
|
||||
+ if (IS_ERR(res->ref_clk))
|
||||
+ return PTR_ERR(res->ref_clk);
|
||||
+
|
||||
res->pci_reset = devm_reset_control_get(dev, "pci");
|
||||
if (IS_ERR(res->pci_reset))
|
||||
return PTR_ERR(res->pci_reset);
|
||||
@@ -349,6 +421,14 @@ static int qcom_pcie_get_resources_v0(st
|
||||
if (IS_ERR(res->phy_reset))
|
||||
return PTR_ERR(res->phy_reset);
|
||||
|
||||
+ res->ext_reset = devm_reset_control_get(dev, "ext");
|
||||
+ if (IS_ERR(res->ext_reset))
|
||||
+ return PTR_ERR(res->ext_reset);
|
||||
+
|
||||
+ if (of_property_read_u8(dev->of_node, "phy-tx0-term-offset",
|
||||
+ &res->phy_tx0_term_offset))
|
||||
+ res->phy_tx0_term_offset = 0;
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -461,6 +541,57 @@ err_res:
|
||||
qcom_pcie_disable_resources_v1(pcie);
|
||||
}
|
||||
|
||||
+static void qcom_pcie_prog_viewport_cfg0(struct qcom_pcie *pcie, u32 busdev)
|
||||
+{
|
||||
+ struct pcie_port *pp = &pcie->pp;
|
||||
+
|
||||
+ /*
|
||||
+ * program and enable address translation region 0 (device config
|
||||
+ * address space); region type config;
|
||||
+ * axi config address range to device config address range
|
||||
+ */
|
||||
+ writel(PCIE20_PLR_IATU_REGION_OUTBOUND |
|
||||
+ PCIE20_PLR_IATU_REGION_INDEX(0),
|
||||
+ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT);
|
||||
+
|
||||
+ writel(PCIE20_PLR_IATU_TYPE_CFG0, pcie->dbi + PCIE20_PLR_IATU_CTRL1);
|
||||
+ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2);
|
||||
+ writel(pp->cfg0_base, pcie->dbi + PCIE20_PLR_IATU_LBAR);
|
||||
+ writel((pp->cfg0_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR);
|
||||
+ writel((pp->cfg0_base + pp->cfg0_size - 1),
|
||||
+ pcie->dbi + PCIE20_PLR_IATU_LAR);
|
||||
+ writel(busdev, pcie->dbi + PCIE20_PLR_IATU_LTAR);
|
||||
+ writel(0, pcie->dbi + PCIE20_PLR_IATU_UTAR);
|
||||
+}
|
||||
+
|
||||
+static void qcom_pcie_prog_viewport_mem2_outbound(struct qcom_pcie *pcie)
|
||||
+{
|
||||
+ struct pcie_port *pp = &pcie->pp;
|
||||
+
|
||||
+ /*
|
||||
+ * program and enable address translation region 2 (device resource
|
||||
+ * address space); region type memory;
|
||||
+ * axi device bar address range to device bar address range
|
||||
+ */
|
||||
+ writel(PCIE20_PLR_IATU_REGION_OUTBOUND |
|
||||
+ PCIE20_PLR_IATU_REGION_INDEX(2),
|
||||
+ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT);
|
||||
+
|
||||
+ writel(PCIE20_PLR_IATU_TYPE_MEM, pcie->dbi + PCIE20_PLR_IATU_CTRL1);
|
||||
+ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2);
|
||||
+ writel(pp->mem_base, pcie->dbi + PCIE20_PLR_IATU_LBAR);
|
||||
+ writel((pp->mem_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR);
|
||||
+ writel(pp->mem_base + pp->mem_size - 1,
|
||||
+ pcie->dbi + PCIE20_PLR_IATU_LAR);
|
||||
+ writel(pp->mem_bus_addr, pcie->dbi + PCIE20_PLR_IATU_LTAR);
|
||||
+ writel(upper_32_bits(pp->mem_bus_addr),
|
||||
+ pcie->dbi + PCIE20_PLR_IATU_UTAR);
|
||||
+
|
||||
+ /* 256B PCIE buffer setting */
|
||||
+ writel(0x1, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL0);
|
||||
+ writel(0x1, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL1);
|
||||
+}
|
||||
+
|
||||
static void qcom_pcie_host_init_v0(struct pcie_port *pp)
|
||||
{
|
||||
struct qcom_pcie *pcie = to_qcom_pcie(pp);
|
||||
@@ -470,15 +601,34 @@ static void qcom_pcie_host_init_v0(struc
|
||||
|
||||
qcom_ep_reset_assert(pcie);
|
||||
|
||||
+ reset_control_assert(res->ahb_reset);
|
||||
+
|
||||
ret = qcom_pcie_enable_resources_v0(pcie);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0);
|
||||
|
||||
- /* enable external reference clock */
|
||||
- writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16));
|
||||
+ /* Set Tx termination offset */
|
||||
+ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL,
|
||||
+ PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK,
|
||||
+ PHY_CTRL_PHY_TX0_TERM_OFFSET(res->phy_tx0_term_offset));
|
||||
+
|
||||
+ /* PARF programming */
|
||||
+ writel(PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) |
|
||||
+ PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) |
|
||||
+ PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22),
|
||||
+ pcie->parf + PCIE20_PARF_PCS_DEEMPH);
|
||||
+ writel(PCS_SWING_TX_SWING_FULL(0x78) |
|
||||
+ PCS_SWING_TX_SWING_LOW(0x78),
|
||||
+ pcie->parf + PCIE20_PARF_PCS_SWING);
|
||||
+ writel(PHY_RX0_EQ(0x4), pcie->parf + PCIE20_PARF_CONFIG_BITS);
|
||||
+
|
||||
+ /* Enable reference clock */
|
||||
+ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK,
|
||||
+ REF_USE_PAD, REF_SSP_EN);
|
||||
|
||||
+ /* De-assert PHY, PCIe, POR and AXI resets */
|
||||
ret = reset_control_deassert(res->phy_reset);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot deassert phy reset\n");
|
||||
@@ -517,6 +667,9 @@ static void qcom_pcie_host_init_v0(struc
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
+ qcom_pcie_prog_viewport_cfg0(pcie, MSM_PCIE_DEV_CFG_ADDR);
|
||||
+ qcom_pcie_prog_viewport_mem2_outbound(pcie);
|
||||
+
|
||||
return;
|
||||
err:
|
||||
qcom_ep_reset_assert(pcie);
|
|
@ -1,80 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -475,15 +475,21 @@
|
||||
|
||||
clocks = <&gcc PCIE_A_CLK>,
|
||||
<&gcc PCIE_H_CLK>,
|
||||
- <&gcc PCIE_PHY_CLK>;
|
||||
- clock-names = "core", "iface", "phy";
|
||||
+ <&gcc PCIE_PHY_CLK>,
|
||||
+ <&gcc PCIE_AUX_CLK>,
|
||||
+ <&gcc PCIE_ALT_REF_CLK>;
|
||||
+ clock-names = "core", "iface", "phy", "aux", "ref";
|
||||
+
|
||||
+ assigned-clocks = <&gcc PCIE_ALT_REF_CLK>;
|
||||
+ assigned-clock-rates = <100000000>;
|
||||
|
||||
resets = <&gcc PCIE_ACLK_RESET>,
|
||||
<&gcc PCIE_HCLK_RESET>,
|
||||
<&gcc PCIE_POR_RESET>,
|
||||
<&gcc PCIE_PCI_RESET>,
|
||||
- <&gcc PCIE_PHY_RESET>;
|
||||
- reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+ <&gcc PCIE_PHY_RESET>,
|
||||
+ <&gcc PCIE_EXT_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy", "ext";
|
||||
|
||||
pinctrl-0 = <&pcie0_pins>;
|
||||
pinctrl-names = "default";
|
||||
@@ -521,15 +527,21 @@
|
||||
|
||||
clocks = <&gcc PCIE_1_A_CLK>,
|
||||
<&gcc PCIE_1_H_CLK>,
|
||||
- <&gcc PCIE_1_PHY_CLK>;
|
||||
- clock-names = "core", "iface", "phy";
|
||||
+ <&gcc PCIE_1_PHY_CLK>,
|
||||
+ <&gcc PCIE_1_AUX_CLK>,
|
||||
+ <&gcc PCIE_1_ALT_REF_CLK>;
|
||||
+ clock-names = "core", "iface", "phy", "aux", "ref";
|
||||
+
|
||||
+ assigned-clocks = <&gcc PCIE_1_ALT_REF_CLK>;
|
||||
+ assigned-clock-rates = <100000000>;
|
||||
|
||||
resets = <&gcc PCIE_1_ACLK_RESET>,
|
||||
<&gcc PCIE_1_HCLK_RESET>,
|
||||
<&gcc PCIE_1_POR_RESET>,
|
||||
<&gcc PCIE_1_PCI_RESET>,
|
||||
- <&gcc PCIE_1_PHY_RESET>;
|
||||
- reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+ <&gcc PCIE_1_PHY_RESET>,
|
||||
+ <&gcc PCIE_1_EXT_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy", "ext";
|
||||
|
||||
pinctrl-0 = <&pcie1_pins>;
|
||||
pinctrl-names = "default";
|
||||
@@ -567,15 +579,21 @@
|
||||
|
||||
clocks = <&gcc PCIE_2_A_CLK>,
|
||||
<&gcc PCIE_2_H_CLK>,
|
||||
- <&gcc PCIE_2_PHY_CLK>;
|
||||
- clock-names = "core", "iface", "phy";
|
||||
+ <&gcc PCIE_2_PHY_CLK>,
|
||||
+ <&gcc PCIE_2_AUX_CLK>,
|
||||
+ <&gcc PCIE_2_ALT_REF_CLK>;
|
||||
+ clock-names = "core", "iface", "phy", "aux", "ref";
|
||||
+
|
||||
+ assigned-clocks = <&gcc PCIE_2_ALT_REF_CLK>;
|
||||
+ assigned-clock-rates = <100000000>;
|
||||
|
||||
resets = <&gcc PCIE_2_ACLK_RESET>,
|
||||
<&gcc PCIE_2_HCLK_RESET>,
|
||||
<&gcc PCIE_2_POR_RESET>,
|
||||
<&gcc PCIE_2_PCI_RESET>,
|
||||
- <&gcc PCIE_2_PHY_RESET>;
|
||||
- reset-names = "axi", "ahb", "por", "pci", "phy";
|
||||
+ <&gcc PCIE_2_PHY_RESET>,
|
||||
+ <&gcc PCIE_2_EXT_RESET>;
|
||||
+ reset-names = "axi", "ahb", "por", "pci", "phy", "ext";
|
||||
|
||||
pinctrl-0 = <&pcie2_pins>;
|
||||
pinctrl-names = "default";
|
|
@ -1,87 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "skeleton.dtsi"
|
||||
#include <dt-bindings/clock/qcom,gcc-ipq806x.h>
|
||||
+#include <dt-bindings/mfd/qcom-rpm.h>
|
||||
#include <dt-bindings/clock/qcom,lcc-ipq806x.h>
|
||||
#include <dt-bindings/soc/qcom,gsbi.h>
|
||||
#include <dt-bindings/reset/qcom,gcc-ipq806x.h>
|
||||
@@ -93,6 +94,63 @@
|
||||
reg-names = "lpass-lpaif";
|
||||
};
|
||||
|
||||
+ rpm@108000 {
|
||||
+ compatible = "qcom,rpm-ipq8064";
|
||||
+ reg = <0x108000 0x1000>;
|
||||
+ qcom,ipc = <&l2cc 0x8 2>;
|
||||
+
|
||||
+ interrupts = <0 19 0>,
|
||||
+ <0 21 0>,
|
||||
+ <0 22 0>;
|
||||
+ interrupt-names = "ack",
|
||||
+ "err",
|
||||
+ "wakeup";
|
||||
+
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ smb208_s1a: smb208-s1a {
|
||||
+ compatible = "qcom,rpm-smb208";
|
||||
+ reg = <QCOM_RPM_SMB208_S1a>;
|
||||
+
|
||||
+ regulator-min-microvolt = <1050000>;
|
||||
+ regulator-max-microvolt = <1150000>;
|
||||
+
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+
|
||||
+ };
|
||||
+
|
||||
+ smb208_s1b: smb208-s1b {
|
||||
+ compatible = "qcom,rpm-smb208";
|
||||
+ reg = <QCOM_RPM_SMB208_S1b>;
|
||||
+
|
||||
+ regulator-min-microvolt = <1050000>;
|
||||
+ regulator-max-microvolt = <1150000>;
|
||||
+
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+ };
|
||||
+
|
||||
+ smb208_s2a: smb208-s2a {
|
||||
+ compatible = "qcom,rpm-smb208";
|
||||
+ reg = <QCOM_RPM_SMB208_S2a>;
|
||||
+
|
||||
+ regulator-min-microvolt = < 800000>;
|
||||
+ regulator-max-microvolt = <1250000>;
|
||||
+
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+ };
|
||||
+
|
||||
+ smb208_s2b: smb208-s2b {
|
||||
+ compatible = "qcom,rpm-smb208";
|
||||
+ reg = <QCOM_RPM_SMB208_S2b>;
|
||||
+
|
||||
+ regulator-min-microvolt = < 800000>;
|
||||
+ regulator-max-microvolt = <1250000>;
|
||||
+
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
qcom_pinmux: pinmux@800000 {
|
||||
compatible = "qcom,ipq8064-pinctrl";
|
||||
reg = <0x800000 0x4000>;
|
||||
@@ -165,6 +223,12 @@
|
||||
reg = <0x02098000 0x1000>, <0x02008000 0x1000>;
|
||||
};
|
||||
|
||||
+ l2cc: clock-controller@2011000 {
|
||||
+ compatible = "qcom,kpss-gcc", "syscon";
|
||||
+ reg = <0x2011000 0x1000>;
|
||||
+ clock-output-names = "acpu_l2_aux";
|
||||
+ };
|
||||
+
|
||||
saw0: regulator@2089000 {
|
||||
compatible = "qcom,saw2";
|
||||
reg = <0x02089000 0x1000>, <0x02009000 0x1000>;
|
|
@ -1,144 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,01/13] ARM: Add Krait L2 register accessor functions
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063051
|
||||
Message-Id: <1426920332-9340-2-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>,
|
||||
Mark Rutland <mark.rutland@arm.com>, Russell King <linux@arm.linux.org.uk>,
|
||||
Courtney Cavin <courtney.cavin@sonymobile.com>
|
||||
Date: Fri, 20 Mar 2015 23:45:20 -0700
|
||||
|
||||
Krait CPUs have a handful of L2 cache controller registers that
|
||||
live behind a cp15 based indirection register. First you program
|
||||
the indirection register (l2cpselr) to point the L2 'window'
|
||||
register (l2cpdr) at what you want to read/write. Then you
|
||||
read/write the 'window' register to do what you want. The
|
||||
l2cpselr register is not banked per-cpu so we must lock around
|
||||
accesses to it to prevent other CPUs from re-pointing l2cpdr
|
||||
underneath us.
|
||||
|
||||
Cc: Mark Rutland <mark.rutland@arm.com>
|
||||
Cc: Russell King <linux@arm.linux.org.uk>
|
||||
Cc: Courtney Cavin <courtney.cavin@sonymobile.com>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
arch/arm/common/Kconfig | 3 ++
|
||||
arch/arm/common/Makefile | 1 +
|
||||
arch/arm/common/krait-l2-accessors.c | 58 +++++++++++++++++++++++++++++++
|
||||
arch/arm/include/asm/krait-l2-accessors.h | 20 +++++++++++
|
||||
4 files changed, 82 insertions(+)
|
||||
create mode 100644 arch/arm/common/krait-l2-accessors.c
|
||||
create mode 100644 arch/arm/include/asm/krait-l2-accessors.h
|
||||
|
||||
--- a/arch/arm/common/Kconfig
|
||||
+++ b/arch/arm/common/Kconfig
|
||||
@@ -9,6 +9,9 @@ config DMABOUNCE
|
||||
bool
|
||||
select ZONE_DMA
|
||||
|
||||
+config KRAIT_L2_ACCESSORS
|
||||
+ bool
|
||||
+
|
||||
config SHARP_LOCOMO
|
||||
bool
|
||||
|
||||
--- a/arch/arm/common/Makefile
|
||||
+++ b/arch/arm/common/Makefile
|
||||
@@ -7,6 +7,7 @@ obj-y += firmware.o
|
||||
obj-$(CONFIG_ICST) += icst.o
|
||||
obj-$(CONFIG_SA1111) += sa1111.o
|
||||
obj-$(CONFIG_DMABOUNCE) += dmabounce.o
|
||||
+obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o
|
||||
obj-$(CONFIG_SHARP_LOCOMO) += locomo.o
|
||||
obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o
|
||||
obj-$(CONFIG_SHARP_SCOOP) += scoop.o
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/common/krait-l2-accessors.c
|
||||
@@ -0,0 +1,58 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2011-2013, 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/spinlock.h>
|
||||
+#include <linux/export.h>
|
||||
+
|
||||
+#include <asm/barrier.h>
|
||||
+#include <asm/krait-l2-accessors.h>
|
||||
+
|
||||
+static DEFINE_RAW_SPINLOCK(krait_l2_lock);
|
||||
+
|
||||
+void krait_set_l2_indirect_reg(u32 addr, u32 val)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
|
||||
+ /*
|
||||
+ * Select the L2 window by poking l2cpselr, then write to the window
|
||||
+ * via l2cpdr.
|
||||
+ */
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
|
||||
+ isb();
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val));
|
||||
+ isb();
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
|
||||
+}
|
||||
+EXPORT_SYMBOL(krait_set_l2_indirect_reg);
|
||||
+
|
||||
+u32 krait_get_l2_indirect_reg(u32 addr)
|
||||
+{
|
||||
+ u32 val;
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ raw_spin_lock_irqsave(&krait_l2_lock, flags);
|
||||
+ /*
|
||||
+ * Select the L2 window by poking l2cpselr, then read from the window
|
||||
+ * via l2cpdr.
|
||||
+ */
|
||||
+ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr));
|
||||
+ isb();
|
||||
+ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val));
|
||||
+
|
||||
+ raw_spin_unlock_irqrestore(&krait_l2_lock, flags);
|
||||
+
|
||||
+ return val;
|
||||
+}
|
||||
+EXPORT_SYMBOL(krait_get_l2_indirect_reg);
|
||||
--- /dev/null
|
||||
+++ b/arch/arm/include/asm/krait-l2-accessors.h
|
||||
@@ -0,0 +1,20 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2011-2013, 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.
|
||||
+ */
|
||||
+
|
||||
+#ifndef __ASMARM_KRAIT_L2_ACCESSORS_H
|
||||
+#define __ASMARM_KRAIT_L2_ACCESSORS_H
|
||||
+
|
||||
+extern void krait_set_l2_indirect_reg(u32 addr, u32 val);
|
||||
+extern u32 krait_get_l2_indirect_reg(u32 addr);
|
||||
+
|
||||
+#endif
|
|
@ -1,177 +0,0 @@
|
|||
From 4c28a15ea536281c8d619e5c6716ade914c79a6e Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:21 -0700
|
||||
Subject: [PATCH 1/2] clk: mux: Split out register accessors for reuse
|
||||
|
||||
We want to reuse the logic in clk-mux.c for other clock drivers
|
||||
that don't use readl as register accessors. Fortunately, there
|
||||
really isn't much to the mux code besides the table indirection
|
||||
and quirk flags if you assume any bit shifting and masking has
|
||||
been done already. Pull that logic out into reusable functions
|
||||
that operate on an optional table and some flags so that other
|
||||
drivers can use the same logic.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org>
|
||||
---
|
||||
drivers/clk/clk-mux.c | 74 +++++++++++++++++++++++++++-----------------
|
||||
include/linux/clk-provider.h | 9 ++++--
|
||||
2 files changed, 53 insertions(+), 30 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk-mux.c
|
||||
+++ b/drivers/clk/clk-mux.c
|
||||
@@ -28,35 +28,24 @@
|
||||
|
||||
#define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw)
|
||||
|
||||
-static u8 clk_mux_get_parent(struct clk_hw *hw)
|
||||
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
|
||||
+ unsigned int *table, unsigned long flags)
|
||||
{
|
||||
- struct clk_mux *mux = to_clk_mux(hw);
|
||||
int num_parents = clk_hw_get_num_parents(hw);
|
||||
- u32 val;
|
||||
|
||||
- /*
|
||||
- * FIXME need a mux-specific flag to determine if val is bitwise or numeric
|
||||
- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
|
||||
- * to 0x7 (index starts at one)
|
||||
- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
|
||||
- * val = 0x4 really means "bit 2, index starts at bit 0"
|
||||
- */
|
||||
- val = clk_readl(mux->reg) >> mux->shift;
|
||||
- val &= mux->mask;
|
||||
-
|
||||
- if (mux->table) {
|
||||
+ if (table) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_parents; i++)
|
||||
- if (mux->table[i] == val)
|
||||
+ if (table[i] == val)
|
||||
return i;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- if (val && (mux->flags & CLK_MUX_INDEX_BIT))
|
||||
+ if (val && (flags & CLK_MUX_INDEX_BIT))
|
||||
val = ffs(val) - 1;
|
||||
|
||||
- if (val && (mux->flags & CLK_MUX_INDEX_ONE))
|
||||
+ if (val && (flags & CLK_MUX_INDEX_ONE))
|
||||
val--;
|
||||
|
||||
if (val >= num_parents)
|
||||
@@ -64,24 +53,53 @@ static u8 clk_mux_get_parent(struct clk_
|
||||
|
||||
return val;
|
||||
}
|
||||
+EXPORT_SYMBOL_GPL(clk_mux_get_parent);
|
||||
|
||||
-static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+static u8 _clk_mux_get_parent(struct clk_hw *hw)
|
||||
{
|
||||
struct clk_mux *mux = to_clk_mux(hw);
|
||||
u32 val;
|
||||
- unsigned long flags = 0;
|
||||
|
||||
- if (mux->table)
|
||||
- index = mux->table[index];
|
||||
+ /*
|
||||
+ * FIXME need a mux-specific flag to determine if val is bitwise or numeric
|
||||
+ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1
|
||||
+ * to 0x7 (index starts at one)
|
||||
+ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so
|
||||
+ * val = 0x4 really means "bit 2, index starts at bit 0"
|
||||
+ */
|
||||
+ val = clk_readl(mux->reg) >> mux->shift;
|
||||
+ val &= mux->mask;
|
||||
|
||||
- else {
|
||||
- if (mux->flags & CLK_MUX_INDEX_BIT)
|
||||
- index = 1 << index;
|
||||
+ return clk_mux_get_parent(hw, val, mux->table, mux->flags);
|
||||
+}
|
||||
+
|
||||
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
|
||||
+ unsigned long flags)
|
||||
+{
|
||||
+ unsigned int val = index;
|
||||
|
||||
- if (mux->flags & CLK_MUX_INDEX_ONE)
|
||||
- index++;
|
||||
+ if (table) {
|
||||
+ val = table[val];
|
||||
+ } else {
|
||||
+ if (flags & CLK_MUX_INDEX_BIT)
|
||||
+ val = 1 << index;
|
||||
+
|
||||
+ if (flags & CLK_MUX_INDEX_ONE)
|
||||
+ val++;
|
||||
}
|
||||
|
||||
+ return val;
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(clk_mux_reindex);
|
||||
+
|
||||
+static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct clk_mux *mux = to_clk_mux(hw);
|
||||
+ u32 val;
|
||||
+ unsigned long flags = 0;
|
||||
+
|
||||
+ index = clk_mux_reindex(index, mux->table, mux->flags);
|
||||
+
|
||||
if (mux->lock)
|
||||
spin_lock_irqsave(mux->lock, flags);
|
||||
else
|
||||
@@ -105,7 +123,7 @@ static int clk_mux_set_parent(struct clk
|
||||
}
|
||||
|
||||
const struct clk_ops clk_mux_ops = {
|
||||
- .get_parent = clk_mux_get_parent,
|
||||
+ .get_parent = _clk_mux_get_parent,
|
||||
.set_parent = clk_mux_set_parent,
|
||||
.determine_rate = __clk_mux_determine_rate,
|
||||
};
|
||||
@@ -120,7 +138,7 @@ struct clk *clk_register_mux_table(struc
|
||||
const char * const *parent_names, u8 num_parents,
|
||||
unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
- u8 clk_mux_flags, u32 *table, spinlock_t *lock)
|
||||
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock)
|
||||
{
|
||||
struct clk_mux *mux;
|
||||
struct clk *clk;
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -433,7 +433,7 @@ void clk_unregister_divider(struct clk *
|
||||
struct clk_mux {
|
||||
struct clk_hw hw;
|
||||
void __iomem *reg;
|
||||
- u32 *table;
|
||||
+ unsigned int *table;
|
||||
u32 mask;
|
||||
u8 shift;
|
||||
u8 flags;
|
||||
@@ -449,6 +449,11 @@ struct clk_mux {
|
||||
extern const struct clk_ops clk_mux_ops;
|
||||
extern const struct clk_ops clk_mux_ro_ops;
|
||||
|
||||
+unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val,
|
||||
+ unsigned int *table, unsigned long flags);
|
||||
+unsigned int clk_mux_reindex(u8 index, unsigned int *table,
|
||||
+ unsigned long flags);
|
||||
+
|
||||
struct clk *clk_register_mux(struct device *dev, const char *name,
|
||||
const char * const *parent_names, u8 num_parents,
|
||||
unsigned long flags,
|
||||
@@ -459,7 +464,7 @@ struct clk *clk_register_mux_table(struc
|
||||
const char * const *parent_names, u8 num_parents,
|
||||
unsigned long flags,
|
||||
void __iomem *reg, u8 shift, u32 mask,
|
||||
- u8 clk_mux_flags, u32 *table, spinlock_t *lock);
|
||||
+ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock);
|
||||
|
||||
void clk_unregister_mux(struct clk *clk);
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
From 39d42ce5031d2a4f92fa203b87acfbab340b15a2 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:22 -0700
|
||||
Subject: [PATCH 2/2] clk: Avoid sending high rates to downstream clocks during
|
||||
set_rate
|
||||
|
||||
If a clock is on and we call clk_set_rate() on it we may get into
|
||||
a situation where the clock temporarily increases in rate
|
||||
dramatically while we walk the tree and call .set_rate() ops. For
|
||||
example, consider a case where a PLL feeds into a divider.
|
||||
Initially the divider is set to divide by 1 and the PLL is
|
||||
running fairly slow (100MHz). The downstream consumer of the
|
||||
divider output can only handle rates =< 400 MHz, but the divider
|
||||
can only choose between divisors of 1 and 4.
|
||||
|
||||
+-----+ +----------------+
|
||||
| PLL |-->| div 1 or div 4 |---> consumer device
|
||||
+-----+ +----------------+
|
||||
|
||||
To achieve a rate of 400MHz on the output of the divider, we
|
||||
would have to set the rate of the PLL to 1.6 GHz and then divide
|
||||
it by 4. The current code would set the PLL to 1.6GHz first while
|
||||
the divider is still set to 1, thus causing the downstream
|
||||
consumer of the clock to receive a few clock cycles of 1.6GHz
|
||||
clock (far beyond it's maximum acceptable rate). We should be
|
||||
changing the divider first before increasing the PLL rate to
|
||||
avoid this problem.
|
||||
|
||||
Therefore, set the rate of any child clocks that are increasing
|
||||
in rate from their current rate so that they can increase their
|
||||
dividers if necessary. We assume that there isn't such a thing as
|
||||
minimum rate requirements.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org>
|
||||
---
|
||||
drivers/clk/clk.c | 34 ++++++++++++++++++++++------------
|
||||
1 file changed, 22 insertions(+), 12 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -1427,21 +1427,24 @@ static struct clk_core *clk_propagate_ra
|
||||
* walk down a subtree and set the new rates notifying the rate
|
||||
* change on the way
|
||||
*/
|
||||
-static void clk_change_rate(struct clk_core *core)
|
||||
+static void
|
||||
+clk_change_rate(struct clk_core *core, unsigned long best_parent_rate)
|
||||
{
|
||||
struct clk_core *child;
|
||||
struct hlist_node *tmp;
|
||||
unsigned long old_rate;
|
||||
- unsigned long best_parent_rate = 0;
|
||||
bool skip_set_rate = false;
|
||||
struct clk_core *old_parent;
|
||||
|
||||
- old_rate = core->rate;
|
||||
+ hlist_for_each_entry(child, &core->children, child_node) {
|
||||
+ /* Skip children who will be reparented to another clock */
|
||||
+ if (child->new_parent && child->new_parent != core)
|
||||
+ continue;
|
||||
+ if (child->new_rate > child->rate)
|
||||
+ clk_change_rate(child, core->new_rate);
|
||||
+ }
|
||||
|
||||
- if (core->new_parent)
|
||||
- best_parent_rate = core->new_parent->rate;
|
||||
- else if (core->parent)
|
||||
- best_parent_rate = core->parent->rate;
|
||||
+ old_rate = core->rate;
|
||||
|
||||
if (core->new_parent && core->new_parent != core->parent) {
|
||||
old_parent = __clk_set_parent_before(core, core->new_parent);
|
||||
@@ -1467,7 +1470,7 @@ static void clk_change_rate(struct clk_c
|
||||
|
||||
trace_clk_set_rate_complete(core, core->new_rate);
|
||||
|
||||
- core->rate = clk_recalc(core, best_parent_rate);
|
||||
+ core->rate = core->new_rate;
|
||||
|
||||
if (core->notifier_count && old_rate != core->rate)
|
||||
__clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate);
|
||||
@@ -1483,12 +1486,13 @@ static void clk_change_rate(struct clk_c
|
||||
/* Skip children who will be reparented to another clock */
|
||||
if (child->new_parent && child->new_parent != core)
|
||||
continue;
|
||||
- clk_change_rate(child);
|
||||
+ if (child->new_rate != child->rate)
|
||||
+ clk_change_rate(child, core->new_rate);
|
||||
}
|
||||
|
||||
/* handle the new child who might not be in core->children yet */
|
||||
- if (core->new_child)
|
||||
- clk_change_rate(core->new_child);
|
||||
+ if (core->new_child && core->new_child->new_rate != core->new_child->rate)
|
||||
+ clk_change_rate(core->new_child, core->new_rate);
|
||||
}
|
||||
|
||||
static int clk_core_set_rate_nolock(struct clk_core *core,
|
||||
@@ -1497,6 +1501,7 @@ static int clk_core_set_rate_nolock(stru
|
||||
struct clk_core *top, *fail_clk;
|
||||
unsigned long rate = req_rate;
|
||||
int ret = 0;
|
||||
+ unsigned long parent_rate;
|
||||
|
||||
if (!core)
|
||||
return 0;
|
||||
@@ -1522,8 +1527,13 @@ static int clk_core_set_rate_nolock(stru
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
+ if (top->parent)
|
||||
+ parent_rate = top->parent->rate;
|
||||
+ else
|
||||
+ parent_rate = 0;
|
||||
+
|
||||
/* change the rates */
|
||||
- clk_change_rate(top);
|
||||
+ clk_change_rate(top, parent_rate);
|
||||
|
||||
core->req_rate = req_rate;
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
From f7a00ea959be31f9b742042294a359d508edce94 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:23 -0700
|
||||
Subject: [PATCH] clk: Add safe switch hook
|
||||
|
||||
Sometimes clocks can't accept their parent source turning off
|
||||
while the source is reprogrammed to a different rate. Most
|
||||
notably CPU clocks require a way to switch away from the current
|
||||
PLL they're running on, reprogram that PLL to a new rate, and
|
||||
then switch back to the PLL with the new rate once they're done.
|
||||
Add a hook that drivers can implement allowing them to return a
|
||||
'safe parent' that they can switch their parent to while the
|
||||
upstream source is reprogrammed to support this.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Signed-off-by: Ram Chandra Jangir <rjangi@codeaurora.org>
|
||||
---
|
||||
drivers/clk/clk.c | 61 ++++++++++++++++++++++++++++++++++++++------
|
||||
include/linux/clk-provider.h | 1 +
|
||||
2 files changed, 54 insertions(+), 8 deletions(-)
|
||||
|
||||
--- a/drivers/clk/clk.c
|
||||
+++ b/drivers/clk/clk.c
|
||||
@@ -51,9 +51,12 @@ struct clk_core {
|
||||
struct clk_core **parents;
|
||||
u8 num_parents;
|
||||
u8 new_parent_index;
|
||||
+ u8 safe_parent_index;
|
||||
unsigned long rate;
|
||||
unsigned long req_rate;
|
||||
+ unsigned long old_rate;
|
||||
unsigned long new_rate;
|
||||
+ struct clk_core *safe_parent;
|
||||
struct clk_core *new_parent;
|
||||
struct clk_core *new_child;
|
||||
unsigned long flags;
|
||||
@@ -1271,7 +1274,8 @@ out:
|
||||
static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate,
|
||||
struct clk_core *new_parent, u8 p_index)
|
||||
{
|
||||
- struct clk_core *child;
|
||||
+ struct clk_core *child, *parent;
|
||||
+ struct clk_hw *parent_hw;
|
||||
|
||||
core->new_rate = new_rate;
|
||||
core->new_parent = new_parent;
|
||||
@@ -1281,6 +1285,18 @@ static void clk_calc_subtree(struct clk_
|
||||
if (new_parent && new_parent != core->parent)
|
||||
new_parent->new_child = core;
|
||||
|
||||
+ if (core->ops->get_safe_parent) {
|
||||
+ parent_hw = core->ops->get_safe_parent(core->hw);
|
||||
+ if (parent_hw) {
|
||||
+ parent = parent_hw->core;
|
||||
+ p_index = clk_fetch_parent_index(core, parent);
|
||||
+ core->safe_parent_index = p_index;
|
||||
+ core->safe_parent = parent;
|
||||
+ }
|
||||
+ } else {
|
||||
+ core->safe_parent = NULL;
|
||||
+ }
|
||||
+
|
||||
hlist_for_each_entry(child, &core->children, child_node) {
|
||||
child->new_rate = clk_recalc(child, new_rate);
|
||||
clk_calc_subtree(child, child->new_rate, NULL, 0);
|
||||
@@ -1393,14 +1409,43 @@ static struct clk_core *clk_propagate_ra
|
||||
unsigned long event)
|
||||
{
|
||||
struct clk_core *child, *tmp_clk, *fail_clk = NULL;
|
||||
+ struct clk_core *old_parent;
|
||||
int ret = NOTIFY_DONE;
|
||||
|
||||
- if (core->rate == core->new_rate)
|
||||
+ if (core->rate == core->new_rate && event != POST_RATE_CHANGE)
|
||||
return NULL;
|
||||
|
||||
+ switch (event) {
|
||||
+ case PRE_RATE_CHANGE:
|
||||
+ if (core->safe_parent)
|
||||
+ core->ops->set_parent(core->hw, core->safe_parent_index);
|
||||
+ core->old_rate = core->rate;
|
||||
+ break;
|
||||
+ case POST_RATE_CHANGE:
|
||||
+ if (core->safe_parent) {
|
||||
+ old_parent = __clk_set_parent_before(core,
|
||||
+ core->new_parent);
|
||||
+ if (core->ops->set_rate_and_parent) {
|
||||
+ core->ops->set_rate_and_parent(core->hw,
|
||||
+ core->new_rate,
|
||||
+ core->new_parent ?
|
||||
+ core->new_parent->rate : 0,
|
||||
+ core->new_parent_index);
|
||||
+ } else if (core->ops->set_parent) {
|
||||
+ core->ops->set_parent(core->hw,
|
||||
+ core->new_parent_index);
|
||||
+ }
|
||||
+ __clk_set_parent_after(core, core->new_parent,
|
||||
+ old_parent);
|
||||
+ }
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
if (core->notifier_count) {
|
||||
- ret = __clk_notify(core, event, core->rate, core->new_rate);
|
||||
- if (ret & NOTIFY_STOP_MASK)
|
||||
+ if (event != POST_RATE_CHANGE || core->old_rate != core->rate)
|
||||
+ ret = __clk_notify(core, event, core->old_rate,
|
||||
+ core->new_rate);
|
||||
+ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE)
|
||||
fail_clk = core;
|
||||
}
|
||||
|
||||
@@ -1446,7 +1491,8 @@ clk_change_rate(struct clk_core *core, u
|
||||
|
||||
old_rate = core->rate;
|
||||
|
||||
- if (core->new_parent && core->new_parent != core->parent) {
|
||||
+ if (core->new_parent && core->new_parent != core->parent &&
|
||||
+ !core->safe_parent) {
|
||||
old_parent = __clk_set_parent_before(core, core->new_parent);
|
||||
trace_clk_set_parent(core, core->new_parent);
|
||||
|
||||
@@ -1472,9 +1518,6 @@ clk_change_rate(struct clk_core *core, u
|
||||
|
||||
core->rate = core->new_rate;
|
||||
|
||||
- if (core->notifier_count && old_rate != core->rate)
|
||||
- __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate);
|
||||
-
|
||||
if (core->flags & CLK_RECALC_NEW_RATES)
|
||||
(void)clk_calc_new_rates(core, core->new_rate);
|
||||
|
||||
@@ -1537,6 +1580,8 @@ static int clk_core_set_rate_nolock(stru
|
||||
|
||||
core->req_rate = req_rate;
|
||||
|
||||
+ clk_propagate_rate_change(top, POST_RATE_CHANGE);
|
||||
+
|
||||
return ret;
|
||||
}
|
||||
|
||||
--- a/include/linux/clk-provider.h
|
||||
+++ b/include/linux/clk-provider.h
|
||||
@@ -202,6 +202,7 @@ struct clk_ops {
|
||||
struct clk_rate_request *req);
|
||||
int (*set_parent)(struct clk_hw *hw, u8 index);
|
||||
u8 (*get_parent)(struct clk_hw *hw);
|
||||
+ struct clk_hw *(*get_safe_parent)(struct clk_hw *hw);
|
||||
int (*set_rate)(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate);
|
||||
int (*set_rate_and_parent)(struct clk_hw *hw,
|
|
@ -1,351 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs)
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063261
|
||||
Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:24 -0700
|
||||
|
||||
HFPLLs are the main frequency source for Krait CPU clocks. Add
|
||||
support for changing the rate of these PLLs.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
I'd really like to get rid of __clk_hfpll_init_once() if possible...
|
||||
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-hfpll.h | 54 +++++++++
|
||||
3 files changed, 308 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-hfpll.c
|
||||
create mode 100644 drivers/clk/qcom/clk-hfpll.h
|
||||
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
clk-qcom-y += clk-regmap-divider.o
|
||||
clk-qcom-y += clk-regmap-mux.o
|
||||
+clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
|
||||
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-hfpll.c
|
||||
@@ -0,0 +1,253 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/export.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-hfpll.h"
|
||||
+
|
||||
+#define PLL_OUTCTRL BIT(0)
|
||||
+#define PLL_BYPASSNL BIT(1)
|
||||
+#define PLL_RESET_N BIT(2)
|
||||
+
|
||||
+/* Initialize a HFPLL at a given rate and enable it. */
|
||||
+static void __clk_hfpll_init_once(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+
|
||||
+ if (likely(h->init_done))
|
||||
+ return;
|
||||
+
|
||||
+ /* Configure PLL parameters for integer mode. */
|
||||
+ if (hd->config_val)
|
||||
+ regmap_write(regmap, hd->config_reg, hd->config_val);
|
||||
+ regmap_write(regmap, hd->m_reg, 0);
|
||||
+ regmap_write(regmap, hd->n_reg, 1);
|
||||
+
|
||||
+ if (hd->user_reg) {
|
||||
+ u32 regval = hd->user_val;
|
||||
+ unsigned long rate;
|
||||
+
|
||||
+ rate = clk_hw_get_rate(hw);
|
||||
+
|
||||
+ /* Pick the right VCO. */
|
||||
+ if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
|
||||
+ regval |= hd->user_vco_mask;
|
||||
+ regmap_write(regmap, hd->user_reg, regval);
|
||||
+ }
|
||||
+
|
||||
+ if (hd->droop_reg)
|
||||
+ regmap_write(regmap, hd->droop_reg, hd->droop_val);
|
||||
+
|
||||
+ h->init_done = true;
|
||||
+}
|
||||
+
|
||||
+static void __clk_hfpll_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 val;
|
||||
+
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+
|
||||
+ /* Disable PLL bypass mode. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);
|
||||
+
|
||||
+ /*
|
||||
+ * H/W requires a 5us delay between disabling the bypass and
|
||||
+ * de-asserting the reset. Delay 10us just to be safe.
|
||||
+ */
|
||||
+ udelay(10);
|
||||
+
|
||||
+ /* De-assert active-low PLL reset. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);
|
||||
+
|
||||
+ /* Wait for PLL to lock. */
|
||||
+ if (hd->status_reg) {
|
||||
+ do {
|
||||
+ regmap_read(regmap, hd->status_reg, &val);
|
||||
+ } while (!(val & BIT(hd->lock_bit)));
|
||||
+ } else {
|
||||
+ udelay(60);
|
||||
+ }
|
||||
+
|
||||
+ /* Enable PLL output. */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
|
||||
+}
|
||||
+
|
||||
+/* Enable an already-configured HFPLL. */
|
||||
+static int clk_hfpll_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
|
||||
+ __clk_hfpll_enable(hw);
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void __clk_hfpll_disable(struct clk_hfpll *h)
|
||||
+{
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+
|
||||
+ /*
|
||||
+ * Disable the PLL output, disable test mode, enable the bypass mode,
|
||||
+ * and assert the reset.
|
||||
+ */
|
||||
+ regmap_update_bits(regmap, hd->mode_reg,
|
||||
+ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
|
||||
+}
|
||||
+
|
||||
+static void clk_hfpll_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ unsigned long flags;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+ __clk_hfpll_disable(h);
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+}
|
||||
+
|
||||
+static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ unsigned long rrate;
|
||||
+
|
||||
+ rate = clamp(rate, hd->min_rate, hd->max_rate);
|
||||
+
|
||||
+ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate;
|
||||
+ if (rrate > hd->max_rate)
|
||||
+ rrate -= *parent_rate;
|
||||
+
|
||||
+ return rrate;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * For optimization reasons, assumes no downstream clocks are actively using
|
||||
+ * it.
|
||||
+ */
|
||||
+static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ unsigned long flags;
|
||||
+ u32 l_val, val;
|
||||
+ bool enabled;
|
||||
+
|
||||
+ l_val = rate / parent_rate;
|
||||
+
|
||||
+ spin_lock_irqsave(&h->lock, flags);
|
||||
+
|
||||
+ enabled = __clk_is_enabled(hw->clk);
|
||||
+ if (enabled)
|
||||
+ __clk_hfpll_disable(h);
|
||||
+
|
||||
+ /* Pick the right VCO. */
|
||||
+ if (hd->user_reg && hd->user_vco_mask) {
|
||||
+ regmap_read(regmap, hd->user_reg, &val);
|
||||
+ if (rate <= hd->low_vco_max_rate)
|
||||
+ val &= ~hd->user_vco_mask;
|
||||
+ else
|
||||
+ val |= hd->user_vco_mask;
|
||||
+ regmap_write(regmap, hd->user_reg, val);
|
||||
+ }
|
||||
+
|
||||
+ regmap_write(regmap, hd->l_reg, l_val);
|
||||
+
|
||||
+ if (enabled)
|
||||
+ __clk_hfpll_enable(hw);
|
||||
+
|
||||
+ spin_unlock_irqrestore(&h->lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 l_val;
|
||||
+
|
||||
+ regmap_read(regmap, hd->l_reg, &l_val);
|
||||
+
|
||||
+ return l_val * parent_rate;
|
||||
+}
|
||||
+
|
||||
+static void clk_hfpll_init(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode, status;
|
||||
+
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (hd->status_reg) {
|
||||
+ regmap_read(regmap, hd->status_reg, &status);
|
||||
+ if (!(status & BIT(hd->lock_bit))) {
|
||||
+ WARN(1, "HFPLL %s is ON, but not locked!\n",
|
||||
+ __clk_get_name(hw->clk));
|
||||
+ clk_hfpll_disable(hw);
|
||||
+ __clk_hfpll_init_once(hw);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static int hfpll_is_enabled(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_hfpll *h = to_clk_hfpll(hw);
|
||||
+ struct hfpll_data const *hd = h->d;
|
||||
+ struct regmap *regmap = h->clkr.regmap;
|
||||
+ u32 mode;
|
||||
+
|
||||
+ regmap_read(regmap, hd->mode_reg, &mode);
|
||||
+ mode &= 0x7;
|
||||
+ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops clk_ops_hfpll = {
|
||||
+ .enable = clk_hfpll_enable,
|
||||
+ .disable = clk_hfpll_disable,
|
||||
+ .is_enabled = hfpll_is_enabled,
|
||||
+ .round_rate = clk_hfpll_round_rate,
|
||||
+ .set_rate = clk_hfpll_set_rate,
|
||||
+ .recalc_rate = clk_hfpll_recalc_rate,
|
||||
+ .init = clk_hfpll_init,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(clk_ops_hfpll);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-hfpll.h
|
||||
@@ -0,0 +1,54 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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.
|
||||
+ */
|
||||
+#ifndef __QCOM_CLK_HFPLL_H__
|
||||
+#define __QCOM_CLK_HFPLL_H__
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+#include "clk-regmap.h"
|
||||
+
|
||||
+struct hfpll_data {
|
||||
+ u32 mode_reg;
|
||||
+ u32 l_reg;
|
||||
+ u32 m_reg;
|
||||
+ u32 n_reg;
|
||||
+ u32 user_reg;
|
||||
+ u32 droop_reg;
|
||||
+ u32 config_reg;
|
||||
+ u32 status_reg;
|
||||
+ u8 lock_bit;
|
||||
+
|
||||
+ u32 droop_val;
|
||||
+ u32 config_val;
|
||||
+ u32 user_val;
|
||||
+ u32 user_vco_mask;
|
||||
+ unsigned long low_vco_max_rate;
|
||||
+
|
||||
+ unsigned long min_rate;
|
||||
+ unsigned long max_rate;
|
||||
+};
|
||||
+
|
||||
+struct clk_hfpll {
|
||||
+ struct hfpll_data const *d;
|
||||
+ int init_done;
|
||||
+
|
||||
+ struct clk_regmap clkr;
|
||||
+ spinlock_t lock;
|
||||
+};
|
||||
+
|
||||
+#define to_clk_hfpll(_hw) \
|
||||
+ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr)
|
||||
+
|
||||
+extern const struct clk_ops clk_ops_hfpll;
|
||||
+
|
||||
+#endif
|
|
@ -1,206 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,06/13] clk: qcom: Add HFPLL driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063231
|
||||
Message-Id: <1426920332-9340-7-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:25 -0700
|
||||
|
||||
On some devices (MSM8974 for example), the HFPLLs are
|
||||
instantiated within the Krait processor subsystem as separate
|
||||
register regions. Add a driver for these PLLs so that we can
|
||||
provide HFPLL clocks for use by the system.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/qcom,hfpll.txt | 40 ++++++++
|
||||
drivers/clk/qcom/Kconfig | 8 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/hfpll.c | 109 +++++++++++++++++++++
|
||||
4 files changed, 158 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/qcom,hfpll.txt
|
||||
create mode 100644 drivers/clk/qcom/hfpll.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt
|
||||
@@ -0,0 +1,40 @@
|
||||
+High-Frequency PLL (HFPLL)
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: must be "qcom,hfpll"
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: address and size of HPLL registers. An optional second
|
||||
+ element specifies the address and size of the alias
|
||||
+ register region.
|
||||
+
|
||||
+- clock-output-names:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the PLL. Typically hfpllX where X is a CPU number
|
||||
+ starting at 0. Otherwise hfpll_Y where Y is more specific
|
||||
+ such as "l2".
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+1) An HFPLL for the L2 cache.
|
||||
+
|
||||
+ clock-controller@f9016000 {
|
||||
+ compatible = "qcom,hfpll";
|
||||
+ reg = <0xf9016000 0x30>;
|
||||
+ clock-output-names = "hfpll_l2";
|
||||
+ };
|
||||
+
|
||||
+2) An HFPLL for CPU0. This HFPLL has the alias register region.
|
||||
+
|
||||
+ clock-controller@f908a000 {
|
||||
+ compatible = "qcom,hfpll";
|
||||
+ reg = <0xf908a000 0x30>, <0xf900a000 0x30>;
|
||||
+ clock-output-names = "hfpll0";
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -135,3 +135,11 @@ config MSM_MMCC_8974
|
||||
Support for the multimedia clock controller on msm8974 devices.
|
||||
Say Y if you want to support multimedia devices such as display,
|
||||
graphics, video encode/decode, camera, etc.
|
||||
+
|
||||
+config QCOM_HFPLL
|
||||
+ tristate "High-Frequency PLL (HFPLL) Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM
|
||||
+ help
|
||||
+ Support for the high-frequency PLLs present on Qualcomm devices.
|
||||
+ Say Y if you want to support CPU frequency scaling on devices
|
||||
+ such as MSM8974, APQ8084, etc.
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -25,3 +25,4 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8
|
||||
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
|
||||
+obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/hfpll.c
|
||||
@@ -0,0 +1,109 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-hfpll.h"
|
||||
+
|
||||
+static const struct hfpll_data hdata = {
|
||||
+ .mode_reg = 0x00,
|
||||
+ .l_reg = 0x04,
|
||||
+ .m_reg = 0x08,
|
||||
+ .n_reg = 0x0c,
|
||||
+ .user_reg = 0x10,
|
||||
+ .config_reg = 0x14,
|
||||
+ .config_val = 0x430405d,
|
||||
+ .status_reg = 0x1c,
|
||||
+ .lock_bit = 16,
|
||||
+
|
||||
+ .user_val = 0x8,
|
||||
+ .user_vco_mask = 0x100000,
|
||||
+ .low_vco_max_rate = 1248000000,
|
||||
+ .min_rate = 537600000UL,
|
||||
+ .max_rate = 2900000000UL,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id qcom_hfpll_match_table[] = {
|
||||
+ { .compatible = "qcom,hfpll" },
|
||||
+ { }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table);
|
||||
+
|
||||
+static const struct regmap_config hfpll_regmap_config = {
|
||||
+ .reg_bits = 32,
|
||||
+ .reg_stride = 4,
|
||||
+ .val_bits = 32,
|
||||
+ .max_register = 0x30,
|
||||
+ .fast_io = true,
|
||||
+};
|
||||
+
|
||||
+static int qcom_hfpll_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct clk *clk;
|
||||
+ struct resource *res;
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ void __iomem *base;
|
||||
+ struct regmap *regmap;
|
||||
+ struct clk_hfpll *h;
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = (const char *[]){ "xo" },
|
||||
+ .num_parents = 1,
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ };
|
||||
+
|
||||
+ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
|
||||
+ if (!h)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ base = devm_ioremap_resource(dev, res);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config);
|
||||
+ if (IS_ERR(regmap))
|
||||
+ return PTR_ERR(regmap);
|
||||
+
|
||||
+ if (of_property_read_string_index(dev->of_node, "clock-output-names",
|
||||
+ 0, &init.name))
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ h->d = &hdata;
|
||||
+ h->clkr.hw.init = &init;
|
||||
+ spin_lock_init(&h->lock);
|
||||
+
|
||||
+ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver qcom_hfpll_driver = {
|
||||
+ .probe = qcom_hfpll_probe,
|
||||
+ .driver = {
|
||||
+ .name = "qcom-hfpll",
|
||||
+ .of_match_table = qcom_hfpll_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(qcom_hfpll_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("QCOM HFPLL Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:qcom-hfpll");
|
|
@ -1,127 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,08/13] clk: qcom: Add IPQ806X's HFPLLs
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063241
|
||||
Message-Id: <1426920332-9340-9-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:27 -0700
|
||||
|
||||
Describe the HFPLLs present on IPQ806X devices.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/qcom/gcc-ipq806x.c | 83 ++++++++++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 83 insertions(+)
|
||||
|
||||
--- a/drivers/clk/qcom/gcc-ipq806x.c
|
||||
+++ b/drivers/clk/qcom/gcc-ipq806x.c
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "clk-pll.h"
|
||||
#include "clk-rcg.h"
|
||||
#include "clk-branch.h"
|
||||
+#include "clk-hfpll.h"
|
||||
#include "reset.h"
|
||||
|
||||
static struct clk_pll pll0 = {
|
||||
@@ -113,6 +114,85 @@ static struct clk_regmap pll8_vote = {
|
||||
},
|
||||
};
|
||||
|
||||
+static struct hfpll_data hfpll0_data = {
|
||||
+ .mode_reg = 0x3200,
|
||||
+ .l_reg = 0x3208,
|
||||
+ .m_reg = 0x320c,
|
||||
+ .n_reg = 0x3210,
|
||||
+ .config_reg = 0x3204,
|
||||
+ .status_reg = 0x321c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3214,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll0 = {
|
||||
+ .d = &hfpll0_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll0",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock),
|
||||
+};
|
||||
+
|
||||
+static struct hfpll_data hfpll1_data = {
|
||||
+ .mode_reg = 0x3240,
|
||||
+ .l_reg = 0x3248,
|
||||
+ .m_reg = 0x324c,
|
||||
+ .n_reg = 0x3250,
|
||||
+ .config_reg = 0x3244,
|
||||
+ .status_reg = 0x325c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3314,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll1 = {
|
||||
+ .d = &hfpll1_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll1",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock),
|
||||
+};
|
||||
+
|
||||
+static struct hfpll_data hfpll_l2_data = {
|
||||
+ .mode_reg = 0x3300,
|
||||
+ .l_reg = 0x3308,
|
||||
+ .m_reg = 0x330c,
|
||||
+ .n_reg = 0x3310,
|
||||
+ .config_reg = 0x3304,
|
||||
+ .status_reg = 0x331c,
|
||||
+ .config_val = 0x7845c665,
|
||||
+ .droop_reg = 0x3314,
|
||||
+ .droop_val = 0x0108c000,
|
||||
+ .min_rate = 600000000UL,
|
||||
+ .max_rate = 1800000000UL,
|
||||
+};
|
||||
+
|
||||
+static struct clk_hfpll hfpll_l2 = {
|
||||
+ .d = &hfpll_l2_data,
|
||||
+ .clkr.hw.init = &(struct clk_init_data){
|
||||
+ .parent_names = (const char *[]){ "pxo" },
|
||||
+ .num_parents = 1,
|
||||
+ .name = "hfpll_l2",
|
||||
+ .ops = &clk_ops_hfpll,
|
||||
+ .flags = CLK_IGNORE_UNUSED,
|
||||
+ },
|
||||
+ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock),
|
||||
+};
|
||||
+
|
||||
+
|
||||
static struct clk_pll pll14 = {
|
||||
.l_reg = 0x31c4,
|
||||
.m_reg = 0x31c8,
|
||||
@@ -2837,6 +2917,9 @@ static struct clk_regmap *gcc_ipq806x_cl
|
||||
[UBI32_CORE2_CLK_SRC] = &ubi32_core2_src_clk.clkr,
|
||||
[NSSTCM_CLK_SRC] = &nss_tcm_src.clkr,
|
||||
[NSSTCM_CLK] = &nss_tcm_clk.clkr,
|
||||
+ [PLL9] = &hfpll0.clkr,
|
||||
+ [PLL10] = &hfpll1.clkr,
|
||||
+ [PLL12] = &hfpll_l2.clkr,
|
||||
};
|
||||
|
||||
static const struct qcom_reset_map gcc_ipq806x_resets[] = {
|
|
@ -1,272 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,09/13] clk: qcom: Add support for Krait clocks
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063251
|
||||
Message-Id: <1426920332-9340-10-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:28 -0700
|
||||
|
||||
The Krait clocks are made up of a series of muxes and a divider
|
||||
that choose between a fixed rate clock and dedicated HFPLLs for
|
||||
each CPU. Instead of using mmio accesses to remux parents, the
|
||||
Krait implementation exposes the remux control via cp15
|
||||
registers. Support these clocks.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/clk/qcom/Kconfig | 4 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-krait.c | 166 +++++++++++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-krait.h | 49 +++++++++++++
|
||||
4 files changed, 220 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-krait.c
|
||||
create mode 100644 drivers/clk/qcom/clk-krait.h
|
||||
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -143,3 +143,7 @@ config QCOM_HFPLL
|
||||
Support for the high-frequency PLLs present on Qualcomm devices.
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as MSM8974, APQ8084, etc.
|
||||
+
|
||||
+config KRAIT_CLOCKS
|
||||
+ bool
|
||||
+ select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
clk-qcom-y += clk-regmap-divider.o
|
||||
clk-qcom-y += clk-regmap-mux.o
|
||||
+clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||
clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-krait.c
|
||||
@@ -0,0 +1,167 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013-2014, 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/module.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/spinlock.h>
|
||||
+
|
||||
+#include <asm/krait-l2-accessors.h>
|
||||
+
|
||||
+#include "clk-krait.h"
|
||||
+
|
||||
+/* Secondary and primary muxes share the same cp15 register */
|
||||
+static DEFINE_SPINLOCK(krait_clock_reg_lock);
|
||||
+
|
||||
+#define LPL_SHIFT 8
|
||||
+static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel)
|
||||
+{
|
||||
+ unsigned long flags;
|
||||
+ u32 regval;
|
||||
+
|
||||
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
|
||||
+ regval = krait_get_l2_indirect_reg(mux->offset);
|
||||
+ regval &= ~(mux->mask << mux->shift);
|
||||
+ regval |= (sel & mux->mask) << mux->shift;
|
||||
+ if (mux->lpl) {
|
||||
+ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT));
|
||||
+ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT);
|
||||
+ }
|
||||
+ krait_set_l2_indirect_reg(mux->offset, regval);
|
||||
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
|
||||
+
|
||||
+ /* Wait for switch to complete. */
|
||||
+ mb();
|
||||
+ udelay(1);
|
||||
+}
|
||||
+
|
||||
+static int krait_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ u32 sel;
|
||||
+
|
||||
+ sel = clk_mux_reindex(index, mux->parent_map, 0);
|
||||
+ mux->en_mask = sel;
|
||||
+ /* Don't touch mux if CPU is off as it won't work */
|
||||
+ if (__clk_is_enabled(hw->clk))
|
||||
+ __krait_mux_set_sel(mux, sel);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static u8 krait_mux_get_parent(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ u32 sel;
|
||||
+
|
||||
+ sel = krait_get_l2_indirect_reg(mux->offset);
|
||||
+ sel >>= mux->shift;
|
||||
+ sel &= mux->mask;
|
||||
+ mux->en_mask = sel;
|
||||
+
|
||||
+ return clk_mux_get_parent(hw, sel, mux->parent_map, 0);
|
||||
+}
|
||||
+
|
||||
+static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw,
|
||||
+ unsigned long *safe_freq)
|
||||
+{
|
||||
+ int i;
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+ int num_parents = clk_hw_get_num_parents(hw);
|
||||
+
|
||||
+ i = mux->safe_sel;
|
||||
+ for (i = 0; i < num_parents; i++)
|
||||
+ if (mux->safe_sel == mux->parent_map[i])
|
||||
+ break;
|
||||
+
|
||||
+ return clk_hw_get_parent_by_index(hw, i);
|
||||
+}
|
||||
+
|
||||
+static int krait_mux_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+
|
||||
+ __krait_mux_set_sel(mux, mux->en_mask);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void krait_mux_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||
+
|
||||
+ __krait_mux_set_sel(mux, mux->safe_sel);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops krait_mux_clk_ops = {
|
||||
+ .enable = krait_mux_enable,
|
||||
+ .disable = krait_mux_disable,
|
||||
+ .set_parent = krait_mux_set_parent,
|
||||
+ .get_parent = krait_mux_get_parent,
|
||||
+ .determine_rate = __clk_mux_determine_rate_closest,
|
||||
+ .get_safe_parent = krait_mux_get_safe_parent,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(krait_mux_clk_ops);
|
||||
+
|
||||
+/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */
|
||||
+static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long *parent_rate)
|
||||
+{
|
||||
+ *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), rate * 2);
|
||||
+ return DIV_ROUND_UP(*parent_rate, 2);
|
||||
+}
|
||||
+
|
||||
+static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long parent_rate)
|
||||
+{
|
||||
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
|
||||
+ unsigned long flags;
|
||||
+ u32 val;
|
||||
+ u32 mask = BIT(d->width) - 1;
|
||||
+
|
||||
+ if (d->lpl)
|
||||
+ mask = mask << (d->shift + LPL_SHIFT) | mask << d->shift;
|
||||
+
|
||||
+ spin_lock_irqsave(&krait_clock_reg_lock, flags);
|
||||
+ val = krait_get_l2_indirect_reg(d->offset);
|
||||
+ val &= ~mask;
|
||||
+ krait_set_l2_indirect_reg(d->offset, val);
|
||||
+ spin_unlock_irqrestore(&krait_clock_reg_lock, flags);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static unsigned long
|
||||
+krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
+{
|
||||
+ struct krait_div2_clk *d = to_krait_div2_clk(hw);
|
||||
+ u32 mask = BIT(d->width) - 1;
|
||||
+ u32 div;
|
||||
+
|
||||
+ div = krait_get_l2_indirect_reg(d->offset);
|
||||
+ div >>= d->shift;
|
||||
+ div &= mask;
|
||||
+ div = (div + 1) * 2;
|
||||
+
|
||||
+ return DIV_ROUND_UP(parent_rate, div);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops krait_div2_clk_ops = {
|
||||
+ .round_rate = krait_div2_round_rate,
|
||||
+ .set_rate = krait_div2_set_rate,
|
||||
+ .recalc_rate = krait_div2_recalc_rate,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(krait_div2_clk_ops);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-krait.h
|
||||
@@ -0,0 +1,49 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2013, 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.
|
||||
+ */
|
||||
+
|
||||
+#ifndef __QCOM_CLK_KRAIT_H
|
||||
+#define __QCOM_CLK_KRAIT_H
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+
|
||||
+struct krait_mux_clk {
|
||||
+ unsigned int *parent_map;
|
||||
+ bool has_safe_parent;
|
||||
+ u8 safe_sel;
|
||||
+ u32 offset;
|
||||
+ u32 mask;
|
||||
+ u32 shift;
|
||||
+ u32 en_mask;
|
||||
+ bool lpl;
|
||||
+
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw)
|
||||
+
|
||||
+extern const struct clk_ops krait_mux_clk_ops;
|
||||
+
|
||||
+struct krait_div2_clk {
|
||||
+ u32 offset;
|
||||
+ u8 width;
|
||||
+ u32 shift;
|
||||
+ bool lpl;
|
||||
+
|
||||
+ struct clk_hw hw;
|
||||
+};
|
||||
+
|
||||
+#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw)
|
||||
+
|
||||
+extern const struct clk_ops krait_div2_clk_ops;
|
||||
+
|
||||
+#endif
|
|
@ -1,207 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,10/13] clk: qcom: Add KPSS ACC/GCC driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063201
|
||||
Message-Id: <1426920332-9340-11-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:29 -0700
|
||||
|
||||
The ACC and GCC regions present in KPSSv1 contain registers to
|
||||
control clocks and power to each Krait CPU and L2. For CPUfreq
|
||||
purposes probe these devices and expose a mux clock that chooses
|
||||
between PXO and PLL8.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/arm/msm/qcom,kpss-acc.txt | 7 ++
|
||||
.../devicetree/bindings/arm/msm/qcom,kpss-gcc.txt | 28 +++++++
|
||||
drivers/clk/qcom/Kconfig | 8 ++
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/kpss-xcc.c | 95 ++++++++++++++++++++++
|
||||
5 files changed, 139 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt
|
||||
create mode 100644 drivers/clk/qcom/kpss-xcc.c
|
||||
|
||||
--- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt
|
||||
@@ -21,10 +21,17 @@ PROPERTIES
|
||||
the register region. An optional second element specifies
|
||||
the base address and size of the alias register region.
|
||||
|
||||
+- clock-output-names:
|
||||
+ Usage: optional
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the output clock. Typically acpuX_aux where X is a
|
||||
+ CPU number starting at 0.
|
||||
+
|
||||
Example:
|
||||
|
||||
clock-controller@2088000 {
|
||||
compatible = "qcom,kpss-acc-v2";
|
||||
reg = <0x02088000 0x1000>,
|
||||
<0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu0_aux";
|
||||
};
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt
|
||||
@@ -0,0 +1,28 @@
|
||||
+Krait Processor Sub-system (KPSS) Global Clock Controller (GCC)
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: should be one of:
|
||||
+ "qcom,kpss-gcc"
|
||||
+
|
||||
+- reg:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: base address and size of the register region
|
||||
+
|
||||
+- clock-output-names:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: Name of the output clock. Typically acpu_l2_aux indicating
|
||||
+ an L2 cache auxiliary clock.
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ l2cc: clock-controller@2011000 {
|
||||
+ compatible = "qcom,kpss-gcc";
|
||||
+ reg = <0x2011000 0x1000>;
|
||||
+ clock-output-names = "acpu_l2_aux";
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -144,6 +144,14 @@ config QCOM_HFPLL
|
||||
Say Y if you want to support CPU frequency scaling on devices
|
||||
such as MSM8974, APQ8084, etc.
|
||||
|
||||
+config KPSS_XCC
|
||||
+ tristate "KPSS Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM
|
||||
+ help
|
||||
+ Support for the Krait ACC and GCC clock controllers. Say Y
|
||||
+ if you want to support CPU frequency scaling on devices such
|
||||
+ as MSM8960, APQ8064, etc.
|
||||
+
|
||||
config KRAIT_CLOCKS
|
||||
bool
|
||||
select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -9,6 +9,7 @@ clk-qcom-y += clk-branch.o
|
||||
clk-qcom-y += clk-regmap-divider.o
|
||||
clk-qcom-y += clk-regmap-mux.o
|
||||
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||
+obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||
clk-qcom-y += clk-hfpll.o
|
||||
clk-qcom-y += reset.o
|
||||
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/kpss-xcc.c
|
||||
@@ -0,0 +1,95 @@
|
||||
+/* Copyright (c) 2014-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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+
|
||||
+static const char *aux_parents[] = {
|
||||
+ "pll8_vote",
|
||||
+ "pxo",
|
||||
+};
|
||||
+
|
||||
+static unsigned int aux_parent_map[] = {
|
||||
+ 3,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static const struct of_device_id kpss_xcc_match_table[] = {
|
||||
+ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL },
|
||||
+ { .compatible = "qcom,kpss-gcc" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, kpss_xcc_match_table);
|
||||
+
|
||||
+static int kpss_xcc_driver_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ const struct of_device_id *id;
|
||||
+ struct clk *clk;
|
||||
+ struct resource *res;
|
||||
+ void __iomem *base;
|
||||
+ const char *name;
|
||||
+
|
||||
+ id = of_match_device(kpss_xcc_match_table, &pdev->dev);
|
||||
+ if (!id)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
+ base = devm_ioremap_resource(&pdev->dev, res);
|
||||
+ if (IS_ERR(base))
|
||||
+ return PTR_ERR(base);
|
||||
+
|
||||
+ if (id->data) {
|
||||
+ if (of_property_read_string_index(pdev->dev.of_node,
|
||||
+ "clock-output-names", 0, &name))
|
||||
+ return -ENODEV;
|
||||
+ base += 0x14;
|
||||
+ } else {
|
||||
+ name = "acpu_l2_aux";
|
||||
+ base += 0x28;
|
||||
+ }
|
||||
+
|
||||
+ clk = clk_register_mux_table(&pdev->dev, name, aux_parents,
|
||||
+ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3,
|
||||
+ 0, aux_parent_map, NULL);
|
||||
+
|
||||
+ platform_set_drvdata(pdev, clk);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static int kpss_xcc_driver_remove(struct platform_device *pdev)
|
||||
+{
|
||||
+ clk_unregister_mux(platform_get_drvdata(pdev));
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver kpss_xcc_driver = {
|
||||
+ .probe = kpss_xcc_driver_probe,
|
||||
+ .remove = kpss_xcc_driver_remove,
|
||||
+ .driver = {
|
||||
+ .name = "kpss-xcc",
|
||||
+ .of_match_table = kpss_xcc_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(kpss_xcc_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:kpss-xcc");
|
|
@ -1,435 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,11/13] clk: qcom: Add Krait clock controller driver
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063121
|
||||
Message-Id: <1426920332-9340-12-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:30 -0700
|
||||
|
||||
The Krait CPU clocks are made up of a primary mux and secondary
|
||||
mux for each CPU and the L2, controlled via cp15 accessors. For
|
||||
Kraits within KPSSv1 each secondary mux accepts a different aux
|
||||
source, but on KPSSv2 each secondary mux accepts the same aux
|
||||
source.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/clock/qcom,krait-cc.txt | 22 ++
|
||||
drivers/clk/qcom/Kconfig | 8 +
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/krait-cc.c | 352 +++++++++++++++++++++
|
||||
4 files changed, 383 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/clock/qcom,krait-cc.txt
|
||||
create mode 100644 drivers/clk/qcom/krait-cc.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt
|
||||
@@ -0,0 +1,22 @@
|
||||
+Krait Clock Controller
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- compatible:
|
||||
+ Usage: required
|
||||
+ Value type: <string>
|
||||
+ Definition: must be one of:
|
||||
+ "qcom,krait-cc-v1"
|
||||
+ "qcom,krait-cc-v2"
|
||||
+
|
||||
+- #clock-cells:
|
||||
+ Usage: required
|
||||
+ Value type: <u32>
|
||||
+ Definition: must be 1
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+ kraitcc: clock-controller {
|
||||
+ compatible = "qcom,krait-cc-v1";
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
--- a/drivers/clk/qcom/Kconfig
|
||||
+++ b/drivers/clk/qcom/Kconfig
|
||||
@@ -152,6 +152,14 @@ config KPSS_XCC
|
||||
if you want to support CPU frequency scaling on devices such
|
||||
as MSM8960, APQ8064, etc.
|
||||
|
||||
+config KRAITCC
|
||||
+ tristate "Krait Clock Controller"
|
||||
+ depends on COMMON_CLK_QCOM && ARM
|
||||
+ select KRAIT_CLOCKS
|
||||
+ help
|
||||
+ Support for the Krait CPU clocks on Qualcomm devices.
|
||||
+ Say Y if you want to support CPU frequency scaling.
|
||||
+
|
||||
config KRAIT_CLOCKS
|
||||
bool
|
||||
select KRAIT_L2_ACCESSORS
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -28,3 +28,4 @@ obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8
|
||||
obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o
|
||||
obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o
|
||||
obj-$(CONFIG_QCOM_HFPLL) += hfpll.o
|
||||
+obj-$(CONFIG_KRAITCC) += krait-cc.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/krait-cc.c
|
||||
@@ -0,0 +1,352 @@
|
||||
+/* 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/init.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/of_device.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include <linux/slab.h>
|
||||
+
|
||||
+#include "clk-krait.h"
|
||||
+
|
||||
+static unsigned int sec_mux_map[] = {
|
||||
+ 2,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static unsigned int pri_mux_map[] = {
|
||||
+ 1,
|
||||
+ 2,
|
||||
+ 0,
|
||||
+};
|
||||
+
|
||||
+static int
|
||||
+krait_add_div(struct device *dev, int id, const char *s, unsigned offset)
|
||||
+{
|
||||
+ struct krait_div2_clk *div;
|
||||
+ struct clk_init_data init = {
|
||||
+ .num_parents = 1,
|
||||
+ .ops = &krait_div2_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ const char *p_names[1];
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL);
|
||||
+ if (!div)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ div->width = 2;
|
||||
+ div->shift = 6;
|
||||
+ div->lpl = id >= 0;
|
||||
+ div->offset = offset;
|
||||
+ div->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
|
||||
+ if (!init.name)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ init.parent_names = p_names;
|
||||
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
|
||||
+ if (!p_names[0]) {
|
||||
+ kfree(init.name);
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &div->hw);
|
||||
+ kfree(p_names[0]);
|
||||
+ kfree(init.name);
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static int
|
||||
+krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset,
|
||||
+ bool unique_aux)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux;
|
||||
+ static const char *sec_mux_list[] = {
|
||||
+ "acpu_aux",
|
||||
+ "qsb",
|
||||
+ };
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = sec_mux_list,
|
||||
+ .num_parents = ARRAY_SIZE(sec_mux_list),
|
||||
+ .ops = &krait_mux_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
+ if (!mux)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ mux->offset = offset;
|
||||
+ mux->lpl = id >= 0;
|
||||
+ mux->has_safe_parent = true;
|
||||
+ mux->safe_sel = 2;
|
||||
+ mux->mask = 0x3;
|
||||
+ mux->shift = 2;
|
||||
+ mux->parent_map = sec_mux_map;
|
||||
+ mux->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
|
||||
+ if (!init.name)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ if (unique_aux) {
|
||||
+ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s);
|
||||
+ if (!sec_mux_list[0]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_aux;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &mux->hw);
|
||||
+
|
||||
+ if (unique_aux)
|
||||
+ kfree(sec_mux_list[0]);
|
||||
+err_aux:
|
||||
+ kfree(init.name);
|
||||
+ return PTR_ERR_OR_ZERO(clk);
|
||||
+}
|
||||
+
|
||||
+static struct clk *
|
||||
+krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset)
|
||||
+{
|
||||
+ struct krait_mux_clk *mux;
|
||||
+ const char *p_names[3];
|
||||
+ struct clk_init_data init = {
|
||||
+ .parent_names = p_names,
|
||||
+ .num_parents = ARRAY_SIZE(p_names),
|
||||
+ .ops = &krait_mux_clk_ops,
|
||||
+ .flags = CLK_SET_RATE_PARENT,
|
||||
+ };
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
|
||||
+ if (!mux)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ mux->has_safe_parent = true;
|
||||
+ mux->safe_sel = 0;
|
||||
+ mux->mask = 0x3;
|
||||
+ mux->shift = 0;
|
||||
+ mux->offset = offset;
|
||||
+ mux->lpl = id >= 0;
|
||||
+ mux->parent_map = pri_mux_map;
|
||||
+ mux->hw.init = &init;
|
||||
+
|
||||
+ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s);
|
||||
+ if (!init.name)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+
|
||||
+ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s);
|
||||
+ if (!p_names[0]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p0;
|
||||
+ }
|
||||
+
|
||||
+ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s);
|
||||
+ if (!p_names[1]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p1;
|
||||
+ }
|
||||
+
|
||||
+ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s);
|
||||
+ if (!p_names[2]) {
|
||||
+ clk = ERR_PTR(-ENOMEM);
|
||||
+ goto err_p2;
|
||||
+ }
|
||||
+
|
||||
+ clk = devm_clk_register(dev, &mux->hw);
|
||||
+
|
||||
+ kfree(p_names[2]);
|
||||
+err_p2:
|
||||
+ kfree(p_names[1]);
|
||||
+err_p1:
|
||||
+ kfree(p_names[0]);
|
||||
+err_p0:
|
||||
+ kfree(init.name);
|
||||
+ return clk;
|
||||
+}
|
||||
+
|
||||
+/* id < 0 for L2, otherwise id == physical CPU number */
|
||||
+static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux)
|
||||
+{
|
||||
+ int ret;
|
||||
+ unsigned offset;
|
||||
+ void *p = NULL;
|
||||
+ const char *s;
|
||||
+ struct clk *clk;
|
||||
+
|
||||
+ if (id >= 0) {
|
||||
+ offset = 0x4501 + (0x1000 * id);
|
||||
+ s = p = kasprintf(GFP_KERNEL, "%d", id);
|
||||
+ if (!s)
|
||||
+ return ERR_PTR(-ENOMEM);
|
||||
+ } else {
|
||||
+ offset = 0x500;
|
||||
+ s = "_l2";
|
||||
+ }
|
||||
+
|
||||
+ ret = krait_add_div(dev, id, s, offset);
|
||||
+ if (ret) {
|
||||
+ clk = ERR_PTR(ret);
|
||||
+ goto err;
|
||||
+ }
|
||||
+
|
||||
+ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux);
|
||||
+ if (ret) {
|
||||
+ clk = ERR_PTR(ret);
|
||||
+ goto err;
|
||||
+ }
|
||||
+
|
||||
+ clk = krait_add_pri_mux(dev, id, s, offset);
|
||||
+err:
|
||||
+ kfree(p);
|
||||
+ return clk;
|
||||
+}
|
||||
+
|
||||
+static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data)
|
||||
+{
|
||||
+ unsigned int idx = clkspec->args[0];
|
||||
+ struct clk **clks = data;
|
||||
+
|
||||
+ if (idx >= 5) {
|
||||
+ pr_err("%s: invalid clock index %d\n", __func__, idx);
|
||||
+ return ERR_PTR(-EINVAL);
|
||||
+ }
|
||||
+
|
||||
+ return clks[idx] ? : ERR_PTR(-ENODEV);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id krait_cc_match_table[] = {
|
||||
+ { .compatible = "qcom,krait-cc-v1", (void *)1UL },
|
||||
+ { .compatible = "qcom,krait-cc-v2" },
|
||||
+ {}
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, krait_cc_match_table);
|
||||
+
|
||||
+static int krait_cc_probe(struct platform_device *pdev)
|
||||
+{
|
||||
+ struct device *dev = &pdev->dev;
|
||||
+ const struct of_device_id *id;
|
||||
+ unsigned long cur_rate, aux_rate;
|
||||
+ int cpu;
|
||||
+ struct clk *clk;
|
||||
+ struct clk **clks;
|
||||
+ struct clk *l2_pri_mux_clk;
|
||||
+
|
||||
+ id = of_match_device(krait_cc_match_table, dev);
|
||||
+ if (!id)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */
|
||||
+ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+
|
||||
+ if (!id->data) {
|
||||
+ clk = clk_register_fixed_factor(dev, "acpu_aux",
|
||||
+ "gpll0_vote", 0, 1, 2);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ }
|
||||
+
|
||||
+ /* Krait configurations have at most 4 CPUs and one L2 */
|
||||
+ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL);
|
||||
+ if (!clks)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ clk = krait_add_clks(dev, cpu, id->data);
|
||||
+ if (IS_ERR(clk))
|
||||
+ return PTR_ERR(clk);
|
||||
+ clks[cpu] = clk;
|
||||
+ }
|
||||
+
|
||||
+ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data);
|
||||
+ if (IS_ERR(l2_pri_mux_clk))
|
||||
+ return PTR_ERR(l2_pri_mux_clk);
|
||||
+ clks[4] = l2_pri_mux_clk;
|
||||
+
|
||||
+ /*
|
||||
+ * We don't want the CPU or L2 clocks to be turned off at late init
|
||||
+ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the
|
||||
+ * refcount of these clocks. Any cpufreq/hotplug manager can assume
|
||||
+ * that the clocks have already been prepared and enabled by the time
|
||||
+ * they take over.
|
||||
+ */
|
||||
+ for_each_online_cpu(cpu) {
|
||||
+ clk_prepare_enable(l2_pri_mux_clk);
|
||||
+ WARN(clk_prepare_enable(clks[cpu]),
|
||||
+ "Unable to turn on CPU%d clock", cpu);
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Force reinit of HFPLLs and muxes to overwrite any potential
|
||||
+ * incorrect configuration of HFPLLs and muxes by the bootloader.
|
||||
+ * While at it, also make sure the cores are running at known rates
|
||||
+ * and print the current rate.
|
||||
+ *
|
||||
+ * The clocks are set to aux clock rate first to make sure the
|
||||
+ * secondary mux is not sourcing off of QSB. The rate is then set to
|
||||
+ * two different rates to force a HFPLL reinit under all
|
||||
+ * circumstances.
|
||||
+ */
|
||||
+ cur_rate = clk_get_rate(l2_pri_mux_clk);
|
||||
+ aux_rate = 384000000;
|
||||
+ if (cur_rate == 1) {
|
||||
+ pr_info("L2 @ QSB rate. Forcing new rate.\n");
|
||||
+ cur_rate = aux_rate;
|
||||
+ }
|
||||
+ clk_set_rate(l2_pri_mux_clk, aux_rate);
|
||||
+ clk_set_rate(l2_pri_mux_clk, 2);
|
||||
+ clk_set_rate(l2_pri_mux_clk, cur_rate);
|
||||
+ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000);
|
||||
+ for_each_possible_cpu(cpu) {
|
||||
+ clk = clks[cpu];
|
||||
+ cur_rate = clk_get_rate(clk);
|
||||
+ if (cur_rate == 1) {
|
||||
+ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu);
|
||||
+ cur_rate = aux_rate;
|
||||
+ }
|
||||
+ clk_set_rate(clk, aux_rate);
|
||||
+ clk_set_rate(clk, 2);
|
||||
+ clk_set_rate(clk, cur_rate);
|
||||
+ pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000);
|
||||
+ }
|
||||
+
|
||||
+ of_clk_add_provider(dev->of_node, krait_of_get, clks);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct platform_driver krait_cc_driver = {
|
||||
+ .probe = krait_cc_probe,
|
||||
+ .driver = {
|
||||
+ .name = "krait-cc",
|
||||
+ .of_match_table = krait_cc_match_table,
|
||||
+ },
|
||||
+};
|
||||
+module_platform_driver(krait_cc_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Krait CPU Clock Driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
||||
+MODULE_ALIAS("platform:krait-cc");
|
|
@ -1,304 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,12/13] cpufreq: Add module to register cpufreq on Krait CPUs
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
X-Patchwork-Id: 6063191
|
||||
Message-Id: <1426920332-9340-13-git-send-email-sboyd@codeaurora.org>
|
||||
To: Mike Turquette <mturquette@linaro.org>, Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org,
|
||||
linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
|
||||
Viresh Kumar <viresh.kumar@linaro.org>, <devicetree@vger.kernel.org>
|
||||
Date: Fri, 20 Mar 2015 23:45:31 -0700
|
||||
|
||||
Register a cpufreq-generic device whenever we detect that a
|
||||
"qcom,krait" compatible CPU is present in DT.
|
||||
|
||||
Cc: <devicetree@vger.kernel.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/arm/msm/qcom,pvs.txt | 38 ++++
|
||||
drivers/cpufreq/Kconfig.arm | 9 +
|
||||
drivers/cpufreq/Makefile | 1 +
|
||||
drivers/cpufreq/qcom-cpufreq.c | 204 +++++++++++++++++++++
|
||||
4 files changed, 252 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt
|
||||
create mode 100644 drivers/cpufreq/qcom-cpufreq.c
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt
|
||||
@@ -0,0 +1,38 @@
|
||||
+Qualcomm Process Voltage Scaling Tables
|
||||
+
|
||||
+The node name is required to be "qcom,pvs". There shall only be one
|
||||
+such node present in the root of the tree.
|
||||
+
|
||||
+PROPERTIES
|
||||
+
|
||||
+- qcom,pvs-format-a or qcom,pvs-format-b:
|
||||
+ Usage: required
|
||||
+ Value type: <empty>
|
||||
+ Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties.
|
||||
+ If qcom,pvs-format-a is used the table is two columns
|
||||
+ (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage,
|
||||
+ and current in that order).
|
||||
+
|
||||
+- qcom,speedX-pvsY-bin-vZ:
|
||||
+ Usage: required
|
||||
+ Value type: <prop-encoded-array>
|
||||
+ Definition: The PVS table corresponding to the speed bin X, pvs bin Y,
|
||||
+ and version Z.
|
||||
+Example:
|
||||
+
|
||||
+ qcom,pvs {
|
||||
+ qcom,pvs-format-a;
|
||||
+ qcom,speed0-pvs0-bin-v0 =
|
||||
+ < 384000000 950000 >,
|
||||
+ < 486000000 975000 >,
|
||||
+ < 594000000 1000000 >,
|
||||
+ < 702000000 1025000 >,
|
||||
+ < 810000000 1075000 >,
|
||||
+ < 918000000 1100000 >,
|
||||
+ < 1026000000 1125000 >,
|
||||
+ < 1134000000 1175000 >,
|
||||
+ < 1242000000 1200000 >,
|
||||
+ < 1350000000 1225000 >,
|
||||
+ < 1458000000 1237500 >,
|
||||
+ < 1512000000 1250000 >;
|
||||
+ };
|
||||
--- a/drivers/cpufreq/Kconfig.arm
|
||||
+++ b/drivers/cpufreq/Kconfig.arm
|
||||
@@ -95,6 +95,15 @@ config ARM_OMAP2PLUS_CPUFREQ
|
||||
depends on ARCH_OMAP2PLUS
|
||||
default ARCH_OMAP2PLUS
|
||||
|
||||
+config ARM_QCOM_CPUFREQ
|
||||
+ tristate "Qualcomm based"
|
||||
+ depends on ARCH_QCOM
|
||||
+ select PM_OPP
|
||||
+ help
|
||||
+ This adds the CPUFreq driver for Qualcomm SoC based boards.
|
||||
+
|
||||
+ If in doubt, say N.
|
||||
+
|
||||
config ARM_S3C_CPUFREQ
|
||||
bool
|
||||
help
|
||||
--- a/drivers/cpufreq/Makefile
|
||||
+++ b/drivers/cpufreq/Makefile
|
||||
@@ -61,6 +61,7 @@ obj-$(CONFIG_ARM_MT8173_CPUFREQ) += mt81
|
||||
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
|
||||
obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o
|
||||
obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o
|
||||
+obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o
|
||||
obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/cpufreq/qcom-cpufreq.c
|
||||
@@ -0,0 +1,204 @@
|
||||
+/* Copyright (c) 2014, 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/cpu.h>
|
||||
+#include <linux/err.h>
|
||||
+#include <linux/init.h>
|
||||
+#include <linux/io.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/platform_device.h>
|
||||
+#include <linux/pm_opp.h>
|
||||
+#include <linux/slab.h>
|
||||
+#include <linux/cpufreq-dt.h>
|
||||
+
|
||||
+static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver)
|
||||
+{
|
||||
+ void __iomem *base;
|
||||
+ u32 pte_efuse;
|
||||
+
|
||||
+ *speed = *pvs = *pvs_ver = 0;
|
||||
+
|
||||
+ base = ioremap(0x007000c0, 4);
|
||||
+ if (!base) {
|
||||
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ pte_efuse = readl_relaxed(base);
|
||||
+ iounmap(base);
|
||||
+
|
||||
+ *speed = pte_efuse & 0xf;
|
||||
+ if (*speed == 0xf)
|
||||
+ *speed = (pte_efuse >> 4) & 0xf;
|
||||
+
|
||||
+ if (*speed == 0xf) {
|
||||
+ *speed = 0;
|
||||
+ pr_warn("Speed bin: Defaulting to %d\n", *speed);
|
||||
+ } else {
|
||||
+ pr_info("Speed bin: %d\n", *speed);
|
||||
+ }
|
||||
+
|
||||
+ *pvs = (pte_efuse >> 10) & 0x7;
|
||||
+ if (*pvs == 0x7)
|
||||
+ *pvs = (pte_efuse >> 13) & 0x7;
|
||||
+
|
||||
+ if (*pvs == 0x7) {
|
||||
+ *pvs = 0;
|
||||
+ pr_warn("PVS bin: Defaulting to %d\n", *pvs);
|
||||
+ } else {
|
||||
+ pr_info("PVS bin: %d\n", *pvs);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver)
|
||||
+{
|
||||
+ u32 pte_efuse, redundant_sel;
|
||||
+ void __iomem *base;
|
||||
+
|
||||
+ *speed = 0;
|
||||
+ *pvs = 0;
|
||||
+ *pvs_ver = 0;
|
||||
+
|
||||
+ base = ioremap(0xfc4b80b0, 8);
|
||||
+ if (!base) {
|
||||
+ pr_warn("Unable to read efuse data. Defaulting to 0!\n");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ pte_efuse = readl_relaxed(base);
|
||||
+ redundant_sel = (pte_efuse >> 24) & 0x7;
|
||||
+ *speed = pte_efuse & 0x7;
|
||||
+ /* 4 bits of PVS are in efuse register bits 31, 8-6. */
|
||||
+ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7);
|
||||
+ *pvs_ver = (pte_efuse >> 4) & 0x3;
|
||||
+
|
||||
+ switch (redundant_sel) {
|
||||
+ case 1:
|
||||
+ *speed = (pte_efuse >> 27) & 0xf;
|
||||
+ break;
|
||||
+ case 2:
|
||||
+ *pvs = (pte_efuse >> 27) & 0xf;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ /* Check SPEED_BIN_BLOW_STATUS */
|
||||
+ if (pte_efuse & BIT(3)) {
|
||||
+ pr_info("Speed bin: %d\n", *speed);
|
||||
+ } else {
|
||||
+ pr_warn("Speed bin not set. Defaulting to 0!\n");
|
||||
+ *speed = 0;
|
||||
+ }
|
||||
+
|
||||
+ /* Check PVS_BLOW_STATUS */
|
||||
+ pte_efuse = readl_relaxed(base + 0x4) & BIT(21);
|
||||
+ if (pte_efuse) {
|
||||
+ pr_info("PVS bin: %d\n", *pvs);
|
||||
+ } else {
|
||||
+ pr_warn("PVS bin not set. Defaulting to 0!\n");
|
||||
+ *pvs = 0;
|
||||
+ }
|
||||
+
|
||||
+ pr_info("PVS version: %d\n", *pvs_ver);
|
||||
+ iounmap(base);
|
||||
+}
|
||||
+
|
||||
+static int __init qcom_cpufreq_populate_opps(void)
|
||||
+{
|
||||
+ int len, rows, cols, i, k, speed, pvs, pvs_ver;
|
||||
+ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX";
|
||||
+ struct device_node *np;
|
||||
+ struct device *dev;
|
||||
+ int cpu = 0;
|
||||
+
|
||||
+ np = of_find_node_by_name(NULL, "qcom,pvs");
|
||||
+ if (!np)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ if (of_property_read_bool(np, "qcom,pvs-format-a")) {
|
||||
+ get_krait_bin_format_a(&speed, &pvs, &pvs_ver);
|
||||
+ cols = 2;
|
||||
+ } else if (of_property_read_bool(np, "qcom,pvs-format-b")) {
|
||||
+ get_krait_bin_format_b(&speed, &pvs, &pvs_ver);
|
||||
+ cols = 3;
|
||||
+ } else {
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+
|
||||
+ snprintf(table_name, sizeof(table_name),
|
||||
+ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver);
|
||||
+
|
||||
+ if (!of_find_property(np, table_name, &len))
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ len /= sizeof(u32);
|
||||
+ if (len % cols || len == 0)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ rows = len / cols;
|
||||
+
|
||||
+ for (i = 0, k = 0; i < rows; i++) {
|
||||
+ u32 freq, volt;
|
||||
+
|
||||
+ of_property_read_u32_index(np, table_name, k++, &freq);
|
||||
+ of_property_read_u32_index(np, table_name, k++, &volt);
|
||||
+ while (k % cols)
|
||||
+ k++; /* Skip uA entries if present */
|
||||
+ for (cpu = 0; cpu < num_possible_cpus(); cpu++) {
|
||||
+ dev = get_cpu_device(cpu);
|
||||
+ if (!dev)
|
||||
+ return -ENODEV;
|
||||
+ if (dev_pm_opp_add(dev, freq, volt))
|
||||
+ pr_warn("failed to add OPP %u\n", freq);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int __init qcom_cpufreq_driver_init(void)
|
||||
+{
|
||||
+ struct cpufreq_dt_platform_data pdata = { .independent_clocks = true };
|
||||
+ struct platform_device_info devinfo = {
|
||||
+ .name = "cpufreq-dt",
|
||||
+ .data = &pdata,
|
||||
+ .size_data = sizeof(pdata),
|
||||
+ };
|
||||
+ struct device *cpu_dev;
|
||||
+ struct device_node *np;
|
||||
+ int ret;
|
||||
+
|
||||
+ cpu_dev = get_cpu_device(0);
|
||||
+ if (!cpu_dev)
|
||||
+ return -ENODEV;
|
||||
+
|
||||
+ np = of_node_get(cpu_dev->of_node);
|
||||
+ if (!np)
|
||||
+ return -ENOENT;
|
||||
+
|
||||
+ if (!of_device_is_compatible(np, "qcom,krait")) {
|
||||
+ of_node_put(np);
|
||||
+ return -ENODEV;
|
||||
+ }
|
||||
+ of_node_put(np);
|
||||
+
|
||||
+ ret = qcom_cpufreq_populate_opps();
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo));
|
||||
+}
|
||||
+module_init(qcom_cpufreq_driver_init);
|
||||
+
|
||||
+MODULE_DESCRIPTION("Qualcomm CPUfreq driver");
|
||||
+MODULE_LICENSE("GPL v2");
|
|
@ -1,96 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -26,6 +26,11 @@
|
||||
next-level-cache = <&L2>;
|
||||
qcom,acc = <&acc0>;
|
||||
qcom,saw = <&saw0>;
|
||||
+ clocks = <&kraitcc 0>, <&kraitcc 4>;
|
||||
+ clock-names = "cpu", "l2";
|
||||
+ clock-latency = <100000>;
|
||||
+ cpu-supply = <&smb208_s2a>;
|
||||
+ voltage-tolerance = <5>;
|
||||
};
|
||||
|
||||
cpu@1 {
|
||||
@@ -36,12 +41,20 @@
|
||||
next-level-cache = <&L2>;
|
||||
qcom,acc = <&acc1>;
|
||||
qcom,saw = <&saw1>;
|
||||
+ clocks = <&kraitcc 1>, <&kraitcc 4>;
|
||||
+ clock-names = "cpu", "l2";
|
||||
+ clock-latency = <100000>;
|
||||
+ cpu-supply = <&smb208_s2b>;
|
||||
};
|
||||
|
||||
L2: l2-cache {
|
||||
compatible = "cache";
|
||||
cache-level = <2>;
|
||||
};
|
||||
+
|
||||
+ qcom,l2 {
|
||||
+ qcom,l2-rates = <384000000 1000000000 1200000000>;
|
||||
+ };
|
||||
};
|
||||
|
||||
cpu-pmu {
|
||||
@@ -73,6 +86,46 @@
|
||||
};
|
||||
};
|
||||
|
||||
+ kraitcc: clock-controller {
|
||||
+ compatible = "qcom,krait-cc-v1";
|
||||
+ #clock-cells = <1>;
|
||||
+ };
|
||||
+
|
||||
+ qcom,pvs {
|
||||
+ qcom,pvs-format-a;
|
||||
+ qcom,speed0-pvs0-bin-v0 =
|
||||
+ < 1400000000 1250000 >,
|
||||
+ < 1200000000 1200000 >,
|
||||
+ < 1000000000 1150000 >,
|
||||
+ < 800000000 1100000 >,
|
||||
+ < 600000000 1050000 >,
|
||||
+ < 384000000 1000000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs1-bin-v0 =
|
||||
+ < 1400000000 1175000 >,
|
||||
+ < 1200000000 1125000 >,
|
||||
+ < 1000000000 1075000 >,
|
||||
+ < 800000000 1025000 >,
|
||||
+ < 600000000 975000 >,
|
||||
+ < 384000000 925000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs2-bin-v0 =
|
||||
+ < 1400000000 1125000 >,
|
||||
+ < 1200000000 1075000 >,
|
||||
+ < 1000000000 1025000 >,
|
||||
+ < 800000000 995000 >,
|
||||
+ < 600000000 925000 >,
|
||||
+ < 384000000 875000 >;
|
||||
+
|
||||
+ qcom,speed0-pvs3-bin-v0 =
|
||||
+ < 1400000000 1050000 >,
|
||||
+ < 1200000000 1000000 >,
|
||||
+ < 1000000000 950000 >,
|
||||
+ < 800000000 900000 >,
|
||||
+ < 600000000 850000 >,
|
||||
+ < 384000000 800000 >;
|
||||
+ };
|
||||
+
|
||||
soc: soc {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
@@ -216,11 +269,13 @@
|
||||
acc0: clock-controller@2088000 {
|
||||
compatible = "qcom,kpss-acc-v1";
|
||||
reg = <0x02088000 0x1000>, <0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu0_aux";
|
||||
};
|
||||
|
||||
acc1: clock-controller@2098000 {
|
||||
compatible = "qcom,kpss-acc-v1";
|
||||
reg = <0x02098000 0x1000>, <0x02008000 0x1000>;
|
||||
+ clock-output-names = "acpu1_aux";
|
||||
};
|
||||
|
||||
l2cc: clock-controller@2011000 {
|
|
@ -1,76 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v6,1/2] dt/bindings: qcom_adm: Fix channel specifiers
|
||||
From: Andy Gross <agross@codeaurora.org>
|
||||
X-Patchwork-Id: 6027361
|
||||
Message-Id: <1426571172-9711-2-git-send-email-agross@codeaurora.org>
|
||||
To: Vinod Koul <vinod.koul@intel.com>
|
||||
Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org,
|
||||
linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org,
|
||||
linux-arm-kernel@lists.infradead.org, Kumar Gala <galak@codeaurora.org>,
|
||||
Bjorn Andersson <bjorn.andersson@sonymobile.com>,
|
||||
Andy Gross <agross@codeaurora.org>
|
||||
Date: Tue, 17 Mar 2015 00:46:11 -0500
|
||||
|
||||
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>
|
||||
|
||||
---
|
||||
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";
|
||||
};
|
|
@ -1,964 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v6,2/2] dmaengine: Add ADM driver
|
||||
From: Andy Gross <agross@codeaurora.org>
|
||||
X-Patchwork-Id: 6027351
|
||||
Message-Id: <1426571172-9711-3-git-send-email-agross@codeaurora.org>
|
||||
To: Vinod Koul <vinod.koul@intel.com>
|
||||
Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org,
|
||||
linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org,
|
||||
linux-arm-kernel@lists.infradead.org, Kumar Gala <galak@codeaurora.org>,
|
||||
Bjorn Andersson <bjorn.andersson@sonymobile.com>,
|
||||
Andy Gross <agross@codeaurora.org>
|
||||
Date: Tue, 17 Mar 2015 00:46:12 -0500
|
||||
|
||||
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>
|
||||
Reviewed-by: sricharan <sricharan@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/dma/Kconfig | 10 +
|
||||
drivers/dma/Makefile | 1 +
|
||||
drivers/dma/qcom_adm.c | 900 ++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 911 insertions(+)
|
||||
create mode 100644 drivers/dma/qcom_adm.c
|
||||
|
||||
--- a/drivers/dma/Kconfig
|
||||
+++ b/drivers/dma/Kconfig
|
||||
@@ -558,4 +558,14 @@ config DMATEST
|
||||
config DMA_ENGINE_RAID
|
||||
bool
|
||||
|
||||
+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.
|
||||
+
|
||||
endif
|
||||
--- /dev/null
|
||||
+++ b/drivers/dma/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");
|
||||
--- a/drivers/dma/Makefile
|
||||
+++ b/drivers/dma/Makefile
|
||||
@@ -65,5 +65,6 @@ obj-$(CONFIG_TI_DMA_CROSSBAR) += ti-dma-
|
||||
obj-$(CONFIG_TI_EDMA) += edma.o
|
||||
obj-$(CONFIG_XGENE_DMA) += xgene-dma.o
|
||||
obj-$(CONFIG_ZX_DMA) += zx296702_dma.o
|
||||
+obj-$(CONFIG_QCOM_ADM) += qcom_adm.o
|
||||
|
||||
obj-y += xilinx/
|
|
@ -1,42 +0,0 @@
|
|||
From 1fb18acab2d71e7e4efd9c10492edb1baf84dcc0 Mon Sep 17 00:00:00 2001
|
||||
From: Andy Gross <agross@codeaurora.org>
|
||||
Date: Wed, 20 May 2015 15:41:07 +0530
|
||||
Subject: [PATCH] ARM: DT: ipq8064: Add ADM device node
|
||||
|
||||
This patch adds support for the ADM DMA on the IPQ8064 SOC
|
||||
|
||||
Signed-off-by: Andy Gross <agross@codeaurora.org>
|
||||
---
|
||||
arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 4 ++++
|
||||
arch/arm/boot/dts/qcom-ipq8064.dtsi | 21 +++++++++++++++++++++
|
||||
2 files changed, 25 insertions(+)
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -721,6 +721,26 @@
|
||||
|
||||
status = "disabled";
|
||||
};
|
||||
+
|
||||
+ adm_dma: dma@18300000 {
|
||||
+ compatible = "qcom,adm";
|
||||
+ reg = <0x18300000 0x100000>;
|
||||
+ interrupts = <0 170 0>;
|
||||
+ #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";
|
||||
+ };
|
||||
};
|
||||
|
||||
sfpb_mutex: sfpb-mutex {
|
|
@ -1,83 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,
|
||||
1/5] mtd: nand: Create a BBT flag to access bad block markers in raw
|
||||
mode
|
||||
From: Archit Taneja <architt@codeaurora.org>
|
||||
X-Patchwork-Id: 6927081
|
||||
Message-Id: <1438578498-32254-2-git-send-email-architt@codeaurora.org>
|
||||
To: linux-mtd@lists.infradead.org, dehrenberg@google.com,
|
||||
cernekee@gmail.com, computersforpeace@gmail.com
|
||||
Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org,
|
||||
sboyd@codeaurora.org, linux-kernel@vger.kernel.org,
|
||||
Archit Taneja <architt@codeaurora.org>
|
||||
Date: Mon, 3 Aug 2015 10:38:14 +0530
|
||||
|
||||
Some controllers can access the factory bad block marker from OOB only
|
||||
when they read it in raw mode. When ECC is enabled, these controllers
|
||||
discard reading/writing bad block markers, preventing access to them
|
||||
altogether.
|
||||
|
||||
The bbt driver assumes MTD_OPS_PLACE_OOB when scanning for bad blocks.
|
||||
This results in the nand driver's ecc->read_oob() op to be called, which
|
||||
works with ECC enabled.
|
||||
|
||||
Create a new BBT option flag that tells nand_bbt to force the mode to
|
||||
MTD_OPS_RAW. This would result in the correct op being called for the
|
||||
underlying nand controller driver.
|
||||
|
||||
Reviewed-by: Andy Gross <agross@codeaurora.org>
|
||||
Signed-off-by: Archit Taneja <architt@codeaurora.org>
|
||||
|
||||
---
|
||||
drivers/mtd/nand/nand_base.c | 6 +++++-
|
||||
drivers/mtd/nand/nand_bbt.c | 6 +++++-
|
||||
include/linux/mtd/bbm.h | 7 +++++++
|
||||
3 files changed, 17 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/drivers/mtd/nand/nand_base.c
|
||||
+++ b/drivers/mtd/nand/nand_base.c
|
||||
@@ -394,7 +394,11 @@ static int nand_default_block_markbad(st
|
||||
} else {
|
||||
ops.len = ops.ooblen = 1;
|
||||
}
|
||||
- ops.mode = MTD_OPS_PLACE_OOB;
|
||||
+
|
||||
+ if (unlikely(chip->bbt_options & NAND_BBT_ACCESS_BBM_RAW))
|
||||
+ ops.mode = MTD_OPS_RAW;
|
||||
+ else
|
||||
+ ops.mode = MTD_OPS_PLACE_OOB;
|
||||
|
||||
/* Write to first/last page(s) if necessary */
|
||||
if (chip->bbt_options & NAND_BBT_SCANLASTPAGE)
|
||||
--- a/drivers/mtd/nand/nand_bbt.c
|
||||
+++ b/drivers/mtd/nand/nand_bbt.c
|
||||
@@ -420,7 +420,11 @@ static int scan_block_fast(struct mtd_in
|
||||
ops.oobbuf = buf;
|
||||
ops.ooboffs = 0;
|
||||
ops.datbuf = NULL;
|
||||
- ops.mode = MTD_OPS_PLACE_OOB;
|
||||
+
|
||||
+ if (unlikely(bd->options & NAND_BBT_ACCESS_BBM_RAW))
|
||||
+ ops.mode = MTD_OPS_RAW;
|
||||
+ else
|
||||
+ ops.mode = MTD_OPS_PLACE_OOB;
|
||||
|
||||
for (j = 0; j < numpages; j++) {
|
||||
/*
|
||||
--- a/include/linux/mtd/bbm.h
|
||||
+++ b/include/linux/mtd/bbm.h
|
||||
@@ -116,6 +116,12 @@ struct nand_bbt_descr {
|
||||
#define NAND_BBT_NO_OOB_BBM 0x00080000
|
||||
|
||||
/*
|
||||
+ * Force MTD_OPS_RAW mode when trying to access bad block markes from OOB. To
|
||||
+ * be used by controllers which can access BBM only when ECC is disabled, i.e,
|
||||
+ * when in RAW access mode
|
||||
+ */
|
||||
+#define NAND_BBT_ACCESS_BBM_RAW 0x00100000
|
||||
+/*
|
||||
* Flag set by nand_create_default_bbt_descr(), marking that the nand_bbt_descr
|
||||
* was allocated dynamicaly and must be freed in nand_release(). Has no meaning
|
||||
* in nand_chip.bbt_options.
|
File diff suppressed because it is too large
Load Diff
|
@ -1,82 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,3/5] dt/bindings: qcom_nandc: Add DT bindings
|
||||
From: Archit Taneja <architt@codeaurora.org>
|
||||
X-Patchwork-Id: 6927141
|
||||
Message-Id: <1438578498-32254-4-git-send-email-architt@codeaurora.org>
|
||||
To: linux-mtd@lists.infradead.org, dehrenberg@google.com,
|
||||
cernekee@gmail.com, computersforpeace@gmail.com
|
||||
Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org,
|
||||
sboyd@codeaurora.org, linux-kernel@vger.kernel.org,
|
||||
Archit Taneja <architt@codeaurora.org>, devicetree@vger.kernel.org
|
||||
Date: Mon, 3 Aug 2015 10:38:16 +0530
|
||||
|
||||
Add DT bindings document for the Qualcomm NAND controller driver.
|
||||
|
||||
Cc: devicetree@vger.kernel.org
|
||||
|
||||
v3:
|
||||
- Don't use '0x' when specifying nand controller address space
|
||||
- Add optional property for on-flash bbt usage
|
||||
|
||||
Acked-by: Andy Gross <agross@codeaurora.org>
|
||||
Signed-off-by: Archit Taneja <architt@codeaurora.org>
|
||||
|
||||
---
|
||||
.../devicetree/bindings/mtd/qcom_nandc.txt | 49 ++++++++++++++++++++++
|
||||
1 file changed, 49 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/mtd/qcom_nandc.txt
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/mtd/qcom_nandc.txt
|
||||
@@ -0,0 +1,49 @@
|
||||
+* Qualcomm NAND controller
|
||||
+
|
||||
+Required properties:
|
||||
+- compatible: should be "qcom,ebi2-nand" for IPQ806x
|
||||
+- reg: MMIO address range
|
||||
+- clocks: must contain core clock and always on clock
|
||||
+- clock-names: must contain "core" for the core clock and "aon" for the
|
||||
+ always on clock
|
||||
+- dmas: DMA specifier, consisting of a phandle to the ADM DMA
|
||||
+ controller node and the channel number to be used for
|
||||
+ NAND. Refer to dma.txt and qcom_adm.txt for more details
|
||||
+- dma-names: must be "rxtx"
|
||||
+- qcom,cmd-crci: must contain the ADM command type CRCI block instance
|
||||
+ number specified for the NAND controller on the given
|
||||
+ platform
|
||||
+- qcom,data-crci: must contain the ADM data type CRCI block instance
|
||||
+ number specified for the NAND controller on the given
|
||||
+ platform
|
||||
+
|
||||
+Optional properties:
|
||||
+- nand-bus-width: bus width. Must be 8 or 16. If not present, 8 is chosen
|
||||
+ as default
|
||||
+
|
||||
+- nand-ecc-strength: number of bits to correct per ECC step. Must be 4 or 8
|
||||
+ bits. If not present, 4 is chosen as default
|
||||
+- nand-on-flash-bbt: Create/use on-flash bad block table
|
||||
+
|
||||
+The device tree may optionally contain sub-nodes describing partitions of the
|
||||
+address space. See partition.txt for more detail.
|
||||
+
|
||||
+Example:
|
||||
+
|
||||
+nand@1ac00000 {
|
||||
+ compatible = "qcom,ebi2-nandc";
|
||||
+ 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>;
|
||||
+
|
||||
+ partition@0 {
|
||||
+ ...
|
||||
+ };
|
||||
+};
|
|
@ -1,51 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,4/5] arm: qcom: dts: Add NAND controller node for ipq806x
|
||||
From: Archit Taneja <architt@codeaurora.org>
|
||||
X-Patchwork-Id: 6927121
|
||||
Message-Id: <1438578498-32254-5-git-send-email-architt@codeaurora.org>
|
||||
To: linux-mtd@lists.infradead.org, dehrenberg@google.com,
|
||||
cernekee@gmail.com, computersforpeace@gmail.com
|
||||
Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org,
|
||||
sboyd@codeaurora.org, linux-kernel@vger.kernel.org,
|
||||
Archit Taneja <architt@codeaurora.org>, devicetree@vger.kernel.org
|
||||
Date: Mon, 3 Aug 2015 10:38:17 +0530
|
||||
|
||||
The nand controller in IPQ806x is of the 'EBI2 type'. Use the corresponding
|
||||
compatible string.
|
||||
|
||||
Cc: devicetree@vger.kernel.org
|
||||
|
||||
Reviewed-by: Andy Gross <agross@codeaurora.org>
|
||||
Signed-off-by: Archit Taneja <architt@codeaurora.org>
|
||||
|
||||
---
|
||||
arch/arm/boot/dts/qcom-ipq8064.dtsi | 15 +++++++++++++++
|
||||
1 file changed, 15 insertions(+)
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -741,6 +741,22 @@
|
||||
|
||||
status = "disabled";
|
||||
};
|
||||
+
|
||||
+ nand@1ac00000 {
|
||||
+ compatible = "qcom,ebi2-nandc";
|
||||
+ 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";
|
||||
+ };
|
||||
};
|
||||
|
||||
sfpb_mutex: sfpb-mutex {
|
|
@ -1,76 +0,0 @@
|
|||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v3,5/5] arm: qcom: dts: Enable NAND node on IPQ8064 AP148 platform
|
||||
From: Archit Taneja <architt@codeaurora.org>
|
||||
X-Patchwork-Id: 6927091
|
||||
Message-Id: <1438578498-32254-6-git-send-email-architt@codeaurora.org>
|
||||
To: linux-mtd@lists.infradead.org, dehrenberg@google.com,
|
||||
cernekee@gmail.com, computersforpeace@gmail.com
|
||||
Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org,
|
||||
sboyd@codeaurora.org, linux-kernel@vger.kernel.org,
|
||||
Archit Taneja <architt@codeaurora.org>, devicetree@vger.kernel.org
|
||||
Date: Mon, 3 Aug 2015 10:38:18 +0530
|
||||
|
||||
Enable the NAND controller node on the AP148 platform. Provide pinmux
|
||||
information.
|
||||
|
||||
Cc: devicetree@vger.kernel.org
|
||||
|
||||
Signed-off-by: Archit Taneja <architt@codeaurora.org>
|
||||
|
||||
---
|
||||
arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 36 ++++++++++++++++++++++++++++++++
|
||||
1 file changed, 36 insertions(+)
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -43,6 +43,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 {
|
||||
@@ -126,5 +148,19 @@
|
||||
status = "ok";
|
||||
phy-tx0-term-offset = <7>;
|
||||
};
|
||||
+
|
||||
+ nand@1ac00000 {
|
||||
+ status = "ok";
|
||||
+
|
||||
+ pinctrl-0 = <&nand_pins>;
|
||||
+ pinctrl-names = "default";
|
||||
+
|
||||
+ nand-ecc-strength = <4>;
|
||||
+ nand-bus-width = <8>;
|
||||
+ };
|
||||
};
|
||||
};
|
||||
+
|
||||
+&adm_dma {
|
||||
+ status = "ok";
|
||||
+};
|
|
@ -1,11 +0,0 @@
|
|||
--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts
|
||||
@@ -157,6 +157,8 @@
|
||||
|
||||
nand-ecc-strength = <4>;
|
||||
nand-bus-width = <8>;
|
||||
+
|
||||
+ linux,part-probe = "qcom-smem";
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
|
||||
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
|
||||
@@ -59,6 +59,7 @@ Regulator nodes are identified by their
|
||||
"qcom,rpm-pm8058-regulators"
|
||||
"qcom,rpm-pm8901-regulators"
|
||||
"qcom,rpm-pm8921-regulators"
|
||||
+ "qcom,rpm-smb208-regulators"
|
||||
|
||||
- vdd_l0_l1_lvs-supply:
|
||||
- vdd_l2_l11_l12-supply:
|
||||
@@ -156,6 +157,9 @@ pm8921:
|
||||
l29, lvs1, lvs2, lvs3, lvs4, lvs5, lvs6, lvs7, usb-switch, hdmi-switch,
|
||||
ncp
|
||||
|
||||
+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
|
||||
@@ -869,10 +869,19 @@ 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-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);
|
|
@ -1,74 +0,0 @@
|
|||
Change DT to use new smb208 regulator driver.
|
||||
|
||||
Signed-off-by: Adrian Panella <ianchi74@outlook.com>
|
||||
|
||||
--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
+++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi
|
||||
@@ -162,45 +162,37 @@
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
- smb208_s1a: smb208-s1a {
|
||||
- compatible = "qcom,rpm-smb208";
|
||||
- reg = <QCOM_RPM_SMB208_S1a>;
|
||||
+ regulators {
|
||||
+ compatible = "qcom,rpm-smb208-regulators";
|
||||
|
||||
- regulator-min-microvolt = <1050000>;
|
||||
- regulator-max-microvolt = <1150000>;
|
||||
+ smb208_s1a: s1a {
|
||||
+ regulator-min-microvolt = <1050000>;
|
||||
+ regulator-max-microvolt = <1150000>;
|
||||
|
||||
- qcom,switch-mode-frequency = <1200000>;
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
|
||||
- };
|
||||
-
|
||||
- smb208_s1b: smb208-s1b {
|
||||
- compatible = "qcom,rpm-smb208";
|
||||
- reg = <QCOM_RPM_SMB208_S1b>;
|
||||
-
|
||||
- regulator-min-microvolt = <1050000>;
|
||||
- regulator-max-microvolt = <1150000>;
|
||||
+ };
|
||||
|
||||
- qcom,switch-mode-frequency = <1200000>;
|
||||
- };
|
||||
-
|
||||
- smb208_s2a: smb208-s2a {
|
||||
- compatible = "qcom,rpm-smb208";
|
||||
- reg = <QCOM_RPM_SMB208_S2a>;
|
||||
+ smb208_s1b: s1b {
|
||||
+ regulator-min-microvolt = <1050000>;
|
||||
+ regulator-max-microvolt = <1150000>;
|
||||
|
||||
- regulator-min-microvolt = < 800000>;
|
||||
- regulator-max-microvolt = <1250000>;
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+ };
|
||||
|
||||
- qcom,switch-mode-frequency = <1200000>;
|
||||
- };
|
||||
+ smb208_s2a: s2a {
|
||||
+ regulator-min-microvolt = < 800000>;
|
||||
+ regulator-max-microvolt = <1250000>;
|
||||
|
||||
- smb208_s2b: smb208-s2b {
|
||||
- compatible = "qcom,rpm-smb208";
|
||||
- reg = <QCOM_RPM_SMB208_S2b>;
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+ };
|
||||
|
||||
- regulator-min-microvolt = < 800000>;
|
||||
- regulator-max-microvolt = <1250000>;
|
||||
+ smb208_s2b: s2b {
|
||||
+ regulator-min-microvolt = < 800000>;
|
||||
+ regulator-max-microvolt = <1250000>;
|
||||
|
||||
- qcom,switch-mode-frequency = <1200000>;
|
||||
+ qcom,switch-mode-frequency = <1200000>;
|
||||
+ };
|
||||
};
|
||||
};
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
From 111139c943a082364fbbcd9e0cc94cd442481340 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 1 Jun 2015 18:47:56 -0700
|
||||
Subject: PM / OPP: Support adjusting OPP voltages at runtime
|
||||
|
||||
On some SoCs the Adaptive Voltage Scaling (AVS) technique is
|
||||
employed to optimize the operating voltage of a device. At a
|
||||
given frequency, the hardware monitors dynamic factors and either
|
||||
makes a suggestion for how much to adjust a voltage for the
|
||||
current frequency, or it automatically adjusts the voltage
|
||||
without software intervention. Add an API to the OPP library for
|
||||
the former case, so that AVS type devices can update the voltages
|
||||
for an OPP when the hardware determines the voltage should
|
||||
change. The assumption is that drivers like CPUfreq or devfreq
|
||||
will register for the OPP notifiers and adjust the voltage
|
||||
according to suggestions that AVS makes.
|
||||
|
||||
Cc: Nishanth Menon <nm@ti.com>
|
||||
Cc: Viresh Kumar <viresh.kumar@linaro.org>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Acked-by: Viresh Kumar <viresh.kumar@linaro.org>
|
||||
---
|
||||
drivers/base/power/opp/core.c | 77 +++++++++++++++++++++++++++++++++++++++++++
|
||||
include/linux/pm_opp.h | 10 ++++++
|
||||
2 files changed, 87 insertions(+)
|
||||
|
||||
--- a/drivers/base/power/opp/core.c
|
||||
+++ b/drivers/base/power/opp/core.c
|
||||
@@ -1039,6 +1039,83 @@ unlock:
|
||||
}
|
||||
|
||||
/**
|
||||
+ * dev_pm_opp_adjust_voltage() - helper to change the voltage of an opp
|
||||
+ * @dev: device for which we do this operation
|
||||
+ * @freq: OPP frequency to adjust voltage of
|
||||
+ * @u_volt: new OPP voltage
|
||||
+ *
|
||||
+ * Change the voltage of an OPP with an RCU operation.
|
||||
+ *
|
||||
+ * Return: -EINVAL for bad pointers, -ENOMEM if no memory available for the
|
||||
+ * copy operation, returns 0 if no modifcation was done OR modification was
|
||||
+ * successful.
|
||||
+ *
|
||||
+ * Locking: The internal device_opp and opp structures are RCU protected.
|
||||
+ * Hence this function internally uses RCU updater strategy with mutex locks to
|
||||
+ * keep the integrity of the internal data structures. Callers should ensure
|
||||
+ * that this function is *NOT* called under RCU protection or in contexts where
|
||||
+ * mutex locking or synchronize_rcu() blocking calls cannot be used.
|
||||
+ */
|
||||
+int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq,
|
||||
+ unsigned long u_volt)
|
||||
+{
|
||||
+ struct device_opp *dev_opp;
|
||||
+ struct dev_pm_opp *new_opp, *tmp_opp, *opp = ERR_PTR(-ENODEV);
|
||||
+ int r = 0;
|
||||
+
|
||||
+ /* keep the node allocated */
|
||||
+ new_opp = kmalloc(sizeof(*new_opp), GFP_KERNEL);
|
||||
+ if (!new_opp)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ mutex_lock(&dev_opp_list_lock);
|
||||
+
|
||||
+ /* Find the device_opp */
|
||||
+ dev_opp = _find_device_opp(dev);
|
||||
+ if (IS_ERR(dev_opp)) {
|
||||
+ r = PTR_ERR(dev_opp);
|
||||
+ dev_warn(dev, "%s: Device OPP not found (%d)\n", __func__, r);
|
||||
+ goto unlock;
|
||||
+ }
|
||||
+
|
||||
+ /* Do we have the frequency? */
|
||||
+ list_for_each_entry(tmp_opp, &dev_opp->opp_list, node) {
|
||||
+ if (tmp_opp->rate == freq) {
|
||||
+ opp = tmp_opp;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ if (IS_ERR(opp)) {
|
||||
+ r = PTR_ERR(opp);
|
||||
+ goto unlock;
|
||||
+ }
|
||||
+
|
||||
+ /* Is update really needed? */
|
||||
+ if (opp->u_volt == u_volt)
|
||||
+ goto unlock;
|
||||
+ /* copy the old data over */
|
||||
+ *new_opp = *opp;
|
||||
+
|
||||
+ /* plug in new node */
|
||||
+ new_opp->u_volt = u_volt;
|
||||
+
|
||||
+ list_replace_rcu(&opp->node, &new_opp->node);
|
||||
+ mutex_unlock(&dev_opp_list_lock);
|
||||
+ call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
|
||||
+
|
||||
+ /* Notify the change of the OPP */
|
||||
+ srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADJUST_VOLTAGE,
|
||||
+ new_opp);
|
||||
+
|
||||
+ return 0;
|
||||
+
|
||||
+unlock:
|
||||
+ mutex_unlock(&dev_opp_list_lock);
|
||||
+ kfree(new_opp);
|
||||
+ return r;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
* dev_pm_opp_enable() - Enable a specific OPP
|
||||
* @dev: device for which we do this operation
|
||||
* @freq: OPP frequency to enable
|
||||
--- a/include/linux/pm_opp.h
|
||||
+++ b/include/linux/pm_opp.h
|
||||
@@ -22,6 +22,7 @@ struct device;
|
||||
|
||||
enum dev_pm_opp_event {
|
||||
OPP_EVENT_ADD, OPP_EVENT_REMOVE, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE,
|
||||
+ OPP_EVENT_ADJUST_VOLTAGE,
|
||||
};
|
||||
|
||||
#if defined(CONFIG_PM_OPP)
|
||||
@@ -50,6 +51,9 @@ int dev_pm_opp_add(struct device *dev, u
|
||||
unsigned long u_volt);
|
||||
void dev_pm_opp_remove(struct device *dev, unsigned long freq);
|
||||
|
||||
+int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq,
|
||||
+ unsigned long u_volt);
|
||||
+
|
||||
int dev_pm_opp_enable(struct device *dev, unsigned long freq);
|
||||
|
||||
int dev_pm_opp_disable(struct device *dev, unsigned long freq);
|
||||
@@ -114,6 +118,12 @@ static inline void dev_pm_opp_remove(str
|
||||
{
|
||||
}
|
||||
|
||||
+static inline int dev_pm_opp_adjust_voltage(struct device *dev,
|
||||
+ unsigned long freq, unsigned long u_volt)
|
||||
+{
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq)
|
||||
{
|
||||
return 0;
|
|
@ -1,114 +0,0 @@
|
|||
From d330eae026b4a73e77ca0422f5cae5207d80f738 Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 1 Jun 2015 18:47:57 -0700
|
||||
Subject: OPP: Allow notifiers to call dev_pm_opp_get_{voltage, freq} RCU-free
|
||||
|
||||
We pass the dev_pm_opp structure to OPP notifiers but the users
|
||||
of the notifier need to surround calls to dev_pm_opp_get_*() with
|
||||
RCU read locks to avoid lockdep warnings. The notifier is already
|
||||
called with the dev_opp's srcu lock held, so it should be safe to
|
||||
assume the devm_pm_opp structure is already protected inside the
|
||||
notifier. Update the lockdep check for this.
|
||||
|
||||
Cc: Krzysztof Kozlowski <k.kozlowski@samsung.com>
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
---
|
||||
drivers/base/power/opp/core.c | 27 +++++++++++++++------------
|
||||
1 file changed, 15 insertions(+), 12 deletions(-)
|
||||
|
||||
--- a/drivers/base/power/opp/core.c
|
||||
+++ b/drivers/base/power/opp/core.c
|
||||
@@ -31,9 +31,10 @@ static LIST_HEAD(dev_opp_list);
|
||||
/* Lock to allow exclusive modification to the device and opp lists */
|
||||
DEFINE_MUTEX(dev_opp_list_lock);
|
||||
|
||||
-#define opp_rcu_lockdep_assert() \
|
||||
+#define opp_rcu_lockdep_assert(s) \
|
||||
do { \
|
||||
RCU_LOCKDEP_WARN(!rcu_read_lock_held() && \
|
||||
+ !(s && srcu_read_lock_held(s)) && \
|
||||
!lockdep_is_held(&dev_opp_list_lock), \
|
||||
"Missing rcu_read_lock() or " \
|
||||
"dev_opp_list_lock protection"); \
|
||||
@@ -91,7 +92,7 @@ struct device_opp *_find_device_opp(stru
|
||||
{
|
||||
struct device_opp *dev_opp;
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(NULL);
|
||||
|
||||
if (IS_ERR_OR_NULL(dev)) {
|
||||
pr_err("%s: Invalid parameters\n", __func__);
|
||||
@@ -125,10 +126,11 @@ unsigned long dev_pm_opp_get_voltage(str
|
||||
struct dev_pm_opp *tmp_opp;
|
||||
unsigned long v = 0;
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(&opp->dev_opp->srcu_head.srcu);
|
||||
|
||||
- tmp_opp = rcu_dereference(opp);
|
||||
- if (IS_ERR_OR_NULL(tmp_opp))
|
||||
+ tmp_opp = srcu_dereference_check(opp, &opp->dev_opp->srcu_head.srcu,
|
||||
+ rcu_read_lock_held());
|
||||
+ if (IS_ERR_OR_NULL(tmp_opp) || !tmp_opp->available)
|
||||
pr_err("%s: Invalid parameters\n", __func__);
|
||||
else
|
||||
v = tmp_opp->u_volt;
|
||||
@@ -157,9 +159,10 @@ unsigned long dev_pm_opp_get_freq(struct
|
||||
struct dev_pm_opp *tmp_opp;
|
||||
unsigned long f = 0;
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(&opp->dev_opp->srcu_head.srcu);
|
||||
|
||||
- tmp_opp = rcu_dereference(opp);
|
||||
+ tmp_opp = srcu_dereference_check(opp, &opp->dev_opp->srcu_head.srcu,
|
||||
+ rcu_read_lock_held());
|
||||
if (IS_ERR_OR_NULL(tmp_opp) || !tmp_opp->available)
|
||||
pr_err("%s: Invalid parameters\n", __func__);
|
||||
else
|
||||
@@ -191,7 +194,7 @@ bool dev_pm_opp_is_turbo(struct dev_pm_o
|
||||
{
|
||||
struct dev_pm_opp *tmp_opp;
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(&opp->dev_opp->srcu_head.srcu);
|
||||
|
||||
tmp_opp = rcu_dereference(opp);
|
||||
if (IS_ERR_OR_NULL(tmp_opp) || !tmp_opp->available) {
|
||||
@@ -246,7 +249,7 @@ struct dev_pm_opp *dev_pm_opp_get_suspen
|
||||
{
|
||||
struct device_opp *dev_opp;
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(NULL);
|
||||
|
||||
dev_opp = _find_device_opp(dev);
|
||||
if (IS_ERR(dev_opp) || !dev_opp->suspend_opp ||
|
||||
@@ -326,7 +329,7 @@ struct dev_pm_opp *dev_pm_opp_find_freq_
|
||||
struct device_opp *dev_opp;
|
||||
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(NULL);
|
||||
|
||||
dev_opp = _find_device_opp(dev);
|
||||
if (IS_ERR(dev_opp)) {
|
||||
@@ -374,7 +377,7 @@ struct dev_pm_opp *dev_pm_opp_find_freq_
|
||||
struct device_opp *dev_opp;
|
||||
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(NULL);
|
||||
|
||||
if (!dev || !freq) {
|
||||
dev_err(dev, "%s: Invalid argument freq=%p\n", __func__, freq);
|
||||
@@ -424,7 +427,7 @@ struct dev_pm_opp *dev_pm_opp_find_freq_
|
||||
struct device_opp *dev_opp;
|
||||
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
|
||||
|
||||
- opp_rcu_lockdep_assert();
|
||||
+ opp_rcu_lockdep_assert(NULL);
|
||||
|
||||
if (!dev || !freq) {
|
||||
dev_err(dev, "%s: Invalid argument freq=%p\n", __func__, freq);
|
|
@ -1,184 +0,0 @@
|
|||
From 175329015c8a0b480240da222822d2f8316f074d Mon Sep 17 00:00:00 2001
|
||||
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Date: Mon, 1 Jun 2015 18:47:58 -0700
|
||||
Subject: cpufreq-dt: Handle OPP voltage adjust events
|
||||
|
||||
On some SoCs the Adaptive Voltage Scaling (AVS) technique is
|
||||
employed to optimize the operating voltage of a device. At a
|
||||
given frequency, the hardware monitors dynamic factors and either
|
||||
makes a suggestion for how much to adjust a voltage for the
|
||||
current frequency, or it automatically adjusts the voltage
|
||||
without software intervention.
|
||||
|
||||
In the former case, an AVS driver will call
|
||||
dev_pm_opp_modify_voltage() and update the voltage for the
|
||||
particular OPP the CPUs are using. Add an OPP notifier to
|
||||
cpufreq-dt so that we can adjust the voltage of the CPU when AVS
|
||||
updates the OPP.
|
||||
|
||||
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||
---
|
||||
drivers/cpufreq/cpufreq-dt.c | 72 ++++++++++++++++++++++++++++++++++++++++----
|
||||
1 file changed, 66 insertions(+), 6 deletions(-)
|
||||
|
||||
--- a/drivers/cpufreq/cpufreq-dt.c
|
||||
+++ b/drivers/cpufreq/cpufreq-dt.c
|
||||
@@ -34,6 +34,9 @@ struct private_data {
|
||||
struct regulator *cpu_reg;
|
||||
struct thermal_cooling_device *cdev;
|
||||
unsigned int voltage_tolerance; /* in percentage */
|
||||
+ struct notifier_block opp_nb;
|
||||
+ struct mutex lock;
|
||||
+ unsigned long opp_freq;
|
||||
};
|
||||
|
||||
static struct freq_attr *cpufreq_dt_attr[] = {
|
||||
@@ -42,6 +45,42 @@ static struct freq_attr *cpufreq_dt_attr
|
||||
NULL,
|
||||
};
|
||||
|
||||
+static int opp_notifier(struct notifier_block *nb, unsigned long event,
|
||||
+ void *data)
|
||||
+{
|
||||
+ struct dev_pm_opp *opp = data;
|
||||
+ struct private_data *priv = container_of(nb, struct private_data,
|
||||
+ opp_nb);
|
||||
+ struct device *cpu_dev = priv->cpu_dev;
|
||||
+ struct regulator *cpu_reg = priv->cpu_reg;
|
||||
+ unsigned long volt, tol, freq;
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ switch (event) {
|
||||
+ case OPP_EVENT_ADJUST_VOLTAGE:
|
||||
+ volt = dev_pm_opp_get_voltage(opp);
|
||||
+ freq = dev_pm_opp_get_freq(opp);
|
||||
+ tol = volt * priv->voltage_tolerance / 100;
|
||||
+
|
||||
+ mutex_lock(&priv->lock);
|
||||
+ if (freq == priv->opp_freq)
|
||||
+ ret = regulator_set_voltage_tol(cpu_reg, volt,
|
||||
+ tol);
|
||||
+ mutex_unlock(&priv->lock);
|
||||
+ if (ret) {
|
||||
+ dev_err(cpu_dev,
|
||||
+ "failed to scale voltage up: %d\n",
|
||||
+ ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+ break;
|
||||
+ default:
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static int set_target(struct cpufreq_policy *policy, unsigned int index)
|
||||
{
|
||||
struct dev_pm_opp *opp;
|
||||
@@ -53,6 +92,7 @@ static int set_target(struct cpufreq_pol
|
||||
unsigned long volt = 0, volt_old = 0, tol = 0;
|
||||
unsigned int old_freq, new_freq;
|
||||
long freq_Hz, freq_exact;
|
||||
+ unsigned long opp_freq = 0;
|
||||
int ret;
|
||||
|
||||
freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000);
|
||||
@@ -63,8 +103,8 @@ static int set_target(struct cpufreq_pol
|
||||
new_freq = freq_Hz / 1000;
|
||||
old_freq = clk_get_rate(cpu_clk) / 1000;
|
||||
|
||||
+ mutex_lock(&priv->lock);
|
||||
if (!IS_ERR(cpu_reg)) {
|
||||
- unsigned long opp_freq;
|
||||
|
||||
rcu_read_lock();
|
||||
opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz);
|
||||
@@ -72,7 +112,8 @@ static int set_target(struct cpufreq_pol
|
||||
rcu_read_unlock();
|
||||
dev_err(cpu_dev, "failed to find OPP for %ld\n",
|
||||
freq_Hz);
|
||||
- return PTR_ERR(opp);
|
||||
+ ret = PTR_ERR(opp);
|
||||
+ goto out;
|
||||
}
|
||||
volt = dev_pm_opp_get_voltage(opp);
|
||||
opp_freq = dev_pm_opp_get_freq(opp);
|
||||
@@ -93,7 +134,7 @@ static int set_target(struct cpufreq_pol
|
||||
if (ret) {
|
||||
dev_err(cpu_dev, "failed to scale voltage up: %d\n",
|
||||
ret);
|
||||
- return ret;
|
||||
+ goto out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +143,7 @@ static int set_target(struct cpufreq_pol
|
||||
dev_err(cpu_dev, "failed to set clock rate: %d\n", ret);
|
||||
if (!IS_ERR(cpu_reg) && volt_old > 0)
|
||||
regulator_set_voltage_tol(cpu_reg, volt_old, tol);
|
||||
- return ret;
|
||||
+ goto out;
|
||||
}
|
||||
|
||||
/* scaling down? scale voltage after frequency */
|
||||
@@ -112,9 +153,12 @@ static int set_target(struct cpufreq_pol
|
||||
dev_err(cpu_dev, "failed to scale voltage down: %d\n",
|
||||
ret);
|
||||
clk_set_rate(cpu_clk, old_freq * 1000);
|
||||
+ goto out;
|
||||
}
|
||||
}
|
||||
-
|
||||
+ priv->opp_freq = opp_freq;
|
||||
+out:
|
||||
+ mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -201,6 +245,7 @@ static int cpufreq_init(struct cpufreq_p
|
||||
unsigned int transition_latency;
|
||||
bool need_update = false;
|
||||
int ret;
|
||||
+ struct srcu_notifier_head *opp_srcu_head;
|
||||
|
||||
ret = allocate_resources(policy->cpu, &cpu_dev, &cpu_reg, &cpu_clk);
|
||||
if (ret) {
|
||||
@@ -277,6 +322,19 @@ static int cpufreq_init(struct cpufreq_p
|
||||
goto out_free_opp;
|
||||
}
|
||||
|
||||
+ mutex_init(&priv->lock);
|
||||
+
|
||||
+ opp_srcu_head = dev_pm_opp_get_notifier(cpu_dev);
|
||||
+ if (IS_ERR(opp_srcu_head)) {
|
||||
+ ret = PTR_ERR(opp_srcu_head);
|
||||
+ goto out_free_priv;
|
||||
+ }
|
||||
+
|
||||
+ priv->opp_nb.notifier_call = opp_notifier;
|
||||
+ ret = srcu_notifier_chain_register(opp_srcu_head, &priv->opp_nb);
|
||||
+ if (ret)
|
||||
+ goto out_free_priv;
|
||||
+
|
||||
of_property_read_u32(np, "voltage-tolerance", &priv->voltage_tolerance);
|
||||
|
||||
if (!transition_latency)
|
||||
@@ -326,7 +384,7 @@ static int cpufreq_init(struct cpufreq_p
|
||||
ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
|
||||
if (ret) {
|
||||
pr_err("failed to init cpufreq table: %d\n", ret);
|
||||
- goto out_free_priv;
|
||||
+ goto out_unregister_nb;
|
||||
}
|
||||
|
||||
priv->cpu_dev = cpu_dev;
|
||||
@@ -365,6 +423,8 @@ static int cpufreq_init(struct cpufreq_p
|
||||
|
||||
out_free_cpufreq_table:
|
||||
dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
|
||||
+out_unregister_nb:
|
||||
+ srcu_notifier_chain_unregister(opp_srcu_head, &priv->opp_nb);
|
||||
out_free_priv:
|
||||
kfree(priv);
|
||||
out_free_opp:
|
|
@ -1,161 +0,0 @@
|
|||
From b4629f9e30e865402c643de6d4668be790fc0539 Mon Sep 17 00:00:00 2001
|
||||
From: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
Date: Tue, 8 Sep 2015 11:24:41 +0300
|
||||
Subject: cpufreq-dt: Add L2 frequency scaling support
|
||||
|
||||
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
|
||||
Conflicts:
|
||||
drivers/cpufreq/cpufreq-dt.c
|
||||
---
|
||||
drivers/cpufreq/cpufreq-dt.c | 54 ++++++++++++++++++++++++++++++++++++++------
|
||||
include/linux/cpufreq.h | 2 ++
|
||||
2 files changed, 49 insertions(+), 7 deletions(-)
|
||||
|
||||
--- a/drivers/cpufreq/cpufreq-dt.c
|
||||
+++ b/drivers/cpufreq/cpufreq-dt.c
|
||||
@@ -86,11 +86,13 @@ static int set_target(struct cpufreq_pol
|
||||
struct dev_pm_opp *opp;
|
||||
struct cpufreq_frequency_table *freq_table = policy->freq_table;
|
||||
struct clk *cpu_clk = policy->clk;
|
||||
+ struct clk *l2_clk = policy->l2_clk;
|
||||
struct private_data *priv = policy->driver_data;
|
||||
struct device *cpu_dev = priv->cpu_dev;
|
||||
struct regulator *cpu_reg = priv->cpu_reg;
|
||||
unsigned long volt = 0, volt_old = 0, tol = 0;
|
||||
- unsigned int old_freq, new_freq;
|
||||
+ unsigned int old_freq, new_freq, l2_freq;
|
||||
+ unsigned long new_l2_freq = 0;
|
||||
long freq_Hz, freq_exact;
|
||||
unsigned long opp_freq = 0;
|
||||
int ret;
|
||||
@@ -146,6 +148,30 @@ static int set_target(struct cpufreq_pol
|
||||
goto out;
|
||||
}
|
||||
|
||||
+ if (!IS_ERR(l2_clk) && policy->l2_rate[0] && policy->l2_rate[1] &&
|
||||
+ policy->l2_rate[2]) {
|
||||
+ static unsigned long krait_l2[CONFIG_NR_CPUS] = { };
|
||||
+ int cpu, ret = 0;
|
||||
+
|
||||
+ if (freq_exact >= policy->l2_rate[2])
|
||||
+ new_l2_freq = policy->l2_rate[2];
|
||||
+ else if (freq_exact >= policy->l2_rate[1])
|
||||
+ new_l2_freq = policy->l2_rate[1];
|
||||
+ else
|
||||
+ new_l2_freq = policy->l2_rate[0];
|
||||
+
|
||||
+ krait_l2[policy->cpu] = new_l2_freq;
|
||||
+ for_each_present_cpu(cpu)
|
||||
+ new_l2_freq = max(new_l2_freq, krait_l2[cpu]);
|
||||
+
|
||||
+ l2_freq = clk_get_rate(l2_clk);
|
||||
+
|
||||
+ if (l2_freq != new_l2_freq) {
|
||||
+ /* scale l2 with the core */
|
||||
+ ret = clk_set_rate(l2_clk, new_l2_freq);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/* scaling down? scale voltage after frequency */
|
||||
if (!IS_ERR(cpu_reg) && new_freq < old_freq) {
|
||||
ret = regulator_set_voltage_tol(cpu_reg, volt, tol);
|
||||
@@ -156,18 +182,21 @@ static int set_target(struct cpufreq_pol
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
+
|
||||
priv->opp_freq = opp_freq;
|
||||
+
|
||||
out:
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int allocate_resources(int cpu, struct device **cdev,
|
||||
- struct regulator **creg, struct clk **cclk)
|
||||
+ struct regulator **creg, struct clk **cclk,
|
||||
+ struct clk **l2)
|
||||
{
|
||||
struct device *cpu_dev;
|
||||
struct regulator *cpu_reg;
|
||||
- struct clk *cpu_clk;
|
||||
+ struct clk *cpu_clk, *l2_clk = NULL;
|
||||
int ret = 0;
|
||||
char *reg_cpu0 = "cpu0", *reg_cpu = "cpu", *reg;
|
||||
|
||||
@@ -227,6 +256,10 @@ try_again:
|
||||
*cdev = cpu_dev;
|
||||
*creg = cpu_reg;
|
||||
*cclk = cpu_clk;
|
||||
+
|
||||
+ l2_clk = clk_get(cpu_dev, "l2");
|
||||
+ if (!IS_ERR(l2_clk))
|
||||
+ *l2 = l2_clk;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -236,18 +269,20 @@ static int cpufreq_init(struct cpufreq_p
|
||||
{
|
||||
struct cpufreq_frequency_table *freq_table;
|
||||
struct device_node *np;
|
||||
+ struct device_node *l2_np;
|
||||
struct private_data *priv;
|
||||
struct device *cpu_dev;
|
||||
struct regulator *cpu_reg;
|
||||
- struct clk *cpu_clk;
|
||||
struct dev_pm_opp *suspend_opp;
|
||||
+ struct clk *cpu_clk, *l2_clk;
|
||||
unsigned long min_uV = ~0, max_uV = 0;
|
||||
unsigned int transition_latency;
|
||||
bool need_update = false;
|
||||
int ret;
|
||||
struct srcu_notifier_head *opp_srcu_head;
|
||||
|
||||
- ret = allocate_resources(policy->cpu, &cpu_dev, &cpu_reg, &cpu_clk);
|
||||
+ ret = allocate_resources(policy->cpu, &cpu_dev, &cpu_reg, &cpu_clk,
|
||||
+ &l2_clk);
|
||||
if (ret) {
|
||||
pr_err("%s: Failed to allocate resources: %d\n", __func__, ret);
|
||||
return ret;
|
||||
@@ -398,6 +433,11 @@ static int cpufreq_init(struct cpufreq_p
|
||||
if (suspend_opp)
|
||||
policy->suspend_freq = dev_pm_opp_get_freq(suspend_opp) / 1000;
|
||||
rcu_read_unlock();
|
||||
+ policy->l2_clk = l2_clk;
|
||||
+
|
||||
+ l2_np = of_find_node_by_name(NULL, "qcom,l2");
|
||||
+ if (l2_np)
|
||||
+ of_property_read_u32_array(l2_np, "qcom,l2-rates", policy->l2_rate, 3);
|
||||
|
||||
ret = cpufreq_table_validate_and_show(policy, freq_table);
|
||||
if (ret) {
|
||||
@@ -498,7 +538,7 @@ static int dt_cpufreq_probe(struct platf
|
||||
{
|
||||
struct device *cpu_dev;
|
||||
struct regulator *cpu_reg;
|
||||
- struct clk *cpu_clk;
|
||||
+ struct clk *cpu_clk, *l2_clk;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
@@ -508,7 +548,7 @@ static int dt_cpufreq_probe(struct platf
|
||||
*
|
||||
* FIXME: Is checking this only for CPU0 sufficient ?
|
||||
*/
|
||||
- ret = allocate_resources(0, &cpu_dev, &cpu_reg, &cpu_clk);
|
||||
+ ret = allocate_resources(0, &cpu_dev, &cpu_reg, &cpu_clk, &l2_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
--- a/include/linux/cpufreq.h
|
||||
+++ b/include/linux/cpufreq.h
|
||||
@@ -67,6 +67,8 @@ struct cpufreq_policy {
|
||||
unsigned int cpu; /* cpu managing this policy, must be online */
|
||||
|
||||
struct clk *clk;
|
||||
+ struct clk *l2_clk; /* L2 clock */
|
||||
+ unsigned int l2_rate[3]; /* L2 bus clock rate thresholds */
|
||||
struct cpufreq_cpuinfo cpuinfo;/* see above */
|
||||
|
||||
unsigned int min; /* in kHz */
|
|
@ -1,33 +0,0 @@
|
|||
From dafae9c5b39e2871bfd8db0b4bad6e850e42ef49 Mon Sep 17 00:00:00 2001
|
||||
From: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
Date: Wed, 13 Jan 2016 15:10:25 +0200
|
||||
Subject: cpufreq-dt: Add missing rcu_read_lock() for find_device_opp()
|
||||
|
||||
The function dev_pm_opp_get_notifier() must be called with held
|
||||
rcu_read_lock. In order to keep the pointer valid, add rcu_read_lock().
|
||||
|
||||
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
---
|
||||
drivers/cpufreq/cpufreq-dt.c | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
--- a/drivers/cpufreq/cpufreq-dt.c
|
||||
+++ b/drivers/cpufreq/cpufreq-dt.c
|
||||
@@ -359,14 +359,17 @@ static int cpufreq_init(struct cpufreq_p
|
||||
|
||||
mutex_init(&priv->lock);
|
||||
|
||||
+ rcu_read_lock();
|
||||
opp_srcu_head = dev_pm_opp_get_notifier(cpu_dev);
|
||||
if (IS_ERR(opp_srcu_head)) {
|
||||
ret = PTR_ERR(opp_srcu_head);
|
||||
+ rcu_read_unlock();
|
||||
goto out_free_priv;
|
||||
}
|
||||
|
||||
priv->opp_nb.notifier_call = opp_notifier;
|
||||
ret = srcu_notifier_chain_register(opp_srcu_head, &priv->opp_nb);
|
||||
+ rcu_read_unlock();
|
||||
if (ret)
|
||||
goto out_free_priv;
|
||||
|
|
@ -1,386 +0,0 @@
|
|||
From 7727b1cae43e9abac746ef016c3dbf50ee81a6d6 Mon Sep 17 00:00:00 2001
|
||||
From: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
Date: Wed, 18 Mar 2015 17:23:29 +0200
|
||||
Subject: clk: qcom: Add support for regmap mux-div clocks
|
||||
|
||||
Add support for hardware that support switching both parent clocks and the
|
||||
divider at the same time. This avoids generating intermediate frequencies
|
||||
from either the old parent clock and new divider or new parent clock and
|
||||
old divider combinations.
|
||||
|
||||
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
---
|
||||
drivers/clk/qcom/Makefile | 1 +
|
||||
drivers/clk/qcom/clk-regmap-mux-div.c | 288 ++++++++++++++++++++++++++++++++++
|
||||
drivers/clk/qcom/clk-regmap-mux-div.h | 63 ++++++++
|
||||
3 files changed, 352 insertions(+)
|
||||
create mode 100644 drivers/clk/qcom/clk-regmap-mux-div.c
|
||||
create mode 100644 drivers/clk/qcom/clk-regmap-mux-div.h
|
||||
|
||||
--- a/drivers/clk/qcom/Makefile
|
||||
+++ b/drivers/clk/qcom/Makefile
|
||||
@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
|
||||
clk-qcom-y += clk-branch.o
|
||||
clk-qcom-y += clk-regmap-divider.o
|
||||
clk-qcom-y += clk-regmap-mux.o
|
||||
+clk-qcom-y += clk-regmap-mux-div.o
|
||||
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||
obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||
clk-qcom-y += clk-hfpll.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-regmap-mux-div.c
|
||||
@@ -0,0 +1,288 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2015, 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/bitops.h>
|
||||
+#include <linux/clk.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/export.h>
|
||||
+#include <linux/kernel.h>
|
||||
+#include <linux/regmap.h>
|
||||
+
|
||||
+#include "clk-regmap-mux-div.h"
|
||||
+
|
||||
+#define CMD_RCGR 0x0
|
||||
+#define CMD_RCGR_UPDATE BIT(0)
|
||||
+#define CMD_RCGR_DIRTY_CFG BIT(4)
|
||||
+#define CMD_RCGR_ROOT_OFF BIT(31)
|
||||
+#define CFG_RCGR 0x4
|
||||
+
|
||||
+static int __mux_div_update_config(struct clk_regmap_mux_div *md)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 val, count;
|
||||
+ const char *name = clk_hw_get_name(&md->clkr.hw);
|
||||
+
|
||||
+ ret = regmap_update_bits(md->clkr.regmap, CMD_RCGR + md->reg_offset,
|
||||
+ CMD_RCGR_UPDATE, CMD_RCGR_UPDATE);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ /* Wait for update to take effect */
|
||||
+ for (count = 500; count > 0; count--) {
|
||||
+ ret = regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset,
|
||||
+ &val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+ if (!(val & CMD_RCGR_UPDATE))
|
||||
+ return 0;
|
||||
+ udelay(1);
|
||||
+ }
|
||||
+
|
||||
+ pr_err("%s: RCG did not update its configuration", name);
|
||||
+ return -EBUSY;
|
||||
+}
|
||||
+
|
||||
+static int __mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src_sel,
|
||||
+ u32 src_div)
|
||||
+{
|
||||
+ int ret;
|
||||
+ u32 val, mask;
|
||||
+
|
||||
+ val = (src_div << md->hid_shift) | (src_sel << md->src_shift);
|
||||
+ mask = ((BIT(md->hid_width) - 1) << md->hid_shift) |
|
||||
+ ((BIT(md->src_width) - 1) << md->src_shift);
|
||||
+
|
||||
+ ret = regmap_update_bits(md->clkr.regmap, CFG_RCGR + md->reg_offset,
|
||||
+ mask, val);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return __mux_div_update_config(md);
|
||||
+}
|
||||
+
|
||||
+static void __mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src_sel,
|
||||
+ u32 *src_div)
|
||||
+{
|
||||
+ u32 val, div, src;
|
||||
+ const char *name = clk_hw_get_name(&md->clkr.hw);
|
||||
+
|
||||
+ regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val);
|
||||
+
|
||||
+ if (val & CMD_RCGR_DIRTY_CFG) {
|
||||
+ pr_err("%s: RCG configuration is pending\n", name);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val);
|
||||
+ src = (val >> md->src_shift);
|
||||
+ src &= BIT(md->src_width) - 1;
|
||||
+ *src_sel = src;
|
||||
+
|
||||
+ div = (val >> md->hid_shift);
|
||||
+ div &= BIT(md->hid_width) - 1;
|
||||
+ *src_div = div;
|
||||
+}
|
||||
+
|
||||
+static int mux_div_enable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+
|
||||
+ return __mux_div_set_src_div(md, md->src_sel, md->div);
|
||||
+}
|
||||
+
|
||||
+static inline bool is_better_rate(unsigned long req, unsigned long best,
|
||||
+ unsigned long new)
|
||||
+{
|
||||
+ return (req <= new && new < best) || (best < req && best < new);
|
||||
+}
|
||||
+
|
||||
+static int mux_div_determine_rate(struct clk_hw *hw,
|
||||
+ struct clk_rate_request *req)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+ unsigned int i, div, max_div;
|
||||
+ unsigned long actual_rate, best_rate = 0;
|
||||
+ unsigned long req_rate = req->rate;
|
||||
+
|
||||
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
|
||||
+ struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
|
||||
+ unsigned long parent_rate = clk_hw_get_rate(parent);
|
||||
+
|
||||
+ max_div = BIT(md->hid_width) - 1;
|
||||
+ for (div = 1; div < max_div; div++) {
|
||||
+ parent_rate = mult_frac(req_rate, div, 2);
|
||||
+ parent_rate = clk_hw_round_rate(parent, parent_rate);
|
||||
+ actual_rate = mult_frac(parent_rate, 2, div);
|
||||
+
|
||||
+ if (is_better_rate(req_rate, best_rate, actual_rate)) {
|
||||
+ best_rate = actual_rate;
|
||||
+ req->rate = best_rate;
|
||||
+ req->best_parent_rate = parent_rate;
|
||||
+ req->best_parent_hw = parent;
|
||||
+ }
|
||||
+
|
||||
+ if (actual_rate < req_rate || best_rate <= req_rate)
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (!best_rate)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int __mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long prate, u32 src_sel)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+ int ret, i;
|
||||
+ u32 div, max_div, best_src = 0, best_div = 0;
|
||||
+ unsigned long actual_rate, best_rate = 0;
|
||||
+
|
||||
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
|
||||
+ struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
|
||||
+ unsigned long parent_rate = clk_hw_get_rate(parent);
|
||||
+
|
||||
+ max_div = BIT(md->hid_width) - 1;
|
||||
+ for (div = 1; div < max_div; div++) {
|
||||
+ parent_rate = mult_frac(rate, div, 2);
|
||||
+ parent_rate = clk_hw_round_rate(parent, parent_rate);
|
||||
+ actual_rate = mult_frac(parent_rate, 2, div);
|
||||
+
|
||||
+ if (is_better_rate(rate, best_rate, actual_rate)) {
|
||||
+ best_rate = actual_rate;
|
||||
+ best_src = md->parent_map[i].cfg;
|
||||
+ best_div = div - 1;
|
||||
+ }
|
||||
+
|
||||
+ if (actual_rate < rate || best_rate <= rate)
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ ret = __mux_div_set_src_div(md, best_src, best_div);
|
||||
+ if (!ret) {
|
||||
+ md->div = best_div;
|
||||
+ md->src_sel = best_src;
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static u8 mux_div_get_parent(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+ const char *name = clk_hw_get_name(hw);
|
||||
+ u32 i, div, src;
|
||||
+
|
||||
+ __mux_div_get_src_div(md, &src, &div);
|
||||
+
|
||||
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++)
|
||||
+ if (src == md->parent_map[i].cfg)
|
||||
+ return i;
|
||||
+
|
||||
+ pr_err("%s: Can't find parent %d\n", name, src);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int mux_div_set_parent(struct clk_hw *hw, u8 index)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+
|
||||
+ return __mux_div_set_src_div(md, md->parent_map[index].cfg, md->div);
|
||||
+}
|
||||
+
|
||||
+static int mux_div_set_rate(struct clk_hw *hw,
|
||||
+ unsigned long rate, unsigned long prate)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+
|
||||
+ return __mux_div_set_rate_and_parent(hw, rate, prate, md->src_sel);
|
||||
+}
|
||||
+
|
||||
+static int mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
|
||||
+ unsigned long prate, u8 index)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+
|
||||
+ return __mux_div_set_rate_and_parent(hw, rate, prate,
|
||||
+ md->parent_map[index].cfg);
|
||||
+}
|
||||
+
|
||||
+static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+ u32 div, src;
|
||||
+ int i, num_parents = clk_hw_get_num_parents(hw);
|
||||
+ const char *name = clk_hw_get_name(hw);
|
||||
+
|
||||
+ __mux_div_get_src_div(md, &src, &div);
|
||||
+ for (i = 0; i < num_parents; i++)
|
||||
+ if (src == md->parent_map[i].cfg) {
|
||||
+ struct clk_hw *p = clk_hw_get_parent_by_index(hw, i);
|
||||
+ unsigned long parent_rate = clk_hw_get_rate(p);
|
||||
+
|
||||
+ return mult_frac(parent_rate, 2, div + 1);
|
||||
+ }
|
||||
+
|
||||
+ pr_err("%s: Can't find parent %d\n", name, src);
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct clk_hw *mux_div_get_safe_parent(struct clk_hw *hw,
|
||||
+ unsigned long *safe_freq)
|
||||
+{
|
||||
+ int i;
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+
|
||||
+ if (md->safe_freq)
|
||||
+ *safe_freq = md->safe_freq;
|
||||
+
|
||||
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++)
|
||||
+ if (md->safe_src == md->parent_map[i].cfg)
|
||||
+ break;
|
||||
+
|
||||
+ return clk_hw_get_parent_by_index(hw, i);
|
||||
+}
|
||||
+
|
||||
+static void mux_div_disable(struct clk_hw *hw)
|
||||
+{
|
||||
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||
+ struct clk_hw *parent;
|
||||
+ u32 div;
|
||||
+
|
||||
+ if (!md->safe_freq || !md->safe_src)
|
||||
+ return;
|
||||
+
|
||||
+ parent = mux_div_get_safe_parent(hw, &md->safe_freq);
|
||||
+ div = divider_get_val(md->safe_freq, clk_get_rate(parent->clk), NULL,
|
||||
+ md->hid_width, CLK_DIVIDER_ROUND_CLOSEST);
|
||||
+ div = 2 * div + 1;
|
||||
+
|
||||
+ __mux_div_set_src_div(md, md->safe_src, div);
|
||||
+}
|
||||
+
|
||||
+const struct clk_ops clk_regmap_mux_div_ops = {
|
||||
+ .enable = mux_div_enable,
|
||||
+ .disable = mux_div_disable,
|
||||
+ .get_parent = mux_div_get_parent,
|
||||
+ .set_parent = mux_div_set_parent,
|
||||
+ .set_rate = mux_div_set_rate,
|
||||
+ .set_rate_and_parent = mux_div_set_rate_and_parent,
|
||||
+ .determine_rate = mux_div_determine_rate,
|
||||
+ .recalc_rate = mux_div_recalc_rate,
|
||||
+ .get_safe_parent = mux_div_get_safe_parent,
|
||||
+};
|
||||
+EXPORT_SYMBOL_GPL(clk_regmap_mux_div_ops);
|
||||
--- /dev/null
|
||||
+++ b/drivers/clk/qcom/clk-regmap-mux-div.h
|
||||
@@ -0,0 +1,63 @@
|
||||
+/*
|
||||
+ * Copyright (c) 2015, 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.
|
||||
+ */
|
||||
+
|
||||
+#ifndef __QCOM_CLK_REGMAP_MUX_DIV_H__
|
||||
+#define __QCOM_CLK_REGMAP_MUX_DIV_H__
|
||||
+
|
||||
+#include <linux/clk-provider.h>
|
||||
+#include "clk-regmap.h"
|
||||
+#include "clk-rcg.h"
|
||||
+
|
||||
+/**
|
||||
+ * struct mux_div_clk - combined mux/divider clock
|
||||
+ * @reg_offset: offset of the mux/divider register
|
||||
+ * @hid_width: number of bits in half integer divider
|
||||
+ * @hid_shift: lowest bit of hid value field
|
||||
+ * @src_width: number of bits in source select
|
||||
+ * @src_shift: lowest bit of source select field
|
||||
+ * @div: the divider raw configuration value
|
||||
+ * @src_sel: the mux index which will be used if the clock is enabled
|
||||
+ * @safe_src: the safe source mux index for this clock
|
||||
+ * @safe_freq: When switching rates from A to B, the mux div clock will
|
||||
+ * instead switch from A -> safe_freq -> B. This allows the
|
||||
+ * mux_div clock to change rates while enabled, even if this
|
||||
+ * behavior is not supported by the parent clocks.
|
||||
+ * If changing the rate of parent A also causes the rate of
|
||||
+ * parent B to change, then safe_freq must be defined.
|
||||
+ * safe_freq is expected to have a source clock which is always
|
||||
+ * on and runs at only one rate.
|
||||
+ * @parent_map: pointer to parent_map struct
|
||||
+ * @clkr: handle between common and hardware-specific interfaces
|
||||
+ */
|
||||
+
|
||||
+struct clk_regmap_mux_div {
|
||||
+ u32 reg_offset;
|
||||
+ u32 hid_width;
|
||||
+ u32 hid_shift;
|
||||
+ u32 src_width;
|
||||
+ u32 src_shift;
|
||||
+ u32 div;
|
||||
+ u32 src_sel;
|
||||
+ u32 safe_src;
|
||||
+ unsigned long safe_freq;
|
||||
+ const struct parent_map *parent_map;
|
||||
+ struct clk_regmap clkr;
|
||||
+};
|
||||
+
|
||||
+#define to_clk_regmap_mux_div(_hw) \
|
||||
+ container_of(to_clk_regmap(_hw), struct clk_regmap_mux_div, clkr)
|
||||
+
|
||||
+extern const struct clk_ops clk_regmap_mux_div_ops;
|
||||
+
|
||||
+#endif
|
|
@ -1,53 +0,0 @@
|
|||
From patchwork Wed Nov 2 15:56:58 2016
|
||||
Content-Type: text/plain; charset="utf-8"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Subject: [v9,3/3] clk: qcom: Always add factor clock for xo clocks
|
||||
From: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
X-Patchwork-Id: 9409421
|
||||
Message-Id: <20161102155658.32203-4-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:58 +0200
|
||||
|
||||
Currently the RPM/RPM-SMD clock drivers do not register the xo clocks,
|
||||
so we should always add factor clock. When we later add xo clocks support
|
||||
into the drivers, we should update this function to skip registration.
|
||||
By doing so we avoid any DT dependencies.
|
||||
|
||||
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
|
||||
---
|
||||
drivers/clk/qcom/common.c | 15 ++++++---------
|
||||
1 file changed, 6 insertions(+), 9 deletions(-)
|
||||
|
||||
--
|
||||
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
|
||||
|
||||
--- a/drivers/clk/qcom/common.c
|
||||
+++ b/drivers/clk/qcom/common.c
|
||||
@@ -154,15 +154,12 @@ int qcom_cc_register_board_clk(struct de
|
||||
const char *name, unsigned long rate)
|
||||
{
|
||||
bool add_factor = true;
|
||||
- struct device_node *node;
|
||||
|
||||
- /* The RPM clock driver will add the factor clock if present */
|
||||
- if (IS_ENABLED(CONFIG_QCOM_RPMCC)) {
|
||||
- node = of_find_compatible_node(NULL, NULL, "qcom,rpmcc");
|
||||
- if (of_device_is_available(node))
|
||||
- add_factor = false;
|
||||
- of_node_put(node);
|
||||
- }
|
||||
+ /*
|
||||
+ * TODO: The RPM clock driver currently does not support the xo clock.
|
||||
+ * When xo is added to the RPM clock driver, we should change this
|
||||
+ * function to skip registration of xo factor clocks.
|
||||
+ */
|
||||
|
||||
return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor);
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
From 252a4e72dfd7add3c37604449fd20db29d84c6f8 Mon Sep 17 00:00:00 2001
|
||||
From: Lina Iyer <lina.iyer@linaro.org>
|
||||
Date: Wed, 25 Mar 2015 14:25:29 -0600
|
||||
Subject: ARM: cpuidle: Add cpuidle support for QCOM cpus
|
||||
|
||||
Define ARM_QCOM_CPUIDLE config item to enable cpuidle support.
|
||||
|
||||
Cc: Stephen Boyd <sboyd@codeaurora.org>
|
||||
Cc: Arnd Bergmann <arnd@arndb.de>
|
||||
Cc: Kevin Hilman <khilman@linaro.org>
|
||||
Cc: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
Signed-off-by: Lina Iyer <lina.iyer@linaro.org>
|
||||
---
|
||||
drivers/cpuidle/Kconfig.arm | 7 +++++++
|
||||
1 file changed, 7 insertions(+)
|
||||
|
||||
--- a/drivers/cpuidle/Kconfig.arm
|
||||
+++ b/drivers/cpuidle/Kconfig.arm
|
||||
@@ -74,3 +74,10 @@ config ARM_MVEBU_V7_CPUIDLE
|
||||
depends on ARCH_MVEBU && !ARM64
|
||||
help
|
||||
Select this to enable cpuidle on Armada 370, 38x and XP processors.
|
||||
+
|
||||
+config ARM_QCOM_CPUIDLE
|
||||
+ bool "CPU Idle Driver for QCOM processors"
|
||||
+ depends on ARCH_QCOM
|
||||
+ select ARM_CPUIDLE
|
||||
+ help
|
||||
+ Select this to enable cpuidle on QCOM processors.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue