aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2015-11-18 10:28:39 +0000
committerSrinivas Kandagatla <srinivas.kandagatla@linaro.org>2015-11-18 10:28:39 +0000
commitafc14404aa958a99e74f43c83a09642d3a49d0cf (patch)
tree95d8d735a0a0ecbae0dc48c02dd6cb5a9f8179c4
parent23a0fedb1db59a6af0a7d550c5b4b06eead3e553 (diff)
parent12e0bb703bbd96559f4557b5464d6aca176fe6ec (diff)
Merge branch 'tracking-qcomlt-mainline-rpm-smd-pil' into integration-linux-qcomlt
* tracking-qcomlt-mainline-rpm-smd-pil: (50 commits) soc: qcom: smd: Support opening additional channels soc: qcom: smd: Support multiple channels per sdev soc: qcom: smd: Refactor channel open and close handling soc: qcom: smd: Split discovery and state change work soc: qcom: smd: Introduce callback setter soc: qcom: smd: Implement id_table driver matching soc: qcom: smd-rpm: Correct the active vs sleep state flagging soc: smd: Migrate the wifi driver from old smd driver to new smd driver. qcom-smd-rpm: Add MSM8916 support fixup! regulator: smd: Add floor and corner operations gpio:smp2p:qcom: kill set_irq_flags and use genirq qcom/smd: support transmit/receive the data not aligned to 32bit. remoteproc: tz_pil: skip waiting ready irq if it not provided remoteproc: tz_pil: take relocation flag into account remoteproc: tz_pil: make irqs, smd edge & crash-reason optional properties regulator: smd: remove left over debug statement regulator: smd: Add correct ifdef flag for stubs regulator: smd: Make set_{corner,floor} work with regulator struct regulator: smd: Add floor and corner operations HACK: soc: qcom: smd: Add debug to fix timing ...
-rw-r--r--Documentation/devicetree/bindings/gpio/qcom,smp2p.txt105
-rw-r--r--Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt51
-rw-r--r--arch/arm64/configs/qcom_defconfig274
-rw-r--r--drivers/gpio/Kconfig15
-rw-r--r--drivers/gpio/Makefile2
-rw-r--r--drivers/gpio/gpio-qcom-smp2p.c591
-rw-r--r--drivers/gpio/gpio-qcom-smsm.c328
-rw-r--r--drivers/regulator/Kconfig12
-rw-r--r--drivers/regulator/Makefile1
-rw-r--r--drivers/regulator/qcom_smd-regulator.c116
-rw-r--r--drivers/remoteproc/Kconfig16
-rw-r--r--drivers/remoteproc/Makefile2
-rw-r--r--drivers/remoteproc/qcom_q6v5_pil.c1075
-rw-r--r--drivers/remoteproc/qcom_tz_pil.c719
-rw-r--r--drivers/remoteproc/remoteproc_core.c44
-rw-r--r--drivers/soc/qcom/smd-rpm.c3
-rw-r--r--drivers/soc/qcom/smd.c396
-rw-r--r--drivers/soc/qcom/smem.c122
-rw-r--r--include/linux/regulator/qcom_smd-regulator.h30
-rw-r--r--include/linux/remoteproc.h4
-rw-r--r--include/linux/soc/qcom/smd.h67
21 files changed, 3782 insertions, 191 deletions
diff --git a/Documentation/devicetree/bindings/gpio/qcom,smp2p.txt b/Documentation/devicetree/bindings/gpio/qcom,smp2p.txt
new file mode 100644
index 000000000000..e758c8b5e3fc
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpio/qcom,smp2p.txt
@@ -0,0 +1,105 @@
+Qualcomm Shared Memory Point 2 Point binding
+
+The Shared Memory Point to Point (SMP2P) protocol facilitates communication of
+a single 32-bit value between two processors. Each value has a single writer
+(the local side) and a single reader (the remote side). Values are uniquely
+identified in the system by the directed edge (local processor ID to remote
+processor ID) and a string identifier. This documents defines the binding for a
+driver that implements and exposes this protocol as a set of GPIO and interrupt
+controllers.
+
+- compatible:
+ Usage: required
+ Value type: <string>
+ Definition: must be one of:
+ "qcom,smp2p"
+
+- interrupts:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: one entry specifying the smp2p notification interrupt
+
+- qcom,ipc:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: three entries specifying the outgoing ipc bit used for
+ signaling the remote end of the smp2p edge:
+ - phandle to a syscon node representing the apcs registers
+ - u32 representing offset to the register within the syscon
+ - u32 representing the ipc bit within the register
+
+- qcom,smem:
+ Usage: required
+ Value type: <u32 array>
+ Definition: two identifiers of the inbound and outbound smem items used
+ for this edge
+
+- qcom,local-pid:
+ Usage: required
+ Value type: <u32>
+ Definition: specifies the identfier of the local endpoint of this edge
+
+- qcom,remote-pid:
+ Usage: required
+ Value type: <u32>
+ Definition: specifies the identfier of the remote endpoint of this edge
+
+= SUBNODES
+Each SMP2P pair contain a set of inbound and outbound entries, these are
+described in subnodes of the smp2p device node. The node names are not
+important.
+
+- qcom,entry-name:
+ Usage: required
+ Value type: <string>
+ Definition: specifies the name of this entry, for inbound entries this
+ will be used to match against the remotely allocated entry
+ and for outbound entries this name is used for allocating
+ entries
+
+- qcom,inbound:
+ Usage: optional
+ Value type: <empty>
+ Definition: marks the entry as inbound; the node should be specified
+ as a two cell interrupt-controller as defined in
+ "../interrupt-controller/interrupts.txt"
+
+- qcom,outbound:
+ Usage: optional
+ Value type: <empty>
+ Definition: marks the entry as outbound; the node should be specified
+ as a two cell gpio-controller as defined in "gpio.txt"
+
+
+= EXAMPLE
+The following example shows the SMP2P setup with the wireless processor,
+defined from the 8974 apps processor's point-of-view. It encompasses one
+inbound and one outbound entry:
+
+wcnss-smp2p {
+ compatible = "qcom,smp2p";
+ qcom,smem = <431>, <451>;
+
+ interrupts = <0 143 1>;
+
+ qcom,ipc = <&apcs 8 18>;
+
+ qcom,local-pid = <0>;
+ qcom,remote-pid = <4>;
+
+ wcnss_smp2p_out: master-kernel {
+ qcom,entry-name = "master-kernel";
+ qcom,outbound;
+
+ gpio-controller;
+ #gpio-cells = <2>;
+ };
+
+ wcnss_smp2p_in: slave-kernel {
+ qcom,entry-name = "slave-kernel";
+ qcom,inbound;
+
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt
new file mode 100644
index 000000000000..19cad6cc427c
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt
@@ -0,0 +1,51 @@
+Qualcomm Shared Memory Manager binding
+
+This binding describes the Qualcomm Shared Memory Manager, used to share data
+between various subsystems and OSes in Qualcomm platforms.
+
+- compatible:
+ Usage: required
+ Value type: <stringlist>
+ Definition: must be:
+ "qcom,smem"
+
+- memory-region:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: handle to memory reservation for main SMEM memory region.
+
+- reg:
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: base address and size pair for any additional memory areas
+ of the shared memory.
+
+- hwlocks:
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: reference to a hwspinlock used to protect allocations from
+ the shared memory
+
+= EXAMPLE
+The following example shows the SMEM setup for MSM8974, with a main SMEM region
+at 0xfa00000 and an auxiliary region at 0xfc428000:
+
+ reserved-memory {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+
+ smem_region: smem@fa00000 {
+ reg = <0xfa00000 0x200000>;
+ no-map;
+ };
+ };
+
+ smem@fa00000 {
+ compatible = "qcom,smem";
+
+ memory-region = <&smem_region>;
+ reg = <0xfc428000 0x4000>;
+
+ hwlocks = <&tcsr_mutex 3>;
+ };
diff --git a/arch/arm64/configs/qcom_defconfig b/arch/arm64/configs/qcom_defconfig
new file mode 100644
index 000000000000..6ece7d6477f5
--- /dev/null
+++ b/arch/arm64/configs/qcom_defconfig
@@ -0,0 +1,274 @@
+CONFIG_SYSVIPC=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_CGROUPS=y
+CONFIG_CGROUP_DEBUG=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_RT_GROUP_SCHED=y
+CONFIG_NAMESPACES=y
+# CONFIG_UTS_NS is not set
+# CONFIG_IPC_NS is not set
+# CONFIG_PID_NS is not set
+CONFIG_RELAY=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_CC_OPTIMIZE_FOR_SIZE=y
+CONFIG_KALLSYMS_ALL=y
+CONFIG_EMBEDDED=y
+CONFIG_PROFILING=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODULE_FORCE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_ARCH_QCOM=y
+CONFIG_SMP=y
+CONFIG_SCHED_MC=y
+CONFIG_PREEMPT=y
+CONFIG_CMA=y
+# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set
+CONFIG_COMPAT=y
+CONFIG_PM_AUTOSLEEP=y
+CONFIG_PM_WAKELOCKS=y
+CONFIG_PM_WAKELOCKS_LIMIT=0
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_UNIX=y
+CONFIG_XFRM_USER=y
+CONFIG_INET=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_INET_AH=y
+CONFIG_INET_ESP=y
+# CONFIG_INET_XFRM_MODE_BEET is not set
+# CONFIG_INET_LRO is not set
+CONFIG_IPV6=y
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_IPV6_OPTIMISTIC_DAD=y
+CONFIG_INET6_AH=y
+CONFIG_INET6_ESP=y
+CONFIG_INET6_IPCOMP=y
+CONFIG_IPV6_MIP6=y
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_NETFILTER=y
+CONFIG_NF_CONNTRACK=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CT_PROTO_DCCP=y
+CONFIG_NF_CT_PROTO_SCTP=y
+CONFIG_NF_CT_PROTO_UDPLITE=y
+CONFIG_NF_CONNTRACK_AMANDA=y
+CONFIG_NF_CONNTRACK_FTP=y
+CONFIG_NF_CONNTRACK_H323=y
+CONFIG_NF_CONNTRACK_IRC=y
+CONFIG_NF_CONNTRACK_NETBIOS_NS=y
+CONFIG_NF_CONNTRACK_PPTP=y
+CONFIG_NF_CONNTRACK_SANE=y
+CONFIG_NF_CONNTRACK_SIP=y
+CONFIG_NF_CONNTRACK_TFTP=y
+CONFIG_NF_CT_NETLINK=y
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=y
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=y
+CONFIG_NETFILTER_XT_TARGET_LOG=y
+CONFIG_NETFILTER_XT_TARGET_MARK=y
+CONFIG_NETFILTER_XT_TARGET_NFLOG=y
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=y
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=y
+CONFIG_NETFILTER_XT_TARGET_TPROXY=y
+CONFIG_NETFILTER_XT_TARGET_TRACE=y
+CONFIG_NETFILTER_XT_MATCH_COMMENT=y
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=y
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=y
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
+CONFIG_NETFILTER_XT_MATCH_DSCP=y
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=y
+CONFIG_NETFILTER_XT_MATCH_HELPER=y
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=y
+CONFIG_NETFILTER_XT_MATCH_LENGTH=y
+CONFIG_NETFILTER_XT_MATCH_LIMIT=y
+CONFIG_NETFILTER_XT_MATCH_MAC=y
+CONFIG_NETFILTER_XT_MATCH_MARK=y
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
+CONFIG_NETFILTER_XT_MATCH_POLICY=y
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=y
+CONFIG_NETFILTER_XT_MATCH_QUOTA=y
+CONFIG_NETFILTER_XT_MATCH_SOCKET=y
+CONFIG_NETFILTER_XT_MATCH_STATE=y
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
+CONFIG_NETFILTER_XT_MATCH_STRING=y
+CONFIG_NETFILTER_XT_MATCH_TIME=y
+CONFIG_NETFILTER_XT_MATCH_U32=y
+CONFIG_NF_CONNTRACK_IPV4=y
+CONFIG_NF_NAT_IPV4=y
+CONFIG_IP_NF_IPTABLES=y
+CONFIG_IP_NF_MATCH_AH=y
+CONFIG_IP_NF_MATCH_ECN=y
+CONFIG_IP_NF_MATCH_TTL=y
+CONFIG_IP_NF_FILTER=y
+CONFIG_IP_NF_TARGET_REJECT=y
+CONFIG_IP_NF_MANGLE=y
+CONFIG_IP_NF_RAW=y
+CONFIG_IP_NF_ARPTABLES=y
+CONFIG_IP_NF_ARPFILTER=y
+CONFIG_IP_NF_ARP_MANGLE=y
+CONFIG_NF_CONNTRACK_IPV6=y
+CONFIG_IP6_NF_IPTABLES=y
+CONFIG_IP6_NF_FILTER=y
+CONFIG_IP6_NF_TARGET_REJECT=y
+CONFIG_IP6_NF_MANGLE=y
+CONFIG_IP6_NF_RAW=y
+CONFIG_BRIDGE_NF_EBTABLES=y
+CONFIG_BRIDGE_EBT_BROUTE=y
+CONFIG_BRIDGE=y
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=y
+CONFIG_NET_SCH_PRIO=y
+CONFIG_NET_CLS_FW=y
+CONFIG_NET_CLS_U32=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=y
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=y
+CONFIG_NET_EMATCH_NBYTE=y
+CONFIG_NET_EMATCH_U32=y
+CONFIG_NET_EMATCH_META=y
+CONFIG_NET_EMATCH_TEXT=y
+CONFIG_NET_CLS_ACT=y
+CONFIG_BT=y
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=y
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=y
+CONFIG_CFG80211=y
+CONFIG_NL80211_TESTMODE=y
+CONFIG_CFG80211_INTERNAL_REGDB=y
+CONFIG_RFKILL=y
+CONFIG_DMA_CMA=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_RAM=y
+CONFIG_SCSI=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_SG=y
+CONFIG_CHR_DEV_SCH=y
+CONFIG_SCSI_CONSTANTS=y
+CONFIG_SCSI_LOGGING=y
+CONFIG_SCSI_SCAN_ASYNC=y
+CONFIG_MD=y
+CONFIG_BLK_DEV_DM=y
+CONFIG_DM_CRYPT=y
+CONFIG_DM_VERITY=y
+CONFIG_NETDEVICES=y
+CONFIG_DUMMY=y
+CONFIG_TUN=y
+CONFIG_PHYLIB=y
+CONFIG_PPP=y
+CONFIG_PPP_BSDCOMP=y
+CONFIG_PPP_DEFLATE=y
+CONFIG_PPPOE=y
+CONFIG_PPP_ASYNC=y
+CONFIG_PPP_SYNC_TTY=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_INPUT_EVBUG=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ATMEL_MXT=y
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_UINPUT=y
+CONFIG_VT_HW_CONSOLE_BINDING=y
+CONFIG_SERIAL_MSM=y
+CONFIG_SERIAL_MSM_CONSOLE=y
+CONFIG_HW_RANDOM=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_SPI=y
+CONFIG_SPI_SPIDEV=m
+CONFIG_PINCTRL_MSM8916=y
+CONFIG_GPIOLIB=y
+CONFIG_GPIO_SYSFS=y
+CONFIG_THERMAL=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_FAN53555=y
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_MEDIA_CAMERA_SUPPORT=y
+CONFIG_MEDIA_RADIO_SUPPORT=y
+CONFIG_MEDIA_CONTROLLER=y
+CONFIG_VIDEO_V4L2_SUBDEV_API=y
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_FB=y
+CONFIG_SOUND=y
+CONFIG_SND=y
+CONFIG_SND_SOC=y
+# CONFIG_USB_SUPPORT is not set
+CONFIG_MMC=y
+CONFIG_MMC_CLKGATE=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_TEST=m
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_RTC_CLASS=y
+CONFIG_UIO=y
+CONFIG_STAGING=y
+CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y
+CONFIG_ASHMEM=y
+CONFIG_ANDROID_LOW_MEMORY_KILLER=y
+CONFIG_ION=y
+CONFIG_COMMON_CLK_QCOM=y
+CONFIG_MSM_GCC_8916=y
+CONFIG_PWM=y
+CONFIG_ANDROID=y
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_EXT2_FS=y
+CONFIG_EXT2_FS_XATTR=y
+CONFIG_EXT3_FS=y
+# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set
+CONFIG_EXT4_FS=y
+CONFIG_FUSE_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_TMPFS=y
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_PRINTK_TIME=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_DEBUG_INFO=y
+CONFIG_MAGIC_SYSRQ=y
+CONFIG_DEBUG_PAGEALLOC=y
+CONFIG_DEBUG_KMEMLEAK=y
+CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y
+CONFIG_DEBUG_STACK_USAGE=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_LOCKUP_DETECTOR=y
+CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
+# CONFIG_DETECT_HUNG_TASK is not set
+CONFIG_PANIC_TIMEOUT=5
+CONFIG_SCHEDSTATS=y
+CONFIG_TIMER_STATS=y
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_MUTEXES=y
+CONFIG_DEBUG_ATOMIC_SLEEP=y
+CONFIG_DEBUG_LIST=y
+CONFIG_FAULT_INJECTION=y
+CONFIG_FAIL_PAGE_ALLOC=y
+CONFIG_FAULT_INJECTION_DEBUG_FS=y
+CONFIG_FAULT_INJECTION_STACKTRACE_FILTER=y
+CONFIG_KEYS=y
+CONFIG_CRYPTO_NULL=y
+CONFIG_CRYPTO_XCBC=y
+CONFIG_CRYPTO_MD4=y
+CONFIG_CRYPTO_ARC4=y
+CONFIG_CRYPTO_TWOFISH=y
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 8949b3f6f74d..c7bb70a3894e 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -354,6 +354,21 @@ config GPIO_PXA
help
Say yes here to support the PXA GPIO device
+config GPIO_QCOM_SMSM
+ bool "Qualcomm Shared Memory State Machine"
+ depends on QCOM_SMEM
+ help
+ Say yes here to support the Qualcomm Shared Memory State Machine.
+ The state machine is represented by bits in shared memory and is
+ exposed to the system as GPIOs.
+
+config GPIO_QCOM_SMP2P
+ bool "Qualcomm Shared Memory Point to Point support"
+ depends on QCOM_SMEM
+ help
+ Say yes here to support the Qualcomm Shared Memory Point to Point
+ protocol, exposed to the system as GPIOs.
+
config GPIO_RCAR
tristate "Renesas R-Car GPIO"
depends on ARCH_SHMOBILE || COMPILE_TEST
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index f79a7c482a99..cc5c4221ccf2 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -75,6 +75,8 @@ obj-$(CONFIG_GPIO_PCF857X) += gpio-pcf857x.o
obj-$(CONFIG_GPIO_PCH) += gpio-pch.o
obj-$(CONFIG_GPIO_PL061) += gpio-pl061.o
obj-$(CONFIG_GPIO_PXA) += gpio-pxa.o
+obj-$(CONFIG_GPIO_QCOM_SMSM) += gpio-qcom-smsm.o
+obj-$(CONFIG_GPIO_QCOM_SMP2P) += gpio-qcom-smp2p.o
obj-$(CONFIG_GPIO_RC5T583) += gpio-rc5t583.o
obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o
obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o
diff --git a/drivers/gpio/gpio-qcom-smp2p.c b/drivers/gpio/gpio-qcom-smp2p.c
new file mode 100644
index 000000000000..6e4d09a40c4a
--- /dev/null
+++ b/drivers/gpio/gpio-qcom-smp2p.c
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2015, Sony Mobile Communications AB.
+ * Copyright (c) 2012-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/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/memblock.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/regmap.h>
+#include <linux/list.h>
+#include <linux/gpio.h>
+#include <linux/mfd/syscon.h>
+
+#include <linux/delay.h>
+
+#include <linux/soc/qcom/smem.h>
+
+/*
+ * The Shared Memory Point to Point (SMP2P) protocol facilitates communication
+ * of a single 32-bit value between two processors. Each value has a single
+ * writer (the local side) and a single reader (the remote side). Values are
+ * uniquely identified in the system by the directed edge (local processor ID
+ * to remote processor ID) and a string identifier.
+ *
+ * Each processor is responsible for creating the outgoing SMEM items and each
+ * item is writable by the local processor and readable by the remote
+ * processor. By using two separate SMEM items that are single-reader and
+ * single-writer, SMP2P does not require any remote locking mechanisms.
+ *
+ * The driver uses the Linux GPIO and interrupt framework to expose a virtual
+ * GPIO for each outbound entry and a virtual interrupt controller for each
+ * inbound entry.
+ */
+
+#define SMP2P_MAX_ENTRY 16
+#define SMP2P_MAX_ENTRY_NAME 16
+
+#define SMP2P_FEATURE_SSR_ACK 0x1
+
+#define SMP2P_MAGIC 0x504d5324
+
+/**
+ * struct smp2p_smem_item - in memory communication structure
+ * @magic: magic number
+ * @version: version - must be 1
+ * @features: features flag - currently unused
+ * @local_pid: processor id of sending end
+ * @remote_pid: processor id of receiving end
+ * @total_entries: number of entries - always SMP2P_MAX_ENTRY
+ * @valid_entries: number of allocated entries
+ * @flags:
+ * @entries: individual communication entries
+ * @name: name of the entry
+ * @value: content of the entry
+ */
+struct smp2p_smem_item {
+ u32 magic;
+ u8 version;
+ unsigned features:24;
+ u16 local_pid;
+ u16 remote_pid;
+ u16 total_entries;
+ u16 valid_entries;
+ u32 flags;
+
+ struct {
+ u8 name[SMP2P_MAX_ENTRY_NAME];
+ u32 value;
+ } entries[SMP2P_MAX_ENTRY];
+} __packed;
+
+/**
+ * struct smp2p_entry - driver context matching one entry
+ * @node: list entry to keep track of allocated entries
+ * @smp2p: reference to the device driver context
+ * @name: name of the entry, to match against smp2p_smem_item
+ * @value: pointer to smp2p_smem_item entry value
+ * @last_value: last handled value
+ * @domain: irq_domain for inbound entries
+ * @irq_enabled:bitmap to track enabled irq bits
+ * @irq_rising: bitmap to mark irq bits for rising detection
+ * @irq_falling:bitmap to mark irq bits for falling detection
+ * @chip: gpio_chip for outbound entries
+ */
+struct smp2p_entry {
+ struct list_head node;
+ struct qcom_smp2p *smp2p;
+
+ const char *name;
+ u32 *value;
+ u32 last_value;
+
+ struct irq_domain *domain;
+ DECLARE_BITMAP(irq_enabled, 32);
+ DECLARE_BITMAP(irq_rising, 32);
+ DECLARE_BITMAP(irq_falling, 32);
+
+ struct gpio_chip chip;
+};
+
+#define SMP2P_INBOUND 0
+#define SMP2P_OUTBOUND 1
+
+/**
+ * struct qcom_smp2p - device driver context
+ * @dev: device driver handle
+ * @in: pointer to the inbound smem item
+ * @smem_items: ids of the two smem items
+ * @valid_entries: already scanned inbound entries
+ * @local_pid: processor id of the inbound edge
+ * @remote_pid: processor id of the outbound edge
+ * @ipc_regmap: regmap for the outbound ipc
+ * @ipc_offset: offset within the regmap
+ * @ipc_bit: bit in regmap@offset to kick to signal remote processor
+ * @inbound: list of inbound entries
+ * @outbound: list of outbound entries
+ */
+struct qcom_smp2p {
+ struct device *dev;
+
+ struct smp2p_smem_item *in;
+ struct smp2p_smem_item *out;
+
+ unsigned smem_items[SMP2P_OUTBOUND + 1];
+
+ unsigned valid_entries;
+
+ unsigned local_pid;
+ unsigned remote_pid;
+
+ struct regmap *ipc_regmap;
+ int ipc_offset;
+ int ipc_bit;
+
+ struct list_head inbound;
+ struct list_head outbound;
+};
+
+static void qcom_smp2p_kick(struct qcom_smp2p *smp2p)
+{
+ /* Make sure any updated data is written before the kick */
+ wmb();
+ regmap_write(smp2p->ipc_regmap, smp2p->ipc_offset, BIT(smp2p->ipc_bit));
+}
+
+static irqreturn_t qcom_smp2p_intr(int irq, void *data)
+{
+ struct smp2p_smem_item *in;
+ struct smp2p_entry *entry;
+ struct qcom_smp2p *smp2p = data;
+ unsigned smem_id = smp2p->smem_items[SMP2P_INBOUND];
+ unsigned pid = smp2p->remote_pid;
+ size_t size;
+ int irq_pin;
+ u32 status;
+ u32 val;
+ int ret;
+ int i;
+ u8 tmp_name[SMP2P_MAX_ENTRY_NAME];
+
+ in = smp2p->in;
+
+ /* Acquire smem item, if not already found */
+ if (!in) {
+ ret = qcom_smem_get(pid, smem_id, (void **)&in, &size);
+ if (ret < 0) {
+ dev_err(smp2p->dev,
+ "Unable to acquire remote smp2p item\n");
+ return IRQ_HANDLED;
+ }
+
+ smp2p->in = in;
+ }
+
+ /* Match newly created entries */
+ for (i = smp2p->valid_entries; i < in->valid_entries; i++) {
+ list_for_each_entry(entry, &smp2p->inbound, node) {
+ memcpy_fromio(tmp_name, in->entries[i].name,
+ SMP2P_MAX_ENTRY_NAME);
+ if (!strcmp(tmp_name, entry->name)) {
+ entry->value = &in->entries[i].value;
+ break;
+ }
+ }
+ }
+ smp2p->valid_entries = i;
+
+ /* Fire interrupts based on any value changes */
+ list_for_each_entry(entry, &smp2p->inbound, node) {
+ /* Ignore entries not yet allocated by the remote side */
+ if (!entry->value)
+ continue;
+
+ val = *entry->value;
+
+ status = val ^ entry->last_value;
+ entry->last_value = val;
+
+ /* No changes of this entry? */
+ if (!status)
+ continue;
+
+ for_each_set_bit(i, entry->irq_enabled, 32) {
+ if (!(status & BIT(i)))
+ continue;
+
+ if (val & BIT(i)) {
+ if (test_bit(i, entry->irq_rising)) {
+ irq_pin = irq_find_mapping(entry->domain, i);
+ handle_nested_irq(irq_pin);
+ }
+ } else {
+ if (test_bit(i, entry->irq_falling)) {
+ irq_pin = irq_find_mapping(entry->domain, i);
+ handle_nested_irq(irq_pin);
+ }
+ }
+
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void smp2p_mask_irq(struct irq_data *irqd)
+{
+ struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd);
+ irq_hw_number_t irq = irqd_to_hwirq(irqd);
+
+ clear_bit(irq, entry->irq_enabled);
+}
+
+static void smp2p_unmask_irq(struct irq_data *irqd)
+{
+ struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd);
+ irq_hw_number_t irq = irqd_to_hwirq(irqd);
+
+ set_bit(irq, entry->irq_enabled);
+}
+
+static int smp2p_set_irq_type(struct irq_data *irqd, unsigned int type)
+{
+ struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd);
+ irq_hw_number_t irq = irqd_to_hwirq(irqd);
+
+ if (!(type & IRQ_TYPE_EDGE_BOTH))
+ return -EINVAL;
+
+ if (type & IRQ_TYPE_EDGE_RISING)
+ set_bit(irq, entry->irq_rising);
+ else
+ clear_bit(irq, entry->irq_rising);
+
+ if (type & IRQ_TYPE_EDGE_FALLING)
+ set_bit(irq, entry->irq_falling);
+ else
+ clear_bit(irq, entry->irq_falling);
+
+ return 0;
+}
+
+static struct irq_chip smp2p_irq_chip = {
+ .name = "smp2p",
+ .irq_mask = smp2p_mask_irq,
+ .irq_unmask = smp2p_unmask_irq,
+ .irq_set_type = smp2p_set_irq_type,
+};
+
+static int smp2p_irq_map(struct irq_domain *d,
+ unsigned int irq,
+ irq_hw_number_t hw)
+{
+ struct smp2p_entry *entry = d->host_data;
+
+ irq_set_chip_and_handler(irq, &smp2p_irq_chip, handle_level_irq);
+ irq_set_chip_data(irq, entry);
+ irq_set_nested_thread(irq, 1);
+
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops smp2p_irq_ops = {
+ .map = smp2p_irq_map,
+ .xlate = irq_domain_xlate_twocell,
+};
+
+static int smp2p_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset,
+ int value)
+{
+ struct smp2p_entry *entry = container_of(chip, struct smp2p_entry, chip);
+
+ if (value)
+ *entry->value |= BIT(offset);
+ else
+ *entry->value &= ~BIT(offset);
+
+ qcom_smp2p_kick(entry->smp2p);
+
+ return 0;
+}
+
+static void smp2p_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ smp2p_gpio_direction_output(chip, offset, value);
+}
+
+static int qcom_smp2p_inbound_entry(struct qcom_smp2p *smp2p,
+ struct smp2p_entry *entry,
+ struct device_node *node)
+{
+ entry->domain = irq_domain_add_linear(node, 32, &smp2p_irq_ops, entry);
+ if (!entry->domain) {
+ dev_err(smp2p->dev, "failed to add irq_domain\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int qcom_smp2p_outbound_entry(struct qcom_smp2p *smp2p,
+ struct smp2p_entry *entry,
+ struct device_node *node)
+{
+ struct smp2p_smem_item *out = smp2p->out;
+ struct gpio_chip *chip;
+ int ret;
+
+ /* Allocate an entry from the smem item */
+ memcpy_toio(out->entries[out->valid_entries].name, entry->name, SMP2P_MAX_ENTRY_NAME);
+ out->valid_entries++;
+
+ /* Make the logical entry reference the physical value */
+ entry->value = &out->entries[out->valid_entries].value;
+
+ chip = &entry->chip;
+ chip->base = -1;
+ chip->ngpio = 32;
+ chip->label = entry->name;
+ chip->dev = smp2p->dev;
+ chip->owner = THIS_MODULE;
+ chip->of_node = node;
+
+ chip->set = smp2p_gpio_set;
+ chip->direction_output = smp2p_gpio_direction_output;
+
+ ret = gpiochip_add(chip);
+ if (ret)
+ dev_err(smp2p->dev, "failed register gpiochip\n");
+
+ return 0;
+}
+
+static int qcom_smp2p_alloc_outbound_item(struct qcom_smp2p *smp2p)
+{
+ struct smp2p_smem_item *out;
+ unsigned smem_id = smp2p->smem_items[SMP2P_OUTBOUND];
+ unsigned pid = smp2p->remote_pid;
+ int ret;
+
+ ret = qcom_smem_alloc(pid, smem_id, sizeof(*out));
+ if (ret < 0 && ret != -EEXIST) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(smp2p->dev,
+ "unable to allocate local smp2p item\n");
+ return ret;
+ }
+
+ ret = qcom_smem_get(pid, smem_id, (void **)&out, NULL);
+ if (ret < 0) {
+ dev_err(smp2p->dev, "Unable to acquire local smp2p item\n");
+ return ret;
+ }
+
+ memset_io(out, 0, sizeof(*out));
+ out->magic = SMP2P_MAGIC;
+ out->local_pid = smp2p->local_pid;
+ out->remote_pid = smp2p->remote_pid;
+ out->total_entries = SMP2P_MAX_ENTRY;
+ out->valid_entries = 0;
+
+ /*
+ * Make sure the rest of the header is written before we validate the
+ * item by writing a valid version number.
+ */
+ wmb();
+ out->version = 1;
+
+ qcom_smp2p_kick(smp2p);
+
+ smp2p->out = out;
+
+ return 0;
+}
+
+static int smp2p_parse_ipc(struct qcom_smp2p *smp2p)
+{
+ struct device_node *syscon;
+ struct device *dev = smp2p->dev;
+ const char *key;
+ int ret;
+
+ syscon = of_parse_phandle(dev->of_node, "qcom,ipc", 0);
+ if (!syscon) {
+ dev_err(dev, "no qcom,ipc node\n");
+ return -ENODEV;
+ }
+
+ smp2p->ipc_regmap = syscon_node_to_regmap(syscon);
+ if (IS_ERR(smp2p->ipc_regmap))
+ return PTR_ERR(smp2p->ipc_regmap);
+
+ key = "qcom,ipc";
+ ret = of_property_read_u32_index(dev->of_node, key, 1, &smp2p->ipc_offset);
+ if (ret < 0) {
+ dev_err(dev, "no offset in %s\n", key);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(dev->of_node, key, 2, &smp2p->ipc_bit);
+ if (ret < 0) {
+ dev_err(dev, "no bit in %s\n", key);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qcom_smp2p_probe(struct platform_device *pdev)
+{
+ struct smp2p_entry *entry;
+ struct device_node *node;
+ struct qcom_smp2p *smp2p;
+ const char *key;
+ int irq;
+ int ret;
+
+ smp2p = devm_kzalloc(&pdev->dev, sizeof(*smp2p), GFP_KERNEL);
+ if (!smp2p)
+ return -ENOMEM;
+
+ smp2p->dev = &pdev->dev;
+ INIT_LIST_HEAD(&smp2p->inbound);
+ INIT_LIST_HEAD(&smp2p->outbound);
+
+ platform_set_drvdata(pdev, smp2p);
+
+ ret = smp2p_parse_ipc(smp2p);
+ if (ret)
+ return ret;
+
+ key = "qcom,smem";
+ ret = of_property_read_u32_array(pdev->dev.of_node, key,
+ smp2p->smem_items, 2);
+ if (ret)
+ return ret;
+
+ key = "qcom,local-pid";
+ ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->local_pid);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to read %s\n", key);
+ return -EINVAL;
+ }
+
+ key = "qcom,remote-pid";
+ ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->remote_pid);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to read %s\n", key);
+ return -EINVAL;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "unable to acquire smp2p interrupt\n");
+ return irq;
+ }
+
+ ret = qcom_smp2p_alloc_outbound_item(smp2p);
+ if (ret < 0)
+ return ret;
+
+ for_each_available_child_of_node(pdev->dev.of_node, node) {
+ entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto unwind_interfaces;
+ }
+
+ entry->smp2p = smp2p;
+
+ ret = of_property_read_string(node, "qcom,entry-name", &entry->name);
+ if (ret < 0)
+ goto unwind_interfaces;
+
+ if (of_property_read_bool(node, "qcom,inbound")) {
+ ret = qcom_smp2p_inbound_entry(smp2p, entry, node);
+ if (ret < 0)
+ goto unwind_interfaces;
+
+ list_add(&entry->node, &smp2p->inbound);
+ } else if (of_property_read_bool(node, "qcom,outbound")) {
+ ret = qcom_smp2p_outbound_entry(smp2p, entry, node);
+ if (ret < 0)
+ goto unwind_interfaces;
+
+ list_add(&entry->node, &smp2p->outbound);
+ } else {
+ dev_err(&pdev->dev, "neither inbound nor outbound\n");
+ ret = -EINVAL;
+ goto unwind_interfaces;
+ }
+ }
+
+ /* Kick the outgoing edge after allocating entries */
+ qcom_smp2p_kick(smp2p);
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ NULL, qcom_smp2p_intr,
+ IRQF_ONESHOT,
+ "smp2p", (void *)smp2p);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request interrupt\n");
+ goto unwind_interfaces;
+ }
+
+
+ return 0;
+
+unwind_interfaces:
+ list_for_each_entry(entry, &smp2p->inbound, node)
+ irq_domain_remove(entry->domain);
+
+ list_for_each_entry(entry, &smp2p->outbound, node)
+ gpiochip_remove(&entry->chip);
+
+ smp2p->out->valid_entries = 0;
+
+ return ret;
+}
+
+static int qcom_smp2p_remove(struct platform_device *pdev)
+{
+ struct qcom_smp2p *smp2p = platform_get_drvdata(pdev);
+ struct smp2p_entry *entry;
+
+ list_for_each_entry(entry, &smp2p->inbound, node)
+ irq_domain_remove(entry->domain);
+
+ list_for_each_entry(entry, &smp2p->outbound, node)
+ gpiochip_remove(&entry->chip);
+
+ smp2p->out->valid_entries = 0;
+
+ return 0;
+}
+
+static const struct of_device_id qcom_smp2p_of_match[] = {
+ { .compatible = "qcom,smp2p" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_smp2p_of_match);
+
+static struct platform_driver qcom_smp2p_driver = {
+ .probe = qcom_smp2p_probe,
+ .remove = qcom_smp2p_remove,
+ .driver = {
+ .name = "qcom_smp2p",
+ .of_match_table = qcom_smp2p_of_match,
+ },
+};
+module_platform_driver(qcom_smp2p_driver);
+
+MODULE_DESCRIPTION("Qualcomm Shared Memory Point to Point driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/gpio-qcom-smsm.c b/drivers/gpio/gpio-qcom-smsm.c
new file mode 100644
index 000000000000..3bbe9bb73f84
--- /dev/null
+++ b/drivers/gpio/gpio-qcom-smsm.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2014, Sony Mobile Communications AB.
+ * Copyright (c) 2012-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/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/memblock.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/hwspinlock.h>
+#include <linux/regmap.h>
+#include <linux/gpio.h>
+#include <linux/mfd/syscon.h>
+
+#include <linux/delay.h>
+
+#include <linux/soc/qcom/smem.h>
+
+#define SMSM_APPS_STATE 0
+#define SMEM_SMSM_SHARED_STATE 85
+
+#define SMSM_MAX_STATES 8
+
+struct qcom_smsm_state {
+ unsigned state_id;
+ struct gpio_chip chip;
+
+ int irq;
+
+ struct regmap *ipc_regmap;
+ int ipc_bit;
+ int ipc_offset;
+};
+
+struct qcom_smsm {
+ struct device *dev;
+
+ u32 *shared_state;
+ size_t shared_state_size;
+
+ struct qcom_smsm_state states[SMSM_MAX_STATES];
+};
+
+#if 0
+int qcom_smsm_change_state(struct qcom_smsm *smsm, u32 clear_mask, u32 set_mask)
+{
+ u32 state;
+
+ dev_dbg(smsm->dev, "SMSM_APPS_STATE clear 0x%x set 0x%x\n", clear_mask, set_mask);
+ print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_OFFSET, 16, 1, smsm->shared_state, smsm->shared_state_size, true);
+
+ state = readl(&smsm->shared_state[SMSM_APPS_STATE]);
+ state &= ~clear_mask;
+ state |= set_mask;
+ writel(state, &smsm->shared_state[SMSM_APPS_STATE]);
+
+ print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_OFFSET, 16, 1, smsm->shared_state, smsm->shared_state_size, true);
+
+ // qcom_smem_signal(-1, smsm->smem, smsm->signal_offset, smsm->signal_bit);
+
+ return 0;
+}
+EXPORT_SYMBOL(qcom_smsm_change_state);
+#endif
+
+static struct qcom_smsm_state *to_smsm_state(struct gpio_chip *chip)
+{
+ return container_of(chip, struct qcom_smsm_state, chip);
+}
+
+static struct qcom_smsm *to_smsm(struct qcom_smsm_state *state)
+{
+ return container_of(state, struct qcom_smsm, states[state->state_id]);
+}
+
+static int smsm_gpio_direction_input(struct gpio_chip *chip,
+ unsigned offset)
+{
+ struct qcom_smsm_state *state = to_smsm_state(chip);
+
+ if (state->state_id == SMSM_APPS_STATE)
+ return -EINVAL;
+ return 0;
+}
+
+static int smsm_gpio_direction_output(struct gpio_chip *chip,
+ unsigned offset,
+ int value)
+{
+ struct qcom_smsm_state *ipc_state;
+ struct qcom_smsm_state *state = to_smsm_state(chip);
+ struct qcom_smsm *smsm = to_smsm(state);
+ unsigned word;
+ unsigned bit;
+ u32 val;
+ int i;
+
+ /* Only SMSM_APPS_STATE supports writing */
+ if (state->state_id != SMSM_APPS_STATE)
+ return -EINVAL;
+
+ offset += state->state_id * 32;
+
+ word = ALIGN(offset / 32, 4);
+ bit = offset % 32;
+
+ val = readl(smsm->shared_state + word);
+ if (value)
+ val |= BIT(bit);
+ else
+ val &= ~BIT(bit);
+ writel(val, smsm->shared_state + word);
+
+ /* XXX: send out interrupts */
+ for (i = 0; i < SMSM_MAX_STATES; i++) {
+ ipc_state = &smsm->states[i];
+ if (!ipc_state->ipc_regmap)
+ continue;
+
+ regmap_write(ipc_state->ipc_regmap, ipc_state->ipc_offset, BIT(ipc_state->ipc_bit));
+ }
+
+ dev_err(smsm->dev, "set %d %d\n", offset, value);
+
+ return 0;
+}
+
+static int smsm_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct qcom_smsm_state *state = to_smsm_state(chip);
+ struct qcom_smsm *smsm = to_smsm(state);
+ unsigned word;
+ unsigned bit;
+ u32 val;
+
+ offset += state->state_id * 32;
+
+ word = ALIGN(offset / 32, 4);
+ bit = offset % 32;
+
+ val = readl(smsm->shared_state + word);
+
+ return !!(val & BIT(bit));
+}
+
+static void smsm_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ smsm_gpio_direction_output(chip, offset, value);
+}
+
+static int smsm_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ return -EINVAL;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/seq_file.h>
+
+static void smsm_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct qcom_smsm_state *state = to_smsm_state(chip);
+ struct qcom_smsm *smsm = to_smsm(state);
+ unsigned i;
+ u32 val;
+
+ val = readl(smsm->shared_state + state->state_id * 4);
+
+ for (i = 0; i < 32; i++) {
+ if (val & BIT(i))
+ seq_puts(s, "1");
+ else
+ seq_puts(s, "0");
+
+ if (i == 7 || i == 15 || i == 23)
+ seq_puts(s, " ");
+ }
+ seq_puts(s, "\n");
+}
+
+#else
+#define smsm_gpio_dbg_show NULL
+#endif
+
+static struct gpio_chip smsm_gpio_template = {
+ .direction_input = smsm_gpio_direction_input,
+ .direction_output = smsm_gpio_direction_output,
+ .get = smsm_gpio_get,
+ .set = smsm_gpio_set,
+ .to_irq = smsm_gpio_to_irq,
+ .dbg_show = smsm_gpio_dbg_show,
+ .owner = THIS_MODULE,
+};
+
+static int qcom_smsm_probe(struct platform_device *pdev)
+{
+ struct qcom_smsm_state *state;
+ struct device_node *syscon_np;
+ struct device_node *node;
+ struct qcom_smsm *smsm;
+ char *key;
+ u32 sid;
+ int ret;
+
+ smsm = devm_kzalloc(&pdev->dev, sizeof(*smsm), GFP_KERNEL);
+ if (!smsm)
+ return -ENOMEM;
+ smsm->dev = &pdev->dev;
+
+ ret = qcom_smem_alloc(-1, SMEM_SMSM_SHARED_STATE, 8 * sizeof(uint32_t));
+ if (ret < 0 && ret != -EEXIST) {
+ dev_err(&pdev->dev, "unable to allocate shared state entry\n");
+ return ret;
+ }
+
+ ret = qcom_smem_get(-1, SMEM_SMSM_SHARED_STATE,
+ (void**)&smsm->shared_state,
+ &smsm->shared_state_size);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Unable to acquire shared state entry\n");
+ return ret;
+ }
+
+ dev_err(smsm->dev, "SMEM_SMSM_SHARED_STATE: %d, %zu\n", ret, smsm->shared_state_size);
+ print_hex_dump(KERN_DEBUG, "raw data: ", DUMP_PREFIX_OFFSET, 16, 1, smsm->shared_state, smsm->shared_state_size, true);
+
+ for_each_child_of_node(pdev->dev.of_node, node) {
+ key = "reg";
+ ret = of_property_read_u32(node, key, &sid);
+ if (ret || sid >= SMSM_MAX_STATES) {
+ dev_err(&pdev->dev, "smsm state missing %s\n", key);
+ return -EINVAL;
+ }
+ state = &smsm->states[sid];
+ state->state_id = sid;
+
+ state->chip = smsm_gpio_template;
+ state->chip.base = -1;
+ state->chip.dev = &pdev->dev;
+ state->chip.of_node = node;
+ state->chip.label = node->name;
+ state->chip.ngpio = 8 * sizeof(u32);
+ ret = gpiochip_add(&state->chip);
+ if (ret) {
+ dev_err(&pdev->dev, "failed register gpiochip\n");
+ // goto wooha;
+ }
+
+ /* The remaining properties are only for non-apps state */
+ if (sid == SMSM_APPS_STATE)
+ continue;
+
+ state->irq = irq_of_parse_and_map(node, 0);
+ if (state->irq < 0 && state->irq != -EINVAL) {
+ dev_err(&pdev->dev, "failed to parse smsm interrupt\n");
+ return -EINVAL;
+ }
+
+ syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
+ if (!syscon_np) {
+ dev_err(&pdev->dev, "no qcom,ipc node\n");
+ return -ENODEV;
+ }
+
+ state->ipc_regmap = syscon_node_to_regmap(syscon_np);
+ if (IS_ERR(state->ipc_regmap))
+ return PTR_ERR(state->ipc_regmap);
+
+ key = "qcom,ipc";
+ ret = of_property_read_u32_index(node, key, 1, &state->ipc_offset);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no offset in %s\n", key);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_index(node, key, 2, &state->ipc_bit);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no bit in %s\n", key);
+ return -EINVAL;
+ }
+
+ }
+
+ return 0;
+}
+
+static const struct of_device_id qcom_smsm_of_match[] = {
+ { .compatible = "qcom,smsm" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qcom_smsm_of_match);
+
+static struct platform_driver qcom_smsm_driver = {
+ .probe = qcom_smsm_probe,
+ .driver = {
+ .name = "qcom_smsm",
+ .owner = THIS_MODULE,
+ .of_match_table = qcom_smsm_of_match,
+ },
+};
+
+static int __init qcom_smsm_init(void)
+{
+ return platform_driver_register(&qcom_smsm_driver);
+}
+arch_initcall(qcom_smsm_init);
+
+static void __exit qcom_smsm_exit(void)
+{
+ platform_driver_unregister(&qcom_smsm_driver);
+}
+module_exit(qcom_smsm_exit)
+
+MODULE_DESCRIPTION("Qualcomm Shared Memory Signaling Mechanism");
+MODULE_LICENSE("GPLv2");
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 64bccff557be..2d675c1b982d 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -546,6 +546,18 @@ config REGULATOR_QCOM_SPMI
Qualcomm SPMI PMICs as a module. The module will be named
"qcom_spmi-regulator".
+config REGULATOR_QCOM_SMD_RPM
+ tristate "Qualcomm SMD based RPM regulator driver"
+ depends on MFD_QCOM_SMD_RPM
+ help
+ If you say yes to this option, support will be included for the
+ regulators exposed by the Resource Power Manager found in Qualcomm
+ 8974 based devices.
+
+ Say M here if you want to include support for the regulators on the
+ Qualcomm RPM as a module. The module will be named
+ "qcom_smd-regulator".
+
config REGULATOR_RC5T583
tristate "RICOH RC5T583 Power regulators"
depends on MFD_RC5T583
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 0f8174913c17..be39fe4b5cdc 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
+obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
obj-$(CONFIG_REGULATOR_PWM) += pwm-regulator.o
diff --git a/drivers/regulator/qcom_smd-regulator.c b/drivers/regulator/qcom_smd-regulator.c
index 9c6167dd2c8b..86b3cfcd0106 100644
--- a/drivers/regulator/qcom_smd-regulator.c
+++ b/drivers/regulator/qcom_smd-regulator.c
@@ -20,6 +20,9 @@
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/soc/qcom/smd-rpm.h>
+#include <linux/regulator/qcom_smd-regulator.h>
+
+#include "internal.h"
struct qcom_rpm_reg {
struct device *dev;
@@ -44,6 +47,11 @@ struct rpm_regulator_req {
#define RPM_KEY_SWEN 0x6e657773 /* "swen" */
#define RPM_KEY_UV 0x00007675 /* "uv" */
#define RPM_KEY_MA 0x0000616d /* "ma" */
+#define RPM_KEY_FLOOR 0x00636676 /* "vfc" */
+#define RPM_KEY_CORNER 0x6e726f63 /* "corn" */
+
+#define RPM_MIN_FLOOR_CORNER 0
+#define RPM_MAX_FLOOR_CORNER 6
static int rpm_reg_write_active(struct qcom_rpm_reg *vreg,
struct rpm_regulator_req *req,
@@ -56,6 +64,50 @@ static int rpm_reg_write_active(struct qcom_rpm_reg *vreg,
req, size);
}
+int qcom_rpm_set_floor(struct regulator *regulator, int floor)
+{
+ struct regulator_dev *rdev = regulator->rdev;
+ struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev);
+ struct rpm_regulator_req req;
+ int ret;
+
+ req.key = RPM_KEY_FLOOR;
+ req.nbytes = sizeof(u32);
+ req.value = floor;
+
+ if (floor < RPM_MIN_FLOOR_CORNER || floor > RPM_MAX_FLOOR_CORNER)
+ return -EINVAL;
+
+ ret = rpm_reg_write_active(vreg, &req, sizeof(req));
+ if (ret)
+ dev_err(rdev_get_dev(rdev), "Failed to set floor %d\n", floor);
+
+ return ret;
+}
+EXPORT_SYMBOL(qcom_rpm_set_floor);
+
+int qcom_rpm_set_corner(struct regulator *regulator, int corner)
+{
+ struct regulator_dev *rdev = regulator->rdev;
+ struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev);
+ struct rpm_regulator_req req;
+ int ret;
+
+ req.key = RPM_KEY_CORNER;
+ req.nbytes = sizeof(u32);
+ req.value = corner;
+
+ if (corner < RPM_MIN_FLOOR_CORNER || corner > RPM_MAX_FLOOR_CORNER)
+ return -EINVAL;
+
+ ret = rpm_reg_write_active(vreg, &req, sizeof(req));
+ if (ret)
+ dev_err(rdev_get_dev(rdev), "Failed to set corner %d\n", corner);
+
+ return ret;
+}
+EXPORT_SYMBOL(qcom_rpm_set_corner);
+
static int rpm_reg_enable(struct regulator_dev *rdev)
{
struct qcom_rpm_reg *vreg = rdev_get_drvdata(rdev);
@@ -211,6 +263,43 @@ static const struct regulator_desc pm8941_switch = {
.ops = &rpm_switch_ops,
};
+static const struct regulator_desc pm8916_pldo = {
+ .linear_ranges = (struct regulator_linear_range[]) {
+ REGULATOR_LINEAR_RANGE(750000, 0, 208, 12500),
+ },
+ .n_linear_ranges = 1,
+ .n_voltages = 209,
+ .ops = &rpm_smps_ldo_ops,
+};
+
+static const struct regulator_desc pm8916_nldo = {
+ .linear_ranges = (struct regulator_linear_range[]) {
+ REGULATOR_LINEAR_RANGE(375000, 0, 93, 12500),
+ },
+ .n_linear_ranges = 1,
+ .n_voltages = 94,
+ .ops = &rpm_smps_ldo_ops,
+};
+
+static const struct regulator_desc pm8916_buck_lvo_smps = {
+ .linear_ranges = (struct regulator_linear_range[]) {
+ REGULATOR_LINEAR_RANGE(375000, 0, 95, 12500),
+ REGULATOR_LINEAR_RANGE(750000, 96, 127, 25000),
+ },
+ .n_linear_ranges = 2,
+ .n_voltages = 128,
+ .ops = &rpm_smps_ldo_ops,
+};
+
+static const struct regulator_desc pm8916_buck_hvo_smps = {
+ .linear_ranges = (struct regulator_linear_range[]) {
+ REGULATOR_LINEAR_RANGE(1550000, 0, 31, 25000),
+ },
+ .n_linear_ranges = 1,
+ .n_voltages = 32,
+ .ops = &rpm_smps_ldo_ops,
+};
+
struct rpm_regulator_data {
const char *name;
u32 type;
@@ -272,9 +361,36 @@ static const struct rpm_regulator_data rpm_pm8941_regulators[] = {
{}
};
+static const struct rpm_regulator_data rpm_pm8916_regulators[] = {
+ { "s1", QCOM_SMD_RPM_SMPA, 1, &pm8916_buck_lvo_smps, "vdd_s1" },
+ { "s2", QCOM_SMD_RPM_SMPA, 2, &pm8916_buck_lvo_smps, "vdd_s2" },
+ { "s3", QCOM_SMD_RPM_SMPA, 3, &pm8916_buck_lvo_smps, "vdd_s3" },
+ { "s4", QCOM_SMD_RPM_SMPA, 4, &pm8916_buck_hvo_smps, "vdd_s4" },
+ { "l1", QCOM_SMD_RPM_LDOA, 1, &pm8916_nldo, "vdd_l1_l2_l3" },
+ { "l2", QCOM_SMD_RPM_LDOA, 2, &pm8916_nldo, "vdd_l1_l2_l3" },
+ { "l3", QCOM_SMD_RPM_LDOA, 3, &pm8916_nldo, "vdd_l1_l2_l3" },
+ { "l4", QCOM_SMD_RPM_LDOA, 4, &pm8916_pldo, "vdd_l4_l5_l6" },
+ { "l5", QCOM_SMD_RPM_LDOA, 5, &pm8916_pldo, "vdd_l4_l5_l6" },
+ { "l6", QCOM_SMD_RPM_LDOA, 6, &pm8916_pldo, "vdd_l4_l5_l6" },
+ { "l7", QCOM_SMD_RPM_LDOA, 7, &pm8916_pldo, "vdd_l7" },
+ { "l8", QCOM_SMD_RPM_LDOA, 8, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18" },
+ { "l9", QCOM_SMD_RPM_LDOA, 9, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18" },
+ { "l10", QCOM_SMD_RPM_LDOA, 10, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l11", QCOM_SMD_RPM_LDOA, 11, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l12", QCOM_SMD_RPM_LDOA, 12, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l13", QCOM_SMD_RPM_LDOA, 13, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l14", QCOM_SMD_RPM_LDOA, 14, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l15", QCOM_SMD_RPM_LDOA, 15, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l16", QCOM_SMD_RPM_LDOA, 16, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l17", QCOM_SMD_RPM_LDOA, 17, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ { "l18", QCOM_SMD_RPM_LDOA, 18, &pm8916_pldo, "vdd_l8_l9_l10_l11_l12_l13_l14_l15_l16_l17_l18"},
+ {}
+};
+
static const struct of_device_id rpm_of_match[] = {
{ .compatible = "qcom,rpm-pm8841-regulators", .data = &rpm_pm8841_regulators },
{ .compatible = "qcom,rpm-pm8941-regulators", .data = &rpm_pm8941_regulators },
+ { .compatible = "qcom,rpm-pm8916-regulators", .data = &rpm_pm8916_regulators },
{}
};
MODULE_DEVICE_TABLE(of, rpm_of_match);
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 28c711f0ac6b..54d5b637d14c 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -77,4 +77,20 @@ config DA8XX_REMOTEPROC
It's safe to say n here if you're not interested in multimedia
offloading.
+config QCOM_Q6V5_PIL
+ tristate "Qualcomm Hexagon V5 Peripherial Image Loader"
+ depends on OF && ARCH_QCOM
+ select REMOTEPROC
+ help
+ Say y here to support the Qualcomm Peripherial Image Loader for the
+ Hexagon V5 based remote processors.
+
+config QCOM_TZ_PIL
+ tristate "Qualcomm TrustZone based Peripherial Image Loader"
+ depends on OF && ARCH_QCOM
+ select REMOTEPROC
+ help
+ Say y here to support the TrustZone based Qualcomm Peripherial Image
+ Loader.
+
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 81b04d1e2e58..4351f2e462ad 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -11,3 +11,5 @@ obj-$(CONFIG_OMAP_REMOTEPROC) += omap_remoteproc.o
obj-$(CONFIG_STE_MODEM_RPROC) += ste_modem_rproc.o
obj-$(CONFIG_WKUP_M3_RPROC) += wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
+obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
+obj-$(CONFIG_QCOM_TZ_PIL) += qcom_tz_pil.o
diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c
new file mode 100644
index 000000000000..4d63a84db588
--- /dev/null
+++ b/drivers/remoteproc/qcom_q6v5_pil.c
@@ -0,0 +1,1075 @@
+/*
+ * Qualcomm Peripheral Image Loader
+ *
+ * Copyright (C) 2014 Sony Mobile Communications AB
+ * Copyright (c) 2012-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 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/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/remoteproc.h>
+#include <linux/interrupt.h>
+#include <linux/memblock.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/elf.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/reset.h>
+
+#include "remoteproc_internal.h"
+
+#include <linux/qcom_scm.h>
+
+#define SCM_SVC_PIL 0x2
+
+struct qproc {
+ struct device *dev;
+ struct rproc *rproc;
+
+ void __iomem *reg_base;
+ void __iomem *halt_base;
+ void __iomem *rmb_base;
+
+ struct reset_control *mss_restart;
+
+ int wdog_irq;
+ int fatal_irq;
+ int ready_irq;
+ int handover_irq;
+ int stop_ack_irq;
+
+ struct gpio_desc *stop_gpio;
+
+ struct regulator *vdd;
+ struct regulator *cx;
+ struct regulator *mx;
+ struct regulator *pll;
+
+ struct clk *ahb_clk;
+ struct clk *axi_clk;
+ struct clk *rom_clk;
+
+ struct completion start_done;
+
+ void *mba_va;
+ dma_addr_t mba_da;
+ size_t mba_size;
+ struct dma_attrs mba_attrs;
+};
+
+#define VDD_MSS_UV 1000000
+#define VDD_MSS_UV_MAX 1150000
+#define VDD_MSS_UA 100000
+
+/* Q6 Register Offsets */
+#define QDSP6SS_RST_EVB 0x010
+
+/* AXI Halting Registers */
+#define MSS_Q6_HALT_BASE 0x180
+#define MSS_MODEM_HALT_BASE 0x200
+#define MSS_NC_HALT_BASE 0x280
+
+/* RMB Status Register Values */
+#define STATUS_PBL_SUCCESS 0x1
+#define STATUS_XPU_UNLOCKED 0x1
+#define STATUS_XPU_UNLOCKED_SCRIBBLED 0x2
+
+/* PBL/MBA interface registers */
+#define RMB_MBA_IMAGE 0x00
+#define RMB_PBL_STATUS 0x04
+#define RMB_MBA_COMMAND 0x08
+#define RMB_MBA_STATUS 0x0C
+#define RMB_PMI_META_DATA 0x10
+#define RMB_PMI_CODE_START 0x14
+#define RMB_PMI_CODE_LENGTH 0x18
+
+#define POLL_INTERVAL_US 50
+
+#define CMD_META_DATA_READY 0x1
+#define CMD_LOAD_READY 0x2
+
+#define STATUS_META_DATA_AUTH_SUCCESS 0x3
+#define STATUS_AUTH_COMPLETE 0x4
+
+/* External BHS */
+#define EXTERNAL_BHS_ON BIT(0)
+#define EXTERNAL_BHS_STATUS BIT(4)
+#define BHS_TIMEOUT_US 50
+
+#define MSS_RESTART_ID 0xA
+
+/* QDSP6SS Register Offsets */
+#define QDSP6SS_RESET 0x014
+#define QDSP6SS_GFMUX_CTL 0x020
+#define QDSP6SS_PWR_CTL 0x030
+#define QDSP6SS_STRAP_ACC 0x110
+
+/* AXI Halt Register Offsets */
+#define AXI_HALTREQ 0x0
+#define AXI_HALTACK 0x4
+#define AXI_IDLE 0x8
+
+#define HALT_ACK_TIMEOUT_US 100000
+
+/* QDSP6SS_RESET */
+#define Q6SS_STOP_CORE BIT(0)
+#define Q6SS_CORE_ARES BIT(1)
+#define Q6SS_BUS_ARES_ENA BIT(2)
+
+/* QDSP6SS_GFMUX_CTL */
+#define Q6SS_CLK_ENA BIT(1)
+#define Q6SS_CLK_SRC_SEL_C BIT(3)
+#define Q6SS_CLK_SRC_SEL_FIELD 0xC
+#define Q6SS_CLK_SRC_SWITCH_CLK_OVR BIT(8)
+
+/* QDSP6SS_PWR_CTL */
+#define Q6SS_L2DATA_SLP_NRET_N_0 BIT(0)
+#define Q6SS_L2DATA_SLP_NRET_N_1 BIT(1)
+#define Q6SS_L2DATA_SLP_NRET_N_2 BIT(2)
+#define Q6SS_L2TAG_SLP_NRET_N BIT(16)
+#define Q6SS_ETB_SLP_NRET_N BIT(17)
+#define Q6SS_L2DATA_STBY_N BIT(18)
+#define Q6SS_SLP_RET_N BIT(19)
+#define Q6SS_CLAMP_IO BIT(20)
+#define QDSS_BHS_ON BIT(21)
+#define QDSS_LDO_BYP BIT(22)
+
+/* QDSP6v55 parameters */
+#define QDSP6v55_LDO_ON BIT(26)
+#define QDSP6v55_LDO_BYP BIT(25)
+#define QDSP6v55_BHS_ON BIT(24)
+#define QDSP6v55_CLAMP_WL BIT(21)
+#define L1IU_SLP_NRET_N BIT(15)
+#define L1DU_SLP_NRET_N BIT(14)
+#define L2PLRU_SLP_NRET_N BIT(13)
+
+#define HALT_CHECK_MAX_LOOPS (200)
+#define QDSP6SS_XO_CBCR (0x0038)
+
+#define QDSP6SS_ACC_OVERRIDE_VAL 0x20
+
+static int qproc_sanity_check(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ if (!fw) {
+ dev_err(&rproc->dev, "failed to load %s\n", rproc->name);
+ return -EINVAL;
+ }
+
+ /* XXX: ??? */
+
+ return 0;
+}
+
+static struct resource_table * qproc_find_rsc_table(struct rproc *rproc,
+ const struct firmware *fw,
+ int *tablesz)
+{
+ static struct resource_table table = { .ver = 1, };
+
+ *tablesz = sizeof(table);
+ return &table;
+}
+
+static int qproc_load(struct rproc *rproc, const struct firmware *fw)
+{
+ struct qproc *qproc = rproc->priv;
+ DEFINE_DMA_ATTRS(attrs);
+ dma_addr_t phys;
+ dma_addr_t end;
+ void *ptr;
+
+ dma_set_mask(qproc->dev, DMA_BIT_MASK(32));
+ dma_set_attr(DMA_ATTR_FORCE_CONTIGUOUS, &attrs);
+
+ ptr = dma_alloc_attrs(qproc->dev, fw->size, &phys, GFP_KERNEL, &attrs);
+ if (!ptr) {
+ dev_err(qproc->dev, "failed to allocate mba metadata buffer\n");
+ return -ENOMEM;
+ }
+
+ end = phys + fw->size;
+ dev_info(qproc->dev, "loading MBA from %pa to %pa\n", &phys, &end);
+
+ memcpy(ptr, fw->data, fw->size);
+
+ qproc->mba_va = ptr;
+ qproc->mba_da = phys;
+ qproc->mba_size = fw->size;
+ qproc->mba_attrs = attrs;
+
+ return 0;
+}
+
+static const struct rproc_fw_ops qproc_fw_ops = {
+ .find_rsc_table = qproc_find_rsc_table,
+ .load = qproc_load,
+ .sanity_check = qproc_sanity_check,
+};
+
+static void q6v5proc_reset(struct qproc *qproc)
+{
+ u32 val;
+
+ /* Assert resets, stop core */
+ val = readl_relaxed(qproc->reg_base + QDSP6SS_RESET);
+ val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENA | Q6SS_STOP_CORE);
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_RESET);
+
+ /* Enable power block headswitch, and wait for it to stabilize */
+ val = readl_relaxed(qproc->reg_base + QDSP6SS_PWR_CTL);
+ val |= QDSS_BHS_ON | QDSS_LDO_BYP;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_PWR_CTL);
+ mb();
+ udelay(1);
+
+ /*
+ * Turn on memories. L2 banks should be done individually
+ * to minimize inrush current.
+ */
+ val = readl_relaxed(qproc->reg_base + QDSP6SS_PWR_CTL);
+ val |= Q6SS_SLP_RET_N | Q6SS_L2TAG_SLP_NRET_N |
+ Q6SS_ETB_SLP_NRET_N | Q6SS_L2DATA_STBY_N;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_PWR_CTL);
+ val |= Q6SS_L2DATA_SLP_NRET_N_2;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_PWR_CTL);
+ val |= Q6SS_L2DATA_SLP_NRET_N_1;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_PWR_CTL);
+ val |= Q6SS_L2DATA_SLP_NRET_N_0;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_PWR_CTL);
+
+ /* Remove IO clamp */
+ val &= ~Q6SS_CLAMP_IO;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_PWR_CTL);
+
+ /* Bring core out of reset */
+ val = readl_relaxed(qproc->reg_base + QDSP6SS_RESET);
+ val &= ~Q6SS_CORE_ARES;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_RESET);
+
+ /* Turn on core clock */
+ val = readl_relaxed(qproc->reg_base + QDSP6SS_GFMUX_CTL);
+ val |= Q6SS_CLK_ENA;
+
+#if 0
+ /* Need a different clock source for v5.2.0 */
+ if (qproc->qdsp6v5_2_0) {
+ val &= ~Q6SS_CLK_SRC_SEL_FIELD;
+ val |= Q6SS_CLK_SRC_SEL_C;
+ }
+
+ /* force clock on during source switch */
+ if (qproc->qdsp6v56)
+ val |= Q6SS_CLK_SRC_SWITCH_CLK_OVR;
+#endif
+
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_GFMUX_CTL);
+
+ /* Start core execution */
+ val = readl_relaxed(qproc->reg_base + QDSP6SS_RESET);
+ val &= ~Q6SS_STOP_CORE;
+ writel_relaxed(val, qproc->reg_base + QDSP6SS_RESET);
+}
+
+static void q6v5proc_halt_axi_port(struct qproc *qproc, void __iomem *halt)
+{
+ unsigned long timeout;
+
+ if (readl_relaxed(halt + AXI_IDLE))
+ return;
+
+ /* Assert halt request */
+ writel_relaxed(1, halt + AXI_HALTREQ);
+
+ /* Wait for halt */
+ timeout = jiffies + 10 * HZ;
+ for (;;) {
+ if (readl(halt + AXI_HALTACK) || time_after(jiffies, timeout))
+ break;
+
+ msleep(1);
+ }
+
+ if (!readl_relaxed(halt + AXI_IDLE))
+ dev_err(qproc->dev, "port %pa failed halt\n", &halt);
+
+ /* Clear halt request (port will remain halted until reset) */
+ writel_relaxed(0, halt + AXI_HALTREQ);
+}
+
+static int qproc_mba_load_mdt(struct qproc *qproc, const struct firmware *fw)
+{
+ DEFINE_DMA_ATTRS(attrs);
+ unsigned long timeout;
+ dma_addr_t phys;
+ dma_addr_t end;
+ void *ptr;
+ int ret;
+ s32 val;
+
+ dma_set_mask(qproc->dev, DMA_BIT_MASK(32));
+ dma_set_attr(DMA_ATTR_FORCE_CONTIGUOUS, &attrs);
+
+ ptr = dma_alloc_attrs(qproc->dev, fw->size, &phys, GFP_KERNEL, &attrs);
+ if (!ptr) {
+ dev_err(qproc->dev, "failed to allocate mba metadata buffer\n");
+ return -ENOMEM;
+ }
+
+ end = phys + fw->size;
+ dev_info(qproc->dev, "loading mdt header from %pa to %pa\n", &phys, &end);
+
+ memcpy(ptr, fw->data, fw->size);
+
+ writel_relaxed(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+
+ writel_relaxed(phys, qproc->rmb_base + RMB_PMI_META_DATA);
+ writel(CMD_META_DATA_READY, qproc->rmb_base + RMB_MBA_COMMAND);
+
+ timeout = jiffies + HZ;
+ for (;;) {
+ msleep(1);
+
+ val = readl(qproc->rmb_base + RMB_MBA_STATUS);
+ if (val == STATUS_META_DATA_AUTH_SUCCESS || val < 0)
+ break;
+
+ if (time_after(jiffies, timeout))
+ break;
+ }
+ if (val == 0) {
+ dev_err(qproc->dev, "MBA authentication of headers timed out\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ } else if (val < 0) {
+ dev_err(qproc->dev, "MBA returned error %d for headers\n", val);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dev_err(qproc->dev, "mdt authenticated\n");
+
+ ret = 0;
+out:
+ dma_free_attrs(qproc->dev, fw->size, ptr, phys, &attrs);
+
+ return ret;
+}
+
+#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24))
+
+static int
+qproc_load_segments(struct qproc *qproc, const struct firmware *fw)
+{
+ struct device *dev = qproc->dev;
+ struct elf32_hdr *ehdr;
+ struct elf32_phdr *phdr;
+ int i, ret = 0;
+ const u8 *elf_data = fw->data;
+ const struct firmware *seg_fw;
+ char fw_name[20];
+
+ ehdr = (struct elf32_hdr *)elf_data;
+ phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff);
+
+ /* go through the available ELF segments */
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ u32 da = phdr->p_paddr;
+ u32 memsz = phdr->p_memsz;
+ u32 filesz = phdr->p_filesz;
+ void *ptr;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ if (segment_is_hash(phdr->p_flags))
+ continue;
+
+ if (filesz == 0)
+ continue;
+
+ dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n",
+ phdr->p_type, da, memsz, filesz);
+
+ if (filesz > memsz) {
+ dev_err(dev, "bad phdr filesz 0x%x memsz 0x%x\n",
+ filesz, memsz);
+ ret = -EINVAL;
+ break;
+ }
+
+ ptr = ioremap(da, memsz);
+ if (!ptr) {
+ dev_err(qproc->dev, "failed to allocate mba metadata buffer\n");
+ ret = -ENOMEM;
+ break;
+ }
+
+ if (filesz) {
+ snprintf(fw_name, sizeof(fw_name), "modem.b%02d", i);
+ ret = request_firmware(&seg_fw, fw_name, qproc->dev);
+ if (ret) {
+ iounmap(ptr);
+ break;
+ }
+
+ memcpy(ptr, seg_fw->data, filesz);
+
+ release_firmware(seg_fw);
+ }
+
+ if (memsz > filesz)
+ memset(ptr + filesz, 0, memsz - filesz);
+
+ wmb();
+ iounmap(ptr);
+ }
+
+ return ret;
+}
+
+static int qproc_verify_segments(struct qproc *qproc, const struct firmware *fw)
+{
+ struct elf32_hdr *ehdr;
+ struct elf32_phdr *phdr;
+ const u8 *elf_data = fw->data;
+ unsigned long timeout;
+ phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
+ u32 size = 0;
+ s32 val;
+ int ret;
+ int i;
+ u32 v;
+
+ ehdr = (struct elf32_hdr *)elf_data;
+ phdr = (struct elf32_phdr *)(elf_data + ehdr->e_phoff);
+
+ v = readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %pa\n", &v);
+
+ msleep(1);
+
+ v = readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %pa\n", &v);
+
+#if 1
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ phys_addr_t da = phdr->p_paddr;
+ u32 memsz = phdr->p_memsz;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ dev_err(qproc->dev, "0x%x %d %d\n", phdr->p_paddr, segment_is_hash(phdr->p_flags), !!(phdr->p_flags & BIT(27)));
+
+ if (segment_is_hash(phdr->p_flags))
+ continue;
+
+ if (memsz == 0)
+ continue;
+
+ if (da < min_addr)
+ min_addr = da;
+
+ size += memsz;
+ }
+
+ dev_err(qproc->dev, "verify: %pa:%pa\n", &min_addr, &size);
+
+ writel_relaxed(min_addr, qproc->rmb_base + RMB_PMI_CODE_START);
+ writel(CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND);
+ writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+#endif
+
+ v = readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %pa\n", &v);
+
+#if 0
+ writel_relaxed(0x08400000, qproc->rmb_base + RMB_PMI_CODE_START);
+ writel_relaxed(CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND);
+
+ size = 0;
+ size += 0x162f4;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x5f7620;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x719e1c;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x14000;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x2b929;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x0d500;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x19ab8;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x16d68;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x124a98;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x103588;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0xbf99b0;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0xa07a0;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x12000;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x01500;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x792878;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x256c44;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x14fee4;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x20d13c0;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x2c4f0;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x3a2a8;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ size += 0x3ca000;
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+ writel_relaxed(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH);
+ dev_err(qproc->dev, "RMB_PMI_CODE_LENGTH: %x\n", readl_relaxed(qproc->rmb_base + RMB_PMI_CODE_LENGTH));
+ dev_err(qproc->dev, "RMB_MBA_STATUS: 0x%x\n", readl(qproc->rmb_base + RMB_MBA_STATUS));
+#endif
+
+ timeout = jiffies + 10 * HZ;
+ for (;;) {
+ msleep(1);
+
+ val = readl(qproc->rmb_base + RMB_MBA_STATUS);
+ if (val == STATUS_AUTH_COMPLETE || val < 0)
+ break;
+
+ if (time_after(jiffies, timeout))
+ break;
+ }
+ if (val == 0) {
+ dev_err(qproc->dev, "MBA authentication of headers timed out\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ } else if (val < 0) {
+ dev_err(qproc->dev, "MBA returned error %d for segments\n", val);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = 0;
+out:
+ return ret;
+}
+
+static int qproc_load_modem(struct qproc *qproc)
+{
+ const struct firmware *fw;
+ int ret;
+
+ ret = request_firmware(&fw, "modem.mdt", qproc->dev);
+ if (ret < 0) {
+ dev_err(qproc->dev, "unable to load modem.mdt\n");
+ return ret;
+ }
+
+ ret = qproc_mba_load_mdt(qproc, fw);
+ if (ret)
+ goto out;
+
+ ret = qproc_load_segments(qproc, fw);
+ if (ret)
+ goto out;
+
+ ret = qproc_verify_segments(qproc, fw);
+ if (ret)
+ goto out;
+
+out:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int qproc_start(struct rproc *rproc)
+{
+ struct qproc *qproc = (struct qproc *)rproc->priv;
+ unsigned long timeout;
+ int ret;
+ u32 val;
+
+ ret = regulator_enable(qproc->vdd);
+ if (ret) {
+ dev_err(qproc->dev, "failed to enable mss vdd\n");
+ return ret;
+ }
+
+ ret = reset_control_deassert(qproc->mss_restart);
+ if (ret) {
+ dev_err(qproc->dev, "failed to deassert mss restart\n");
+ goto disable_vdd;
+ }
+
+ ret = clk_prepare_enable(qproc->ahb_clk);
+ if (ret)
+ goto assert_reset;
+
+ ret = clk_prepare_enable(qproc->axi_clk);
+ if (ret)
+ goto disable_ahb_clk;
+
+ ret = clk_prepare_enable(qproc->rom_clk);
+ if (ret)
+ goto disable_axi_clk;
+
+ writel_relaxed(qproc->mba_da, qproc->rmb_base + RMB_MBA_IMAGE);
+
+ /* Ensure order of data/entry point and the following reset release */
+ wmb();
+
+ q6v5proc_reset(qproc);
+
+ timeout = jiffies + HZ;
+ for (;;) {
+ msleep(1);
+
+ val = readl(qproc->rmb_base + RMB_PBL_STATUS);
+ if (val || time_after(jiffies, timeout))
+ break;
+ }
+ if (val == 0) {
+ dev_err(qproc->dev, "PBL boot timed out\n");
+ ret = -ETIMEDOUT;
+ goto halt_axi_ports;
+ } else if (val != STATUS_PBL_SUCCESS) {
+ dev_err(qproc->dev, "PBL returned unexpected status %d\n", val);
+ ret = -EINVAL;
+ goto halt_axi_ports;
+ }
+
+ timeout = jiffies + HZ;
+ for (;;) {
+ msleep(1);
+
+ val = readl(qproc->rmb_base + RMB_MBA_STATUS);
+ if (val || time_after(jiffies, timeout))
+ break;
+ }
+ if (val == 0) {
+ dev_err(qproc->dev, "MBA boot timed out\n");
+ ret = -ETIMEDOUT;
+ goto halt_axi_ports;
+ } else if (val != STATUS_XPU_UNLOCKED && val != STATUS_XPU_UNLOCKED_SCRIBBLED) {
+ dev_err(qproc->dev, "MBA returned unexpected status %d\n", val);
+ ret = -EINVAL;
+ goto halt_axi_ports;
+ }
+
+ dev_info(qproc->dev, "MBA boot done\n");
+
+ ret = qproc_load_modem(qproc);
+ if (ret)
+ goto halt_axi_ports;
+
+ return 0;
+
+halt_axi_ports:
+ q6v5proc_halt_axi_port(qproc, qproc->halt_base + MSS_Q6_HALT_BASE);
+ q6v5proc_halt_axi_port(qproc, qproc->halt_base + MSS_MODEM_HALT_BASE);
+ q6v5proc_halt_axi_port(qproc, qproc->halt_base + MSS_NC_HALT_BASE);
+disable_axi_clk:
+ clk_disable_unprepare(qproc->axi_clk);
+disable_ahb_clk:
+ clk_disable_unprepare(qproc->ahb_clk);
+assert_reset:
+ reset_control_assert(qproc->mss_restart);
+disable_vdd:
+ regulator_disable(qproc->vdd);
+
+ dma_free_attrs(qproc->dev, qproc->mba_size, qproc->mba_va, qproc->mba_da, &qproc->mba_attrs);
+ return ret;
+}
+
+static int qproc_stop(struct rproc *rproc)
+{
+ struct qproc *qproc = (struct qproc *)rproc->priv;
+
+ q6v5proc_halt_axi_port(qproc, qproc->halt_base + MSS_Q6_HALT_BASE);
+ q6v5proc_halt_axi_port(qproc, qproc->halt_base + MSS_MODEM_HALT_BASE);
+ q6v5proc_halt_axi_port(qproc, qproc->halt_base + MSS_NC_HALT_BASE);
+
+ reset_control_assert(qproc->mss_restart);
+ clk_disable_unprepare(qproc->axi_clk);
+ clk_disable_unprepare(qproc->ahb_clk);
+ regulator_disable(qproc->vdd);
+
+ dma_free_attrs(qproc->dev, qproc->mba_size, qproc->mba_va, qproc->mba_da, &qproc->mba_attrs);
+
+ return 0;
+}
+
+static const struct rproc_ops qproc_ops = {
+ .start = qproc_start,
+ .stop = qproc_stop,
+};
+
+static irqreturn_t qproc_wdog_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ dev_err(qproc->dev, " WATCHDOG\n");
+
+ rproc_report_crash(qproc->rproc, RPROC_WATCHDOG);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_fatal_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ dev_err(qproc->dev, " FATAL\n");
+
+ rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_ready_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ dev_err(qproc->dev, " READY\n");
+
+ complete(&qproc->start_done);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_handover_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ dev_err(qproc->dev, " HANDOVER\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_stop_ack_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ dev_err(qproc->dev, " STOP-ACK\n");
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t qproc_boot_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qproc *qproc = dev_get_drvdata(dev);
+ int ret;
+
+ ret = rproc_boot(qproc->rproc);
+ return ret ? : size;
+}
+
+static ssize_t qproc_shutdown_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qproc *qproc = dev_get_drvdata(dev);
+
+ rproc_shutdown(qproc->rproc);
+ return size;
+}
+
+static const struct device_attribute qproc_attrs[] = {
+ __ATTR(boot, S_IWUSR, 0, qproc_boot_store),
+ __ATTR(shutdown, S_IWUSR, 0, qproc_shutdown_store),
+};
+
+static int qproc_init_mem(struct qproc *qproc, struct platform_device *pdev)
+{
+ struct resource *res;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6_base");
+ qproc->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(qproc->reg_base))
+ return PTR_ERR(qproc->reg_base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "halt_base");
+ qproc->halt_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(qproc->halt_base))
+ return PTR_ERR(qproc->halt_base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb_base");
+ qproc->rmb_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(qproc->rmb_base))
+ return PTR_ERR(qproc->rmb_base);
+
+ return 0;
+}
+
+static int qproc_init_clocks(struct qproc *qproc)
+{
+ qproc->ahb_clk = devm_clk_get(qproc->dev, "iface");
+ if (IS_ERR(qproc->ahb_clk))
+ return PTR_ERR(qproc->ahb_clk);
+
+ qproc->axi_clk = devm_clk_get(qproc->dev, "bus");
+ if (IS_ERR(qproc->axi_clk))
+ return PTR_ERR(qproc->axi_clk);
+
+ qproc->rom_clk = devm_clk_get(qproc->dev, "mem");
+ if (IS_ERR(qproc->rom_clk))
+ return PTR_ERR(qproc->rom_clk);
+
+ return 0;
+}
+
+static int qproc_init_regulators(struct qproc *qproc)
+{
+ int ret;
+ u32 uV;
+
+ qproc->vdd = devm_regulator_get(qproc->dev, "qcom,vdd");
+ if (IS_ERR(qproc->vdd))
+ return PTR_ERR(qproc->vdd);
+
+ regulator_set_voltage(qproc->vdd, VDD_MSS_UV, VDD_MSS_UV_MAX);
+ regulator_set_load(qproc->vdd, VDD_MSS_UA);
+
+ qproc->cx = devm_regulator_get(qproc->dev, "qcom,cx");
+ if (IS_ERR(qproc->cx))
+ return PTR_ERR(qproc->cx);
+
+ qproc->mx = devm_regulator_get(qproc->dev, "qcom,mx");
+ if (IS_ERR(qproc->mx))
+ return PTR_ERR(qproc->mx);
+
+ ret = of_property_read_u32(qproc->dev->of_node, "qcom,mx-uV", &uV);
+ if (!ret)
+ regulator_set_voltage(qproc->mx, uV, uV);
+
+ qproc->pll = devm_regulator_get(qproc->dev, "qcom,pll");
+ if (IS_ERR(qproc->pll))
+ return PTR_ERR(qproc->pll);
+
+ ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uV", &uV);
+ if (!ret)
+ regulator_set_voltage(qproc->pll, uV, uV);
+
+ return 0;
+}
+
+static int qproc_init_reset(struct qproc *qproc)
+{
+ qproc->mss_restart = devm_reset_control_get(qproc->dev, NULL);
+ if (IS_ERR(qproc->mss_restart)) {
+ dev_err(qproc->dev, "failed to acquire mss restart\n");
+ return PTR_ERR(qproc->mss_restart);
+ }
+
+ return 0;
+}
+
+static int qproc_request_irq(struct qproc *qproc,
+ struct platform_device *pdev,
+ const char *name,
+ irq_handler_t thread_fn)
+{
+ int ret;
+
+ ret = platform_get_irq_byname(pdev, name);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no %s IRQ defined\n", name);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, ret,
+ NULL, thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "qproc", qproc);
+ if (ret)
+ dev_err(&pdev->dev, "request %s IRQ failed\n", name);
+ return ret;
+}
+
+static int qproc_probe(struct platform_device *pdev)
+{
+ struct qproc *qproc;
+ struct rproc *rproc;
+ int ret;
+ int i;
+
+ rproc = rproc_alloc(&pdev->dev, pdev->name, &qproc_ops,
+ "mba.b00", sizeof(*qproc));
+ if (!rproc)
+ return -ENOMEM;
+
+ rproc->fw_ops = &qproc_fw_ops;
+
+ qproc = (struct qproc *)rproc->priv;
+ qproc->dev = &pdev->dev;
+ qproc->rproc = rproc;
+ platform_set_drvdata(pdev, qproc);
+
+ init_completion(&qproc->start_done);
+
+ ret = qproc_init_mem(qproc, pdev);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_clocks(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_regulators(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_reset(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_request_irq(qproc, pdev, "wdog", qproc_wdog_interrupt);
+ if (ret < 0)
+ goto free_rproc;
+ qproc->wdog_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "fatal", qproc_fatal_interrupt);
+ if (ret < 0)
+ goto free_rproc;
+ qproc->fatal_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "ready", qproc_ready_interrupt);
+ if (ret < 0)
+ goto free_rproc;
+ qproc->ready_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "handover", qproc_handover_interrupt);
+ if (ret < 0)
+ goto free_rproc;
+ qproc->handover_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "stop-ack", qproc_stop_ack_interrupt);
+ if (ret < 0)
+ goto free_rproc;
+ qproc->stop_ack_irq = ret;
+
+ qproc->stop_gpio = devm_gpiod_get(&pdev->dev, "qcom,stop", GPIOD_OUT_LOW);
+ if (IS_ERR(qproc->stop_gpio)) {
+ dev_err(&pdev->dev, "failed to acquire stop gpio\n");
+ return PTR_ERR(qproc->stop_gpio);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++) {
+ ret = device_create_file(&pdev->dev, &qproc_attrs[i]);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to create sysfs file\n");
+ goto remove_device_files;
+ }
+ }
+
+ ret = rproc_add(rproc);
+ if (ret)
+ goto remove_device_files;
+
+ return 0;
+
+remove_device_files:
+ for (i--; i >= 0; i--)
+ device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+free_rproc:
+ rproc_put(rproc);
+
+ return ret;
+}
+
+static int qproc_remove(struct platform_device *pdev)
+{
+ struct qproc *qproc = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++)
+ device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+ rproc_put(qproc->rproc);
+
+ return 0;
+}
+
+static const struct of_device_id qproc_of_match[] = {
+ { .compatible = "qcom,q6v5-pil", },
+ { },
+};
+
+static struct platform_driver qproc_driver = {
+ .probe = qproc_probe,
+ .remove = qproc_remove,
+ .driver = {
+ .name = "qcom-q6v5-pil",
+ .of_match_table = qproc_of_match,
+ },
+};
+
+module_platform_driver(qproc_driver);
diff --git a/drivers/remoteproc/qcom_tz_pil.c b/drivers/remoteproc/qcom_tz_pil.c
new file mode 100644
index 000000000000..2f43b00fee13
--- /dev/null
+++ b/drivers/remoteproc/qcom_tz_pil.c
@@ -0,0 +1,719 @@
+/*
+ * Qualcomm Peripheral Image Loader
+ *
+ * Copyright (C) 2014 Sony Mobile Communications AB
+ * Copyright (c) 2012-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 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/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/remoteproc.h>
+#include <linux/interrupt.h>
+#include <linux/memblock.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/elf.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/regulator/consumer.h>
+#include <linux/qcom_scm.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+#include <linux/soc/qcom/smd.h>
+
+#include "remoteproc_internal.h"
+
+#define PAS_INIT_IMAGE_CMD 1
+#define PAS_MEM_SETUP_CMD 2
+#define PAS_AUTH_AND_RESET_CMD 5
+#define PAS_SHUTDOWN_CMD 6
+#define PAS_IS_SUPPORTED_CMD 7
+
+struct qproc {
+ struct device *dev;
+ struct rproc *rproc;
+
+ int pas_id;
+
+ int wdog_irq;
+ int fatal_irq;
+ int ready_irq;
+ int handover_irq;
+ int stop_ack_irq;
+
+ struct gpio_desc *stop_gpio;
+
+ const char *name;
+ struct regulator *pll;
+
+ unsigned proxy_clk_count;
+ struct clk *scm_core_clk;
+ struct clk *scm_iface_clk;
+ struct clk *scm_bus_clk;
+ struct clk *scm_src_clk;
+
+ struct clk **proxy_clks;
+
+ struct completion start_done;
+ struct completion stop_done;
+
+ unsigned crash_reason;
+ struct device_node *smd_edge_node;
+
+ phys_addr_t reloc_phys;
+ size_t reloc_size;
+};
+
+static int qproc_scm_clk_enable(struct qproc *qproc)
+{
+ int ret;
+
+ ret = clk_prepare_enable(qproc->scm_core_clk);
+ if (ret)
+ goto bail;
+ ret = clk_prepare_enable(qproc->scm_iface_clk);
+ if (ret)
+ goto disable_core;
+ ret = clk_prepare_enable(qproc->scm_bus_clk);
+ if (ret)
+ goto disable_iface;
+
+ ret = clk_prepare_enable(qproc->scm_src_clk);
+ if (ret)
+ goto disable_bus;
+
+ return 0;
+
+disable_bus:
+ clk_disable_unprepare(qproc->scm_bus_clk);
+disable_iface:
+ clk_disable_unprepare(qproc->scm_iface_clk);
+disable_core:
+ clk_disable_unprepare(qproc->scm_core_clk);
+bail:
+ return ret;
+}
+
+static void qproc_scm_clk_disable(struct qproc *qproc)
+{
+ clk_disable_unprepare(qproc->scm_core_clk);
+ clk_disable_unprepare(qproc->scm_iface_clk);
+ clk_disable_unprepare(qproc->scm_bus_clk);
+ clk_disable_unprepare(qproc->scm_src_clk);
+}
+
+/**
+ * struct pil_mdt - Representation of <name>.mdt file in memory
+ * @hdr: ELF32 header
+ * @phdr: ELF32 program headers
+ */
+struct mdt_hdr {
+ struct elf32_hdr hdr;
+ struct elf32_phdr phdr[];
+};
+
+#define segment_is_hash(flag) (((flag) & (0x7 << 24)) == (0x2 << 24))
+
+static int segment_is_loadable(const struct elf32_phdr *p)
+{
+ return (p->p_type == PT_LOAD) &&
+ !segment_is_hash(p->p_flags) &&
+ p->p_memsz;
+}
+
+static bool segment_is_relocatable(const struct elf32_phdr *p)
+{
+ return !!(p->p_flags & BIT(27));
+}
+
+/**
+ * rproc_mdt_sanity_check() - sanity check mdt firmware header
+ * @rproc: the remote processor handle
+ * @fw: the mdt header firmware image
+ */
+static int qproc_sanity_check(struct rproc *rproc,
+ const struct firmware *fw)
+{
+ struct elf32_hdr *ehdr;
+ struct mdt_hdr *mdt;
+
+ if (!fw) {
+ dev_err(&rproc->dev, "failed to load %s\n", rproc->name);
+ return -EINVAL;
+ }
+
+ if (fw->size < sizeof(struct elf32_hdr)) {
+ dev_err(&rproc->dev, "image is too small\n");
+ return -EINVAL;
+ }
+
+ mdt = (struct mdt_hdr *)fw->data;
+ ehdr = &mdt->hdr;
+
+ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+ dev_err(&rproc->dev, "image is corrupted (bad magic)\n");
+ return -EINVAL;
+ }
+
+ if (ehdr->e_phnum == 0) {
+ dev_err(&rproc->dev, "no loadable segments\n");
+ return -EINVAL;
+ }
+
+ if (sizeof(struct elf32_phdr) * ehdr->e_phnum +
+ sizeof(struct elf32_hdr) > fw->size) {
+ dev_err(&rproc->dev, "firmware size is too small\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct resource_table * qproc_find_rsc_table(struct rproc *rproc,
+ const struct firmware *fw,
+ int *tablesz)
+{
+ static struct resource_table table = { .ver = 1, };
+
+ *tablesz = sizeof(table);
+ return &table;
+}
+
+static int qproc_load_segment(struct rproc *rproc, const char *fw_name,
+ const struct elf32_phdr *phdr, phys_addr_t paddr)
+{
+ const struct firmware *fw;
+ void *ptr;
+ int ret = 0;
+
+ ptr = ioremap_nocache(paddr, phdr->p_memsz);
+ if (!ptr) {
+ dev_err(&rproc->dev, "failed to ioremap segment area (%pa+0x%x)\n", &paddr, phdr->p_memsz);
+ return -EBUSY;
+ }
+
+ if (phdr->p_filesz) {
+ ret = request_firmware(&fw, fw_name, &rproc->dev);
+ if (ret) {
+ dev_err(&rproc->dev, "failed to load %s\n", fw_name);
+ goto out;
+ }
+
+ memcpy_toio(ptr, fw->data, fw->size);
+
+ release_firmware(fw);
+ }
+
+ if (phdr->p_memsz > phdr->p_filesz)
+ memset_io(ptr + phdr->p_filesz, 0,
+ phdr->p_memsz - phdr->p_filesz);
+
+out:
+ iounmap(ptr);
+ return ret;
+}
+
+static int qproc_load(struct rproc *rproc, const struct firmware *fw)
+{
+ const struct elf32_phdr *phdr;
+ const struct elf32_hdr *ehdr;
+ const struct mdt_hdr *mdt;
+ phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX;
+ phys_addr_t max_addr = 0;
+ phys_addr_t diff_addr;
+ struct qproc *qproc = rproc->priv;
+ char *fw_name;
+ int ret;
+ int i;
+ size_t align = 0;
+ bool relocatable = false;
+ phys_addr_t paddr;
+
+ ret = qproc_scm_clk_enable(qproc);
+ if (ret)
+ return ret;
+
+ mdt = (struct mdt_hdr *)fw->data;
+ ehdr = &mdt->hdr;
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ phdr = &mdt->phdr[i];
+
+ if (!segment_is_loadable(phdr))
+ continue;
+
+ if (phdr->p_paddr < min_addr) {
+ min_addr = phdr->p_paddr;
+
+ if (segment_is_relocatable(phdr)) {
+ align = phdr->p_align;
+ relocatable = true;
+ }
+ }
+
+ if (phdr->p_paddr + phdr->p_memsz > max_addr)
+ max_addr = round_up(phdr->p_paddr + phdr->p_memsz, SZ_4K);
+ }
+
+ ret = qcom_scm_pas_init_image(qproc->dev,
+ qproc->pas_id, fw->data, fw->size);
+ if (ret) {
+ dev_err(qproc->dev, "Invalid firmware metadata\n");
+ return -EINVAL;
+ }
+
+ diff_addr = max_addr - min_addr;
+ dev_dbg(qproc->dev, "pas_mem_setup %pa, %pa\n", &min_addr, &diff_addr);
+
+ ret = qcom_scm_pas_mem_setup(qproc->pas_id,
+ relocatable ? qproc->reloc_phys : min_addr, max_addr - min_addr);
+ if (ret) {
+ dev_err(qproc->dev, "unable to setup memory for image\n");
+ return -EINVAL;
+ }
+
+ fw_name = kzalloc(strlen(qproc->name) + 5, GFP_KERNEL);
+ if (!fw_name)
+ return -ENOMEM;
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ phdr = &mdt->phdr[i];
+
+ if (!segment_is_loadable(phdr))
+ continue;
+
+ paddr = relocatable ?
+ (phdr->p_paddr - min_addr + qproc->reloc_phys) :
+ phdr->p_paddr;
+ sprintf(fw_name, "%s.b%02d", qproc->name, i);
+ ret = qproc_load_segment(rproc, fw_name, phdr, paddr);
+ if (ret)
+ break;
+ }
+
+ kfree(fw_name);
+
+ qproc_scm_clk_disable(qproc);
+
+ return 0;
+}
+
+const struct rproc_fw_ops qproc_fw_ops = {
+ .find_rsc_table = qproc_find_rsc_table,
+ .load = qproc_load,
+ .sanity_check = qproc_sanity_check,
+};
+
+static int qproc_start(struct rproc *rproc)
+{
+ struct qproc *qproc = (struct qproc *)rproc->priv;
+ int ret;
+
+ ret = regulator_enable(qproc->pll);
+ if (ret) {
+ dev_err(qproc->dev, "failed to enable pll supply\n");
+ return ret;
+ }
+
+ ret = qproc_scm_clk_enable(qproc);
+ if (ret)
+ goto disable_regulator;
+
+ ret = qcom_scm_pas_auth_and_reset(qproc->pas_id);
+ if (ret) {
+ dev_err(qproc->dev,
+ "failed to authenticate image and release reset\n");
+ goto unroll_clocks;
+ }
+
+ /* if ready irq not provided skip waiting */
+ if (qproc->ready_irq < 0)
+ goto done;
+
+ ret = wait_for_completion_timeout(&qproc->start_done, msecs_to_jiffies(10000));
+ if (ret == 0) {
+ dev_err(qproc->dev, "start timed out\n");
+
+ qcom_scm_pas_shutdown(qproc->pas_id);
+ goto unroll_clocks;
+ }
+
+done:
+ dev_info(qproc->dev, "start successful\n");
+
+ return 0;
+
+unroll_clocks:
+ qproc_scm_clk_disable(qproc);
+
+disable_regulator:
+ regulator_disable(qproc->pll);
+
+ return ret;
+}
+
+static int qproc_stop(struct rproc *rproc)
+{
+ return 0;
+}
+
+static const struct rproc_ops qproc_ops = {
+ .start = qproc_start,
+ .stop = qproc_stop,
+};
+
+static irqreturn_t qproc_wdog_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ rproc_report_crash(qproc->rproc, RPROC_WATCHDOG);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_fatal_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+ size_t len;
+ char *msg;
+ int ret;
+
+ ret = qcom_smem_get(-1, qproc->crash_reason, (void**)&msg, &len);
+ if (!ret && len > 0 && msg[0])
+ dev_err(qproc->dev, "fatal error received: %s\n", msg);
+
+ rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR);
+
+ if (!ret)
+ msg[0] = '\0';
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_ready_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ complete(&qproc->start_done);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_handover_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ qproc_scm_clk_disable(qproc);
+ regulator_disable(qproc->pll);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t qproc_stop_ack_interrupt(int irq, void *dev)
+{
+ struct qproc *qproc = dev;
+
+ complete(&qproc->stop_done);
+ return IRQ_HANDLED;
+}
+
+static ssize_t qproc_boot_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qproc *qproc = dev_get_drvdata(dev);
+ int ret;
+
+ ret = rproc_boot(qproc->rproc);
+ return ret ? : size;
+}
+
+static ssize_t qproc_shutdown_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct qproc *qproc = dev_get_drvdata(dev);
+
+ rproc_shutdown(qproc->rproc);
+ return size;
+}
+
+static const struct device_attribute qproc_attrs[] = {
+ __ATTR(boot, S_IWUSR, 0, qproc_boot_store),
+ __ATTR(shutdown, S_IWUSR, 0, qproc_shutdown_store),
+};
+
+static int qproc_init_pas(struct qproc *qproc)
+{
+ char *key;
+ int ret;
+
+ key = "qcom,pas-id";
+ ret = of_property_read_u32(qproc->dev->of_node, key, &qproc->pas_id);
+ if (ret) {
+ dev_err(qproc->dev, "Missing or incorrect %s\n", key);
+ return -EINVAL;
+ }
+
+ if (!qcom_scm_pas_supported(qproc->pas_id)) {
+ dev_err(qproc->dev, "PAS is not available for %d\n", qproc->pas_id);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int qproc_init_clocks(struct qproc *qproc)
+{
+ long rate;
+ int ret;
+
+ qproc->scm_core_clk = devm_clk_get(qproc->dev, "scm_core_clk");
+ if (IS_ERR(qproc->scm_core_clk)) {
+ if (PTR_ERR(qproc->scm_core_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_core_clk\n");
+ return PTR_ERR(qproc->scm_core_clk);
+ }
+
+ qproc->scm_iface_clk = devm_clk_get(qproc->dev, "scm_iface_clk");
+ if (IS_ERR(qproc->scm_iface_clk)) {
+ if (PTR_ERR(qproc->scm_iface_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_iface_clk\n");
+ return PTR_ERR(qproc->scm_iface_clk);
+ }
+
+ qproc->scm_bus_clk = devm_clk_get(qproc->dev, "scm_bus_clk");
+ if (IS_ERR(qproc->scm_bus_clk)) {
+ if (PTR_ERR(qproc->scm_bus_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_bus_clk\n");
+ return PTR_ERR(qproc->scm_bus_clk);
+ }
+
+ qproc->scm_src_clk = devm_clk_get(qproc->dev, "scm_src_clk");
+ if (IS_ERR(qproc->scm_src_clk)) {
+ if (PTR_ERR(qproc->scm_src_clk) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to acquire scm_src_clk\n");
+ return PTR_ERR(qproc->scm_src_clk);
+ }
+
+ ret = clk_set_rate(qproc->scm_core_clk,
+clk_round_rate(qproc->scm_core_clk, 19200000));
+ ret = clk_set_rate(qproc->scm_bus_clk,
+clk_round_rate(qproc->scm_bus_clk, 19200000));
+ ret = clk_set_rate(qproc->scm_iface_clk,
+clk_round_rate(qproc->scm_iface_clk, 19200000));
+ rate = clk_round_rate(qproc->scm_core_clk, 80000000);
+ ret = clk_set_rate(qproc->scm_src_clk, rate);
+ if (ret) {
+ dev_err(qproc->dev, "failed to set rate of scm_core_clk\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int qproc_init_regulators(struct qproc *qproc)
+{
+ int ret;
+ u32 uA;
+ u32 uV;
+
+ qproc->pll = devm_regulator_get(qproc->dev, "qcom,pll");
+ if (IS_ERR(qproc->pll)) {
+ if (PTR_ERR(qproc->pll) != -EPROBE_DEFER)
+ dev_err(qproc->dev, "failed to aquire regulator\n");
+ return PTR_ERR(qproc->pll);
+ }
+
+ ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uV", &uV);
+ if (ret)
+ dev_warn(qproc->dev, "failed to read qcom,pll_uV, skipping\n");
+ else
+ regulator_set_voltage(qproc->pll, uV, uV);
+
+ ret = of_property_read_u32(qproc->dev->of_node, "qcom,pll-uA", &uA);
+ if (ret)
+ dev_warn(qproc->dev, "failed to read qcom,pll_uA, skipping\n");
+ else
+ regulator_set_load(qproc->pll, uA);
+
+ return 0;
+}
+
+static int qproc_request_irq(struct qproc *qproc, struct platform_device *pdev, const char *name, irq_handler_t thread_fn)
+{
+ int ret;
+
+ ret = platform_get_irq_byname(pdev, name);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "no %s IRQ defined\n", name);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, ret,
+ NULL, thread_fn,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "qproc", qproc);
+ if (ret)
+ dev_err(&pdev->dev, "request %s IRQ failed\n", name);
+ return ret;
+}
+
+static int qproc_probe(struct platform_device *pdev)
+{
+ struct qproc *qproc;
+ struct rproc *rproc;
+ char *fw_name;
+ const char *name;
+ const char *key;
+ int ret;
+ int i;
+ struct device_node *np;
+ struct resource r;
+
+
+ key = "qcom,firmware-name";
+ ret = of_property_read_string(pdev->dev.of_node, key, &name);
+ if (ret) {
+ dev_err(&pdev->dev, "missing or incorrect %s\n", key);
+ return -EINVAL;
+ }
+
+ fw_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s.mdt", name);
+ if (!fw_name)
+ return -ENOMEM;
+
+ rproc = rproc_alloc(&pdev->dev, pdev->name, &qproc_ops,
+ fw_name, sizeof(*qproc));
+ if (!rproc) {
+ dev_err(&pdev->dev, "unable to allocate remoteproc\n");
+ return -ENOMEM;
+ }
+
+ rproc->fw_ops = &qproc_fw_ops;
+
+ qproc = (struct qproc *)rproc->priv;
+ qproc->dev = &pdev->dev;
+ qproc->rproc = rproc;
+ qproc->name = name;
+ platform_set_drvdata(pdev, qproc);
+
+ init_completion(&qproc->start_done);
+ init_completion(&qproc->stop_done);
+
+ ret = of_property_read_u32(pdev->dev.of_node, "qcom,crash-reason",
+ &qproc->crash_reason);
+ if (ret)
+ dev_info(&pdev->dev, "no crash reason id\n");
+
+ qproc->smd_edge_node = of_parse_phandle(pdev->dev.of_node,
+ "qcom,smd-edges", 0);
+
+ ret = qproc_init_pas(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_clocks(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_init_regulators(qproc);
+ if (ret)
+ goto free_rproc;
+
+ ret = qproc_request_irq(qproc, pdev, "wdog", qproc_wdog_interrupt);
+ qproc->wdog_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "fatal", qproc_fatal_interrupt);
+ qproc->fatal_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "ready", qproc_ready_interrupt);
+ qproc->ready_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "handover", qproc_handover_interrupt);
+ qproc->handover_irq = ret;
+
+ ret = qproc_request_irq(qproc, pdev, "stop-ack", qproc_stop_ack_interrupt);
+ qproc->stop_ack_irq = ret;
+
+ for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++) {
+ ret = device_create_file(&pdev->dev, &qproc_attrs[i]);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to create sysfs file\n");
+ goto remove_device_files;
+ }
+ }
+
+ np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0);
+ if (!np) {
+ dev_err(&pdev->dev, "No memory region specified\n");
+ } else {
+
+ ret = of_address_to_resource(np, 0, &r);
+ of_node_put(np);
+ if (ret)
+ return ret;
+
+ qproc->reloc_phys = r.start;
+ qproc->reloc_size = resource_size(&r);
+
+ dev_info(&pdev->dev, "Found relocation area %lu@%pad\n",
+ qproc->reloc_size, &qproc->reloc_phys);
+ }
+
+ ret = rproc_add(rproc);
+ if (ret)
+ goto remove_device_files;
+
+ return 0;
+
+remove_device_files:
+ for (i--; i >= 0; i--)
+ device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+free_rproc:
+ rproc_put(rproc);
+
+ return ret;
+}
+
+static int qproc_remove(struct platform_device *pdev)
+{
+ struct qproc *qproc = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(qproc_attrs); i++)
+ device_remove_file(&pdev->dev, &qproc_attrs[i]);
+
+ rproc_put(qproc->rproc);
+
+ return 0;
+}
+
+static const struct of_device_id qproc_of_match[] = {
+ { .compatible = "qcom,tz-pil", },
+ { },
+};
+
+static struct platform_driver qproc_driver = {
+ .probe = qproc_probe,
+ .remove = qproc_remove,
+ .driver = {
+ .name = "qcom-tz-pil",
+ .of_match_table = qproc_of_match,
+ },
+};
+
+module_platform_driver(qproc_driver);
diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c
index 8b3130f22b42..39656aa723b6 100644
--- a/drivers/remoteproc/remoteproc_core.c
+++ b/drivers/remoteproc/remoteproc_core.c
@@ -57,6 +57,8 @@ static DEFINE_IDA(rproc_dev_index);
static const char * const rproc_crash_names[] = {
[RPROC_MMUFAULT] = "mmufault",
+ [RPROC_WATCHDOG] = "watchdog",
+ [RPROC_FATAL_ERROR] = "fatal error",
};
/* translate rproc_crash_type to string */
@@ -789,6 +791,8 @@ static void rproc_resource_cleanup(struct rproc *rproc)
}
}
+static int __rproc_fw_config_virtio(struct rproc *rproc, const struct firmware *fw);
+
/*
* take a firmware and boot a remote processor with it.
*/
@@ -799,13 +803,16 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
struct resource_table *table, *loaded_table;
int ret, tablesz;
- if (!rproc->table_ptr)
- return -ENOMEM;
-
ret = rproc_fw_sanity_check(rproc, fw);
if (ret)
return ret;
+ if (!rproc->table_ptr) {
+ ret = __rproc_fw_config_virtio(rproc, fw);
+ if (ret)
+ return ret;
+ }
+
dev_info(dev, "Booting fw image %s, size %zd\n", name, fw->size);
/*
@@ -854,12 +861,8 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw)
* copy this information to device memory.
*/
loaded_table = rproc_find_loaded_rsc_table(rproc, fw);
- if (!loaded_table) {
- ret = -EINVAL;
- goto clean_up;
- }
-
- memcpy(loaded_table, rproc->cached_table, tablesz);
+ if (loaded_table)
+ memcpy(loaded_table, rproc->cached_table, tablesz);
/* power up the remote processor */
ret = rproc->ops->start(rproc);
@@ -895,19 +898,15 @@ clean_up:
* to unregister the device. one other option is just to use kref here,
* that might be cleaner).
*/
-static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
+static int __rproc_fw_config_virtio(struct rproc *rproc, const struct firmware *fw)
{
- struct rproc *rproc = context;
struct resource_table *table;
int ret, tablesz;
- if (rproc_fw_sanity_check(rproc, fw) < 0)
- goto out;
-
/* look for the resource table */
table = rproc_find_rsc_table(rproc, fw, &tablesz);
if (!table)
- goto out;
+ return -EINVAL;
rproc->table_csum = crc32(0, table, tablesz);
@@ -919,7 +918,7 @@ static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
*/
rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL);
if (!rproc->cached_table)
- goto out;
+ return -ENOMEM;
rproc->table_ptr = rproc->cached_table;
@@ -928,12 +927,21 @@ static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
ret = rproc_handle_resources(rproc, tablesz,
rproc_count_vrings_handler);
if (ret)
- goto out;
+ return ret;
/* look for virtio devices and register them */
ret = rproc_handle_resources(rproc, tablesz, rproc_vdev_handler);
-out:
+ return ret;
+}
+
+static void rproc_fw_config_virtio(const struct firmware *fw, void *context)
+{
+ struct rproc *rproc = context;
+
+ if (rproc_fw_sanity_check(rproc, fw) >= 0)
+ __rproc_fw_config_virtio(rproc, fw);
+
release_firmware(fw);
/* allow rproc_del() contexts, if any, to proceed */
complete_all(&rproc->firmware_loading_complete);
diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c
index 1392ccf14a20..f58d02e51bb8 100644
--- a/drivers/soc/qcom/smd-rpm.c
+++ b/drivers/soc/qcom/smd-rpm.c
@@ -121,7 +121,7 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm,
pkt.hdr.length = sizeof(struct qcom_rpm_request) + count;
pkt.req.msg_id = msg_id++;
- pkt.req.flags = BIT(state);
+ pkt.req.flags = state;
pkt.req.type = type;
pkt.req.id = id;
pkt.req.data_len = count;
@@ -212,6 +212,7 @@ static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
static const struct of_device_id qcom_smd_rpm_of_match[] = {
{ .compatible = "qcom,rpm-msm8974" },
+ { .compatible = "qcom,rpm-msm8916" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c
index a6155c917d52..b51332e0c54f 100644
--- a/drivers/soc/qcom/smd.c
+++ b/drivers/soc/qcom/smd.c
@@ -104,9 +104,9 @@ static const struct {
* @channels: list of all channels detected on this edge
* @channels_lock: guard for modifications of @channels
* @allocated: array of bitmaps representing already allocated channels
- * @need_rescan: flag that the @work needs to scan smem for new channels
* @smem_available: last available amount of smem triggering a channel scan
- * @work: work item for edge house keeping
+ * @scan_work: work item for discovering new channels
+ * @state_work: work item for edge state changes
*/
struct qcom_smd_edge {
struct qcom_smd *smd;
@@ -121,29 +121,19 @@ struct qcom_smd_edge {
int ipc_bit;
struct list_head channels;
- spinlock_t channels_lock;
+ rwlock_t channels_lock;
DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE);
- bool need_rescan;
unsigned smem_available;
- struct work_struct work;
-};
+ wait_queue_head_t new_channel_event;
-/*
- * SMD channel states.
- */
-enum smd_channel_state {
- SMD_CHANNEL_CLOSED,
- SMD_CHANNEL_OPENING,
- SMD_CHANNEL_OPENED,
- SMD_CHANNEL_FLUSHING,
- SMD_CHANNEL_CLOSING,
- SMD_CHANNEL_RESET,
- SMD_CHANNEL_RESET_OPENING
+ struct work_struct scan_work;
+ struct work_struct state_work;
};
+
/**
* struct qcom_smd_channel - smd channel struct
* @edge: qcom_smd_edge this channel is living on
@@ -166,38 +156,6 @@ enum smd_channel_state {
* @pkt_size: size of the currently handled packet
* @list: lite entry for @channels in qcom_smd_edge
*/
-struct qcom_smd_channel {
- struct qcom_smd_edge *edge;
-
- struct qcom_smd_device *qsdev;
-
- char *name;
- enum smd_channel_state state;
- enum smd_channel_state remote_state;
-
- struct smd_channel_info *tx_info;
- struct smd_channel_info *rx_info;
-
- struct smd_channel_info_word *tx_info_word;
- struct smd_channel_info_word *rx_info_word;
-
- struct mutex tx_lock;
- wait_queue_head_t fblockread_event;
-
- void *tx_fifo;
- void *rx_fifo;
- int fifo_size;
-
- void *bounce_buffer;
- int (*cb)(struct qcom_smd_device *, const void *, size_t);
-
- spinlock_t recv_lock;
-
- int pkt_size;
-
- struct list_head list;
-};
-
/**
* struct qcom_smd - smd struct
* @dev: device struct
@@ -323,6 +281,19 @@ static void qcom_smd_channel_reset(struct qcom_smd_channel *channel)
}
/*
+ * Set the callback for a channel, with appropriate locking
+ */
+static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel,
+ qcom_smd_cb_t cb)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&channel->recv_lock, flags);
+ channel->cb = cb;
+ spin_unlock_irqrestore(&channel->recv_lock, flags);
+};
+
+/*
* Calculate the amount of data available in the rx fifo
*/
static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel)
@@ -362,43 +333,37 @@ static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel,
}
/*
- * Copy count bytes of data using 32bit accesses, if that's required.
+ * Copy count bytes of data from memory to device memory using 32bit accesses
*/
-static void smd_copy_to_fifo(void __iomem *_dst,
- const void *_src,
- size_t count,
- bool word_aligned)
+static void smd_copy_to_fifo(void __iomem *_dst, const void *_src, size_t count, bool word_aligned)
{
- u32 *dst = (u32 *)_dst;
- u32 *src = (u32 *)_src;
-
- if (word_aligned) {
- count /= sizeof(u32);
- while (count--)
- writel_relaxed(*src++, dst++);
- } else {
- memcpy_toio(_dst, _src, count);
- }
+ u32 *dst = (u32 *)_dst;
+ u32 *src = (u32 *)_src;
+
+ if (word_aligned) {
+ count /= sizeof(u32);
+ while (count--)
+ writel_relaxed(*src++, dst++);
+ } else {
+ memcpy_toio(_dst, _src, count);
+ }
}
/*
- * Copy count bytes of data using 32bit accesses, if that is required.
+ * Copy count bytes of data from device memory to memory using 32bit accesses
*/
-static void smd_copy_from_fifo(void *_dst,
- const void __iomem *_src,
- size_t count,
- bool word_aligned)
+static void smd_copy_from_fifo(void *_dst, const void __iomem *_src, size_t count, bool word_aligned)
{
- u32 *dst = (u32 *)_dst;
- u32 *src = (u32 *)_src;
-
- if (word_aligned) {
- count /= sizeof(u32);
- while (count--)
- *dst++ = readl_relaxed(src++);
- } else {
- memcpy_fromio(_dst, _src, count);
- }
+ u32 *dst = (u32 *)_dst;
+ u32 *src = (u32 *)_src;
+
+ if (word_aligned) {
+ count /= sizeof(u32);
+ while (count--)
+ *dst++ = readl_relaxed(src++);
+ } else {
+ memcpy_fromio(_dst, _src, count);
+ }
}
/*
@@ -416,19 +381,11 @@ static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel,
tail = GET_RX_CHANNEL_INFO(channel, tail);
len = min_t(size_t, count, channel->fifo_size - tail);
- if (len) {
- smd_copy_from_fifo(buf,
- channel->rx_fifo + tail,
- len,
- word_aligned);
- }
+ if (len)
+ smd_copy_from_fifo(buf, channel->rx_fifo + tail, len, word_aligned);
- if (len != count) {
- smd_copy_from_fifo(buf + len,
- channel->rx_fifo,
- count - len,
- word_aligned);
- }
+ if (len != count)
+ smd_copy_from_fifo(buf + len, channel->rx_fifo, count - len, word_aligned);
return count;
}
@@ -561,13 +518,13 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
/*
* Handle state changes or data on each of the channels on this edge
*/
- spin_lock(&edge->channels_lock);
+ read_lock(&edge->channels_lock);
list_for_each_entry(channel, &edge->channels, list) {
spin_lock(&channel->recv_lock);
kick_worker |= qcom_smd_channel_intr(channel);
spin_unlock(&channel->recv_lock);
}
- spin_unlock(&edge->channels_lock);
+ read_unlock(&edge->channels_lock);
/*
* Creating a new channel requires allocating an smem entry, so we only
@@ -577,12 +534,11 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data)
available = qcom_smem_get_free_space(edge->remote_pid);
if (available != edge->smem_available) {
edge->smem_available = available;
- edge->need_rescan = true;
kick_worker = true;
}
if (kick_worker)
- schedule_work(&edge->work);
+ schedule_work(&edge->scan_work);
return IRQ_HANDLED;
}
@@ -631,19 +587,11 @@ static int qcom_smd_write_fifo(struct qcom_smd_channel *channel,
head = GET_TX_CHANNEL_INFO(channel, head);
len = min_t(size_t, count, channel->fifo_size - head);
- if (len) {
- smd_copy_to_fifo(channel->tx_fifo + head,
- data,
- len,
- word_aligned);
- }
+ if (len)
+ smd_copy_to_fifo(channel->tx_fifo + head, data, len, word_aligned);
- if (len != count) {
- smd_copy_to_fifo(channel->tx_fifo,
- data + len,
- count - len,
- word_aligned);
- }
+ if (len != count)
+ smd_copy_to_fifo(channel->tx_fifo, data + len, count - len, word_aligned);
head += count;
head &= (channel->fifo_size - 1);
@@ -667,16 +615,19 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len)
{
u32 hdr[5] = {len,};
int tlen = sizeof(hdr) + len;
- int ret;
+ int ret, length;
/* Word aligned channels only accept word size aligned data */
if (channel->rx_info_word != NULL && len % 4)
return -EINVAL;
+ length = qcom_smd_get_tx_avail(channel);
+
ret = mutex_lock_interruptible(&channel->tx_lock);
if (ret)
return ret;
+ length = qcom_smd_get_tx_avail(channel);
while (qcom_smd_get_tx_avail(channel) < tlen) {
if (channel->state != SMD_CHANNEL_OPENED) {
ret = -EPIPE;
@@ -696,9 +647,12 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len)
SET_TX_CHANNEL_INFO(channel, fTAIL, 0);
+ length = qcom_smd_get_tx_avail(channel);
qcom_smd_write_fifo(channel, hdr, sizeof(hdr));
qcom_smd_write_fifo(channel, data, len);
+ length = qcom_smd_get_tx_avail(channel);
+
SET_TX_CHANNEL_INFO(channel, fHEAD, 1);
/* Ensure ordering of channel info updates */
@@ -727,22 +681,29 @@ static struct qcom_smd_driver *to_smd_driver(struct device *dev)
static int qcom_smd_dev_match(struct device *dev, struct device_driver *drv)
{
+ struct qcom_smd_device *qsdev = to_smd_device(dev);
+ struct qcom_smd_driver *qsdrv = container_of(drv, struct qcom_smd_driver, driver);
+ const struct qcom_smd_id *match = qsdrv->smd_match_table;
+ const char *name = qsdev->channel->name;
+
+ if (match) {
+ while (match->name[0]) {
+ if (!strcmp(match->name, name))
+ return 1;
+ match++;
+ }
+ }
+
return of_driver_match_device(dev, drv);
}
/*
- * Probe the smd client.
- *
- * The remote side have indicated that it want the channel to be opened, so
- * complete the state handshake and probe our client driver.
+ * Helper for opening a channel
*/
-static int qcom_smd_dev_probe(struct device *dev)
+static int qcom_smd_channel_open(struct qcom_smd_channel *channel,
+ qcom_smd_cb_t cb)
{
- struct qcom_smd_device *qsdev = to_smd_device(dev);
- struct qcom_smd_driver *qsdrv = to_smd_driver(dev);
- struct qcom_smd_channel *channel = qsdev->channel;
size_t bb_size;
- int ret;
/*
* Packets are maximum 4k, but reduce if the fifo is smaller
@@ -752,12 +713,44 @@ static int qcom_smd_dev_probe(struct device *dev)
if (!channel->bounce_buffer)
return -ENOMEM;
- channel->cb = qsdrv->callback;
-
+ qcom_smd_channel_set_callback(channel, cb);
qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING);
-
qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED);
+ return 0;
+}
+
+/*
+ * Helper for closing and resetting a channel
+ */
+static void qcom_smd_channel_close(struct qcom_smd_channel *channel)
+{
+ qcom_smd_channel_set_callback(channel, NULL);
+
+ kfree(channel->bounce_buffer);
+ channel->bounce_buffer = NULL;
+
+ qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
+ qcom_smd_channel_reset(channel);
+}
+
+/*
+ * Probe the smd client.
+ *
+ * The remote side have indicated that it want the channel to be opened, so
+ * complete the state handshake and probe our client driver.
+ */
+static int qcom_smd_dev_probe(struct device *dev)
+{
+ struct qcom_smd_device *qsdev = to_smd_device(dev);
+ struct qcom_smd_driver *qsdrv = to_smd_driver(dev);
+ struct qcom_smd_channel *channel = qsdev->channel;
+ int ret;
+
+ ret = qcom_smd_channel_open(channel, qsdrv->callback);
+ if (ret)
+ return ret;
+
ret = qsdrv->probe(qsdev);
if (ret)
goto err;
@@ -769,11 +762,7 @@ static int qcom_smd_dev_probe(struct device *dev)
err:
dev_err(&qsdev->dev, "probe failed\n");
- channel->cb = NULL;
- kfree(channel->bounce_buffer);
- channel->bounce_buffer = NULL;
-
- qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
+ qcom_smd_channel_close(channel);
return ret;
}
@@ -788,16 +777,15 @@ static int qcom_smd_dev_remove(struct device *dev)
struct qcom_smd_device *qsdev = to_smd_device(dev);
struct qcom_smd_driver *qsdrv = to_smd_driver(dev);
struct qcom_smd_channel *channel = qsdev->channel;
- unsigned long flags;
+ struct qcom_smd_channel *tmp;
+ struct qcom_smd_channel *ch;
qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSING);
/*
* Make sure we don't race with the code receiving data.
*/
- spin_lock_irqsave(&channel->recv_lock, flags);
- channel->cb = NULL;
- spin_unlock_irqrestore(&channel->recv_lock, flags);
+ qcom_smd_channel_set_callback(channel, NULL);
/* Wake up any sleepers in qcom_smd_send() */
wake_up_interruptible(&channel->fblockread_event);
@@ -810,15 +798,14 @@ static int qcom_smd_dev_remove(struct device *dev)
qsdrv->remove(qsdev);
/*
- * The client is now gone, cleanup and reset the channel state.
+ * The client is now gone, close and release all channels associated
+ * with this sdev
*/
- channel->qsdev = NULL;
- kfree(channel->bounce_buffer);
- channel->bounce_buffer = NULL;
-
- qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED);
-
- qcom_smd_channel_reset(channel);
+ list_for_each_entry_safe(ch, tmp, &channel->dev_list, dev_list) {
+ qcom_smd_channel_close(ch);
+ list_del(&ch->dev_list);
+ ch->qsdev = NULL;
+ }
return 0;
}
@@ -880,19 +867,17 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel)
if (channel->qsdev)
return -EEXIST;
- node = qcom_smd_match_channel(edge->of_node, channel->name);
- if (!node) {
- dev_dbg(smd->dev, "no match for '%s'\n", channel->name);
- return -ENXIO;
- }
-
dev_dbg(smd->dev, "registering '%s'\n", channel->name);
qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL);
if (!qsdev)
return -ENOMEM;
- dev_set_name(&qsdev->dev, "%s.%s", edge->of_node->name, node->name);
+ node = qcom_smd_match_channel(edge->of_node, channel->name);
+ dev_set_name(&qsdev->dev, "%s.%s",
+ edge->of_node->name,
+ node ? node->name : channel->name);
+
qsdev->dev.parent = smd->dev;
qsdev->dev.bus = &qcom_smd_bus;
qsdev->dev.release = qcom_smd_release_device;
@@ -948,6 +933,76 @@ void qcom_smd_driver_unregister(struct qcom_smd_driver *qsdrv)
}
EXPORT_SYMBOL(qcom_smd_driver_unregister);
+static struct qcom_smd_channel *
+qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name)
+{
+ struct qcom_smd_channel *channel;
+ struct qcom_smd_channel *ret = NULL;
+ unsigned state;
+
+ read_lock(&edge->channels_lock);
+ list_for_each_entry(channel, &edge->channels, list) {
+ if (strcmp(channel->name, name))
+ continue;
+
+ state = GET_RX_CHANNEL_INFO(channel, state);
+ if (state != SMD_CHANNEL_OPENING &&
+ state != SMD_CHANNEL_OPENED)
+ continue;
+
+ ret = channel;
+ break;
+ }
+ read_unlock(&edge->channels_lock);
+
+ return ret;
+}
+
+/**
+ * qcom_smd_open_channel() - claim additional channels on the same edge
+ * @sdev: smd_device handle
+ * @name: channel name
+ * @cb: callback method to use for incoming data
+ *
+ * Returns a channel handle on success, or -EPROBE_DEFER if the channel isn't
+ * ready.
+ */
+struct qcom_smd_channel *qcom_smd_open_channel(struct qcom_smd_device *sdev,
+ const char *name,
+ qcom_smd_cb_t cb)
+{
+ struct qcom_smd_channel *channel;
+ struct qcom_smd_edge *edge = sdev->channel->edge;
+ int ret;
+
+ /* Wait up to HZ for the channel to appear */
+ ret = wait_event_interruptible_timeout(edge->new_channel_event,
+ (channel = qcom_smd_find_channel(edge, name)) != NULL,
+ HZ);
+ if (!ret)
+ return ERR_PTR(-ETIMEDOUT);
+
+ if (channel->state != SMD_CHANNEL_CLOSED) {
+ dev_err(&sdev->dev, "channel %s is busy\n", channel->name);
+ return ERR_PTR(-EBUSY);
+ }
+
+ channel->qsdev = sdev;
+ ret = qcom_smd_channel_open(channel, cb);
+ if (ret) {
+ channel->qsdev = NULL;
+ return ERR_PTR(ret);
+ }
+
+ /*
+ * Append the list of channel to the channels associated with the sdev
+ */
+ list_add_tail(&channel->dev_list, &sdev->channel->dev_list);
+
+ return channel;
+}
+EXPORT_SYMBOL(qcom_smd_open_channel);
+
/*
* Allocate the qcom_smd_channel object for a newly found smd channel,
* retrieving and validating the smem items involved.
@@ -969,6 +1024,7 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed
if (!channel)
return ERR_PTR(-ENOMEM);
+ INIT_LIST_HEAD(&channel->dev_list);
channel->edge = edge;
channel->name = devm_kstrdup(smd->dev, name, GFP_KERNEL);
if (!channel->name)
@@ -1008,7 +1064,7 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed
/* The channel consist of a rx and tx fifo of equal size */
fifo_size /= 2;
- dev_dbg(smd->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
+ dev_err(smd->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
name, info_size, fifo_size);
channel->tx_fifo = fifo_base;
@@ -1031,8 +1087,9 @@ free_name_and_channel:
* qcom_smd_create_channel() to create representations of these and add
* them to the edge's list of channels.
*/
-static void qcom_discover_channels(struct qcom_smd_edge *edge)
+static void qcom_channel_scan_worker(struct work_struct *work)
{
+ struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work);
struct qcom_smd_alloc_entry *alloc_tbl;
struct qcom_smd_alloc_entry *entry;
struct qcom_smd_channel *channel;
@@ -1076,16 +1133,18 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge)
if (IS_ERR(channel))
continue;
- spin_lock_irqsave(&edge->channels_lock, flags);
+ write_lock_irqsave(&edge->channels_lock, flags);
list_add(&channel->list, &edge->channels);
- spin_unlock_irqrestore(&edge->channels_lock, flags);
+ write_unlock_irqrestore(&edge->channels_lock, flags);
dev_dbg(smd->dev, "new channel found: '%s'\n", channel->name);
set_bit(i, edge->allocated[tbl]);
+
+ wake_up_interruptible(&edge->new_channel_event);
}
}
- schedule_work(&edge->work);
+ schedule_work(&edge->state_work);
}
/*
@@ -1093,29 +1152,22 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge)
* then scans all registered channels for state changes that should be handled
* by creating or destroying smd client devices for the registered channels.
*
- * LOCKING: edge->channels_lock is not needed to be held during the traversal
- * of the channels list as it's done synchronously with the only writer.
+ * LOCKING: edge->channels_lock only needs to cover the list operations, as the
+ * worker is killed before any channels are deallocated
*/
static void qcom_channel_state_worker(struct work_struct *work)
{
struct qcom_smd_channel *channel;
struct qcom_smd_edge *edge = container_of(work,
struct qcom_smd_edge,
- work);
+ state_work);
unsigned remote_state;
/*
- * Rescan smem if we have reason to belive that there are new channels.
- */
- if (edge->need_rescan) {
- edge->need_rescan = false;
- qcom_discover_channels(edge);
- }
-
- /*
* Register a device for any closed channel where the remote processor
* is showing interest in opening the channel.
*/
+ read_lock(&edge->channels_lock);
list_for_each_entry(channel, &edge->channels, list) {
if (channel->state != SMD_CHANNEL_CLOSED)
continue;
@@ -1125,7 +1177,9 @@ static void qcom_channel_state_worker(struct work_struct *work)
remote_state != SMD_CHANNEL_OPENED)
continue;
+ read_unlock(&edge->channels_lock);
qcom_smd_create_device(channel);
+ read_lock(&edge->channels_lock);
}
/*
@@ -1142,8 +1196,11 @@ static void qcom_channel_state_worker(struct work_struct *work)
remote_state == SMD_CHANNEL_OPENED)
continue;
+ read_unlock(&edge->channels_lock);
qcom_smd_destroy_device(channel);
+ read_lock(&edge->channels_lock);
}
+ read_unlock(&edge->channels_lock);
}
/*
@@ -1159,9 +1216,10 @@ static int qcom_smd_parse_edge(struct device *dev,
int ret;
INIT_LIST_HEAD(&edge->channels);
- spin_lock_init(&edge->channels_lock);
+ rwlock_init(&edge->channels_lock);
- INIT_WORK(&edge->work, qcom_channel_state_worker);
+ INIT_WORK(&edge->scan_work, qcom_channel_scan_worker);
+ INIT_WORK(&edge->state_work, qcom_channel_state_worker);
edge->of_node = of_node_get(node);
@@ -1190,7 +1248,11 @@ static int qcom_smd_parse_edge(struct device *dev,
edge->remote_pid = QCOM_SMEM_HOST_ANY;
key = "qcom,remote-pid";
- of_property_read_u32(node, key, &edge->remote_pid);
+ ret = of_property_read_u32(node, key, &edge->remote_pid);
+ if (ret) {
+ dev_err(dev, "edge missing %s property\n", key);
+ return -EINVAL;
+ }
syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
if (!syscon_np) {
@@ -1244,13 +1306,13 @@ static int qcom_smd_probe(struct platform_device *pdev)
for_each_available_child_of_node(pdev->dev.of_node, node) {
edge = &smd->edges[i++];
edge->smd = smd;
+ init_waitqueue_head(&edge->new_channel_event);
ret = qcom_smd_parse_edge(&pdev->dev, node, edge);
if (ret)
continue;
- edge->need_rescan = true;
- schedule_work(&edge->work);
+ schedule_work(&edge->scan_work);
}
platform_set_drvdata(pdev, smd);
@@ -1273,8 +1335,10 @@ static int qcom_smd_remove(struct platform_device *pdev)
edge = &smd->edges[i];
disable_irq(edge->irq);
- cancel_work_sync(&edge->work);
+ cancel_work_sync(&edge->scan_work);
+ cancel_work_sync(&edge->state_work);
+ /* No need to lock here, because the writer is gone */
list_for_each_entry(channel, &edge->channels, list) {
if (!channel->qsdev)
continue;
diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
index 52365188a1c2..7fddf3b491b6 100644
--- a/drivers/soc/qcom/smem.c
+++ b/drivers/soc/qcom/smem.c
@@ -20,6 +20,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>
+#include <linux/debugfs.h>
/*
* The Qualcomm shared memory system is a allocate only heap structure that
@@ -85,6 +86,8 @@
/* Max number of processors/hosts in a system */
#define SMEM_HOST_COUNT 9
+#define SMEM_HEAP_INFO 1
+
/**
* struct smem_proc_comm - proc_comm communication struct (legacy)
* @command: current command to be executed
@@ -238,6 +241,9 @@ struct qcom_smem {
struct smem_partition_header *partitions[SMEM_HOST_COUNT];
+ struct dentry *dent;
+ u32 version;
+
unsigned num_regions;
struct smem_region regions[0];
};
@@ -642,6 +648,104 @@ static int qcom_smem_count_mem_regions(struct platform_device *pdev)
return num_regions;
}
+static void smem_debug_read_mem(struct seq_file *s)
+{
+ u32 *info;
+ size_t size;
+ int ret, i;
+ long flags;
+
+ ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HEAP_INFO,
+ (void **)&info, &size);
+
+ if (ret < 0)
+ seq_printf(s, "Can't get global heap information pool\n");
+ else {
+ seq_printf(s, "global heap\n");
+ seq_printf(s, " initialized: %d offset: %08x avail: %08x\n",
+ info[0], info[1], info[2]);
+
+ for (i = 0; i < 512; i++) {
+ ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, i,
+ (void **)&info, &size);
+ if (ret < 0)
+ continue;
+
+ seq_printf(s, " [%d]: p: %p s: %li\n", i, info,
+ size);
+ }
+ }
+
+ seq_printf(s, "\nSecure partitions accessible from APPS:\n");
+
+ ret = hwspin_lock_timeout_irqsave(__smem->hwlock,
+ HWSPINLOCK_TIMEOUT,
+ &flags);
+
+ for (i = 0; i < SMEM_HOST_COUNT; i++) {
+ struct smem_partition_header *part_hdr = __smem->partitions[i];
+ void *p;
+
+ if (!part_hdr)
+ continue;
+
+ if (part_hdr->magic != SMEM_PART_MAGIC) {
+ seq_printf(s, " part[%d]: incorrect magic\n", i);
+ continue;
+ }
+
+ seq_printf(s, " part[%d]: (%d <-> %d) size: %d off: %08x\n",
+ i, part_hdr->host0, part_hdr->host1, part_hdr->size,
+ part_hdr->offset_free_uncached);
+
+ p = (void *)part_hdr + sizeof(*part_hdr);
+ while (p < (void *)part_hdr + part_hdr->offset_free_uncached) {
+ struct smem_private_entry *entry = p;
+
+ seq_printf(s,
+ " [%d]: %s size: %d pd: %d\n",
+ entry->item,
+ (entry->canary == SMEM_PRIVATE_CANARY) ?
+ "valid" : "invalid",
+ entry->size,
+ entry->padding_data);
+
+ p += sizeof(*entry) + entry->padding_hdr + entry->size;
+ }
+ }
+
+ hwspin_unlock_irqrestore(__smem->hwlock, &flags);
+}
+
+static void smem_debug_read_version(struct seq_file *s)
+{
+ seq_printf(s, "SBL version: %08x\n", __smem->version >> 16);
+}
+
+static int debugfs_show(struct seq_file *s, void *data)
+{
+ void (*show)(struct seq_file *) = s->private;
+
+ show(s);
+
+ return 0;
+}
+
+static int smem_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_show, inode->i_private);
+}
+
+
+static const struct file_operations smem_debug_ops = {
+ .open = smem_debug_open,
+ .release = single_release,
+ .read = seq_read,
+ .llseek = seq_lseek,
+};
+
+
+
static int qcom_smem_probe(struct platform_device *pdev)
{
struct smem_header *header;
@@ -652,7 +756,6 @@ static int qcom_smem_probe(struct platform_device *pdev)
size_t array_size;
int num_regions = 0;
int hwlock_id;
- u32 version;
int ret;
int i;
@@ -703,9 +806,9 @@ static int qcom_smem_probe(struct platform_device *pdev)
return -EINVAL;
}
- version = qcom_smem_get_sbl_version(smem);
- if (version >> 16 != SMEM_EXPECTED_VERSION) {
- dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version);
+ smem->version = qcom_smem_get_sbl_version(smem);
+ if (smem->version >> 16 != SMEM_EXPECTED_VERSION) {
+ dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", smem->version);
return -EINVAL;
}
@@ -725,6 +828,17 @@ static int qcom_smem_probe(struct platform_device *pdev)
__smem = smem;
+ /* setup debugfs information */
+ __smem->dent = debugfs_create_dir("smem", 0);
+ if (IS_ERR(__smem->dent))
+ dev_info(smem->dev, "unable to create debugfs\n");
+
+ if (!debugfs_create_file("mem", 0444, __smem->dent, smem_debug_read_mem, &smem_debug_ops))
+ dev_err(smem->dev, "couldnt create mem file\n");
+ if (!debugfs_create_file("version", 0444, __smem->dent, smem_debug_read_version,
+ &smem_debug_ops))
+ dev_err(smem->dev, "couldnt create mem file\n");
+
return 0;
}
diff --git a/include/linux/regulator/qcom_smd-regulator.h b/include/linux/regulator/qcom_smd-regulator.h
new file mode 100644
index 000000000000..16029448d6b6
--- /dev/null
+++ b/include/linux/regulator/qcom_smd-regulator.h
@@ -0,0 +1,30 @@
+/* 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.
+ */
+#ifndef __QCOM_SMD_REGULATOR_H_
+#define __QCOM_SMD_REGULATOR_H_
+
+#if IS_ENABLED(CONFIG_REGULATOR_QCOM_SMD_RPM)
+int qcom_rpm_set_floor(struct regulator *regulator, int floor);
+int qcom_rpm_set_corner(struct regulator *regulator, int corner);
+#else
+static inline int qcom_rpm_set_floor(struct regulator *regulator, int floor)
+{
+ return -EINVAL;
+}
+
+static inline int qcom_rpm_set_corner(struct regulator *regulator, int corner)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif
diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h
index 9c4e1384f636..1c457a8dd5a6 100644
--- a/include/linux/remoteproc.h
+++ b/include/linux/remoteproc.h
@@ -365,6 +365,8 @@ enum rproc_state {
/**
* enum rproc_crash_type - remote processor crash types
* @RPROC_MMUFAULT: iommu fault
+ * @RPROC_WATCHDOG: watchdog bite
+ * @RPROC_FATAL_ERROR fatal error
*
* Each element of the enum is used as an array index. So that, the value of
* the elements should be always something sane.
@@ -373,6 +375,8 @@ enum rproc_state {
*/
enum rproc_crash_type {
RPROC_MMUFAULT,
+ RPROC_WATCHDOG,
+ RPROC_FATAL_ERROR,
};
/**
diff --git a/include/linux/soc/qcom/smd.h b/include/linux/soc/qcom/smd.h
index d7e50aa6a4ac..8a955e00357f 100644
--- a/include/linux/soc/qcom/smd.h
+++ b/include/linux/soc/qcom/smd.h
@@ -5,8 +5,64 @@
#include <linux/mod_devicetable.h>
struct qcom_smd;
-struct qcom_smd_channel;
struct qcom_smd_lookup;
+struct qcom_smd_device;
+
+typedef int (*qcom_smd_cb_t)(struct qcom_smd_device *, const void *, size_t);
+
+/*
+ * SMD channel states.
+ */
+enum smd_channel_state {
+ SMD_CHANNEL_CLOSED,
+ SMD_CHANNEL_OPENING,
+ SMD_CHANNEL_OPENED,
+ SMD_CHANNEL_FLUSHING,
+ SMD_CHANNEL_CLOSING,
+ SMD_CHANNEL_RESET,
+ SMD_CHANNEL_RESET_OPENING
+};
+
+struct qcom_smd_channel {
+ struct qcom_smd_edge *edge;
+
+ struct qcom_smd_device *qsdev;
+
+ char *name;
+ enum smd_channel_state state;
+ enum smd_channel_state remote_state;
+
+ struct smd_channel_info *tx_info;
+ struct smd_channel_info *rx_info;
+
+ struct smd_channel_info_word *tx_info_word;
+ struct smd_channel_info_word *rx_info_word;
+
+ struct mutex tx_lock;
+ wait_queue_head_t fblockread_event;
+
+ void *tx_fifo;
+ void *rx_fifo;
+ int fifo_size;
+
+ void *bounce_buffer;
+ qcom_smd_cb_t cb;
+
+ spinlock_t recv_lock;
+
+ int pkt_size;
+
+ struct list_head list;
+ struct list_head dev_list;
+};
+
+/**
+ * struct qcom_smd_id - struct used for matching a smd device
+ * @name: name of the channel
+ */
+struct qcom_smd_id {
+ char name[20];
+};
/**
* struct qcom_smd_device - smd device struct
@@ -21,6 +77,7 @@ struct qcom_smd_device {
/**
* struct qcom_smd_driver - smd driver struct
* @driver: underlying device driver
+ * @smd_match_table: static channel match table
* @probe: invoked when the smd channel is found
* @remove: invoked when the smd channel is closed
* @callback: invoked when an inbound message is received on the channel,
@@ -29,9 +86,11 @@ struct qcom_smd_device {
*/
struct qcom_smd_driver {
struct device_driver driver;
+ const struct qcom_smd_id *smd_match_table;
+
int (*probe)(struct qcom_smd_device *dev);
void (*remove)(struct qcom_smd_device *dev);
- int (*callback)(struct qcom_smd_device *, const void *, size_t);
+ qcom_smd_cb_t callback;
};
int qcom_smd_driver_register(struct qcom_smd_driver *drv);
@@ -43,4 +102,8 @@ void qcom_smd_driver_unregister(struct qcom_smd_driver *drv);
int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len);
+struct qcom_smd_channel *qcom_smd_open_channel(struct qcom_smd_device *sdev,
+ const char *name,
+ qcom_smd_cb_t cb);
+
#endif