diff options
author | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2015-11-18 10:28:39 +0000 |
---|---|---|
committer | Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 2015-11-18 10:28:39 +0000 |
commit | afc14404aa958a99e74f43c83a09642d3a49d0cf (patch) | |
tree | 95d8d735a0a0ecbae0dc48c02dd6cb5a9f8179c4 | |
parent | 23a0fedb1db59a6af0a7d550c5b4b06eead3e553 (diff) | |
parent | 12e0bb703bbd96559f4557b5464d6aca176fe6ec (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.txt | 105 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt | 51 | ||||
-rw-r--r-- | arch/arm64/configs/qcom_defconfig | 274 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 15 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpio/gpio-qcom-smp2p.c | 591 | ||||
-rw-r--r-- | drivers/gpio/gpio-qcom-smsm.c | 328 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 12 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/qcom_smd-regulator.c | 116 | ||||
-rw-r--r-- | drivers/remoteproc/Kconfig | 16 | ||||
-rw-r--r-- | drivers/remoteproc/Makefile | 2 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_q6v5_pil.c | 1075 | ||||
-rw-r--r-- | drivers/remoteproc/qcom_tz_pil.c | 719 | ||||
-rw-r--r-- | drivers/remoteproc/remoteproc_core.c | 44 | ||||
-rw-r--r-- | drivers/soc/qcom/smd-rpm.c | 3 | ||||
-rw-r--r-- | drivers/soc/qcom/smd.c | 396 | ||||
-rw-r--r-- | drivers/soc/qcom/smem.c | 122 | ||||
-rw-r--r-- | include/linux/regulator/qcom_smd-regulator.h | 30 | ||||
-rw-r--r-- | include/linux/remoteproc.h | 4 | ||||
-rw-r--r-- | include/linux/soc/qcom/smd.h | 67 |
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 |