Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a6293c3 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'rproc-v4.13' of git://github.com/andersson/remoteproc

Pull remoteproc updates from Bjorn Andersson:
 "This introduces the Keystone 2 DSP driver and refactors the start/stop
  code in recovery. The Davinci DSP driver gets a few fixes and the
  Kconfig gets cleaned up"

* tag 'rproc-v4.13' of git://github.com/andersson/remoteproc:
  remoteproc/keystone: Fix circular dependencies for ARM configs
  remoteproc: Drop redundant REMOTEPROC dependency from driver Kconfigs
  remoteproc: Drop VIRTUALIZATION dependency from REMOTEPROC
  remoteproc/keystone: Ensure the DSPs are in reset in probe
  remoteproc/keystone: Add a remoteproc driver for Keystone 2 DSPs
  dt-bindings: remoteproc: Add Keystone DSP remoteproc binding
  remoteproc/davinci: fix unbalanced reset between start and stop ops
  remoteproc/davinci: simplify the reset function
  remoteproc/davinci: Update Kconfig to depend on DMA_CMA
  remoteproc: fix spelling mistake: "Resouce" -> "Resource"
  remoteproc: Modify recovery path to use rproc_{start,stop}()
  remoteproc: Introduce rproc_{start,stop}() functions
parents 426b8eeb b0af7b7d
Loading
Loading
Loading
Loading
+133 −0
Original line number Diff line number Diff line
TI Keystone DSP devices
=======================

The TI Keystone 2 family of SoCs usually have one or more (upto 8) TI DSP Core
sub-systems that are used to offload some of the processor-intensive tasks or
algorithms, for achieving various system level goals.

These processor sub-systems usually contain additional sub-modules like L1
and/or L2 caches/SRAMs, an Interrupt Controller, an external memory controller,
a dedicated local power/sleep controller etc. The DSP processor core in
Keystone 2 SoCs is usually a TMS320C66x CorePac processor.

DSP Device Node:
================
Each DSP Core sub-system is represented as a single DT node, and should also
have an alias with the stem 'rproc' defined. Each node has a number of required
or optional properties that enable the OS running on the host processor (ARM
CorePac) to perform the device management of the remote processor and to
communicate with the remote processor.

Required properties:
--------------------
The following are the mandatory properties:

- compatible:		Should be one of the following,
			    "ti,k2hk-dsp" for DSPs on Keystone 2 66AK2H/K SoCs
			    "ti,k2l-dsp" for DSPs on Keystone 2 66AK2L SoCs
			    "ti,k2e-dsp" for DSPs on Keystone 2 66AK2E SoCs

- reg:			Should contain an entry for each value in 'reg-names'.
			Each entry should have the memory region's start address
			and the size of the region, the representation matching
			the parent node's '#address-cells' and '#size-cells' values.

- reg-names:		Should contain strings with the following names, each
			representing a specific internal memory region, and
			should be defined in this order,
			     "l2sram", "l1pram", "l1dram"

- clocks: 		Should contain the device's input clock, and should be
			defined as per the bindings in,
			Documentation/devicetree/bindings/clock/keystone-gate.txt

- ti,syscon-dev:	Should be a pair of the phandle to the Keystone Device
			State Control node, and the register offset of the DSP
			boot address register within that node's address space.

- resets:		Should contain the phandle to the reset controller node
			managing the resets for this device, and a reset
			specifier. Please refer to the following reset bindings
			for the reset argument specifier as per SoC,
			Documentation/devicetree/bindings/reset/ti-syscon-reset.txt
			    for 66AK2HK/66AK2L/66AK2E SoCs

- interrupt-parent:	Should contain a phandle to the Keystone 2 IRQ controller
			IP node that is used by the ARM CorePac processor to
			receive interrupts from the DSP remote processors. See
			Documentation/devicetree/bindings/interrupt-controller/ti,keystone-irq.txt
			for details.

- interrupts: 		Should contain an entry for each value in 'interrupt-names'.
			Each entry should have the interrupt source number used by
			the remote processor to the host processor. The values should
			follow the interrupt-specifier format as dictated by the
			'interrupt-parent' node. The purpose of each is as per the
			description in the 'interrupt-names' property.

- interrupt-names:	Should contain strings with the following names, each
			representing a specific interrupt,
			    "vring" - interrupt for virtio based IPC
			    "exception" - interrupt for exception notification

- kick-gpios: 		Should specify the gpio device needed for the virtio IPC
			stack. This will be used to interrupt the remote processor.
			The gpio device to be used is as per the bindings in,
			Documentation/devicetree/bindings/gpio/gpio-dsp-keystone.txt

Optional properties:
--------------------

- memory-region:	phandle to the reserved memory node to be associated
			with the remoteproc device. The reserved memory node
			can be a CMA memory node, and should be defined as
			per the bindings in
			Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt


Example:
--------
	/* 66AK2H/K DSP aliases */
	aliases {
		rproc0 = &dsp0;
		rproc1 = &dsp1;
		rproc2 = &dsp2;
		rproc3 = &dsp3;
		rproc4 = &dsp4;
		rproc5 = &dsp5;
		rproc6 = &dsp6;
		rproc7 = &dsp7;
	};

	/* 66AK2H/K DSP memory node */
	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;

		dsp_common_memory: dsp-common-memory@81f800000 {
			compatible = "shared-dma-pool";
			reg = <0x00000008 0x1f800000 0x00000000 0x800000>;
			reusable;
		};
	};

	/* 66AK2H/K DSP node */
	soc {
		dsp0: dsp@10800000 {
			compatible = "ti,k2hk-dsp";
			reg = <0x10800000 0x00100000>,
			      <0x10e00000 0x00008000>,
			      <0x10f00000 0x00008000>;
			reg-names = "l2sram", "l1pram", "l1dram";
			clocks = <&clkgem0>;
			ti,syscon-dev = <&devctrl 0x40>;
			resets = <&pscrst 0>;
			interrupt-parent = <&kirq0>;
			interrupts = <0 8>;
			interrupt-names = "vring", "exception";
			kick-gpios = <&dspgpio0 27 0>;
			memory-region = <&dsp_common_memory>;
		};

	};
+12 −10
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@ config REMOTEPROC
	select CRC32
	select FW_LOADER
	select VIRTIO
	select VIRTUALIZATION
	help
	  Support for remote processors (such as DSP coprocessors). These
	  are mainly used on embedded systems.
@@ -18,7 +17,6 @@ config OMAP_REMOTEPROC
	depends on HAS_DMA
	depends on ARCH_OMAP4 || SOC_OMAP5
	depends on OMAP_IOMMU
	depends on REMOTEPROC
	select MAILBOX
	select OMAP2PLUS_MBOX
	select RPMSG_VIRTIO
@@ -38,7 +36,6 @@ config OMAP_REMOTEPROC
config WKUP_M3_RPROC
	tristate "AMx3xx Wakeup M3 remoteproc support"
	depends on SOC_AM33XX || SOC_AM43XX
	depends on REMOTEPROC
	help
	  Say y here to support Wakeup M3 remote processor on TI AM33xx
	  and AM43xx family of SoCs.
@@ -51,8 +48,7 @@ config WKUP_M3_RPROC
config DA8XX_REMOTEPROC
	tristate "DA8xx/OMAP-L13x remoteproc support"
	depends on ARCH_DAVINCI_DA8XX
	depends on REMOTEPROC
	select CMA if MMU
	depends on DMA_CMA
	select RPMSG_VIRTIO
	help
	  Say y here to support DA8xx/OMAP-L13x remote processors via the
@@ -71,10 +67,20 @@ config DA8XX_REMOTEPROC
	  It's safe to say n here if you're not interested in multimedia
	  offloading.

config KEYSTONE_REMOTEPROC
	tristate "Keystone Remoteproc support"
	depends on ARCH_KEYSTONE
	select RPMSG_VIRTIO
	help
	  Say Y here here to support Keystone remote processors (DSP)
	  via the remote processor framework.

	  It's safe to say N here if you're not interested in the Keystone
	  DSPs or just want to use a bare minimum kernel.

config QCOM_ADSP_PIL
	tristate "Qualcomm ADSP Peripheral Image Loader"
	depends on OF && ARCH_QCOM
	depends on REMOTEPROC
	depends on QCOM_SMEM
	depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
	select MFD_SYSCON
@@ -92,7 +98,6 @@ config QCOM_Q6V5_PIL
	tristate "Qualcomm Hexagon V5 Peripherial Image Loader"
	depends on OF && ARCH_QCOM
	depends on QCOM_SMEM
	depends on REMOTEPROC
	depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
	select MFD_SYSCON
	select QCOM_RPROC_COMMON
@@ -106,7 +111,6 @@ config QCOM_WCNSS_PIL
	depends on OF && ARCH_QCOM
	depends on RPMSG_QCOM_SMD || (COMPILE_TEST && RPMSG_QCOM_SMD=n)
	depends on QCOM_SMEM
	depends on REMOTEPROC
	select QCOM_MDT_LOADER
	select QCOM_RPROC_COMMON
	select QCOM_SCM
@@ -117,7 +121,6 @@ config QCOM_WCNSS_PIL
config ST_REMOTEPROC
	tristate "ST remoteproc support"
	depends on ARCH_STI
	depends on REMOTEPROC
	select MAILBOX
	select STI_MBOX
	select RPMSG_VIRTIO
@@ -128,7 +131,6 @@ config ST_REMOTEPROC

config ST_SLIM_REMOTEPROC
	tristate
	depends on REMOTEPROC

endif # REMOTEPROC

+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ remoteproc-y += remoteproc_elf_loader.o
obj-$(CONFIG_OMAP_REMOTEPROC)		+= omap_remoteproc.o
obj-$(CONFIG_WKUP_M3_RPROC)		+= wkup_m3_rproc.o
obj-$(CONFIG_DA8XX_REMOTEPROC)		+= da8xx_remoteproc.o
obj-$(CONFIG_KEYSTONE_REMOTEPROC)	+= keystone_remoteproc.o
obj-$(CONFIG_QCOM_ADSP_PIL)		+= qcom_adsp_pil.o
obj-$(CONFIG_QCOM_RPROC_COMMON)		+= qcom_common.o
obj-$(CONFIG_QCOM_Q6V5_PIL)		+= qcom_q6v5_pil.o
+3 −29
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ static int da8xx_rproc_stop(struct rproc *rproc)
{
	struct da8xx_rproc *drproc = rproc->priv;

	davinci_clk_reset_assert(drproc->dsp_clk);
	clk_disable(drproc->dsp_clk);

	return 0;
@@ -157,22 +158,6 @@ static const struct rproc_ops da8xx_rproc_ops = {
	.kick = da8xx_rproc_kick,
};

static int reset_assert(struct device *dev)
{
	struct clk *dsp_clk;

	dsp_clk = clk_get(dev, NULL);
	if (IS_ERR(dsp_clk)) {
		dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk));
		return PTR_ERR(dsp_clk);
	}

	davinci_clk_reset_assert(dsp_clk);
	clk_put(dsp_clk);

	return 0;
}

static int da8xx_rproc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
@@ -223,6 +208,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev)

	drproc = rproc->priv;
	drproc->rproc = rproc;
	drproc->dsp_clk = dsp_clk;
	rproc->has_iommu = false;

	platform_set_drvdata(pdev, rproc);
@@ -241,7 +227,7 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
	 * *not* in reset, but da8xx_rproc_start() needs the DSP to be
	 * held in reset at the time it is called.
	 */
	ret = reset_assert(dev);
	ret = davinci_clk_reset_assert(drproc->dsp_clk);
	if (ret)
		goto free_rproc;

@@ -250,7 +236,6 @@ static int da8xx_rproc_probe(struct platform_device *pdev)
	drproc->ack_fxn = irq_data->chip->irq_ack;
	drproc->irq_data = irq_data;
	drproc->irq = irq;
	drproc->dsp_clk = dsp_clk;

	ret = rproc_add(rproc);
	if (ret) {
@@ -268,20 +253,9 @@ static int da8xx_rproc_probe(struct platform_device *pdev)

static int da8xx_rproc_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;

	/*
	 * It's important to place the DSP in reset before going away,
	 * since a subsequent insmod of this module may enable the DSP's
	 * clock before its program/boot-address has been loaded and
	 * before this module's probe has had a chance to reset the DSP.
	 * Without the reset, the DSP can lockup permanently when it
	 * begins executing garbage.
	 */
	reset_assert(dev);

	/*
	 * The devm subsystem might end up releasing things before
	 * freeing the irq, thus allowing an interrupt to sneak in while
+525 −0
Original line number Diff line number Diff line
/*
 * TI Keystone DSP remoteproc driver
 *
 * Copyright (C) 2015-2017 Texas Instruments Incorporated - http://www.ti.com/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 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/slab.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/workqueue.h>
#include <linux/of_address.h>
#include <linux/of_reserved_mem.h>
#include <linux/of_gpio.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/remoteproc.h>
#include <linux/reset.h>

#include "remoteproc_internal.h"

#define KEYSTONE_RPROC_LOCAL_ADDRESS_MASK	(SZ_16M - 1)

/**
 * struct keystone_rproc_mem - internal memory structure
 * @cpu_addr: MPU virtual address of the memory region
 * @bus_addr: Bus address used to access the memory region
 * @dev_addr: Device address of the memory region from DSP view
 * @size: Size of the memory region
 */
struct keystone_rproc_mem {
	void __iomem *cpu_addr;
	phys_addr_t bus_addr;
	u32 dev_addr;
	size_t size;
};

/**
 * struct keystone_rproc - keystone remote processor driver structure
 * @dev: cached device pointer
 * @rproc: remoteproc device handle
 * @mem: internal memory regions data
 * @num_mems: number of internal memory regions
 * @dev_ctrl: device control regmap handle
 * @reset: reset control handle
 * @boot_offset: boot register offset in @dev_ctrl regmap
 * @irq_ring: irq entry for vring
 * @irq_fault: irq entry for exception
 * @kick_gpio: gpio used for virtio kicks
 * @workqueue: workqueue for processing virtio interrupts
 */
struct keystone_rproc {
	struct device *dev;
	struct rproc *rproc;
	struct keystone_rproc_mem *mem;
	int num_mems;
	struct regmap *dev_ctrl;
	struct reset_control *reset;
	u32 boot_offset;
	int irq_ring;
	int irq_fault;
	int kick_gpio;
	struct work_struct workqueue;
};

/* Put the DSP processor into reset */
static void keystone_rproc_dsp_reset(struct keystone_rproc *ksproc)
{
	reset_control_assert(ksproc->reset);
}

/* Configure the boot address and boot the DSP processor */
static int keystone_rproc_dsp_boot(struct keystone_rproc *ksproc, u32 boot_addr)
{
	int ret;

	if (boot_addr & (SZ_1K - 1)) {
		dev_err(ksproc->dev, "invalid boot address 0x%x, must be aligned on a 1KB boundary\n",
			boot_addr);
		return -EINVAL;
	}

	ret = regmap_write(ksproc->dev_ctrl, ksproc->boot_offset, boot_addr);
	if (ret) {
		dev_err(ksproc->dev, "regmap_write of boot address failed, status = %d\n",
			ret);
		return ret;
	}

	reset_control_deassert(ksproc->reset);

	return 0;
}

/*
 * Process the remoteproc exceptions
 *
 * The exception reporting on Keystone DSP remote processors is very simple
 * compared to the equivalent processors on the OMAP family, it is notified
 * through a software-designed specific interrupt source in the IPC interrupt
 * generation register.
 *
 * This function just invokes the rproc_report_crash to report the exception
 * to the remoteproc driver core, to trigger a recovery.
 */
static irqreturn_t keystone_rproc_exception_interrupt(int irq, void *dev_id)
{
	struct keystone_rproc *ksproc = dev_id;

	rproc_report_crash(ksproc->rproc, RPROC_FATAL_ERROR);

	return IRQ_HANDLED;
}

/*
 * Main virtqueue message workqueue function
 *
 * This function is executed upon scheduling of the keystone remoteproc
 * driver's workqueue. The workqueue is scheduled by the vring ISR handler.
 *
 * There is no payload message indicating the virtqueue index as is the
 * case with mailbox-based implementations on OMAP family. As such, this
 * handler processes both the Tx and Rx virtqueue indices on every invocation.
 * The rproc_vq_interrupt function can detect if there are new unprocessed
 * messages or not (returns IRQ_NONE vs IRQ_HANDLED), but there is no need
 * to check for these return values. The index 0 triggering will process all
 * pending Rx buffers, and the index 1 triggering will process all newly
 * available Tx buffers and will wakeup any potentially blocked senders.
 *
 * NOTE:
 * 1. A payload could be added by using some of the source bits in the
 *    IPC interrupt generation registers, but this would need additional
 *    changes to the overall IPC stack, and currently there are no benefits
 *    of adapting that approach.
 * 2. The current logic is based on an inherent design assumption of supporting
 *    only 2 vrings, but this can be changed if needed.
 */
static void handle_event(struct work_struct *work)
{
	struct keystone_rproc *ksproc =
		container_of(work, struct keystone_rproc, workqueue);

	rproc_vq_interrupt(ksproc->rproc, 0);
	rproc_vq_interrupt(ksproc->rproc, 1);
}

/*
 * Interrupt handler for processing vring kicks from remote processor
 */
static irqreturn_t keystone_rproc_vring_interrupt(int irq, void *dev_id)
{
	struct keystone_rproc *ksproc = dev_id;

	schedule_work(&ksproc->workqueue);

	return IRQ_HANDLED;
}

/*
 * Power up the DSP remote processor.
 *
 * This function will be invoked only after the firmware for this rproc
 * was loaded, parsed successfully, and all of its resource requirements
 * were met.
 */
static int keystone_rproc_start(struct rproc *rproc)
{
	struct keystone_rproc *ksproc = rproc->priv;
	int ret;

	INIT_WORK(&ksproc->workqueue, handle_event);

	ret = request_irq(ksproc->irq_ring, keystone_rproc_vring_interrupt, 0,
			  dev_name(ksproc->dev), ksproc);
	if (ret) {
		dev_err(ksproc->dev, "failed to enable vring interrupt, ret = %d\n",
			ret);
		goto out;
	}

	ret = request_irq(ksproc->irq_fault, keystone_rproc_exception_interrupt,
			  0, dev_name(ksproc->dev), ksproc);
	if (ret) {
		dev_err(ksproc->dev, "failed to enable exception interrupt, ret = %d\n",
			ret);
		goto free_vring_irq;
	}

	ret = keystone_rproc_dsp_boot(ksproc, rproc->bootaddr);
	if (ret)
		goto free_exc_irq;

	return 0;

free_exc_irq:
	free_irq(ksproc->irq_fault, ksproc);
free_vring_irq:
	free_irq(ksproc->irq_ring, ksproc);
	flush_work(&ksproc->workqueue);
out:
	return ret;
}

/*
 * Stop the DSP remote processor.
 *
 * This function puts the DSP processor into reset, and finishes processing
 * of any pending messages.
 */
static int keystone_rproc_stop(struct rproc *rproc)
{
	struct keystone_rproc *ksproc = rproc->priv;

	keystone_rproc_dsp_reset(ksproc);
	free_irq(ksproc->irq_fault, ksproc);
	free_irq(ksproc->irq_ring, ksproc);
	flush_work(&ksproc->workqueue);

	return 0;
}

/*
 * Kick the remote processor to notify about pending unprocessed messages.
 * The vqid usage is not used and is inconsequential, as the kick is performed
 * through a simulated GPIO (a bit in an IPC interrupt-triggering register),
 * the remote processor is expected to process both its Tx and Rx virtqueues.
 */
static void keystone_rproc_kick(struct rproc *rproc, int vqid)
{
	struct keystone_rproc *ksproc = rproc->priv;

	if (WARN_ON(ksproc->kick_gpio < 0))
		return;

	gpio_set_value(ksproc->kick_gpio, 1);
}

/*
 * Custom function to translate a DSP device address (internal RAMs only) to a
 * kernel virtual address.  The DSPs can access their RAMs at either an internal
 * address visible only from a DSP, or at the SoC-level bus address. Both these
 * addresses need to be looked through for translation. The translated addresses
 * can be used either by the remoteproc core for loading (when using kernel
 * remoteproc loader), or by any rpmsg bus drivers.
 */
static void *keystone_rproc_da_to_va(struct rproc *rproc, u64 da, int len)
{
	struct keystone_rproc *ksproc = rproc->priv;
	void __iomem *va = NULL;
	phys_addr_t bus_addr;
	u32 dev_addr, offset;
	size_t size;
	int i;

	if (len <= 0)
		return NULL;

	for (i = 0; i < ksproc->num_mems; i++) {
		bus_addr = ksproc->mem[i].bus_addr;
		dev_addr = ksproc->mem[i].dev_addr;
		size = ksproc->mem[i].size;

		if (da < KEYSTONE_RPROC_LOCAL_ADDRESS_MASK) {
			/* handle DSP-view addresses */
			if ((da >= dev_addr) &&
			    ((da + len) <= (dev_addr + size))) {
				offset = da - dev_addr;
				va = ksproc->mem[i].cpu_addr + offset;
				break;
			}
		} else {
			/* handle SoC-view addresses */
			if ((da >= bus_addr) &&
			    (da + len) <= (bus_addr + size)) {
				offset = da - bus_addr;
				va = ksproc->mem[i].cpu_addr + offset;
				break;
			}
		}
	}

	return (__force void *)va;
}

static const struct rproc_ops keystone_rproc_ops = {
	.start		= keystone_rproc_start,
	.stop		= keystone_rproc_stop,
	.kick		= keystone_rproc_kick,
	.da_to_va	= keystone_rproc_da_to_va,
};

static int keystone_rproc_of_get_memories(struct platform_device *pdev,
					  struct keystone_rproc *ksproc)
{
	static const char * const mem_names[] = {"l2sram", "l1pram", "l1dram"};
	struct device *dev = &pdev->dev;
	struct resource *res;
	int num_mems = 0;
	int i;

	num_mems = ARRAY_SIZE(mem_names);
	ksproc->mem = devm_kcalloc(ksproc->dev, num_mems,
				   sizeof(*ksproc->mem), GFP_KERNEL);
	if (!ksproc->mem)
		return -ENOMEM;

	for (i = 0; i < num_mems; i++) {
		res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
						   mem_names[i]);
		ksproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res);
		if (IS_ERR(ksproc->mem[i].cpu_addr)) {
			dev_err(dev, "failed to parse and map %s memory\n",
				mem_names[i]);
			return PTR_ERR(ksproc->mem[i].cpu_addr);
		}
		ksproc->mem[i].bus_addr = res->start;
		ksproc->mem[i].dev_addr =
				res->start & KEYSTONE_RPROC_LOCAL_ADDRESS_MASK;
		ksproc->mem[i].size = resource_size(res);

		/* zero out memories to start in a pristine state */
		memset((__force void *)ksproc->mem[i].cpu_addr, 0,
		       ksproc->mem[i].size);
	}
	ksproc->num_mems = num_mems;

	return 0;
}

static int keystone_rproc_of_get_dev_syscon(struct platform_device *pdev,
					    struct keystone_rproc *ksproc)
{
	struct device_node *np = pdev->dev.of_node;
	struct device *dev = &pdev->dev;
	int ret;

	if (!of_property_read_bool(np, "ti,syscon-dev")) {
		dev_err(dev, "ti,syscon-dev property is absent\n");
		return -EINVAL;
	}

	ksproc->dev_ctrl =
		syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev");
	if (IS_ERR(ksproc->dev_ctrl)) {
		ret = PTR_ERR(ksproc->dev_ctrl);
		return ret;
	}

	if (of_property_read_u32_index(np, "ti,syscon-dev", 1,
				       &ksproc->boot_offset)) {
		dev_err(dev, "couldn't read the boot register offset\n");
		return -EINVAL;
	}

	return 0;
}

static int keystone_rproc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct keystone_rproc *ksproc;
	struct rproc *rproc;
	int dsp_id;
	char *fw_name = NULL;
	char *template = "keystone-dsp%d-fw";
	int name_len = 0;
	int ret = 0;

	if (!np) {
		dev_err(dev, "only DT-based devices are supported\n");
		return -ENODEV;
	}

	dsp_id = of_alias_get_id(np, "rproc");
	if (dsp_id < 0) {
		dev_warn(dev, "device does not have an alias id\n");
		return dsp_id;
	}

	/* construct a custom default fw name - subject to change in future */
	name_len = strlen(template); /* assuming a single digit alias */
	fw_name = devm_kzalloc(dev, name_len, GFP_KERNEL);
	if (!fw_name)
		return -ENOMEM;
	snprintf(fw_name, name_len, template, dsp_id);

	rproc = rproc_alloc(dev, dev_name(dev), &keystone_rproc_ops, fw_name,
			    sizeof(*ksproc));
	if (!rproc)
		return -ENOMEM;

	rproc->has_iommu = false;
	ksproc = rproc->priv;
	ksproc->rproc = rproc;
	ksproc->dev = dev;

	ret = keystone_rproc_of_get_dev_syscon(pdev, ksproc);
	if (ret)
		goto free_rproc;

	ksproc->reset = devm_reset_control_get(dev, NULL);
	if (IS_ERR(ksproc->reset)) {
		ret = PTR_ERR(ksproc->reset);
		goto free_rproc;
	}

	/* enable clock for accessing DSP internal memories */
	pm_runtime_enable(dev);
	ret = pm_runtime_get_sync(dev);
	if (ret < 0) {
		dev_err(dev, "failed to enable clock, status = %d\n", ret);
		pm_runtime_put_noidle(dev);
		goto disable_rpm;
	}

	ret = keystone_rproc_of_get_memories(pdev, ksproc);
	if (ret)
		goto disable_clk;

	ksproc->irq_ring = platform_get_irq_byname(pdev, "vring");
	if (ksproc->irq_ring < 0) {
		ret = ksproc->irq_ring;
		dev_err(dev, "failed to get vring interrupt, status = %d\n",
			ret);
		goto disable_clk;
	}

	ksproc->irq_fault = platform_get_irq_byname(pdev, "exception");
	if (ksproc->irq_fault < 0) {
		ret = ksproc->irq_fault;
		dev_err(dev, "failed to get exception interrupt, status = %d\n",
			ret);
		goto disable_clk;
	}

	ksproc->kick_gpio = of_get_named_gpio_flags(np, "kick-gpios", 0, NULL);
	if (ksproc->kick_gpio < 0) {
		ret = ksproc->kick_gpio;
		dev_err(dev, "failed to get gpio for virtio kicks, status = %d\n",
			ret);
		goto disable_clk;
	}

	if (of_reserved_mem_device_init(dev))
		dev_warn(dev, "device does not have specific CMA pool\n");

	/* ensure the DSP is in reset before loading firmware */
	ret = reset_control_status(ksproc->reset);
	if (ret < 0) {
		dev_err(dev, "failed to get reset status, status = %d\n", ret);
		goto release_mem;
	} else if (ret == 0) {
		WARN(1, "device is not in reset\n");
		keystone_rproc_dsp_reset(ksproc);
	}

	ret = rproc_add(rproc);
	if (ret) {
		dev_err(dev, "failed to add register device with remoteproc core, status = %d\n",
			ret);
		goto release_mem;
	}

	platform_set_drvdata(pdev, ksproc);

	return 0;

release_mem:
	of_reserved_mem_device_release(dev);
disable_clk:
	pm_runtime_put_sync(dev);
disable_rpm:
	pm_runtime_disable(dev);
free_rproc:
	rproc_free(rproc);
	return ret;
}

static int keystone_rproc_remove(struct platform_device *pdev)
{
	struct keystone_rproc *ksproc = platform_get_drvdata(pdev);

	rproc_del(ksproc->rproc);
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
	rproc_free(ksproc->rproc);
	of_reserved_mem_device_release(&pdev->dev);

	return 0;
}

static const struct of_device_id keystone_rproc_of_match[] = {
	{ .compatible = "ti,k2hk-dsp", },
	{ .compatible = "ti,k2l-dsp", },
	{ .compatible = "ti,k2e-dsp", },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, keystone_rproc_of_match);

static struct platform_driver keystone_rproc_driver = {
	.probe	= keystone_rproc_probe,
	.remove	= keystone_rproc_remove,
	.driver	= {
		.name = "keystone-rproc",
		.of_match_table = keystone_rproc_of_match,
	},
};

module_platform_driver(keystone_rproc_driver);

MODULE_AUTHOR("Suman Anna <s-anna@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("TI Keystone DSP Remoteproc driver");
Loading