mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 08:44:41 +00:00 
			
		
		
		
	remoteproc updates for v4.8
Introduce remoteproc driver for controlling the modem/DSP Hexagon CPU found in a multitude of Qualcomm platform. Also cleans up a race condition/potential leak during registration of remoteprocs and includes devicetree bindings in the MAINTAINERS entry. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJXmQY5AAoJEAsfOT8Nma3FzGMP/jq1S/DLm2vRIw547Ev04LQ0 4suaKkovLMhGp2xsGsz50gxpV1YBo4MTI3DYXBP/kr4U3aCHIw+1mXwiJ1pWsv30 4MWVheD5hLeyK0JMFiEPwmI22zlX81YA0WPOimu82WUAyqQ86Zcix1KPVfHbq5SP 80aiX64BZu0PXUFAhhBUaW4qy/2nj4gcvN6Ied8LCwDTmCIBSE2LsjxaxKvxLIgA OMWpMGfz6jIleC4kqFDs2qL4rhQh/5cRm+QOntlehOsJfrp7QN1yaJ0WahsQWRjk AQjweQn7zgbzj5VPcpqCx9K4NhOa1Y/vZM/i3EPlYUKwV3cJnk5BgsyB1zyXd2s5 +Ul42y8pPTeBgwKisT6BhWLQE13uYViuM4N+rqCX1sH4c5wTzScCDz5tC300Plj9 omXiJmLRnAJ6soQGEnoYl6S7+e873hqOuHFvqCfkxFWVOlJ5blODOFAHXC/8Gwch cKG2P0Hpp8KZNljgBhIvU7AHRWOfeojZvezx5epb902rZKLXrvgKOOkbAwklr9wu lAKdbS/lunPIA5Lbom4db7zOAPa8QWNHpHGsCc4MDMY6hcoGA5FZALIdEdqimnro rrG52apH2EKyWPpsLtJ0vglpmFUz1euabQCGkR1roYvDjXxN8SpC08mcX92r4XOM cFxetFZDcF4f98PG96AY =XlWX -----END PGP SIGNATURE----- Merge tag 'rproc-v4.8' of git://github.com/andersson/remoteproc Pull remoteproc updates from Bjorn Andersson: "Introduce remoteproc driver for controlling the modem/DSP Hexagon CPU found in a multitude of Qualcomm platform. Also cleans up a race condition/potential leak during registration of remoteprocs and includes devicetree bindings in the MAINTAINERS entry" * tag 'rproc-v4.8' of git://github.com/andersson/remoteproc: remoteproc: qcom: hexagon: Clean up mpss validation remoteproc: qcom: remove redundant dev_err call in q6v5_init_mem() remoteproc: qcom: Driver for the self-authenticating Hexagon v5 dt-binding: remoteproc: Introduce Hexagon loader binding remoteproc: Fix potential race condition in rproc_add MAINTAINERS: Add file patterns for remoteproc device tree bindings
This commit is contained in:
		
						commit
						ce8c891c34
					
				
					 8 changed files with 1264 additions and 7 deletions
				
			
		
							
								
								
									
										137
									
								
								Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								Documentation/devicetree/bindings/remoteproc/qcom,q6v5.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,137 @@ | |||
| Qualcomm Hexagon Peripheral Image Loader | ||||
| 
 | ||||
| This document defines the binding for a component that loads and boots firmware | ||||
| on the Qualcomm Hexagon core. | ||||
| 
 | ||||
| - compatible: | ||||
| 	Usage: required | ||||
| 	Value type: <string> | ||||
| 	Definition: must be one of: | ||||
| 		    "qcom,q6v5-pil" | ||||
| 
 | ||||
| - reg: | ||||
| 	Usage: required | ||||
| 	Value type: <prop-encoded-array> | ||||
| 	Definition: must specify the base address and size of the qdsp6 and | ||||
| 		    rmb register blocks | ||||
| 
 | ||||
| - reg-names: | ||||
| 	Usage: required | ||||
| 	Value type: <stringlist> | ||||
| 	Definition: must be "q6dsp" and "rmb" | ||||
| 
 | ||||
| - interrupts-extended: | ||||
| 	Usage: required | ||||
| 	Value type: <prop-encoded-array> | ||||
| 	Definition: must list the watchdog, fatal IRQs ready, handover and | ||||
| 		    stop-ack IRQs | ||||
| 
 | ||||
| - interrupt-names: | ||||
| 	Usage: required | ||||
| 	Value type: <stringlist> | ||||
| 	Definition: must be "wdog", "fatal", "ready", "handover", "stop-ack" | ||||
| 
 | ||||
| - clocks: | ||||
| 	Usage: required | ||||
| 	Value type: <phandle> | ||||
| 	Definition: reference to the iface, bus and mem clocks to be held on | ||||
| 		    behalf of the booting of the Hexagon core | ||||
| 
 | ||||
| - clock-names: | ||||
| 	Usage: required | ||||
| 	Value type: <stringlist> | ||||
| 	Definition: must be "iface", "bus", "mem" | ||||
| 
 | ||||
| - resets: | ||||
| 	Usage: required | ||||
| 	Value type: <phandle> | ||||
| 	Definition: reference to the reset-controller for the modem sub-system | ||||
| 
 | ||||
| - reset-names: | ||||
| 	Usage: required | ||||
| 	Value type: <stringlist> | ||||
| 	Definition: must be "mss_restart" | ||||
| 
 | ||||
| - cx-supply: | ||||
| - mss-supply: | ||||
| - mx-supply: | ||||
| - pll-supply: | ||||
| 	Usage: required | ||||
| 	Value type: <phandle> | ||||
| 	Definition: reference to the regulators to be held on behalf of the | ||||
| 		    booting of the Hexagon core | ||||
| 
 | ||||
| - qcom,smem-states: | ||||
| 	Usage: required | ||||
| 	Value type: <phandle> | ||||
| 	Definition: reference to the smem state for requesting the Hexagon to | ||||
| 		    shut down | ||||
| 
 | ||||
| - qcom,smem-state-names: | ||||
| 	Usage: required | ||||
| 	Value type: <stringlist> | ||||
| 	Definition: must be "stop" | ||||
| 
 | ||||
| - qcom,halt-regs: | ||||
| 	Usage: required | ||||
| 	Value type: <prop-encoded-array> | ||||
| 	Definition: a phandle reference to a syscon representing TCSR followed | ||||
| 		    by the three offsets within syscon for q6, modem and nc | ||||
| 		    halt registers. | ||||
| 
 | ||||
| = SUBNODES: | ||||
| The Hexagon node must contain two subnodes, named "mba" and "mpss" representing | ||||
| the memory regions used by the Hexagon firmware. Each sub-node must contain: | ||||
| 
 | ||||
| - memory-region: | ||||
| 	Usage: required | ||||
| 	Value type: <phandle> | ||||
| 	Definition: reference to the reserved-memory for the region | ||||
| 
 | ||||
| = EXAMPLE | ||||
| The following example describes the resources needed to boot control the | ||||
| Hexagon, as it is found on MSM8974 boards. | ||||
| 
 | ||||
| 	modem-rproc@fc880000 { | ||||
| 		compatible = "qcom,q6v5-pil"; | ||||
| 		reg = <0xfc880000 0x100>, | ||||
| 		      <0xfc820000 0x020>; | ||||
| 		reg-names = "qdsp6", "rmb"; | ||||
| 
 | ||||
| 		interrupts-extended = <&intc 0 24 1>, | ||||
| 				      <&modem_smp2p_in 0 0>, | ||||
| 				      <&modem_smp2p_in 1 0>, | ||||
| 				      <&modem_smp2p_in 2 0>, | ||||
| 				      <&modem_smp2p_in 3 0>; | ||||
| 		interrupt-names = "wdog", | ||||
| 				  "fatal", | ||||
| 				  "ready", | ||||
| 				  "handover", | ||||
| 				  "stop-ack"; | ||||
| 
 | ||||
| 		clocks = <&gcc GCC_MSS_Q6_BIMC_AXI_CLK>, | ||||
| 			 <&gcc GCC_MSS_CFG_AHB_CLK>, | ||||
| 			 <&gcc GCC_BOOT_ROM_AHB_CLK>; | ||||
| 		clock-names = "iface", "bus", "mem"; | ||||
| 
 | ||||
| 		qcom,halt-regs = <&tcsr_mutex_block 0x1180 0x1200 0x1280>; | ||||
| 
 | ||||
| 		resets = <&gcc GCC_MSS_RESTART>; | ||||
| 		reset-names = "mss_restart"; | ||||
| 
 | ||||
| 		cx-supply = <&pm8841_s2>; | ||||
| 		mss-supply = <&pm8841_s3>; | ||||
| 		mx-supply = <&pm8841_s1>; | ||||
| 		pll-supply = <&pm8941_l12>; | ||||
| 
 | ||||
| 		qcom,smem-states = <&modem_smp2p_out 0>; | ||||
| 		qcom,smem-state-names = "stop"; | ||||
| 
 | ||||
| 		mba { | ||||
| 			memory-region = <&mba_region>; | ||||
| 		}; | ||||
| 
 | ||||
| 		mpss { | ||||
| 			memory-region = <&mpss_region>; | ||||
| 		}; | ||||
| 	}; | ||||
|  | @ -9732,8 +9732,9 @@ M:	Bjorn Andersson <bjorn.andersson@linaro.org> | |||
| L:	linux-remoteproc@vger.kernel.org | ||||
| T:	git git://git.kernel.org/pub/scm/linux/kernel/git/ohad/remoteproc.git | ||||
| S:	Maintained | ||||
| F:	drivers/remoteproc/ | ||||
| F:	Documentation/devicetree/bindings/remoteproc/ | ||||
| F:	Documentation/remoteproc.txt | ||||
| F:	drivers/remoteproc/ | ||||
| F:	include/linux/remoteproc.h | ||||
| 
 | ||||
| REMOTE PROCESSOR MESSAGING (RPMSG) SUBSYSTEM | ||||
|  |  | |||
|  | @ -77,6 +77,20 @@ config DA8XX_REMOTEPROC | |||
| 	  It's safe to say n here if you're not interested in multimedia | ||||
| 	  offloading. | ||||
| 
 | ||||
| config QCOM_MDT_LOADER | ||||
| 	tristate | ||||
| 
 | ||||
| config QCOM_Q6V5_PIL | ||||
| 	tristate "Qualcomm Hexagon V5 Peripherial Image Loader" | ||||
| 	depends on OF && ARCH_QCOM | ||||
| 	depends on QCOM_SMEM | ||||
| 	select MFD_SYSCON | ||||
| 	select QCOM_MDT_LOADER | ||||
| 	select REMOTEPROC | ||||
| 	help | ||||
| 	  Say y here to support the Qualcomm Peripherial Image Loader for the | ||||
| 	  Hexagon V5 based remote processors. | ||||
| 
 | ||||
| config ST_REMOTEPROC | ||||
| 	tristate "ST remoteproc support" | ||||
| 	depends on ARCH_STI | ||||
|  |  | |||
|  | @ -11,4 +11,6 @@ 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_MDT_LOADER)		+= qcom_mdt_loader.o | ||||
| obj-$(CONFIG_QCOM_Q6V5_PIL)		+= qcom_q6v5_pil.o | ||||
| obj-$(CONFIG_ST_REMOTEPROC)		+= st_remoteproc.o | ||||
|  |  | |||
							
								
								
									
										179
									
								
								drivers/remoteproc/qcom_mdt_loader.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								drivers/remoteproc/qcom_mdt_loader.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | |||
| /*
 | ||||
|  * Qualcomm Peripheral Image Loader | ||||
|  * | ||||
|  * Copyright (C) 2016 Linaro Ltd | ||||
|  * Copyright (C) 2015 Sony Mobile Communications Inc | ||||
|  * 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/elf.h> | ||||
| #include <linux/firmware.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/remoteproc.h> | ||||
| #include <linux/slab.h> | ||||
| 
 | ||||
| #include "remoteproc_internal.h" | ||||
| #include "qcom_mdt_loader.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * qcom_mdt_find_rsc_table() - provide dummy resource table for remoteproc | ||||
|  * @rproc:	remoteproc handle | ||||
|  * @fw:		firmware header | ||||
|  * @tablesz:	outgoing size of the table | ||||
|  * | ||||
|  * Returns a dummy table. | ||||
|  */ | ||||
| struct resource_table *qcom_mdt_find_rsc_table(struct rproc *rproc, | ||||
| 					       const struct firmware *fw, | ||||
| 					       int *tablesz) | ||||
| { | ||||
| 	static struct resource_table table = { .ver = 1, }; | ||||
| 
 | ||||
| 	*tablesz = sizeof(table); | ||||
| 	return &table; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(qcom_mdt_find_rsc_table); | ||||
| 
 | ||||
| /**
 | ||||
|  * qcom_mdt_parse() - extract useful parameters from the mdt header | ||||
|  * @fw:		firmware handle | ||||
|  * @fw_addr:	optional reference for base of the firmware's memory region | ||||
|  * @fw_size:	optional reference for size of the firmware's memory region | ||||
|  * @fw_relocate: optional reference for flagging if the firmware is relocatable | ||||
|  * | ||||
|  * Returns 0 on success, negative errno otherwise. | ||||
|  */ | ||||
| int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr, | ||||
| 		   size_t *fw_size, bool *fw_relocate) | ||||
| { | ||||
| 	const struct elf32_phdr *phdrs; | ||||
| 	const struct elf32_phdr *phdr; | ||||
| 	const struct elf32_hdr *ehdr; | ||||
| 	phys_addr_t min_addr = (phys_addr_t)ULLONG_MAX; | ||||
| 	phys_addr_t max_addr = 0; | ||||
| 	bool relocate = false; | ||||
| 	int i; | ||||
| 
 | ||||
| 	ehdr = (struct elf32_hdr *)fw->data; | ||||
| 	phdrs = (struct elf32_phdr *)(ehdr + 1); | ||||
| 
 | ||||
| 	for (i = 0; i < ehdr->e_phnum; i++) { | ||||
| 		phdr = &phdrs[i]; | ||||
| 
 | ||||
| 		if (phdr->p_type != PT_LOAD) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (!phdr->p_memsz) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (phdr->p_flags & QCOM_MDT_RELOCATABLE) | ||||
| 			relocate = true; | ||||
| 
 | ||||
| 		if (phdr->p_paddr < min_addr) | ||||
| 			min_addr = phdr->p_paddr; | ||||
| 
 | ||||
| 		if (phdr->p_paddr + phdr->p_memsz > max_addr) | ||||
| 			max_addr = ALIGN(phdr->p_paddr + phdr->p_memsz, SZ_4K); | ||||
| 	} | ||||
| 
 | ||||
| 	if (fw_addr) | ||||
| 		*fw_addr = min_addr; | ||||
| 	if (fw_size) | ||||
| 		*fw_size = max_addr - min_addr; | ||||
| 	if (fw_relocate) | ||||
| 		*fw_relocate = relocate; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(qcom_mdt_parse); | ||||
| 
 | ||||
| /**
 | ||||
|  * qcom_mdt_load() - load the firmware which header is defined in fw | ||||
|  * @rproc:	rproc handle | ||||
|  * @fw:		frimware object for the header | ||||
|  * @firmware:	filename of the firmware, for building .bXX names | ||||
|  * | ||||
|  * Returns 0 on success, negative errno otherwise. | ||||
|  */ | ||||
| int qcom_mdt_load(struct rproc *rproc, | ||||
| 		  const struct firmware *fw, | ||||
| 		  const char *firmware) | ||||
| { | ||||
| 	const struct elf32_phdr *phdrs; | ||||
| 	const struct elf32_phdr *phdr; | ||||
| 	const struct elf32_hdr *ehdr; | ||||
| 	size_t fw_name_len; | ||||
| 	char *fw_name; | ||||
| 	void *ptr; | ||||
| 	int ret; | ||||
| 	int i; | ||||
| 
 | ||||
| 	ehdr = (struct elf32_hdr *)fw->data; | ||||
| 	phdrs = (struct elf32_phdr *)(ehdr + 1); | ||||
| 
 | ||||
| 	fw_name_len = strlen(firmware); | ||||
| 	if (fw_name_len <= 4) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	fw_name = kstrdup(firmware, GFP_KERNEL); | ||||
| 	if (!fw_name) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	for (i = 0; i < ehdr->e_phnum; i++) { | ||||
| 		phdr = &phdrs[i]; | ||||
| 
 | ||||
| 		if (phdr->p_type != PT_LOAD) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (!phdr->p_memsz) | ||||
| 			continue; | ||||
| 
 | ||||
| 		ptr = rproc_da_to_va(rproc, phdr->p_paddr, phdr->p_memsz); | ||||
| 		if (!ptr) { | ||||
| 			dev_err(&rproc->dev, "segment outside memory range\n"); | ||||
| 			ret = -EINVAL; | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		if (phdr->p_filesz) { | ||||
| 			sprintf(fw_name + fw_name_len - 3, "b%02d", i); | ||||
| 			ret = request_firmware(&fw, fw_name, &rproc->dev); | ||||
| 			if (ret) { | ||||
| 				dev_err(&rproc->dev, "failed to load %s\n", | ||||
| 					fw_name); | ||||
| 				break; | ||||
| 			} | ||||
| 
 | ||||
| 			memcpy(ptr, fw->data, fw->size); | ||||
| 
 | ||||
| 			release_firmware(fw); | ||||
| 		} | ||||
| 
 | ||||
| 		if (phdr->p_memsz > phdr->p_filesz) | ||||
| 			memset(ptr + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); | ||||
| 	} | ||||
| 
 | ||||
| 	kfree(fw_name); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| EXPORT_SYMBOL_GPL(qcom_mdt_load); | ||||
| 
 | ||||
| MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); | ||||
| MODULE_LICENSE("GPL v2"); | ||||
							
								
								
									
										13
									
								
								drivers/remoteproc/qcom_mdt_loader.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								drivers/remoteproc/qcom_mdt_loader.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| #ifndef __QCOM_MDT_LOADER_H__ | ||||
| #define __QCOM_MDT_LOADER_H__ | ||||
| 
 | ||||
| #define QCOM_MDT_TYPE_MASK	(7 << 24) | ||||
| #define QCOM_MDT_TYPE_HASH	(2 << 24) | ||||
| #define QCOM_MDT_RELOCATABLE	BIT(27) | ||||
| 
 | ||||
| struct resource_table * qcom_mdt_find_rsc_table(struct rproc *rproc, const struct firmware *fw, int *tablesz); | ||||
| int qcom_mdt_load(struct rproc *rproc, const struct firmware *fw, const char *fw_name); | ||||
| 
 | ||||
| int qcom_mdt_parse(const struct firmware *fw, phys_addr_t *fw_addr, size_t *fw_size, bool *fw_relocate); | ||||
| 
 | ||||
| #endif | ||||
							
								
								
									
										908
									
								
								drivers/remoteproc/qcom_q6v5_pil.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										908
									
								
								drivers/remoteproc/qcom_q6v5_pil.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,908 @@ | |||
| /*
 | ||||
|  * Qualcomm Peripheral Image Loader | ||||
|  * | ||||
|  * Copyright (C) 2016 Linaro Ltd. | ||||
|  * 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/clk.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/dma-mapping.h> | ||||
| #include <linux/interrupt.h> | ||||
| #include <linux/kernel.h> | ||||
| #include <linux/mfd/syscon.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/of_address.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/regmap.h> | ||||
| #include <linux/regulator/consumer.h> | ||||
| #include <linux/remoteproc.h> | ||||
| #include <linux/reset.h> | ||||
| #include <linux/soc/qcom/smem.h> | ||||
| #include <linux/soc/qcom/smem_state.h> | ||||
| 
 | ||||
| #include "remoteproc_internal.h" | ||||
| #include "qcom_mdt_loader.h" | ||||
| 
 | ||||
| #include <linux/qcom_scm.h> | ||||
| 
 | ||||
| #define MBA_FIRMWARE_NAME		"mba.b00" | ||||
| #define MPSS_FIRMWARE_NAME		"modem.mdt" | ||||
| 
 | ||||
| #define MPSS_CRASH_REASON_SMEM		421 | ||||
| 
 | ||||
| /* RMB Status Register Values */ | ||||
| #define RMB_PBL_SUCCESS			0x1 | ||||
| 
 | ||||
| #define RMB_MBA_XPU_UNLOCKED		0x1 | ||||
| #define RMB_MBA_XPU_UNLOCKED_SCRIBBLED	0x2 | ||||
| #define RMB_MBA_META_DATA_AUTH_SUCCESS	0x3 | ||||
| #define RMB_MBA_AUTH_COMPLETE		0x4 | ||||
| 
 | ||||
| /* PBL/MBA interface registers */ | ||||
| #define RMB_MBA_IMAGE_REG		0x00 | ||||
| #define RMB_PBL_STATUS_REG		0x04 | ||||
| #define RMB_MBA_COMMAND_REG		0x08 | ||||
| #define RMB_MBA_STATUS_REG		0x0C | ||||
| #define RMB_PMI_META_DATA_REG		0x10 | ||||
| #define RMB_PMI_CODE_START_REG		0x14 | ||||
| #define RMB_PMI_CODE_LENGTH_REG		0x18 | ||||
| 
 | ||||
| #define RMB_CMD_META_DATA_READY		0x1 | ||||
| #define RMB_CMD_LOAD_READY		0x2 | ||||
| 
 | ||||
| /* QDSP6SS Register Offsets */ | ||||
| #define QDSP6SS_RESET_REG		0x014 | ||||
| #define QDSP6SS_GFMUX_CTL_REG		0x020 | ||||
| #define QDSP6SS_PWR_CTL_REG		0x030 | ||||
| 
 | ||||
| /* AXI Halt Register Offsets */ | ||||
| #define AXI_HALTREQ_REG			0x0 | ||||
| #define AXI_HALTACK_REG			0x4 | ||||
| #define AXI_IDLE_REG			0x8 | ||||
| 
 | ||||
| #define HALT_ACK_TIMEOUT_MS		100 | ||||
| 
 | ||||
| /* QDSP6SS_RESET */ | ||||
| #define Q6SS_STOP_CORE			BIT(0) | ||||
| #define Q6SS_CORE_ARES			BIT(1) | ||||
| #define Q6SS_BUS_ARES_ENABLE		BIT(2) | ||||
| 
 | ||||
| /* QDSP6SS_GFMUX_CTL */ | ||||
| #define Q6SS_CLK_ENABLE			BIT(1) | ||||
| 
 | ||||
| /* 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) | ||||
| 
 | ||||
| struct q6v5 { | ||||
| 	struct device *dev; | ||||
| 	struct rproc *rproc; | ||||
| 
 | ||||
| 	void __iomem *reg_base; | ||||
| 	void __iomem *rmb_base; | ||||
| 
 | ||||
| 	struct regmap *halt_map; | ||||
| 	u32 halt_q6; | ||||
| 	u32 halt_modem; | ||||
| 	u32 halt_nc; | ||||
| 
 | ||||
| 	struct reset_control *mss_restart; | ||||
| 
 | ||||
| 	struct qcom_smem_state *state; | ||||
| 	unsigned stop_bit; | ||||
| 
 | ||||
| 	struct regulator_bulk_data supply[4]; | ||||
| 
 | ||||
| 	struct clk *ahb_clk; | ||||
| 	struct clk *axi_clk; | ||||
| 	struct clk *rom_clk; | ||||
| 
 | ||||
| 	struct completion start_done; | ||||
| 	struct completion stop_done; | ||||
| 	bool running; | ||||
| 
 | ||||
| 	phys_addr_t mba_phys; | ||||
| 	void *mba_region; | ||||
| 	size_t mba_size; | ||||
| 
 | ||||
| 	phys_addr_t mpss_phys; | ||||
| 	phys_addr_t mpss_reloc; | ||||
| 	void *mpss_region; | ||||
| 	size_t mpss_size; | ||||
| }; | ||||
| 
 | ||||
| enum { | ||||
| 	Q6V5_SUPPLY_CX, | ||||
| 	Q6V5_SUPPLY_MX, | ||||
| 	Q6V5_SUPPLY_MSS, | ||||
| 	Q6V5_SUPPLY_PLL, | ||||
| }; | ||||
| 
 | ||||
| static int q6v5_regulator_init(struct q6v5 *qproc) | ||||
| { | ||||
| 	int ret; | ||||
| 
 | ||||
| 	qproc->supply[Q6V5_SUPPLY_CX].supply = "cx"; | ||||
| 	qproc->supply[Q6V5_SUPPLY_MX].supply = "mx"; | ||||
| 	qproc->supply[Q6V5_SUPPLY_MSS].supply = "mss"; | ||||
| 	qproc->supply[Q6V5_SUPPLY_PLL].supply = "pll"; | ||||
| 
 | ||||
| 	ret = devm_regulator_bulk_get(qproc->dev, | ||||
| 				      ARRAY_SIZE(qproc->supply), qproc->supply); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(qproc->dev, "failed to get supplies\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	regulator_set_load(qproc->supply[Q6V5_SUPPLY_CX].consumer, 100000); | ||||
| 	regulator_set_load(qproc->supply[Q6V5_SUPPLY_MSS].consumer, 100000); | ||||
| 	regulator_set_load(qproc->supply[Q6V5_SUPPLY_PLL].consumer, 10000); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_regulator_enable(struct q6v5 *qproc) | ||||
| { | ||||
| 	struct regulator *mss = qproc->supply[Q6V5_SUPPLY_MSS].consumer; | ||||
| 	struct regulator *mx = qproc->supply[Q6V5_SUPPLY_MX].consumer; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* TODO: Q6V5_SUPPLY_CX is supposed to be set to super-turbo here */ | ||||
| 
 | ||||
| 	ret = regulator_set_voltage(mx, 1050000, INT_MAX); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	regulator_set_voltage(mss, 1000000, 1150000); | ||||
| 
 | ||||
| 	return regulator_bulk_enable(ARRAY_SIZE(qproc->supply), qproc->supply); | ||||
| } | ||||
| 
 | ||||
| static void q6v5_regulator_disable(struct q6v5 *qproc) | ||||
| { | ||||
| 	struct regulator *mss = qproc->supply[Q6V5_SUPPLY_MSS].consumer; | ||||
| 	struct regulator *mx = qproc->supply[Q6V5_SUPPLY_MX].consumer; | ||||
| 
 | ||||
| 	/* TODO: Q6V5_SUPPLY_CX corner votes should be released */ | ||||
| 
 | ||||
| 	regulator_bulk_disable(ARRAY_SIZE(qproc->supply), qproc->supply); | ||||
| 	regulator_set_voltage(mx, 0, INT_MAX); | ||||
| 	regulator_set_voltage(mss, 0, 1150000); | ||||
| } | ||||
| 
 | ||||
| static int q6v5_load(struct rproc *rproc, const struct firmware *fw) | ||||
| { | ||||
| 	struct q6v5 *qproc = rproc->priv; | ||||
| 
 | ||||
| 	memcpy(qproc->mba_region, fw->data, fw->size); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct rproc_fw_ops q6v5_fw_ops = { | ||||
| 	.find_rsc_table = qcom_mdt_find_rsc_table, | ||||
| 	.load = q6v5_load, | ||||
| }; | ||||
| 
 | ||||
| static int q6v5_rmb_pbl_wait(struct q6v5 *qproc, int ms) | ||||
| { | ||||
| 	unsigned long timeout; | ||||
| 	s32 val; | ||||
| 
 | ||||
| 	timeout = jiffies + msecs_to_jiffies(ms); | ||||
| 	for (;;) { | ||||
| 		val = readl(qproc->rmb_base + RMB_PBL_STATUS_REG); | ||||
| 		if (val) | ||||
| 			break; | ||||
| 
 | ||||
| 		if (time_after(jiffies, timeout)) | ||||
| 			return -ETIMEDOUT; | ||||
| 
 | ||||
| 		msleep(1); | ||||
| 	} | ||||
| 
 | ||||
| 	return val; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_rmb_mba_wait(struct q6v5 *qproc, u32 status, int ms) | ||||
| { | ||||
| 
 | ||||
| 	unsigned long timeout; | ||||
| 	s32 val; | ||||
| 
 | ||||
| 	timeout = jiffies + msecs_to_jiffies(ms); | ||||
| 	for (;;) { | ||||
| 		val = readl(qproc->rmb_base + RMB_MBA_STATUS_REG); | ||||
| 		if (val < 0) | ||||
| 			break; | ||||
| 
 | ||||
| 		if (!status && val) | ||||
| 			break; | ||||
| 		else if (status && val == status) | ||||
| 			break; | ||||
| 
 | ||||
| 		if (time_after(jiffies, timeout)) | ||||
| 			return -ETIMEDOUT; | ||||
| 
 | ||||
| 		msleep(1); | ||||
| 	} | ||||
| 
 | ||||
| 	return val; | ||||
| } | ||||
| 
 | ||||
| static int q6v5proc_reset(struct q6v5 *qproc) | ||||
| { | ||||
| 	u32 val; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Assert resets, stop core */ | ||||
| 	val = readl(qproc->reg_base + QDSP6SS_RESET_REG); | ||||
| 	val |= (Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE); | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_RESET_REG); | ||||
| 
 | ||||
| 	/* Enable power block headswitch, and wait for it to stabilize */ | ||||
| 	val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 	val |= QDSS_BHS_ON | QDSS_LDO_BYP; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 	udelay(1); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Turn on memories. L2 banks should be done individually | ||||
| 	 * to minimize inrush current. | ||||
| 	 */ | ||||
| 	val = readl(qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 	val |= Q6SS_SLP_RET_N | Q6SS_L2TAG_SLP_NRET_N | | ||||
| 		Q6SS_ETB_SLP_NRET_N | Q6SS_L2DATA_STBY_N; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 	val |= Q6SS_L2DATA_SLP_NRET_N_2; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 	val |= Q6SS_L2DATA_SLP_NRET_N_1; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 	val |= Q6SS_L2DATA_SLP_NRET_N_0; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 
 | ||||
| 	/* Remove IO clamp */ | ||||
| 	val &= ~Q6SS_CLAMP_IO; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_PWR_CTL_REG); | ||||
| 
 | ||||
| 	/* Bring core out of reset */ | ||||
| 	val = readl(qproc->reg_base + QDSP6SS_RESET_REG); | ||||
| 	val &= ~Q6SS_CORE_ARES; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_RESET_REG); | ||||
| 
 | ||||
| 	/* Turn on core clock */ | ||||
| 	val = readl(qproc->reg_base + QDSP6SS_GFMUX_CTL_REG); | ||||
| 	val |= Q6SS_CLK_ENABLE; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_GFMUX_CTL_REG); | ||||
| 
 | ||||
| 	/* Start core execution */ | ||||
| 	val = readl(qproc->reg_base + QDSP6SS_RESET_REG); | ||||
| 	val &= ~Q6SS_STOP_CORE; | ||||
| 	writel(val, qproc->reg_base + QDSP6SS_RESET_REG); | ||||
| 
 | ||||
| 	/* Wait for PBL status */ | ||||
| 	ret = q6v5_rmb_pbl_wait(qproc, 1000); | ||||
| 	if (ret == -ETIMEDOUT) { | ||||
| 		dev_err(qproc->dev, "PBL boot timed out\n"); | ||||
| 	} else if (ret != RMB_PBL_SUCCESS) { | ||||
| 		dev_err(qproc->dev, "PBL returned unexpected status %d\n", ret); | ||||
| 		ret = -EINVAL; | ||||
| 	} else { | ||||
| 		ret = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void q6v5proc_halt_axi_port(struct q6v5 *qproc, | ||||
| 				   struct regmap *halt_map, | ||||
| 				   u32 offset) | ||||
| { | ||||
| 	unsigned long timeout; | ||||
| 	unsigned int val; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Check if we're already idle */ | ||||
| 	ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); | ||||
| 	if (!ret && val) | ||||
| 		return; | ||||
| 
 | ||||
| 	/* Assert halt request */ | ||||
| 	regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1); | ||||
| 
 | ||||
| 	/* Wait for halt */ | ||||
| 	timeout = jiffies + msecs_to_jiffies(HALT_ACK_TIMEOUT_MS); | ||||
| 	for (;;) { | ||||
| 		ret = regmap_read(halt_map, offset + AXI_HALTACK_REG, &val); | ||||
| 		if (ret || val || time_after(jiffies, timeout)) | ||||
| 			break; | ||||
| 
 | ||||
| 		msleep(1); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); | ||||
| 	if (ret || !val) | ||||
| 		dev_err(qproc->dev, "port failed halt\n"); | ||||
| 
 | ||||
| 	/* Clear halt request (port will remain halted until reset) */ | ||||
| 	regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); | ||||
| } | ||||
| 
 | ||||
| static int q6v5_mpss_init_image(struct q6v5 *qproc, const struct firmware *fw) | ||||
| { | ||||
| 	DEFINE_DMA_ATTRS(attrs); | ||||
| 	dma_addr_t phys; | ||||
| 	void *ptr; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	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 mdt buffer\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 
 | ||||
| 	memcpy(ptr, fw->data, fw->size); | ||||
| 
 | ||||
| 	writel(phys, qproc->rmb_base + RMB_PMI_META_DATA_REG); | ||||
| 	writel(RMB_CMD_META_DATA_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); | ||||
| 
 | ||||
| 	ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_META_DATA_AUTH_SUCCESS, 1000); | ||||
| 	if (ret == -ETIMEDOUT) | ||||
| 		dev_err(qproc->dev, "MPSS header authentication timed out\n"); | ||||
| 	else if (ret < 0) | ||||
| 		dev_err(qproc->dev, "MPSS header authentication failed: %d\n", ret); | ||||
| 
 | ||||
| 	dma_free_attrs(qproc->dev, fw->size, ptr, phys, &attrs); | ||||
| 
 | ||||
| 	return ret < 0 ? ret : 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_mpss_validate(struct q6v5 *qproc, const struct firmware *fw) | ||||
| { | ||||
| 	const struct elf32_phdr *phdrs; | ||||
| 	const struct elf32_phdr *phdr; | ||||
| 	struct elf32_hdr *ehdr; | ||||
| 	phys_addr_t boot_addr; | ||||
| 	phys_addr_t fw_addr; | ||||
| 	bool relocate; | ||||
| 	size_t size; | ||||
| 	int ret; | ||||
| 	int i; | ||||
| 
 | ||||
| 	ret = qcom_mdt_parse(fw, &fw_addr, NULL, &relocate); | ||||
| 	if (ret) { | ||||
| 		dev_err(qproc->dev, "failed to parse mdt header\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	if (relocate) | ||||
| 		boot_addr = qproc->mpss_phys; | ||||
| 	else | ||||
| 		boot_addr = fw_addr; | ||||
| 
 | ||||
| 	ehdr = (struct elf32_hdr *)fw->data; | ||||
| 	phdrs = (struct elf32_phdr *)(ehdr + 1); | ||||
| 	for (i = 0; i < ehdr->e_phnum; i++, phdr++) { | ||||
| 		phdr = &phdrs[i]; | ||||
| 
 | ||||
| 		if (phdr->p_type != PT_LOAD) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if ((phdr->p_flags & QCOM_MDT_TYPE_MASK) == QCOM_MDT_TYPE_HASH) | ||||
| 			continue; | ||||
| 
 | ||||
| 		if (!phdr->p_memsz) | ||||
| 			continue; | ||||
| 
 | ||||
| 		size = readl(qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); | ||||
| 		if (!size) { | ||||
| 			writel(boot_addr, qproc->rmb_base + RMB_PMI_CODE_START_REG); | ||||
| 			writel(RMB_CMD_LOAD_READY, qproc->rmb_base + RMB_MBA_COMMAND_REG); | ||||
| 		} | ||||
| 
 | ||||
| 		size += phdr->p_memsz; | ||||
| 		writel(size, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = q6v5_rmb_mba_wait(qproc, RMB_MBA_AUTH_COMPLETE, 10000); | ||||
| 	if (ret == -ETIMEDOUT) | ||||
| 		dev_err(qproc->dev, "MPSS authentication timed out\n"); | ||||
| 	else if (ret < 0) | ||||
| 		dev_err(qproc->dev, "MPSS authentication failed: %d\n", ret); | ||||
| 
 | ||||
| 	return ret < 0 ? ret : 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_mpss_load(struct q6v5 *qproc) | ||||
| { | ||||
| 	const struct firmware *fw; | ||||
| 	phys_addr_t fw_addr; | ||||
| 	bool relocate; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = request_firmware(&fw, MPSS_FIRMWARE_NAME, qproc->dev); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(qproc->dev, "unable to load " MPSS_FIRMWARE_NAME "\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = qcom_mdt_parse(fw, &fw_addr, NULL, &relocate); | ||||
| 	if (ret) { | ||||
| 		dev_err(qproc->dev, "failed to parse mdt header\n"); | ||||
| 		goto release_firmware; | ||||
| 	} | ||||
| 
 | ||||
| 	if (relocate) | ||||
| 		qproc->mpss_reloc = fw_addr; | ||||
| 
 | ||||
| 	/* Initialize the RMB validator */ | ||||
| 	writel(0, qproc->rmb_base + RMB_PMI_CODE_LENGTH_REG); | ||||
| 
 | ||||
| 	ret = q6v5_mpss_init_image(qproc, fw); | ||||
| 	if (ret) | ||||
| 		goto release_firmware; | ||||
| 
 | ||||
| 	ret = qcom_mdt_load(qproc->rproc, fw, MPSS_FIRMWARE_NAME); | ||||
| 	if (ret) | ||||
| 		goto release_firmware; | ||||
| 
 | ||||
| 	ret = q6v5_mpss_validate(qproc, fw); | ||||
| 
 | ||||
| release_firmware: | ||||
| 	release_firmware(fw); | ||||
| 
 | ||||
| 	return ret < 0 ? ret : 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_start(struct rproc *rproc) | ||||
| { | ||||
| 	struct q6v5 *qproc = (struct q6v5 *)rproc->priv; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = q6v5_regulator_enable(qproc); | ||||
| 	if (ret) { | ||||
| 		dev_err(qproc->dev, "failed to enable supplies\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(qproc->mba_phys, qproc->rmb_base + RMB_MBA_IMAGE_REG); | ||||
| 
 | ||||
| 	ret = q6v5proc_reset(qproc); | ||||
| 	if (ret) | ||||
| 		goto halt_axi_ports; | ||||
| 
 | ||||
| 	ret = q6v5_rmb_mba_wait(qproc, 0, 5000); | ||||
| 	if (ret == -ETIMEDOUT) { | ||||
| 		dev_err(qproc->dev, "MBA boot timed out\n"); | ||||
| 		goto halt_axi_ports; | ||||
| 	} else if (ret != RMB_MBA_XPU_UNLOCKED && | ||||
| 		   ret != RMB_MBA_XPU_UNLOCKED_SCRIBBLED) { | ||||
| 		dev_err(qproc->dev, "MBA returned unexpected status %d\n", ret); | ||||
| 		ret = -EINVAL; | ||||
| 		goto halt_axi_ports; | ||||
| 	} | ||||
| 
 | ||||
| 	dev_info(qproc->dev, "MBA booted, loading mpss\n"); | ||||
| 
 | ||||
| 	ret = q6v5_mpss_load(qproc); | ||||
| 	if (ret) | ||||
| 		goto halt_axi_ports; | ||||
| 
 | ||||
| 	ret = wait_for_completion_timeout(&qproc->start_done, | ||||
| 					  msecs_to_jiffies(5000)); | ||||
| 	if (ret == 0) { | ||||
| 		dev_err(qproc->dev, "start timed out\n"); | ||||
| 		ret = -ETIMEDOUT; | ||||
| 		goto halt_axi_ports; | ||||
| 	} | ||||
| 
 | ||||
| 	qproc->running = true; | ||||
| 
 | ||||
| 	/* TODO: All done, release the handover resources */ | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| halt_axi_ports: | ||||
| 	q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6); | ||||
| 	q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem); | ||||
| 	q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); | ||||
| 
 | ||||
| 	clk_disable_unprepare(qproc->rom_clk); | ||||
| 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: | ||||
| 	q6v5_regulator_disable(qproc); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_stop(struct rproc *rproc) | ||||
| { | ||||
| 	struct q6v5 *qproc = (struct q6v5 *)rproc->priv; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	qproc->running = false; | ||||
| 
 | ||||
| 	qcom_smem_state_update_bits(qproc->state, | ||||
| 				    BIT(qproc->stop_bit), BIT(qproc->stop_bit)); | ||||
| 
 | ||||
| 	ret = wait_for_completion_timeout(&qproc->stop_done, | ||||
| 					  msecs_to_jiffies(5000)); | ||||
| 	if (ret == 0) | ||||
| 		dev_err(qproc->dev, "timed out on wait\n"); | ||||
| 
 | ||||
| 	qcom_smem_state_update_bits(qproc->state, BIT(qproc->stop_bit), 0); | ||||
| 
 | ||||
| 	q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_q6); | ||||
| 	q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_modem); | ||||
| 	q6v5proc_halt_axi_port(qproc, qproc->halt_map, qproc->halt_nc); | ||||
| 
 | ||||
| 	reset_control_assert(qproc->mss_restart); | ||||
| 	clk_disable_unprepare(qproc->rom_clk); | ||||
| 	clk_disable_unprepare(qproc->axi_clk); | ||||
| 	clk_disable_unprepare(qproc->ahb_clk); | ||||
| 	q6v5_regulator_disable(qproc); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void *q6v5_da_to_va(struct rproc *rproc, u64 da, int len) | ||||
| { | ||||
| 	struct q6v5 *qproc = rproc->priv; | ||||
| 	int offset; | ||||
| 
 | ||||
| 	offset = da - qproc->mpss_reloc; | ||||
| 	if (offset < 0 || offset + len > qproc->mpss_size) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	return qproc->mpss_region + offset; | ||||
| } | ||||
| 
 | ||||
| static const struct rproc_ops q6v5_ops = { | ||||
| 	.start = q6v5_start, | ||||
| 	.stop = q6v5_stop, | ||||
| 	.da_to_va = q6v5_da_to_va, | ||||
| }; | ||||
| 
 | ||||
| static irqreturn_t q6v5_wdog_interrupt(int irq, void *dev) | ||||
| { | ||||
| 	struct q6v5 *qproc = dev; | ||||
| 	size_t len; | ||||
| 	char *msg; | ||||
| 
 | ||||
| 	/* Sometimes the stop triggers a watchdog rather than a stop-ack */ | ||||
| 	if (!qproc->running) { | ||||
| 		complete(&qproc->stop_done); | ||||
| 		return IRQ_HANDLED; | ||||
| 	} | ||||
| 
 | ||||
| 	msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, MPSS_CRASH_REASON_SMEM, &len); | ||||
| 	if (!IS_ERR(msg) && len > 0 && msg[0]) | ||||
| 		dev_err(qproc->dev, "watchdog received: %s\n", msg); | ||||
| 	else | ||||
| 		dev_err(qproc->dev, "watchdog without message\n"); | ||||
| 
 | ||||
| 	rproc_report_crash(qproc->rproc, RPROC_WATCHDOG); | ||||
| 
 | ||||
| 	if (!IS_ERR(msg)) | ||||
| 		msg[0] = '\0'; | ||||
| 
 | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t q6v5_fatal_interrupt(int irq, void *dev) | ||||
| { | ||||
| 	struct q6v5 *qproc = dev; | ||||
| 	size_t len; | ||||
| 	char *msg; | ||||
| 
 | ||||
| 	msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, MPSS_CRASH_REASON_SMEM, &len); | ||||
| 	if (!IS_ERR(msg) && len > 0 && msg[0]) | ||||
| 		dev_err(qproc->dev, "fatal error received: %s\n", msg); | ||||
| 	else | ||||
| 		dev_err(qproc->dev, "fatal error without message\n"); | ||||
| 
 | ||||
| 	rproc_report_crash(qproc->rproc, RPROC_FATAL_ERROR); | ||||
| 
 | ||||
| 	if (!IS_ERR(msg)) | ||||
| 		msg[0] = '\0'; | ||||
| 
 | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t q6v5_handover_interrupt(int irq, void *dev) | ||||
| { | ||||
| 	struct q6v5 *qproc = dev; | ||||
| 
 | ||||
| 	complete(&qproc->start_done); | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t q6v5_stop_ack_interrupt(int irq, void *dev) | ||||
| { | ||||
| 	struct q6v5 *qproc = dev; | ||||
| 
 | ||||
| 	complete(&qproc->stop_done); | ||||
| 	return IRQ_HANDLED; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_init_mem(struct q6v5 *qproc, struct platform_device *pdev) | ||||
| { | ||||
| 	struct of_phandle_args args; | ||||
| 	struct resource *res; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "qdsp6"); | ||||
| 	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, "rmb"); | ||||
| 	qproc->rmb_base = devm_ioremap_resource(&pdev->dev, res); | ||||
| 	if (IS_ERR(qproc->rmb_base)) | ||||
| 		return PTR_ERR(qproc->rmb_base); | ||||
| 
 | ||||
| 	ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node, | ||||
| 					       "qcom,halt-regs", 3, 0, &args); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(&pdev->dev, "failed to parse qcom,halt-regs\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	qproc->halt_map = syscon_node_to_regmap(args.np); | ||||
| 	of_node_put(args.np); | ||||
| 	if (IS_ERR(qproc->halt_map)) | ||||
| 		return PTR_ERR(qproc->halt_map); | ||||
| 
 | ||||
| 	qproc->halt_q6 = args.args[0]; | ||||
| 	qproc->halt_modem = args.args[1]; | ||||
| 	qproc->halt_nc = args.args[2]; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_init_clocks(struct q6v5 *qproc) | ||||
| { | ||||
| 	qproc->ahb_clk = devm_clk_get(qproc->dev, "iface"); | ||||
| 	if (IS_ERR(qproc->ahb_clk)) { | ||||
| 		dev_err(qproc->dev, "failed to get iface clock\n"); | ||||
| 		return PTR_ERR(qproc->ahb_clk); | ||||
| 	} | ||||
| 
 | ||||
| 	qproc->axi_clk = devm_clk_get(qproc->dev, "bus"); | ||||
| 	if (IS_ERR(qproc->axi_clk)) { | ||||
| 		dev_err(qproc->dev, "failed to get bus clock\n"); | ||||
| 		return PTR_ERR(qproc->axi_clk); | ||||
| 	} | ||||
| 
 | ||||
| 	qproc->rom_clk = devm_clk_get(qproc->dev, "mem"); | ||||
| 	if (IS_ERR(qproc->rom_clk)) { | ||||
| 		dev_err(qproc->dev, "failed to get mem clock\n"); | ||||
| 		return PTR_ERR(qproc->rom_clk); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_init_reset(struct q6v5 *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 q6v5_request_irq(struct q6v5 *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, | ||||
| 					"q6v5", qproc); | ||||
| 	if (ret) | ||||
| 		dev_err(&pdev->dev, "request %s IRQ failed\n", name); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_alloc_memory_region(struct q6v5 *qproc) | ||||
| { | ||||
| 	struct device_node *child; | ||||
| 	struct device_node *node; | ||||
| 	struct resource r; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	child = of_get_child_by_name(qproc->dev->of_node, "mba"); | ||||
| 	node = of_parse_phandle(child, "memory-region", 0); | ||||
| 	ret = of_address_to_resource(node, 0, &r); | ||||
| 	if (ret) { | ||||
| 		dev_err(qproc->dev, "unable to resolve mba region\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	qproc->mba_phys = r.start; | ||||
| 	qproc->mba_size = resource_size(&r); | ||||
| 	qproc->mba_region = devm_ioremap_wc(qproc->dev, qproc->mba_phys, qproc->mba_size); | ||||
| 	if (!qproc->mba_region) { | ||||
| 		dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n", | ||||
| 			&r.start, qproc->mba_size); | ||||
| 		return -EBUSY; | ||||
| 	} | ||||
| 
 | ||||
| 	child = of_get_child_by_name(qproc->dev->of_node, "mpss"); | ||||
| 	node = of_parse_phandle(child, "memory-region", 0); | ||||
| 	ret = of_address_to_resource(node, 0, &r); | ||||
| 	if (ret) { | ||||
| 		dev_err(qproc->dev, "unable to resolve mpss region\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	qproc->mpss_phys = qproc->mpss_reloc = r.start; | ||||
| 	qproc->mpss_size = resource_size(&r); | ||||
| 	qproc->mpss_region = devm_ioremap_wc(qproc->dev, qproc->mpss_phys, qproc->mpss_size); | ||||
| 	if (!qproc->mpss_region) { | ||||
| 		dev_err(qproc->dev, "unable to map memory region: %pa+%zx\n", | ||||
| 			&r.start, qproc->mpss_size); | ||||
| 		return -EBUSY; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_probe(struct platform_device *pdev) | ||||
| { | ||||
| 	struct q6v5 *qproc; | ||||
| 	struct rproc *rproc; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	rproc = rproc_alloc(&pdev->dev, pdev->name, &q6v5_ops, | ||||
| 			    MBA_FIRMWARE_NAME, sizeof(*qproc)); | ||||
| 	if (!rproc) { | ||||
| 		dev_err(&pdev->dev, "failed to allocate rproc\n"); | ||||
| 		return -ENOMEM; | ||||
| 	} | ||||
| 
 | ||||
| 	rproc->fw_ops = &q6v5_fw_ops; | ||||
| 
 | ||||
| 	qproc = (struct q6v5 *)rproc->priv; | ||||
| 	qproc->dev = &pdev->dev; | ||||
| 	qproc->rproc = rproc; | ||||
| 	platform_set_drvdata(pdev, qproc); | ||||
| 
 | ||||
| 	init_completion(&qproc->start_done); | ||||
| 	init_completion(&qproc->stop_done); | ||||
| 
 | ||||
| 	ret = q6v5_init_mem(qproc, pdev); | ||||
| 	if (ret) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_alloc_memory_region(qproc); | ||||
| 	if (ret) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_init_clocks(qproc); | ||||
| 	if (ret) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_regulator_init(qproc); | ||||
| 	if (ret) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_init_reset(qproc); | ||||
| 	if (ret) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_request_irq(qproc, pdev, "wdog", q6v5_wdog_interrupt); | ||||
| 	if (ret < 0) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_request_irq(qproc, pdev, "fatal", q6v5_fatal_interrupt); | ||||
| 	if (ret < 0) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_request_irq(qproc, pdev, "handover", q6v5_handover_interrupt); | ||||
| 	if (ret < 0) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = q6v5_request_irq(qproc, pdev, "stop-ack", q6v5_stop_ack_interrupt); | ||||
| 	if (ret < 0) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	qproc->state = qcom_smem_state_get(&pdev->dev, "stop", &qproc->stop_bit); | ||||
| 	if (IS_ERR(qproc->state)) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	ret = rproc_add(rproc); | ||||
| 	if (ret) | ||||
| 		goto free_rproc; | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| free_rproc: | ||||
| 	rproc_put(rproc); | ||||
| 
 | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int q6v5_remove(struct platform_device *pdev) | ||||
| { | ||||
| 	struct q6v5 *qproc = platform_get_drvdata(pdev); | ||||
| 
 | ||||
| 	rproc_del(qproc->rproc); | ||||
| 	rproc_put(qproc->rproc); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct of_device_id q6v5_of_match[] = { | ||||
| 	{ .compatible = "qcom,q6v5-pil", }, | ||||
| 	{ }, | ||||
| }; | ||||
| 
 | ||||
| static struct platform_driver q6v5_driver = { | ||||
| 	.probe = q6v5_probe, | ||||
| 	.remove = q6v5_remove, | ||||
| 	.driver = { | ||||
| 		.name = "qcom-q6v5-pil", | ||||
| 		.of_match_table = q6v5_of_match, | ||||
| 	}, | ||||
| }; | ||||
| module_platform_driver(q6v5_driver); | ||||
| 
 | ||||
| MODULE_DESCRIPTION("Peripheral Image Loader for Hexagon"); | ||||
| MODULE_LICENSE("GPL v2"); | ||||
|  | @ -1264,11 +1264,6 @@ int rproc_add(struct rproc *rproc) | |||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	/* expose to rproc_get_by_phandle users */ | ||||
| 	mutex_lock(&rproc_list_mutex); | ||||
| 	list_add(&rproc->node, &rproc_list); | ||||
| 	mutex_unlock(&rproc_list_mutex); | ||||
| 
 | ||||
| 	dev_info(dev, "%s is available\n", rproc->name); | ||||
| 
 | ||||
| 	dev_info(dev, "Note: remoteproc is still under development and considered experimental.\n"); | ||||
|  | @ -1276,8 +1271,16 @@ int rproc_add(struct rproc *rproc) | |||
| 
 | ||||
| 	/* create debugfs entries */ | ||||
| 	rproc_create_debug_dir(rproc); | ||||
| 	ret = rproc_add_virtio_devices(rproc); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	return rproc_add_virtio_devices(rproc); | ||||
| 	/* expose to rproc_get_by_phandle users */ | ||||
| 	mutex_lock(&rproc_list_mutex); | ||||
| 	list_add(&rproc->node, &rproc_list); | ||||
| 	mutex_unlock(&rproc_list_mutex); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| EXPORT_SYMBOL(rproc_add); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Linus Torvalds
						Linus Torvalds