ipq806x: remove v4.4 support
Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
		| @@ -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
		Reference in New Issue
	
	Block a user
	 John Crispin
					John Crispin