diff options
31 files changed, 2768 insertions, 551 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-remoteproc b/Documentation/ABI/testing/sysfs-class-remoteproc new file mode 100644 index 000000000000..d188afebc8ba --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-remoteproc @@ -0,0 +1,50 @@ +What: /sys/class/remoteproc/.../firmware +Date: October 2016 +Contact: Matt Redfearn <matt.redfearn@imgtec.com> +Description: Remote processor firmware + + Reports the name of the firmware currently loaded to the + remote processor. + + To change the running firmware, ensure the remote processor is + stopped (using /sys/class/remoteproc/.../state) and write a new filename. + +What: /sys/class/remoteproc/.../state +Date: October 2016 +Contact: Matt Redfearn <matt.redfearn@imgtec.com> +Description: Remote processor state + + Reports the state of the remote processor, which will be one of: + + "offline" + "suspended" + "running" + "crashed" + "invalid" + + "offline" means the remote processor is powered off. + + "suspended" means that the remote processor is suspended and + must be woken to receive messages. + + "running" is the normal state of an available remote processor + + "crashed" indicates that a problem/crash has been detected on + the remote processor. + + "invalid" is returned if the remote processor is in an + unknown state. + + Writing this file controls the state of the remote processor. + The following states can be written: + + "start" + "stop" + + Writing "start" will attempt to start the processor running the + firmware indicated by, or written to, + /sys/class/remoteproc/.../firmware. The remote processor should + transition to "running" state. + + Writing "stop" will attempt to halt the remote processor and + return it to the "offline" state. diff --git a/Documentation/devicetree/bindings/dma/st_fdma.txt b/Documentation/devicetree/bindings/dma/st_fdma.txt new file mode 100644 index 000000000000..495d853c569b --- /dev/null +++ b/Documentation/devicetree/bindings/dma/st_fdma.txt @@ -0,0 +1,87 @@ +* STMicroelectronics Flexible Direct Memory Access Device Tree bindings + +The FDMA is a general-purpose direct memory access controller capable of +supporting 16 independent DMA channels. It accepts up to 32 DMA requests. +The FDMA is based on a Slim processor which requires a firmware. + +* FDMA Controller + +Required properties: +- compatible : Should be one of + - st,stih407-fdma-mpe31-11, "st,slim-rproc"; + - st,stih407-fdma-mpe31-12, "st,slim-rproc"; + - st,stih407-fdma-mpe31-13, "st,slim-rproc"; +- reg : Should contain an entry for each name in reg-names +- reg-names : Must contain "slimcore", "dmem", "peripherals", "imem" entries +- interrupts : Should contain one interrupt shared by all channels +- dma-channels : Number of channels supported by the controller +- #dma-cells : Must be <3>. See DMA client section below +- clocks : Must contain an entry for each clock +See: Documentation/devicetree/bindings/clock/clock-bindings.txt + + +Example: + + fdma0: dma-controller@8e20000 { + compatible = "st,stih407-fdma-mpe31-11", "st,slim-rproc"; + reg = <0x8e20000 0x8000>, + <0x8e30000 0x3000>, + <0x8e37000 0x1000>, + <0x8e38000 0x8000>; + reg-names = "slimcore", "dmem", "peripherals", "imem"; + clocks = <&clk_s_c0_flexgen CLK_FDMA>, + <&clk_s_c0_flexgen CLK_EXT2F_A9>, + <&clk_s_c0_flexgen CLK_EXT2F_A9>, + <&clk_s_c0_flexgen CLK_EXT2F_A9>; + interrupts = <GIC_SPI 5 IRQ_TYPE_NONE>; + dma-channels = <16>; + #dma-cells = <3>; + }; + +* DMA client + +Required properties: +- dmas: Comma separated list of dma channel requests +- dma-names: Names of the aforementioned requested channels + +Each dmas request consists of 4 cells: +1. A phandle pointing to the FDMA controller +2. The request line number +3. A 32bit mask specifying (see include/linux/platform_data/dma-st-fdma.h) + -bit 2-0: Holdoff value, dreq will be masked for + 0x0: 0-0.5us + 0x1: 0.5-1us + 0x2: 1-1.5us + -bit 17: data swap + 0x0: disabled + 0x1: enabled + -bit 21: Increment Address + 0x0: no address increment between transfers + 0x1: increment address between transfers + -bit 22: 2 STBus Initiator Coprocessor interface + 0x0: high priority port + 0x1: low priority port +4. transfers type + 0 free running + 1 paced + +Example: + + sti_uni_player2: sti-uni-player@2 { + compatible = "st,sti-uni-player"; + status = "disabled"; + #sound-dai-cells = <0>; + st,syscfg = <&syscfg_core>; + clocks = <&clk_s_d0_flexgen CLK_PCM_2>; + assigned-clocks = <&clk_s_d0_flexgen CLK_PCM_2>; + assigned-clock-parents = <&clk_s_d0_quadfs 2>; + assigned-clock-rates = <50000000>; + reg = <0x8D82000 0x158>; + interrupts = <GIC_SPI 86 IRQ_TYPE_NONE>; + dmas = <&fdma0 4 0 1>; + dai-name = "Uni Player #1 (DAC)"; + dma-names = "tx"; + st,uniperiph-id = <2>; + st,version = <5>; + st,mode = "PCM"; + }; diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt new file mode 100644 index 000000000000..b85885a298d8 --- /dev/null +++ b/Documentation/devicetree/bindings/remoteproc/qcom,adsp.txt @@ -0,0 +1,98 @@ +Qualcomm ADSP Peripheral Image Loader + +This document defines the binding for a component that loads and boots firmware +on the Qualcomm ADSP Hexagon core. + +- compatible: + Usage: required + Value type: <string> + Definition: must be one of: + "qcom,msm8974-adsp-pil" + "qcom,msm8996-adsp-pil" + +- 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: <prop-encoded-array> + Definition: reference to the xo clock to be held on behalf of the + booting Hexagon core + +- clock-names: + Usage: required + Value type: <stringlist> + Definition: must be "xo" + +- cx-supply: + Usage: required + Value type: <phandle> + Definition: reference to the regulator to be held on behalf of the + booting Hexagon core + +- memory-region: + Usage: required + Value type: <phandle> + Definition: reference to the reserved-memory for the ADSP + +- qcom,smem-states: + Usage: required + Value type: <phandle> + Definition: reference to the smem state for requesting the ADSP to + shut down + +- qcom,smem-state-names: + Usage: required + Value type: <stringlist> + Definition: must be "stop" + + += SUBNODES +The adsp node may have an subnode named "smd-edge" that describes the SMD edge, +channels and devices related to the ADSP. See ../soc/qcom/qcom,smd.txt for +details on how to describe the SMD edge. + + += EXAMPLE +The following example describes the resources needed to boot control the +ADSP, as it is found on MSM8974 boards. + + adsp { + compatible = "qcom,msm8974-adsp-pil"; + + interrupts-extended = <&intc 0 162 IRQ_TYPE_EDGE_RISING>, + <&adsp_smp2p_in 0 IRQ_TYPE_EDGE_RISING>, + <&adsp_smp2p_in 1 IRQ_TYPE_EDGE_RISING>, + <&adsp_smp2p_in 2 IRQ_TYPE_EDGE_RISING>, + <&adsp_smp2p_in 3 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "wdog", + "fatal", + "ready", + "handover", + "stop-ack"; + + clocks = <&rpmcc RPM_CXO_CLK>; + clock-names = "xo"; + + cx-supply = <&pm8841_s2>; + + memory-region = <&adsp_region>; + + qcom,smem-states = <&adsp_smp2p_out 0>; + qcom,smem-state-names = "stop"; + + smd-edge { + interrupts = <0 156 IRQ_TYPE_EDGE_RISING>; + + qcom,ipc = <&apcs 8 8>; + qcom,smd-edge = <1>; + }; + }; diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt b/Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt index 0d2361ebe3d7..d420f84ddfb0 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt +++ b/Documentation/devicetree/bindings/remoteproc/qcom,wcnss-pil.txt @@ -60,8 +60,8 @@ on the Qualcomm WCNSS core. see ../reserved-memory/reserved-memory.txt = SUBNODES -A single subnode of the WCNSS PIL describes the attached rf module and its -resource dependencies. +A required subnode of the WCNSS PIL is used to describe the attached rf module +and its resource dependencies. It is described by the following properties: - compatible: Usage: required @@ -90,6 +90,11 @@ resource dependencies. Definition: reference to the regulators to be held on behalf of the booting of the WCNSS core + +The wcnss node can also have an subnode named "smd-edge" that describes the SMD +edge, channels and devices related to the WCNSS. +See ../soc/qcom/qcom,smd.txt for details on how to describe the SMD edge. + = EXAMPLE The following example describes the resources needed to boot control the WCNSS, with attached WCN3680, as it is commonly found on MSM8974 boards. @@ -129,4 +134,25 @@ pronto@fb204000 { vddpa-supply = <&pm8941_l19>; vdddig-supply = <&pm8941_s3>; }; + + smd-edge { + interrupts = <0 142 1>; + + qcom,ipc = <&apcs 8 17>; + qcom,smd-edge = <6>; + qcom,remote-pid = <4>; + + label = "pronto"; + + wcnss { + compatible = "qcom,wcnss"; + qcom,smd-channels = "WCNSS_CTRL"; + + qcom,mmio = <&pronto>; + + bt { + compatible = "qcom,wcnss-bt"; + }; + }; + }; }; diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt index 97d9b3e1bf39..ea1dc75ec9ea 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smd.txt @@ -43,6 +43,13 @@ The edge is described by the following properties: Definition: the identifier for the remote processor as known by the rest of the system. +- label: + Usage: optional + Value type: <string> + Definition: name of the edge, used for debugging and identification + purposes. The node name will be used if this is not + present. + = SMD DEVICES In turn, subnodes of the "edges" represent devices tied to SMD channels on that diff --git a/MAINTAINERS b/MAINTAINERS index ef461e087275..188534bfd7e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1792,6 +1792,7 @@ F: drivers/char/hw_random/st-rng.c F: drivers/clocksource/arm_global_timer.c F: drivers/clocksource/clksrc_st_lpc.c F: drivers/cpufreq/sti-cpufreq.c +F: drivers/dma/st_fdma* F: drivers/i2c/busses/i2c-st.c F: drivers/media/rc/st_rc.c F: drivers/media/platform/sti/c8sectpfe/ @@ -1802,6 +1803,7 @@ F: drivers/phy/phy-stih407-usb.c F: drivers/phy/phy-stih41x-usb.c F: drivers/pinctrl/pinctrl-st.c F: drivers/remoteproc/st_remoteproc.c +F: drivers/remoteproc/st_slim_rproc.c F: drivers/reset/sti/ F: drivers/rtc/rtc-st-lpc.c F: drivers/tty/serial/st-asc.c @@ -1810,6 +1812,7 @@ F: drivers/usb/host/ehci-st.c F: drivers/usb/host/ohci-st.c F: drivers/watchdog/st_lpc_wdt.c F: drivers/ata/ahci_st.c +F: include/linux/remoteproc/st_slim_rproc.h ARM/STM32 ARCHITECTURE M: Maxime Coquelin <mcoquelin.stm32@gmail.com> diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig index 4846de4c3357..30f39acd61bd 100644 --- a/arch/arm/configs/multi_v7_defconfig +++ b/arch/arm/configs/multi_v7_defconfig @@ -649,6 +649,9 @@ CONFIG_SND_SOC_AK4642=m CONFIG_SND_SOC_SGTL5000=m CONFIG_SND_SOC_SPDIF=m CONFIG_SND_SOC_WM8978=m +CONFIG_SND_SOC_STI=m +CONFIG_SND_SOC_STI_SAS=m +CONFIG_SND_SIMPLE_CARD=m CONFIG_USB=y CONFIG_USB_XHCI_HCD=y CONFIG_USB_XHCI_MVEBU=y @@ -790,6 +793,7 @@ CONFIG_DMA_OMAP=y CONFIG_QCOM_BAM_DMA=y CONFIG_XILINX_DMA=y CONFIG_DMA_SUN6I=y +CONFIG_ST_FDMA=m CONFIG_STAGING=y CONFIG_SENSORS_ISL29018=y CONFIG_SENSORS_ISL29028=y @@ -823,6 +827,8 @@ CONFIG_HWSPINLOCK_QCOM=y CONFIG_ROCKCHIP_IOMMU=y CONFIG_TEGRA_IOMMU_GART=y CONFIG_TEGRA_IOMMU_SMMU=y +CONFIG_REMOTEPROC=m +CONFIG_ST_REMOTEPROC=m CONFIG_PM_DEVFREQ=y CONFIG_ARM_TEGRA_DEVFREQ=m CONFIG_MEMORY=y diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 141aefbe37ec..2154ea3c5d1c 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -436,6 +436,20 @@ config STE_DMA40 help Support for ST-Ericsson DMA40 controller +config ST_FDMA + tristate "ST FDMA dmaengine support" + depends on ARCH_STI + depends on REMOTEPROC + select ST_SLIM_REMOTEPROC + select DMA_ENGINE + select DMA_VIRTUAL_CHANNELS + help + Enable support for ST FDMA controller. + It supports 16 independent DMA channels, accepts up to 32 DMA requests + + Say Y here if you have such a chipset. + If unsure, say N. + config STM32_DMA bool "STMicroelectronics STM32 DMA support" depends on ARCH_STM32 || COMPILE_TEST diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index e4dc9cac7ee8..a4fa3360e609 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_TI_DMA_CROSSBAR) += ti-dma-crossbar.o obj-$(CONFIG_TI_EDMA) += edma.o obj-$(CONFIG_XGENE_DMA) += xgene-dma.o obj-$(CONFIG_ZX_DMA) += zx296702_dma.o +obj-$(CONFIG_ST_FDMA) += st_fdma.o obj-y += qcom/ obj-y += xilinx/ diff --git a/drivers/dma/st_fdma.c b/drivers/dma/st_fdma.c new file mode 100644 index 000000000000..bfb79bd0c6de --- /dev/null +++ b/drivers/dma/st_fdma.c @@ -0,0 +1,889 @@ +/* + * DMA driver for STMicroelectronics STi FDMA controller + * + * Copyright (C) 2014 STMicroelectronics + * + * Author: Ludovic Barre <Ludovic.barre@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_dma.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/remoteproc.h> + +#include "st_fdma.h" + +static inline struct st_fdma_chan *to_st_fdma_chan(struct dma_chan *c) +{ + return container_of(c, struct st_fdma_chan, vchan.chan); +} + +static struct st_fdma_desc *to_st_fdma_desc(struct virt_dma_desc *vd) +{ + return container_of(vd, struct st_fdma_desc, vdesc); +} + +static int st_fdma_dreq_get(struct st_fdma_chan *fchan) +{ + struct st_fdma_dev *fdev = fchan->fdev; + u32 req_line_cfg = fchan->cfg.req_line; + u32 dreq_line; + int try = 0; + + /* + * dreq_mask is shared for n channels of fdma, so all accesses must be + * atomic. if the dreq_mask is changed between ffz and set_bit, + * we retry + */ + do { + if (fdev->dreq_mask == ~0L) { + dev_err(fdev->dev, "No req lines available\n"); + return -EINVAL; + } + + if (try || req_line_cfg >= ST_FDMA_NR_DREQS) { + dev_err(fdev->dev, "Invalid or used req line\n"); + return -EINVAL; + } else { + dreq_line = req_line_cfg; + } + + try++; + } while (test_and_set_bit(dreq_line, &fdev->dreq_mask)); + + dev_dbg(fdev->dev, "get dreq_line:%d mask:%#lx\n", + dreq_line, fdev->dreq_mask); + + return dreq_line; +} + +static void st_fdma_dreq_put(struct st_fdma_chan *fchan) +{ + struct st_fdma_dev *fdev = fchan->fdev; + + dev_dbg(fdev->dev, "put dreq_line:%#x\n", fchan->dreq_line); + clear_bit(fchan->dreq_line, &fdev->dreq_mask); +} + +static void st_fdma_xfer_desc(struct st_fdma_chan *fchan) +{ + struct virt_dma_desc *vdesc; + unsigned long nbytes, ch_cmd, cmd; + + vdesc = vchan_next_desc(&fchan->vchan); + if (!vdesc) + return; + + fchan->fdesc = to_st_fdma_desc(vdesc); + nbytes = fchan->fdesc->node[0].desc->nbytes; + cmd = FDMA_CMD_START(fchan->vchan.chan.chan_id); + ch_cmd = fchan->fdesc->node[0].pdesc | FDMA_CH_CMD_STA_START; + + /* start the channel for the descriptor */ + fnode_write(fchan, nbytes, FDMA_CNTN_OFST); + fchan_write(fchan, ch_cmd, FDMA_CH_CMD_OFST); + writel(cmd, + fchan->fdev->slim_rproc->peri + FDMA_CMD_SET_OFST); + + dev_dbg(fchan->fdev->dev, "start chan:%d\n", fchan->vchan.chan.chan_id); +} + +static void st_fdma_ch_sta_update(struct st_fdma_chan *fchan, + unsigned long int_sta) +{ + unsigned long ch_sta, ch_err; + int ch_id = fchan->vchan.chan.chan_id; + struct st_fdma_dev *fdev = fchan->fdev; + + ch_sta = fchan_read(fchan, FDMA_CH_CMD_OFST); + ch_err = ch_sta & FDMA_CH_CMD_ERR_MASK; + ch_sta &= FDMA_CH_CMD_STA_MASK; + + if (int_sta & FDMA_INT_STA_ERR) { + dev_warn(fdev->dev, "chan:%d, error:%ld\n", ch_id, ch_err); + fchan->status = DMA_ERROR; + return; + } + + switch (ch_sta) { + case FDMA_CH_CMD_STA_PAUSED: + fchan->status = DMA_PAUSED; + break; + + case FDMA_CH_CMD_STA_RUNNING: + fchan->status = DMA_IN_PROGRESS; + break; + } +} + +static irqreturn_t st_fdma_irq_handler(int irq, void *dev_id) +{ + struct st_fdma_dev *fdev = dev_id; + irqreturn_t ret = IRQ_NONE; + struct st_fdma_chan *fchan = &fdev->chans[0]; + unsigned long int_sta, clr; + + int_sta = fdma_read(fdev, FDMA_INT_STA_OFST); + clr = int_sta; + + for (; int_sta != 0 ; int_sta >>= 2, fchan++) { + if (!(int_sta & (FDMA_INT_STA_CH | FDMA_INT_STA_ERR))) + continue; + + spin_lock(&fchan->vchan.lock); + st_fdma_ch_sta_update(fchan, int_sta); + + if (fchan->fdesc) { + if (!fchan->fdesc->iscyclic) { + list_del(&fchan->fdesc->vdesc.node); + vchan_cookie_complete(&fchan->fdesc->vdesc); + fchan->fdesc = NULL; + fchan->status = DMA_COMPLETE; + } else { + vchan_cyclic_callback(&fchan->fdesc->vdesc); + } + + /* Start the next descriptor (if available) */ + if (!fchan->fdesc) + st_fdma_xfer_desc(fchan); + } + + spin_unlock(&fchan->vchan.lock); + ret = IRQ_HANDLED; + } + + fdma_write(fdev, clr, FDMA_INT_CLR_OFST); + + return ret; +} + +static struct dma_chan *st_fdma_of_xlate(struct of_phandle_args *dma_spec, + struct of_dma *ofdma) +{ + struct st_fdma_dev *fdev = ofdma->of_dma_data; + struct dma_chan *chan; + struct st_fdma_chan *fchan; + int ret; + + if (dma_spec->args_count < 1) + return ERR_PTR(-EINVAL); + + if (fdev->dma_device.dev->of_node != dma_spec->np) + return ERR_PTR(-EINVAL); + + ret = rproc_boot(fdev->slim_rproc->rproc); + if (ret == -ENOENT) + return ERR_PTR(-EPROBE_DEFER); + else if (ret) + return ERR_PTR(ret); + + chan = dma_get_any_slave_channel(&fdev->dma_device); + if (!chan) + goto err_chan; + + fchan = to_st_fdma_chan(chan); + + fchan->cfg.of_node = dma_spec->np; + fchan->cfg.req_line = dma_spec->args[0]; + fchan->cfg.req_ctrl = 0; + fchan->cfg.type = ST_FDMA_TYPE_FREE_RUN; + + if (dma_spec->args_count > 1) + fchan->cfg.req_ctrl = dma_spec->args[1] + & FDMA_REQ_CTRL_CFG_MASK; + + if (dma_spec->args_count > 2) + fchan->cfg.type = dma_spec->args[2]; + + if (fchan->cfg.type == ST_FDMA_TYPE_FREE_RUN) { + fchan->dreq_line = 0; + } else { + fchan->dreq_line = st_fdma_dreq_get(fchan); + if (IS_ERR_VALUE(fchan->dreq_line)) { + chan = ERR_PTR(fchan->dreq_line); + goto err_chan; + } + } + + dev_dbg(fdev->dev, "xlate req_line:%d type:%d req_ctrl:%#lx\n", + fchan->cfg.req_line, fchan->cfg.type, fchan->cfg.req_ctrl); + + return chan; + +err_chan: + rproc_shutdown(fdev->slim_rproc->rproc); + return chan; + +} + +static void st_fdma_free_desc(struct virt_dma_desc *vdesc) +{ + struct st_fdma_desc *fdesc; + int i; + + fdesc = to_st_fdma_desc(vdesc); + for (i = 0; i < fdesc->n_nodes; i++) + dma_pool_free(fdesc->fchan->node_pool, fdesc->node[i].desc, + fdesc->node[i].pdesc); + kfree(fdesc); +} + +static struct st_fdma_desc *st_fdma_alloc_desc(struct st_fdma_chan *fchan, + int sg_len) +{ + struct st_fdma_desc *fdesc; + int i; + + fdesc = kzalloc(sizeof(*fdesc) + + sizeof(struct st_fdma_sw_node) * sg_len, GFP_NOWAIT); + if (!fdesc) + return NULL; + + fdesc->fchan = fchan; + fdesc->n_nodes = sg_len; + for (i = 0; i < sg_len; i++) { + fdesc->node[i].desc = dma_pool_alloc(fchan->node_pool, + GFP_NOWAIT, &fdesc->node[i].pdesc); + if (!fdesc->node[i].desc) + goto err; + } + return fdesc; + +err: + while (--i >= 0) + dma_pool_free(fchan->node_pool, fdesc->node[i].desc, + fdesc->node[i].pdesc); + kfree(fdesc); + return NULL; +} + +static int st_fdma_alloc_chan_res(struct dma_chan *chan) +{ + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + + /* Create the dma pool for descriptor allocation */ + fchan->node_pool = dma_pool_create(dev_name(&chan->dev->device), + fchan->fdev->dev, + sizeof(struct st_fdma_hw_node), + __alignof__(struct st_fdma_hw_node), + 0); + + if (!fchan->node_pool) { + dev_err(fchan->fdev->dev, "unable to allocate desc pool\n"); + return -ENOMEM; + } + + dev_dbg(fchan->fdev->dev, "alloc ch_id:%d type:%d\n", + fchan->vchan.chan.chan_id, fchan->cfg.type); + + return 0; +} + +static void st_fdma_free_chan_res(struct dma_chan *chan) +{ + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + struct rproc *rproc = fchan->fdev->slim_rproc->rproc; + unsigned long flags; + + LIST_HEAD(head); + + dev_dbg(fchan->fdev->dev, "%s: freeing chan:%d\n", + __func__, fchan->vchan.chan.chan_id); + + if (fchan->cfg.type != ST_FDMA_TYPE_FREE_RUN) + st_fdma_dreq_put(fchan); + + spin_lock_irqsave(&fchan->vchan.lock, flags); + fchan->fdesc = NULL; + spin_unlock_irqrestore(&fchan->vchan.lock, flags); + + dma_pool_destroy(fchan->node_pool); + fchan->node_pool = NULL; + memset(&fchan->cfg, 0, sizeof(struct st_fdma_cfg)); + + rproc_shutdown(rproc); +} + +static struct dma_async_tx_descriptor *st_fdma_prep_dma_memcpy( + struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, + size_t len, unsigned long flags) +{ + struct st_fdma_chan *fchan; + struct st_fdma_desc *fdesc; + struct st_fdma_hw_node *hw_node; + + if (!len) + return NULL; + + fchan = to_st_fdma_chan(chan); + + /* We only require a single descriptor */ + fdesc = st_fdma_alloc_desc(fchan, 1); + if (!fdesc) { + dev_err(fchan->fdev->dev, "no memory for desc\n"); + return NULL; + } + + hw_node = fdesc->node[0].desc; + hw_node->next = 0; + hw_node->control = FDMA_NODE_CTRL_REQ_MAP_FREE_RUN; + hw_node->control |= FDMA_NODE_CTRL_SRC_INCR; + hw_node->control |= FDMA_NODE_CTRL_DST_INCR; + hw_node->control |= FDMA_NODE_CTRL_INT_EON; + hw_node->nbytes = len; + hw_node->saddr = src; + hw_node->daddr = dst; + hw_node->generic.length = len; + hw_node->generic.sstride = 0; + hw_node->generic.dstride = 0; + + return vchan_tx_prep(&fchan->vchan, &fdesc->vdesc, flags); +} + +static int config_reqctrl(struct st_fdma_chan *fchan, + enum dma_transfer_direction direction) +{ + u32 maxburst = 0, addr = 0; + enum dma_slave_buswidth width; + int ch_id = fchan->vchan.chan.chan_id; + struct st_fdma_dev *fdev = fchan->fdev; + + switch (direction) { + + case DMA_DEV_TO_MEM: + fchan->cfg.req_ctrl &= ~FDMA_REQ_CTRL_WNR; + maxburst = fchan->scfg.src_maxburst; + width = fchan->scfg.src_addr_width; + addr = fchan->scfg.src_addr; + break; + + case DMA_MEM_TO_DEV: + fchan->cfg.req_ctrl |= FDMA_REQ_CTRL_WNR; + maxburst = fchan->scfg.dst_maxburst; + width = fchan->scfg.dst_addr_width; + addr = fchan->scfg.dst_addr; + break; + + default: + return -EINVAL; + } + + fchan->cfg.req_ctrl &= ~FDMA_REQ_CTRL_OPCODE_MASK; + + switch (width) { + + case DMA_SLAVE_BUSWIDTH_1_BYTE: + fchan->cfg.req_ctrl |= FDMA_REQ_CTRL_OPCODE_LD_ST1; + break; + + case DMA_SLAVE_BUSWIDTH_2_BYTES: + fchan->cfg.req_ctrl |= FDMA_REQ_CTRL_OPCODE_LD_ST2; + break; + + case DMA_SLAVE_BUSWIDTH_4_BYTES: + fchan->cfg.req_ctrl |= FDMA_REQ_CTRL_OPCODE_LD_ST4; + break; + + case DMA_SLAVE_BUSWIDTH_8_BYTES: + fchan->cfg.req_ctrl |= FDMA_REQ_CTRL_OPCODE_LD_ST8; + break; + + default: + return -EINVAL; + } + + fchan->cfg.req_ctrl &= ~FDMA_REQ_CTRL_NUM_OPS_MASK; + fchan->cfg.req_ctrl |= FDMA_REQ_CTRL_NUM_OPS(maxburst-1); + dreq_write(fchan, fchan->cfg.req_ctrl, FDMA_REQ_CTRL_OFST); + + fchan->cfg.dev_addr = addr; + fchan->cfg.dir = direction; + + dev_dbg(fdev->dev, "chan:%d config_reqctrl:%#x req_ctrl:%#lx\n", + ch_id, addr, fchan->cfg.req_ctrl); + + return 0; +} + +static void fill_hw_node(struct st_fdma_hw_node *hw_node, + struct st_fdma_chan *fchan, + enum dma_transfer_direction direction) +{ + if (direction == DMA_MEM_TO_DEV) { + hw_node->control |= FDMA_NODE_CTRL_SRC_INCR; + hw_node->control |= FDMA_NODE_CTRL_DST_STATIC; + hw_node->daddr = fchan->cfg.dev_addr; + } else { + hw_node->control |= FDMA_NODE_CTRL_SRC_STATIC; + hw_node->control |= FDMA_NODE_CTRL_DST_INCR; + hw_node->saddr = fchan->cfg.dev_addr; + } + + hw_node->generic.sstride = 0; + hw_node->generic.dstride = 0; +} + +static inline struct st_fdma_chan *st_fdma_prep_common(struct dma_chan *chan, + size_t len, enum dma_transfer_direction direction) +{ + struct st_fdma_chan *fchan; + + if (!chan || !len) + return NULL; + + fchan = to_st_fdma_chan(chan); + + if (!is_slave_direction(direction)) { + dev_err(fchan->fdev->dev, "bad direction?\n"); + return NULL; + } + + return fchan; +} + +static struct dma_async_tx_descriptor *st_fdma_prep_dma_cyclic( + struct dma_chan *chan, dma_addr_t buf_addr, size_t len, + size_t period_len, enum dma_transfer_direction direction, + unsigned long flags) +{ + struct st_fdma_chan *fchan; + struct st_fdma_desc *fdesc; + int sg_len, i; + + fchan = st_fdma_prep_common(chan, len, direction); + if (!fchan) + return NULL; + + if (!period_len) + return NULL; + + if (config_reqctrl(fchan, direction)) { + dev_err(fchan->fdev->dev, "bad width or direction\n"); + return NULL; + } + + /* the buffer length must be a multiple of period_len */ + if (len % period_len != 0) { + dev_err(fchan->fdev->dev, "len is not multiple of period\n"); + return NULL; + } + + sg_len = len / period_len; + fdesc = st_fdma_alloc_desc(fchan, sg_len); + if (!fdesc) { + dev_err(fchan->fdev->dev, "no memory for desc\n"); + return NULL; + } + + fdesc->iscyclic = true; + + for (i = 0; i < sg_len; i++) { + struct st_fdma_hw_node *hw_node = fdesc->node[i].desc; + + hw_node->next = fdesc->node[(i + 1) % sg_len].pdesc; + + hw_node->control = + FDMA_NODE_CTRL_REQ_MAP_DREQ(fchan->dreq_line); + hw_node->control |= FDMA_NODE_CTRL_INT_EON; + + fill_hw_node(hw_node, fchan, direction); + + if (direction == DMA_MEM_TO_DEV) + hw_node->saddr = buf_addr + (i * period_len); + else + hw_node->daddr = buf_addr + (i * period_len); + + hw_node->nbytes = period_len; + hw_node->generic.length = period_len; + } + + return vchan_tx_prep(&fchan->vchan, &fdesc->vdesc, flags); +} + +static struct dma_async_tx_descriptor *st_fdma_prep_slave_sg( + struct dma_chan *chan, struct scatterlist *sgl, + unsigned int sg_len, enum dma_transfer_direction direction, + unsigned long flags, void *context) +{ + struct st_fdma_chan *fchan; + struct st_fdma_desc *fdesc; + struct st_fdma_hw_node *hw_node; + struct scatterlist *sg; + int i; + + fchan = st_fdma_prep_common(chan, sg_len, direction); + if (!fchan) + return NULL; + + if (!sgl) + return NULL; + + fdesc = st_fdma_alloc_desc(fchan, sg_len); + if (!fdesc) { + dev_err(fchan->fdev->dev, "no memory for desc\n"); + return NULL; + } + + fdesc->iscyclic = false; + + for_each_sg(sgl, sg, sg_len, i) { + hw_node = fdesc->node[i].desc; + + hw_node->next = fdesc->node[(i + 1) % sg_len].pdesc; + hw_node->control = FDMA_NODE_CTRL_REQ_MAP_DREQ(fchan->dreq_line); + + fill_hw_node(hw_node, fchan, direction); + + if (direction == DMA_MEM_TO_DEV) + hw_node->saddr = sg_dma_address(sg); + else + hw_node->daddr = sg_dma_address(sg); + + hw_node->nbytes = sg_dma_len(sg); + hw_node->generic.length = sg_dma_len(sg); + } + + /* interrupt at end of last node */ + hw_node->control |= FDMA_NODE_CTRL_INT_EON; + + return vchan_tx_prep(&fchan->vchan, &fdesc->vdesc, flags); +} + +static size_t st_fdma_desc_residue(struct st_fdma_chan *fchan, + struct virt_dma_desc *vdesc, + bool in_progress) +{ + struct st_fdma_desc *fdesc = fchan->fdesc; + size_t residue = 0; + dma_addr_t cur_addr = 0; + int i; + + if (in_progress) { + cur_addr = fchan_read(fchan, FDMA_CH_CMD_OFST); + cur_addr &= FDMA_CH_CMD_DATA_MASK; + } + + for (i = fchan->fdesc->n_nodes - 1 ; i >= 0; i--) { + if (cur_addr == fdesc->node[i].pdesc) { + residue += fnode_read(fchan, FDMA_CNTN_OFST); + break; + } + residue += fdesc->node[i].desc->nbytes; + } + + return residue; +} + +static enum dma_status st_fdma_tx_status(struct dma_chan *chan, + dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + struct virt_dma_desc *vd; + enum dma_status ret; + unsigned long flags; + + ret = dma_cookie_status(chan, cookie, txstate); + if (ret == DMA_COMPLETE || !txstate) + return ret; + + spin_lock_irqsave(&fchan->vchan.lock, flags); + vd = vchan_find_desc(&fchan->vchan, cookie); + if (fchan->fdesc && cookie == fchan->fdesc->vdesc.tx.cookie) + txstate->residue = st_fdma_desc_residue(fchan, vd, true); + else if (vd) + txstate->residue = st_fdma_desc_residue(fchan, vd, false); + else + txstate->residue = 0; + + spin_unlock_irqrestore(&fchan->vchan.lock, flags); + + return ret; +} + +static void st_fdma_issue_pending(struct dma_chan *chan) +{ + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + unsigned long flags; + + spin_lock_irqsave(&fchan->vchan.lock, flags); + + if (vchan_issue_pending(&fchan->vchan) && !fchan->fdesc) + st_fdma_xfer_desc(fchan); + + spin_unlock_irqrestore(&fchan->vchan.lock, flags); +} + +static int st_fdma_pause(struct dma_chan *chan) +{ + unsigned long flags; + LIST_HEAD(head); + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + int ch_id = fchan->vchan.chan.chan_id; + unsigned long cmd = FDMA_CMD_PAUSE(ch_id); + + dev_dbg(fchan->fdev->dev, "pause chan:%d\n", ch_id); + + spin_lock_irqsave(&fchan->vchan.lock, flags); + if (fchan->fdesc) + fdma_write(fchan->fdev, cmd, FDMA_CMD_SET_OFST); + spin_unlock_irqrestore(&fchan->vchan.lock, flags); + + return 0; +} + +static int st_fdma_resume(struct dma_chan *chan) +{ + unsigned long flags; + unsigned long val; + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + int ch_id = fchan->vchan.chan.chan_id; + + dev_dbg(fchan->fdev->dev, "resume chan:%d\n", ch_id); + + spin_lock_irqsave(&fchan->vchan.lock, flags); + if (fchan->fdesc) { + val = fchan_read(fchan, FDMA_CH_CMD_OFST); + val &= FDMA_CH_CMD_DATA_MASK; + fchan_write(fchan, val, FDMA_CH_CMD_OFST); + } + spin_unlock_irqrestore(&fchan->vchan.lock, flags); + + return 0; +} + +static int st_fdma_terminate_all(struct dma_chan *chan) +{ + unsigned long flags; + LIST_HEAD(head); + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + int ch_id = fchan->vchan.chan.chan_id; + unsigned long cmd = FDMA_CMD_PAUSE(ch_id); + + dev_dbg(fchan->fdev->dev, "terminate chan:%d\n", ch_id); + + spin_lock_irqsave(&fchan->vchan.lock, flags); + fdma_write(fchan->fdev, cmd, FDMA_CMD_SET_OFST); + fchan->fdesc = NULL; + vchan_get_all_descriptors(&fchan->vchan, &head); + spin_unlock_irqrestore(&fchan->vchan.lock, flags); + vchan_dma_desc_free_list(&fchan->vchan, &head); + + return 0; +} + +static int st_fdma_slave_config(struct dma_chan *chan, + struct dma_slave_config *slave_cfg) +{ + struct st_fdma_chan *fchan = to_st_fdma_chan(chan); + + memcpy(&fchan->scfg, slave_cfg, sizeof(fchan->scfg)); + return 0; +} + +static const struct st_fdma_driverdata fdma_mpe31_stih407_11 = { + .name = "STiH407", + .id = 0, +}; + +static const struct st_fdma_driverdata fdma_mpe31_stih407_12 = { + .name = "STiH407", + .id = 1, +}; + +static const struct st_fdma_driverdata fdma_mpe31_stih407_13 = { + .name = "STiH407", + .id = 2, +}; + +static const struct of_device_id st_fdma_match[] = { + { .compatible = "st,stih407-fdma-mpe31-11" + , .data = &fdma_mpe31_stih407_11 }, + { .compatible = "st,stih407-fdma-mpe31-12" + , .data = &fdma_mpe31_stih407_12 }, + { .compatible = "st,stih407-fdma-mpe31-13" + , .data = &fdma_mpe31_stih407_13 }, + {}, +}; +MODULE_DEVICE_TABLE(of, st_fdma_match); + +static int st_fdma_parse_dt(struct platform_device *pdev, + const struct st_fdma_driverdata *drvdata, + struct st_fdma_dev *fdev) +{ + snprintf(fdev->fw_name, FW_NAME_SIZE, "fdma_%s_%d.elf", + drvdata->name, drvdata->id); + + return of_property_read_u32(pdev->dev.of_node, "dma-channels", + &fdev->nr_channels); +} +#define FDMA_DMA_BUSWIDTHS (BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \ + BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_3_BYTES) | \ + BIT(DMA_SLAVE_BUSWIDTH_4_BYTES)) + +static void st_fdma_free(struct st_fdma_dev *fdev) +{ + struct st_fdma_chan *fchan; + int i; + + for (i = 0; i < fdev->nr_channels; i++) { + fchan = &fdev->chans[i]; + list_del(&fchan->vchan.chan.device_node); + tasklet_kill(&fchan->vchan.task); + } +} + +static int st_fdma_probe(struct platform_device *pdev) +{ + struct st_fdma_dev *fdev; + const struct of_device_id *match; + struct device_node *np = pdev->dev.of_node; + const struct st_fdma_driverdata *drvdata; + int ret, i; + + match = of_match_device((st_fdma_match), &pdev->dev); + if (!match || !match->data) { + dev_err(&pdev->dev, "No device match found\n"); + return -ENODEV; + } + + drvdata = match->data; + + fdev = devm_kzalloc(&pdev->dev, sizeof(*fdev), GFP_KERNEL); + if (!fdev) + return -ENOMEM; + + ret = st_fdma_parse_dt(pdev, drvdata, fdev); + if (ret) { + dev_err(&pdev->dev, "unable to find platform data\n"); + goto err; + } + + fdev->chans = devm_kcalloc(&pdev->dev, fdev->nr_channels, + sizeof(struct st_fdma_chan), GFP_KERNEL); + if (!fdev->chans) + return -ENOMEM; + + fdev->dev = &pdev->dev; + fdev->drvdata = drvdata; + platform_set_drvdata(pdev, fdev); + + fdev->irq = platform_get_irq(pdev, 0); + if (fdev->irq < 0) { + dev_err(&pdev->dev, "Failed to get irq resource\n"); + return -EINVAL; + } + + ret = devm_request_irq(&pdev->dev, fdev->irq, st_fdma_irq_handler, 0, + dev_name(&pdev->dev), fdev); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq (%d)\n", ret); + goto err; + } + + fdev->slim_rproc = st_slim_rproc_alloc(pdev, fdev->fw_name); + if (IS_ERR(fdev->slim_rproc)) { + ret = PTR_ERR(fdev->slim_rproc); + dev_err(&pdev->dev, "slim_rproc_alloc failed (%d)\n", ret); + goto err; + } + + /* Initialise list of FDMA channels */ + INIT_LIST_HEAD(&fdev->dma_device.channels); + for (i = 0; i < fdev->nr_channels; i++) { + struct st_fdma_chan *fchan = &fdev->chans[i]; + + fchan->fdev = fdev; + fchan->vchan.desc_free = st_fdma_free_desc; + vchan_init(&fchan->vchan, &fdev->dma_device); + } + + /* Initialise the FDMA dreq (reserve 0 & 31 for FDMA use) */ + fdev->dreq_mask = BIT(0) | BIT(31); + + dma_cap_set(DMA_SLAVE, fdev->dma_device.cap_mask); + dma_cap_set(DMA_CYCLIC, fdev->dma_device.cap_mask); + dma_cap_set(DMA_MEMCPY, fdev->dma_device.cap_mask); + + fdev->dma_device.dev = &pdev->dev; + fdev->dma_device.device_alloc_chan_resources = st_fdma_alloc_chan_res; + fdev->dma_device.device_free_chan_resources = st_fdma_free_chan_res; + fdev->dma_device.device_prep_dma_cyclic = st_fdma_prep_dma_cyclic; + fdev->dma_device.device_prep_slave_sg = st_fdma_prep_slave_sg; + fdev->dma_device.device_prep_dma_memcpy = st_fdma_prep_dma_memcpy; + fdev->dma_device.device_tx_status = st_fdma_tx_status; + fdev->dma_device.device_issue_pending = st_fdma_issue_pending; + fdev->dma_device.device_terminate_all = st_fdma_terminate_all; + fdev->dma_device.device_config = st_fdma_slave_config; + fdev->dma_device.device_pause = st_fdma_pause; + fdev->dma_device.device_resume = st_fdma_resume; + + fdev->dma_device.src_addr_widths = FDMA_DMA_BUSWIDTHS; + fdev->dma_device.dst_addr_widths = FDMA_DMA_BUSWIDTHS; + fdev->dma_device.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); + fdev->dma_device.residue_granularity = DMA_RESIDUE_GRANULARITY_BURST; + + ret = dma_async_device_register(&fdev->dma_device); + if (ret) { + dev_err(&pdev->dev, + "Failed to register DMA device (%d)\n", ret); + goto err_rproc; + } + + ret = of_dma_controller_register(np, st_fdma_of_xlate, fdev); + if (ret) { + dev_err(&pdev->dev, + "Failed to register controller (%d)\n", ret); + goto err_dma_dev; + } + + dev_info(&pdev->dev, "ST FDMA engine driver, irq:%d\n", fdev->irq); + + return 0; + +err_dma_dev: + dma_async_device_unregister(&fdev->dma_device); +err_rproc: + st_fdma_free(fdev); + st_slim_rproc_put(fdev->slim_rproc); +err: + return ret; +} + +static int st_fdma_remove(struct platform_device *pdev) +{ + struct st_fdma_dev *fdev = platform_get_drvdata(pdev); + + devm_free_irq(&pdev->dev, fdev->irq, fdev); + st_slim_rproc_put(fdev->slim_rproc); + of_dma_controller_free(pdev->dev.of_node); + dma_async_device_unregister(&fdev->dma_device); + + return 0; +} + +static struct platform_driver st_fdma_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = st_fdma_match, + }, + .probe = st_fdma_probe, + .remove = st_fdma_remove, +}; +module_platform_driver(st_fdma_platform_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("STMicroelectronics FDMA engine driver"); +MODULE_AUTHOR("Ludovic.barre <Ludovic.barre@st.com>"); +MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>"); +MODULE_ALIAS("platform: " DRIVER_NAME); diff --git a/drivers/dma/st_fdma.h b/drivers/dma/st_fdma.h new file mode 100644 index 000000000000..c58e00d4ab37 --- /dev/null +++ b/drivers/dma/st_fdma.h @@ -0,0 +1,249 @@ +/* + * DMA driver header for STMicroelectronics STi FDMA controller + * + * Copyright (C) 2014 STMicroelectronics + * + * Author: Ludovic Barre <Ludovic.barre@st.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef __DMA_ST_FDMA_H +#define __DMA_ST_FDMA_H + +#include <linux/dmaengine.h> +#include <linux/dmapool.h> +#include <linux/io.h> +#include <linux/remoteproc/st_slim_rproc.h> +#include "virt-dma.h" + +#define ST_FDMA_NR_DREQS 32 +#define FW_NAME_SIZE 30 +#define DRIVER_NAME "st-fdma" + +/** + * struct st_fdma_generic_node - Free running/paced generic node + * + * @length: Length in bytes of a line in a 2D mem to mem + * @sstride: Stride, in bytes, between source lines in a 2D data move + * @dstride: Stride, in bytes, between destination lines in a 2D data move + */ +struct st_fdma_generic_node { + u32 length; + u32 sstride; + u32 dstride; +}; + +/** + * struct st_fdma_hw_node - Node structure used by fdma hw + * + * @next: Pointer to next node + * @control: Transfer Control Parameters + * @nbytes: Number of Bytes to read + * @saddr: Source address + * @daddr: Destination address + * + * @generic: generic node for free running/paced transfert type + * 2 others transfert type are possible, but not yet implemented + * + * The NODE structures must be aligned to a 32 byte boundary + */ +struct st_fdma_hw_node { + u32 next; + u32 control; + u32 nbytes; + u32 saddr; + u32 daddr; + union { + struct st_fdma_generic_node generic; + }; +} __aligned(32); + +/* + * node control parameters + */ +#define FDMA_NODE_CTRL_REQ_MAP_MASK GENMASK(4, 0) +#define FDMA_NODE_CTRL_REQ_MAP_FREE_RUN 0x0 +#define FDMA_NODE_CTRL_REQ_MAP_DREQ(n) ((n)&FDMA_NODE_CTRL_REQ_MAP_MASK) +#define FDMA_NODE_CTRL_REQ_MAP_EXT FDMA_NODE_CTRL_REQ_MAP_MASK +#define FDMA_NODE_CTRL_SRC_MASK GENMASK(6, 5) +#define FDMA_NODE_CTRL_SRC_STATIC BIT(5) +#define FDMA_NODE_CTRL_SRC_INCR BIT(6) +#define FDMA_NODE_CTRL_DST_MASK GENMASK(8, 7) +#define FDMA_NODE_CTRL_DST_STATIC BIT(7) +#define FDMA_NODE_CTRL_DST_INCR BIT(8) +#define FDMA_NODE_CTRL_SECURE BIT(15) +#define FDMA_NODE_CTRL_PAUSE_EON BIT(30) +#define FDMA_NODE_CTRL_INT_EON BIT(31) + +/** + * struct st_fdma_sw_node - descriptor structure for link list + * + * @pdesc: Physical address of desc + * @node: link used for putting this into a channel queue + */ +struct st_fdma_sw_node { + dma_addr_t pdesc; + struct st_fdma_hw_node *desc; +}; + +#define NAME_SZ 10 + +struct st_fdma_driverdata { + u32 id; + char name[NAME_SZ]; +}; + +struct st_fdma_desc { + struct virt_dma_desc vdesc; + struct st_fdma_chan *fchan; + bool iscyclic; + unsigned int n_nodes; + struct st_fdma_sw_node node[]; +}; + +enum st_fdma_type { + ST_FDMA_TYPE_FREE_RUN, + ST_FDMA_TYPE_PACED, +}; + +struct st_fdma_cfg { + struct device_node *of_node; + enum st_fdma_type type; + dma_addr_t dev_addr; + enum dma_transfer_direction dir; + int req_line; /* request line */ + long req_ctrl; /* Request control */ +}; + +struct st_fdma_chan { + struct st_fdma_dev *fdev; + struct dma_pool *node_pool; + struct dma_slave_config scfg; + struct st_fdma_cfg cfg; + + int dreq_line; + + struct virt_dma_chan vchan; + struct st_fdma_desc *fdesc; + enum dma_status status; +}; + +struct st_fdma_dev { + struct device *dev; + const struct st_fdma_driverdata *drvdata; + struct dma_device dma_device; + + struct st_slim_rproc *slim_rproc; + + int irq; + + struct st_fdma_chan *chans; + + spinlock_t dreq_lock; + unsigned long dreq_mask; + + u32 nr_channels; + char fw_name[FW_NAME_SIZE]; +}; + +/* Peripheral Registers*/ + +#define FDMA_CMD_STA_OFST 0xFC0 +#define FDMA_CMD_SET_OFST 0xFC4 +#define FDMA_CMD_CLR_OFST 0xFC8 +#define FDMA_CMD_MASK_OFST 0xFCC +#define FDMA_CMD_START(ch) (0x1 << (ch << 1)) +#define FDMA_CMD_PAUSE(ch) (0x2 << (ch << 1)) +#define FDMA_CMD_FLUSH(ch) (0x3 << (ch << 1)) + +#define FDMA_INT_STA_OFST 0xFD0 +#define FDMA_INT_STA_CH 0x1 +#define FDMA_INT_STA_ERR 0x2 + +#define FDMA_INT_SET_OFST 0xFD4 +#define FDMA_INT_CLR_OFST 0xFD8 +#define FDMA_INT_MASK_OFST 0xFDC + +#define fdma_read(fdev, name) \ + readl((fdev)->slim_rproc->peri + name) + +#define fdma_write(fdev, val, name) \ + writel((val), (fdev)->slim_rproc->peri + name) + +/* fchan interface (dmem) */ +#define FDMA_CH_CMD_OFST 0x200 +#define FDMA_CH_CMD_STA_MASK GENMASK(1, 0) +#define FDMA_CH_CMD_STA_IDLE (0x0) +#define FDMA_CH_CMD_STA_START (0x1) +#define FDMA_CH_CMD_STA_RUNNING (0x2) +#define FDMA_CH_CMD_STA_PAUSED (0x3) +#define FDMA_CH_CMD_ERR_MASK GENMASK(4, 2) +#define FDMA_CH_CMD_ERR_INT (0x0 << 2) +#define FDMA_CH_CMD_ERR_NAND (0x1 << 2) +#define FDMA_CH_CMD_ERR_MCHI (0x2 << 2) +#define FDMA_CH_CMD_DATA_MASK GENMASK(31, 5) +#define fchan_read(fchan, name) \ + readl((fchan)->fdev->slim_rproc->mem[ST_SLIM_DMEM].cpu_addr \ + + (fchan)->vchan.chan.chan_id * 0x4 \ + + name) + +#define fchan_write(fchan, val, name) \ + writel((val), (fchan)->fdev->slim_rproc->mem[ST_SLIM_DMEM].cpu_addr \ + + (fchan)->vchan.chan.chan_id * 0x4 \ + + name) + +/* req interface */ +#define FDMA_REQ_CTRL_OFST 0x240 +#define dreq_write(fchan, val, name) \ + writel((val), (fchan)->fdev->slim_rproc->mem[ST_SLIM_DMEM].cpu_addr \ + + fchan->dreq_line * 0x04 \ + + name) +/* node interface */ +#define FDMA_NODE_SZ 128 +#define FDMA_PTRN_OFST 0x800 +#define FDMA_CNTN_OFST 0x808 +#define FDMA_SADDRN_OFST 0x80c +#define FDMA_DADDRN_OFST 0x810 +#define fnode_read(fchan, name) \ + readl((fchan)->fdev->slim_rproc->mem[ST_SLIM_DMEM].cpu_addr \ + + (fchan)->vchan.chan.chan_id * FDMA_NODE_SZ \ + + name) + +#define fnode_write(fchan, val, name) \ + writel((val), (fchan)->fdev->slim_rproc->mem[ST_SLIM_DMEM].cpu_addr \ + + (fchan)->vchan.chan.chan_id * FDMA_NODE_SZ \ + + name) + +/* + * request control bits + */ +#define FDMA_REQ_CTRL_NUM_OPS_MASK GENMASK(31, 24) +#define FDMA_REQ_CTRL_NUM_OPS(n) (FDMA_REQ_CTRL_NUM_OPS_MASK & \ + ((n) << 24)) +#define FDMA_REQ_CTRL_INITIATOR_MASK BIT(22) +#define FDMA_REQ_CTRL_INIT0 (0x0 << 22) +#define FDMA_REQ_CTRL_INIT1 (0x1 << 22) +#define FDMA_REQ_CTRL_INC_ADDR_ON BIT(21) +#define FDMA_REQ_CTRL_DATA_SWAP_ON BIT(17) +#define FDMA_REQ_CTRL_WNR BIT(14) +#define FDMA_REQ_CTRL_OPCODE_MASK GENMASK(7, 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST1 (0x0 << 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST2 (0x1 << 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST4 (0x2 << 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST8 (0x3 << 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST16 (0x4 << 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST32 (0x5 << 4) +#define FDMA_REQ_CTRL_OPCODE_LD_ST64 (0x6 << 4) +#define FDMA_REQ_CTRL_HOLDOFF_MASK GENMASK(2, 0) +#define FDMA_REQ_CTRL_HOLDOFF(n) ((n) & FDMA_REQ_CTRL_HOLDOFF_MASK) + +/* bits used by client to configure request control */ +#define FDMA_REQ_CTRL_CFG_MASK (FDMA_REQ_CTRL_HOLDOFF_MASK | \ + FDMA_REQ_CTRL_DATA_SWAP_ON | \ + FDMA_REQ_CTRL_INC_ADDR_ON | \ + FDMA_REQ_CTRL_INITIATOR_MASK) + +#endif /* __DMA_ST_FDMA_H */ diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig index f396bfef5d42..8f9cf0bc571c 100644 --- a/drivers/remoteproc/Kconfig +++ b/drivers/remoteproc/Kconfig @@ -1,20 +1,21 @@ menu "Remoteproc drivers" -# REMOTEPROC gets selected by whoever wants it config REMOTEPROC - tristate + tristate "Support for Remote Processor subsystem" depends on HAS_DMA select CRC32 select FW_LOADER select VIRTIO select VIRTUALIZATION +if REMOTEPROC + config OMAP_REMOTEPROC tristate "OMAP remoteproc support" depends on HAS_DMA depends on ARCH_OMAP4 || SOC_OMAP5 depends on OMAP_IOMMU - select REMOTEPROC + depends on REMOTEPROC select MAILBOX select OMAP2PLUS_MBOX select RPMSG_VIRTIO @@ -31,20 +32,10 @@ config OMAP_REMOTEPROC It's safe to say n here if you're not interested in multimedia offloading or just want a bare minimum kernel. -config STE_MODEM_RPROC - tristate "STE-Modem remoteproc support" - depends on HAS_DMA - select REMOTEPROC - default n - help - Say y or m here to support STE-Modem shared memory driver. - This can be either built-in or a loadable module. - If unsure say N. - config WKUP_M3_RPROC tristate "AMx3xx Wakeup M3 remoteproc support" depends on SOC_AM33XX || SOC_AM43XX - select REMOTEPROC + depends on REMOTEPROC help Say y here to support Wakeup M3 remote processor on TI AM33xx and AM43xx family of SoCs. @@ -57,8 +48,8 @@ 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 - select REMOTEPROC select RPMSG_VIRTIO help Say y here to support DA8xx/OMAP-L13x remote processors via the @@ -77,6 +68,18 @@ config DA8XX_REMOTEPROC It's safe to say n here if you're not interested in multimedia offloading. +config QCOM_ADSP_PIL + tristate "Qualcomm ADSP Peripheral Image Loader" + depends on OF && ARCH_QCOM + depends on REMOTEPROC + depends on QCOM_SMEM + select MFD_SYSCON + select QCOM_MDT_LOADER + select QCOM_SCM + help + Say y here to support the TrustZone based Peripherial Image Loader + for the Qualcomm ADSP remote processors. + config QCOM_MDT_LOADER tristate @@ -84,25 +87,22 @@ config QCOM_Q6V5_PIL tristate "Qualcomm Hexagon V5 Peripherial Image Loader" depends on OF && ARCH_QCOM depends on QCOM_SMEM + depends on REMOTEPROC select MFD_SYSCON select QCOM_MDT_LOADER - select REMOTEPROC + select QCOM_SCM help Say y here to support the Qualcomm Peripherial Image Loader for the Hexagon V5 based remote processors. -config QCOM_WCNSS_IRIS - tristate - depends on OF && ARCH_QCOM - config QCOM_WCNSS_PIL tristate "Qualcomm WCNSS Peripheral Image Loader" depends on OF && ARCH_QCOM + depends on QCOM_SMD || (COMPILE_TEST && QCOM_SMD=n) depends on QCOM_SMEM + depends on REMOTEPROC select QCOM_MDT_LOADER select QCOM_SCM - select QCOM_WCNSS_IRIS - select REMOTEPROC help Say y here to support the Peripheral Image Loader for the Qualcomm Wireless Connectivity Subsystem. @@ -110,10 +110,16 @@ config QCOM_WCNSS_PIL config ST_REMOTEPROC tristate "ST remoteproc support" depends on ARCH_STI - select REMOTEPROC + depends on REMOTEPROC help Say y here to support ST's adjunct processors via the remote processor framework. This can be either built-in or a loadable module. +config ST_SLIM_REMOTEPROC + tristate + depends on REMOTEPROC + +endif # REMOTEPROC + endmenu diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile index 6dfb62ed643f..0938ea3c41ba 100644 --- a/drivers/remoteproc/Makefile +++ b/drivers/remoteproc/Makefile @@ -5,14 +5,17 @@ obj-$(CONFIG_REMOTEPROC) += remoteproc.o remoteproc-y := remoteproc_core.o remoteproc-y += remoteproc_debugfs.o +remoteproc-y += remoteproc_sysfs.o remoteproc-y += remoteproc_virtio.o remoteproc-y += remoteproc_elf_loader.o 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_ADSP_PIL) += qcom_adsp_pil.o obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o -obj-$(CONFIG_QCOM_WCNSS_IRIS) += qcom_wcnss_iris.o -obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss.o +obj-$(CONFIG_QCOM_WCNSS_PIL) += qcom_wcnss_pil.o +qcom_wcnss_pil-y += qcom_wcnss.o +qcom_wcnss_pil-y += qcom_wcnss_iris.o obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o +obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o diff --git a/drivers/remoteproc/qcom_adsp_pil.c b/drivers/remoteproc/qcom_adsp_pil.c new file mode 100644 index 000000000000..43a4ed2f346c --- /dev/null +++ b/drivers/remoteproc/qcom_adsp_pil.c @@ -0,0 +1,428 @@ +/* + * Qualcomm ADSP Peripheral Image Loader for MSM8974 and MSM8996 + * + * 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/firmware.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/qcom_scm.h> +#include <linux/regulator/consumer.h> +#include <linux/remoteproc.h> +#include <linux/soc/qcom/smem.h> +#include <linux/soc/qcom/smem_state.h> + +#include "qcom_mdt_loader.h" +#include "remoteproc_internal.h" + +#define ADSP_CRASH_REASON_SMEM 423 +#define ADSP_FIRMWARE_NAME "adsp.mdt" +#define ADSP_PAS_ID 1 + +struct qcom_adsp { + struct device *dev; + struct rproc *rproc; + + int wdog_irq; + int fatal_irq; + int ready_irq; + int handover_irq; + int stop_ack_irq; + + struct qcom_smem_state *state; + unsigned stop_bit; + + struct clk *xo; + + struct regulator *cx_supply; + + struct completion start_done; + struct completion stop_done; + + phys_addr_t mem_phys; + phys_addr_t mem_reloc; + void *mem_region; + size_t mem_size; +}; + +static int adsp_load(struct rproc *rproc, const struct firmware *fw) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + phys_addr_t fw_addr; + size_t fw_size; + bool relocate; + int ret; + + ret = qcom_scm_pas_init_image(ADSP_PAS_ID, fw->data, fw->size); + if (ret) { + dev_err(&rproc->dev, "invalid firmware metadata\n"); + return ret; + } + + ret = qcom_mdt_parse(fw, &fw_addr, &fw_size, &relocate); + if (ret) { + dev_err(&rproc->dev, "failed to parse mdt header\n"); + return ret; + } + + if (relocate) { + adsp->mem_reloc = fw_addr; + + ret = qcom_scm_pas_mem_setup(ADSP_PAS_ID, adsp->mem_phys, fw_size); + if (ret) { + dev_err(&rproc->dev, "unable to setup memory for image\n"); + return ret; + } + } + + return qcom_mdt_load(rproc, fw, rproc->firmware); +} + +static const struct rproc_fw_ops adsp_fw_ops = { + .find_rsc_table = qcom_mdt_find_rsc_table, + .load = adsp_load, +}; + +static int adsp_start(struct rproc *rproc) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + int ret; + + ret = clk_prepare_enable(adsp->xo); + if (ret) + return ret; + + ret = regulator_enable(adsp->cx_supply); + if (ret) + goto disable_clocks; + + ret = qcom_scm_pas_auth_and_reset(ADSP_PAS_ID); + if (ret) { + dev_err(adsp->dev, + "failed to authenticate image and release reset\n"); + goto disable_regulators; + } + + ret = wait_for_completion_timeout(&adsp->start_done, + msecs_to_jiffies(5000)); + if (!ret) { + dev_err(adsp->dev, "start timed out\n"); + qcom_scm_pas_shutdown(ADSP_PAS_ID); + ret = -ETIMEDOUT; + goto disable_regulators; + } + + ret = 0; + +disable_regulators: + regulator_disable(adsp->cx_supply); +disable_clocks: + clk_disable_unprepare(adsp->xo); + + return ret; +} + +static int adsp_stop(struct rproc *rproc) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + int ret; + + qcom_smem_state_update_bits(adsp->state, + BIT(adsp->stop_bit), + BIT(adsp->stop_bit)); + + ret = wait_for_completion_timeout(&adsp->stop_done, + msecs_to_jiffies(5000)); + if (ret == 0) + dev_err(adsp->dev, "timed out on wait\n"); + + qcom_smem_state_update_bits(adsp->state, + BIT(adsp->stop_bit), + 0); + + ret = qcom_scm_pas_shutdown(ADSP_PAS_ID); + if (ret) + dev_err(adsp->dev, "failed to shutdown: %d\n", ret); + + return ret; +} + +static void *adsp_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct qcom_adsp *adsp = (struct qcom_adsp *)rproc->priv; + int offset; + + offset = da - adsp->mem_reloc; + if (offset < 0 || offset + len > adsp->mem_size) + return NULL; + + return adsp->mem_region + offset; +} + +static const struct rproc_ops adsp_ops = { + .start = adsp_start, + .stop = adsp_stop, + .da_to_va = adsp_da_to_va, +}; + +static irqreturn_t adsp_wdog_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + + rproc_report_crash(adsp->rproc, RPROC_WATCHDOG); + + return IRQ_HANDLED; +} + +static irqreturn_t adsp_fatal_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + size_t len; + char *msg; + + msg = qcom_smem_get(QCOM_SMEM_HOST_ANY, ADSP_CRASH_REASON_SMEM, &len); + if (!IS_ERR(msg) && len > 0 && msg[0]) + dev_err(adsp->dev, "fatal error received: %s\n", msg); + + rproc_report_crash(adsp->rproc, RPROC_FATAL_ERROR); + + if (!IS_ERR(msg)) + msg[0] = '\0'; + + return IRQ_HANDLED; +} + +static irqreturn_t adsp_ready_interrupt(int irq, void *dev) +{ + return IRQ_HANDLED; +} + +static irqreturn_t adsp_handover_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + + complete(&adsp->start_done); + + return IRQ_HANDLED; +} + +static irqreturn_t adsp_stop_ack_interrupt(int irq, void *dev) +{ + struct qcom_adsp *adsp = dev; + + complete(&adsp->stop_done); + + return IRQ_HANDLED; +} + +static int adsp_init_clock(struct qcom_adsp *adsp) +{ + int ret; + + adsp->xo = devm_clk_get(adsp->dev, "xo"); + if (IS_ERR(adsp->xo)) { + ret = PTR_ERR(adsp->xo); + if (ret != -EPROBE_DEFER) + dev_err(adsp->dev, "failed to get xo clock"); + return ret; + } + + return 0; +} + +static int adsp_init_regulator(struct qcom_adsp *adsp) +{ + adsp->cx_supply = devm_regulator_get(adsp->dev, "cx"); + if (IS_ERR(adsp->cx_supply)) + return PTR_ERR(adsp->cx_supply); + + regulator_set_load(adsp->cx_supply, 100000); + + return 0; +} + +static int adsp_request_irq(struct qcom_adsp *adsp, + 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_ONESHOT, + "adsp", adsp); + if (ret) + dev_err(&pdev->dev, "request %s IRQ failed\n", name); + + return ret; +} + +static int adsp_alloc_memory_region(struct qcom_adsp *adsp) +{ + struct device_node *node; + struct resource r; + int ret; + + node = of_parse_phandle(adsp->dev->of_node, "memory-region", 0); + if (!node) { + dev_err(adsp->dev, "no memory-region specified\n"); + return -EINVAL; + } + + ret = of_address_to_resource(node, 0, &r); + if (ret) + return ret; + + adsp->mem_phys = adsp->mem_reloc = r.start; + adsp->mem_size = resource_size(&r); + adsp->mem_region = devm_ioremap_wc(adsp->dev, adsp->mem_phys, adsp->mem_size); + if (!adsp->mem_region) { + dev_err(adsp->dev, "unable to map memory region: %pa+%zx\n", + &r.start, adsp->mem_size); + return -EBUSY; + } + + return 0; +} + +static int adsp_probe(struct platform_device *pdev) +{ + struct qcom_adsp *adsp; + struct rproc *rproc; + int ret; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + if (!qcom_scm_pas_supported(ADSP_PAS_ID)) { + dev_err(&pdev->dev, "PAS is not available for ADSP\n"); + return -ENXIO; + } + + rproc = rproc_alloc(&pdev->dev, pdev->name, &adsp_ops, + ADSP_FIRMWARE_NAME, sizeof(*adsp)); + if (!rproc) { + dev_err(&pdev->dev, "unable to allocate remoteproc\n"); + return -ENOMEM; + } + + rproc->fw_ops = &adsp_fw_ops; + + adsp = (struct qcom_adsp *)rproc->priv; + adsp->dev = &pdev->dev; + adsp->rproc = rproc; + platform_set_drvdata(pdev, adsp); + + init_completion(&adsp->start_done); + init_completion(&adsp->stop_done); + + ret = adsp_alloc_memory_region(adsp); + if (ret) + goto free_rproc; + + ret = adsp_init_clock(adsp); + if (ret) + goto free_rproc; + + ret = adsp_init_regulator(adsp); + if (ret) + goto free_rproc; + + ret = adsp_request_irq(adsp, pdev, "wdog", adsp_wdog_interrupt); + if (ret < 0) + goto free_rproc; + adsp->wdog_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "fatal", adsp_fatal_interrupt); + if (ret < 0) + goto free_rproc; + adsp->fatal_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "ready", adsp_ready_interrupt); + if (ret < 0) + goto free_rproc; + adsp->ready_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "handover", adsp_handover_interrupt); + if (ret < 0) + goto free_rproc; + adsp->handover_irq = ret; + + ret = adsp_request_irq(adsp, pdev, "stop-ack", adsp_stop_ack_interrupt); + if (ret < 0) + goto free_rproc; + adsp->stop_ack_irq = ret; + + adsp->state = qcom_smem_state_get(&pdev->dev, "stop", + &adsp->stop_bit); + if (IS_ERR(adsp->state)) { + ret = PTR_ERR(adsp->state); + goto free_rproc; + } + + ret = rproc_add(rproc); + if (ret) + goto free_rproc; + + return 0; + +free_rproc: + rproc_free(rproc); + + return ret; +} + +static int adsp_remove(struct platform_device *pdev) +{ + struct qcom_adsp *adsp = platform_get_drvdata(pdev); + + qcom_smem_state_put(adsp->state); + rproc_del(adsp->rproc); + rproc_free(adsp->rproc); + + return 0; +} + +static const struct of_device_id adsp_of_match[] = { + { .compatible = "qcom,msm8974-adsp-pil" }, + { .compatible = "qcom,msm8996-adsp-pil" }, + { }, +}; +MODULE_DEVICE_TABLE(of, adsp_of_match); + +static struct platform_driver adsp_driver = { + .probe = adsp_probe, + .remove = adsp_remove, + .driver = { + .name = "qcom_adsp_pil", + .of_match_table = adsp_of_match, + }, +}; + +module_platform_driver(adsp_driver); +MODULE_DESCRIPTION("Qualcomm MSM8974/MSM8996 ADSP Peripherial Image Loader"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_mdt_loader.c b/drivers/remoteproc/qcom_mdt_loader.c index 114e8e4cef67..2ff18cd6c096 100644 --- a/drivers/remoteproc/qcom_mdt_loader.c +++ b/drivers/remoteproc/qcom_mdt_loader.c @@ -20,6 +20,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/remoteproc.h> +#include <linux/sizes.h> #include <linux/slab.h> #include "remoteproc_internal.h" diff --git a/drivers/remoteproc/qcom_q6v5_pil.c b/drivers/remoteproc/qcom_q6v5_pil.c index 2e0caaaa766a..b08989b48df7 100644 --- a/drivers/remoteproc/qcom_q6v5_pil.c +++ b/drivers/remoteproc/qcom_q6v5_pil.c @@ -894,6 +894,7 @@ static const struct of_device_id q6v5_of_match[] = { { .compatible = "qcom,q6v5-pil", }, { }, }; +MODULE_DEVICE_TABLE(of, q6v5_of_match); static struct platform_driver q6v5_driver = { .probe = q6v5_probe, diff --git a/drivers/remoteproc/qcom_wcnss.c b/drivers/remoteproc/qcom_wcnss.c index f5cedeaafba1..ebd61f5d18bb 100644 --- a/drivers/remoteproc/qcom_wcnss.c +++ b/drivers/remoteproc/qcom_wcnss.c @@ -30,6 +30,7 @@ #include <linux/remoteproc.h> #include <linux/soc/qcom/smem.h> #include <linux/soc/qcom/smem_state.h> +#include <linux/rpmsg/qcom_smd.h> #include "qcom_mdt_loader.h" #include "remoteproc_internal.h" @@ -94,6 +95,10 @@ struct qcom_wcnss { phys_addr_t mem_reloc; void *mem_region; size_t mem_size; + + struct device_node *smd_node; + struct qcom_smd_edge *smd_edge; + struct rproc_subdev smd_subdev; }; static const struct wcnss_data riva_data = { @@ -143,7 +148,6 @@ void qcom_wcnss_assign_iris(struct qcom_wcnss *wcnss, mutex_unlock(&wcnss->iris_lock); } -EXPORT_SYMBOL_GPL(qcom_wcnss_assign_iris); static int wcnss_load(struct rproc *rproc, const struct firmware *fw) { @@ -396,6 +400,23 @@ static irqreturn_t wcnss_stop_ack_interrupt(int irq, void *dev) return IRQ_HANDLED; } +static int wcnss_smd_probe(struct rproc_subdev *subdev) +{ + struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev); + + wcnss->smd_edge = qcom_smd_register_edge(wcnss->dev, wcnss->smd_node); + + return IS_ERR(wcnss->smd_edge) ? PTR_ERR(wcnss->smd_edge) : 0; +} + +static void wcnss_smd_remove(struct rproc_subdev *subdev) +{ + struct qcom_wcnss *wcnss = container_of(subdev, struct qcom_wcnss, smd_subdev); + + qcom_smd_unregister_edge(wcnss->smd_edge); + wcnss->smd_edge = NULL; +} + static int wcnss_init_regulators(struct qcom_wcnss *wcnss, const struct wcnss_vreg_info *info, int num_vregs) @@ -578,6 +599,10 @@ static int wcnss_probe(struct platform_device *pdev) } } + wcnss->smd_node = of_get_child_by_name(pdev->dev.of_node, "smd-edge"); + if (wcnss->smd_node) + rproc_add_subdev(rproc, &wcnss->smd_subdev, wcnss_smd_probe, wcnss_smd_remove); + ret = rproc_add(rproc); if (ret) goto free_rproc; @@ -596,6 +621,7 @@ static int wcnss_remove(struct platform_device *pdev) of_platform_depopulate(&pdev->dev); + of_node_put(wcnss->smd_node); qcom_smem_state_put(wcnss->state); rproc_del(wcnss->rproc); rproc_free(wcnss->rproc); @@ -609,6 +635,7 @@ static const struct of_device_id wcnss_of_match[] = { { .compatible = "qcom,pronto-v2-pil", &pronto_v2_data }, { }, }; +MODULE_DEVICE_TABLE(of, wcnss_of_match); static struct platform_driver wcnss_driver = { .probe = wcnss_probe, @@ -619,6 +646,28 @@ static struct platform_driver wcnss_driver = { }, }; -module_platform_driver(wcnss_driver); +static int __init wcnss_init(void) +{ + int ret; + + ret = platform_driver_register(&wcnss_driver); + if (ret) + return ret; + + ret = platform_driver_register(&qcom_iris_driver); + if (ret) + platform_driver_unregister(&wcnss_driver); + + return ret; +} +module_init(wcnss_init); + +static void __exit wcnss_exit(void) +{ + platform_driver_unregister(&qcom_iris_driver); + platform_driver_unregister(&wcnss_driver); +} +module_exit(wcnss_exit); + MODULE_DESCRIPTION("Qualcomm Peripherial Image Loader for Wireless Subsystem"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/qcom_wcnss.h b/drivers/remoteproc/qcom_wcnss.h index 9dc4a9fe41e1..25fb7f62a457 100644 --- a/drivers/remoteproc/qcom_wcnss.h +++ b/drivers/remoteproc/qcom_wcnss.h @@ -4,6 +4,8 @@ struct qcom_iris; struct qcom_wcnss; +extern struct platform_driver qcom_iris_driver; + struct wcnss_vreg_info { const char * const name; int min_voltage; diff --git a/drivers/remoteproc/qcom_wcnss_iris.c b/drivers/remoteproc/qcom_wcnss_iris.c index f0ca24a8dd0b..e842be58e8c7 100644 --- a/drivers/remoteproc/qcom_wcnss_iris.c +++ b/drivers/remoteproc/qcom_wcnss_iris.c @@ -94,14 +94,12 @@ disable_regulators: return ret; } -EXPORT_SYMBOL_GPL(qcom_iris_enable); void qcom_iris_disable(struct qcom_iris *iris) { clk_disable_unprepare(iris->xo_clk); regulator_bulk_disable(iris->num_vregs, iris->vregs); } -EXPORT_SYMBOL_GPL(qcom_iris_disable); static int qcom_iris_probe(struct platform_device *pdev) { @@ -173,8 +171,9 @@ static const struct of_device_id iris_of_match[] = { { .compatible = "qcom,wcn3680", .data = &wcn3680_data }, {} }; +MODULE_DEVICE_TABLE(of, iris_of_match); -static struct platform_driver wcnss_driver = { +struct platform_driver qcom_iris_driver = { .probe = qcom_iris_probe, .remove = qcom_iris_remove, .driver = { @@ -182,7 +181,3 @@ static struct platform_driver wcnss_driver = { .of_match_table = iris_of_match, }, }; - -module_platform_driver(wcnss_driver); -MODULE_DESCRIPTION("Qualcomm Wireless Subsystem Iris driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c index c6bfb3496684..9a507e77eced 100644 --- a/drivers/remoteproc/remoteproc_core.c +++ b/drivers/remoteproc/remoteproc_core.c @@ -236,6 +236,10 @@ int rproc_alloc_vring(struct rproc_vdev *rvdev, int i) } notifyid = ret; + /* Potentially bump max_notifyid */ + if (notifyid > rproc->max_notifyid) + rproc->max_notifyid = notifyid; + dev_dbg(dev, "vring%d: va %p dma %pad size 0x%x idr %d\n", i, va, &dma, size, notifyid); @@ -296,6 +300,20 @@ void rproc_free_vring(struct rproc_vring *rvring) rsc->vring[idx].notifyid = -1; } +static int rproc_vdev_do_probe(struct rproc_subdev *subdev) +{ + struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); + + return rproc_add_virtio_dev(rvdev, rvdev->id); +} + +static void rproc_vdev_do_remove(struct rproc_subdev *subdev) +{ + struct rproc_vdev *rvdev = container_of(subdev, struct rproc_vdev, subdev); + + rproc_remove_virtio_dev(rvdev); +} + /** * rproc_handle_vdev() - handle a vdev fw resource * @rproc: the remote processor @@ -356,6 +374,9 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, if (!rvdev) return -ENOMEM; + kref_init(&rvdev->refcount); + + rvdev->id = rsc->id; rvdev->rproc = rproc; /* parse the vrings */ @@ -368,22 +389,51 @@ static int rproc_handle_vdev(struct rproc *rproc, struct fw_rsc_vdev *rsc, /* remember the resource offset*/ rvdev->rsc_offset = offset; + /* allocate the vring resources */ + for (i = 0; i < rsc->num_of_vrings; i++) { + ret = rproc_alloc_vring(rvdev, i); + if (ret) + goto unwind_vring_allocations; + } + + /* track the rvdevs list reference */ + kref_get(&rvdev->refcount); + list_add_tail(&rvdev->node, &rproc->rvdevs); - /* it is now safe to add the virtio device */ - ret = rproc_add_virtio_dev(rvdev, rsc->id); - if (ret) - goto remove_rvdev; + rproc_add_subdev(rproc, &rvdev->subdev, + rproc_vdev_do_probe, rproc_vdev_do_remove); return 0; -remove_rvdev: - list_del(&rvdev->node); +unwind_vring_allocations: + for (i--; i >= 0; i--) + rproc_free_vring(&rvdev->vring[i]); free_rvdev: kfree(rvdev); return ret; } +void rproc_vdev_release(struct kref *ref) +{ + struct rproc_vdev *rvdev = container_of(ref, struct rproc_vdev, refcount); + struct rproc_vring *rvring; + struct rproc *rproc = rvdev->rproc; + int id; + + for (id = 0; id < ARRAY_SIZE(rvdev->vring); id++) { + rvring = &rvdev->vring[id]; + if (!rvring->va) + continue; + + rproc_free_vring(rvring); + } + + rproc_remove_subdev(rproc, &rvdev->subdev); + list_del(&rvdev->node); + kfree(rvdev); +} + /** * rproc_handle_trace() - handle a shared trace buffer resource * @rproc: the remote processor @@ -673,15 +723,6 @@ free_carv: return ret; } -static int rproc_count_vrings(struct rproc *rproc, struct fw_rsc_vdev *rsc, - int offset, int avail) -{ - /* Summarize the number of notification IDs */ - rproc->max_notifyid += rsc->num_of_vrings; - - return 0; -} - /* * A lookup table for resource handlers. The indices are defined in * enum fw_resource_type. @@ -690,10 +731,6 @@ static rproc_handle_resource_t rproc_loading_handlers[RSC_LAST] = { [RSC_CARVEOUT] = (rproc_handle_resource_t)rproc_handle_carveout, [RSC_DEVMEM] = (rproc_handle_resource_t)rproc_handle_devmem, [RSC_TRACE] = (rproc_handle_resource_t)rproc_handle_trace, - [RSC_VDEV] = (rproc_handle_resource_t)rproc_count_vrings, -}; - -static rproc_handle_resource_t rproc_vdev_handler[RSC_LAST] = { [RSC_VDEV] = (rproc_handle_resource_t)rproc_handle_vdev, }; @@ -736,6 +773,34 @@ static int rproc_handle_resources(struct rproc *rproc, int len, return ret; } +static int rproc_probe_subdevices(struct rproc *rproc) +{ + struct rproc_subdev *subdev; + int ret; + + list_for_each_entry(subdev, &rproc->subdevs, node) { + ret = subdev->probe(subdev); + if (ret) + goto unroll_registration; + } + + return 0; + +unroll_registration: + list_for_each_entry_continue_reverse(subdev, &rproc->subdevs, node) + subdev->remove(subdev); + + return ret; +} + +static void rproc_remove_subdevices(struct rproc *rproc) +{ + struct rproc_subdev *subdev; + + list_for_each_entry(subdev, &rproc->subdevs, node) + subdev->remove(subdev); +} + /** * rproc_resource_cleanup() - clean up and free all acquired resources * @rproc: rproc handle @@ -782,7 +847,7 @@ static void rproc_resource_cleanup(struct rproc *rproc) /* clean up remote vdev entries */ list_for_each_entry_safe(rvdev, rvtmp, &rproc->rvdevs, node) - rproc_remove_virtio_dev(rvdev); + kref_put(&rvdev->refcount, rproc_vdev_release); } /* @@ -824,25 +889,16 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) /* * Create a copy of the resource table. When a virtio device starts * and calls vring_new_virtqueue() the address of the allocated vring - * will be stored in the cached_table. Before the device is started, - * cached_table will be copied into device memory. + * will be stored in the table_ptr. Before the device is started, + * table_ptr will be copied into device memory. */ - rproc->cached_table = kmemdup(table, tablesz, GFP_KERNEL); - if (!rproc->cached_table) + rproc->table_ptr = kmemdup(table, tablesz, GFP_KERNEL); + if (!rproc->table_ptr) goto clean_up; - rproc->table_ptr = rproc->cached_table; - /* reset max_notifyid */ rproc->max_notifyid = -1; - /* look for virtio devices and register them */ - ret = rproc_handle_resources(rproc, tablesz, rproc_vdev_handler); - if (ret) { - dev_err(dev, "Failed to handle vdev resources: %d\n", ret); - goto clean_up; - } - /* handle fw resources which are required to boot rproc */ ret = rproc_handle_resources(rproc, tablesz, rproc_loading_handlers); if (ret) { @@ -858,18 +914,16 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) } /* - * The starting device has been given the rproc->cached_table as the + * The starting device has been given the rproc->table_ptr as the * resource table. The address of the vring along with the other - * allocated resources (carveouts etc) is stored in cached_table. + * allocated resources (carveouts etc) is stored in table_ptr. * In order to pass this information to the remote device we must copy * this information to device memory. We also update the table_ptr so * that any subsequent changes will be applied to the loaded version. */ loaded_table = rproc_find_loaded_rsc_table(rproc, fw); - if (loaded_table) { - memcpy(loaded_table, rproc->cached_table, tablesz); - rproc->table_ptr = loaded_table; - } + if (loaded_table) + memcpy(loaded_table, rproc->table_ptr, tablesz); /* power up the remote processor */ ret = rproc->ops->start(rproc); @@ -878,17 +932,26 @@ static int rproc_fw_boot(struct rproc *rproc, const struct firmware *fw) goto clean_up_resources; } + /* probe any subdevices for the remote processor */ + ret = rproc_probe_subdevices(rproc); + if (ret) { + dev_err(dev, "failed to probe subdevices for %s: %d\n", + rproc->name, ret); + goto stop_rproc; + } + rproc->state = RPROC_RUNNING; dev_info(dev, "remote processor %s is now up\n", rproc->name); return 0; +stop_rproc: + rproc->ops->stop(rproc); clean_up_resources: rproc_resource_cleanup(rproc); clean_up: - kfree(rproc->cached_table); - rproc->cached_table = NULL; + kfree(rproc->table_ptr); rproc->table_ptr = NULL; rproc_disable_iommu(rproc); @@ -909,7 +972,7 @@ static void rproc_fw_config_virtio(const struct firmware *fw, void *context) /* if rproc is marked always-on, request it to boot */ if (rproc->auto_boot) - rproc_boot_nowait(rproc); + rproc_boot(rproc); release_firmware(fw); /* allow rproc_del() contexts, if any, to proceed */ @@ -1007,7 +1070,6 @@ static void rproc_crash_handler_work(struct work_struct *work) /** * __rproc_boot() - boot a remote processor * @rproc: handle of a remote processor - * @wait: wait for rproc registration completion * * Boot a remote processor (i.e. load its firmware, power it on, ...). * @@ -1016,7 +1078,7 @@ static void rproc_crash_handler_work(struct work_struct *work) * * Returns 0 on success, and an appropriate error value otherwise. */ -static int __rproc_boot(struct rproc *rproc, bool wait) +static int __rproc_boot(struct rproc *rproc) { const struct firmware *firmware_p; struct device *dev; @@ -1050,10 +1112,6 @@ static int __rproc_boot(struct rproc *rproc, bool wait) goto downref_rproc; } - /* if rproc virtio is not yet configured, wait */ - if (wait) - wait_for_completion(&rproc->firmware_loading_complete); - ret = rproc_fw_boot(rproc, firmware_p); release_firmware(firmware_p); @@ -1072,22 +1130,11 @@ unlock_mutex: */ int rproc_boot(struct rproc *rproc) { - return __rproc_boot(rproc, true); + return __rproc_boot(rproc); } EXPORT_SYMBOL(rproc_boot); /** - * rproc_boot_nowait() - boot a remote processor - * @rproc: handle of a remote processor - * - * Same as rproc_boot() but don't wait for rproc registration completion - */ -int rproc_boot_nowait(struct rproc *rproc) -{ - return __rproc_boot(rproc, false); -} - -/** * rproc_shutdown() - power off the remote processor * @rproc: the remote processor * @@ -1121,6 +1168,9 @@ void rproc_shutdown(struct rproc *rproc) if (!atomic_dec_and_test(&rproc->power)) goto out; + /* remove any subdevices for the remote processor */ + rproc_remove_subdevices(rproc); + /* power off the remote processor */ ret = rproc->ops->stop(rproc); if (ret) { @@ -1135,8 +1185,7 @@ void rproc_shutdown(struct rproc *rproc) rproc_disable_iommu(rproc); /* Free the copy of the resource table */ - kfree(rproc->cached_table); - rproc->cached_table = NULL; + kfree(rproc->table_ptr); rproc->table_ptr = NULL; /* if in crash state, unlock crash handler */ @@ -1233,9 +1282,6 @@ int rproc_add(struct rproc *rproc) dev_info(dev, "%s is available\n", rproc->name); - dev_info(dev, "Note: remoteproc is still under development and considered experimental.\n"); - dev_info(dev, "THE BINARY FORMAT IS NOT YET FINALIZED, and backward compatibility isn't yet guaranteed.\n"); - /* create debugfs entries */ rproc_create_debug_dir(rproc); ret = rproc_add_virtio_devices(rproc); @@ -1273,6 +1319,7 @@ static void rproc_type_release(struct device *dev) if (rproc->index >= 0) ida_simple_remove(&rproc_dev_index, rproc->index); + kfree(rproc->firmware); kfree(rproc); } @@ -1310,31 +1357,31 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, { struct rproc *rproc; char *p, *template = "rproc-%s-fw"; - int name_len = 0; + int name_len; if (!dev || !name || !ops) return NULL; - if (!firmware) + if (!firmware) { /* - * Make room for default firmware name (minus %s plus '\0'). * If the caller didn't pass in a firmware name then - * construct a default name. We're already glomming 'len' - * bytes onto the end of the struct rproc allocation, so do - * a few more for the default firmware name (but only if - * the caller doesn't pass one). + * construct a default name. */ name_len = strlen(name) + strlen(template) - 2 + 1; - - rproc = kzalloc(sizeof(*rproc) + len + name_len, GFP_KERNEL); - if (!rproc) - return NULL; - - if (!firmware) { - p = (char *)rproc + sizeof(struct rproc) + len; + p = kmalloc(name_len, GFP_KERNEL); + if (!p) + return NULL; snprintf(p, name_len, template, name); } else { - p = (char *)firmware; + p = kstrdup(firmware, GFP_KERNEL); + if (!p) + return NULL; + } + + rproc = kzalloc(sizeof(struct rproc) + len, GFP_KERNEL); + if (!rproc) { + kfree(p); + return NULL; } rproc->firmware = p; @@ -1346,6 +1393,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, device_initialize(&rproc->dev); rproc->dev.parent = dev; rproc->dev.type = &rproc_type; + rproc->dev.class = &rproc_class; /* Assign a unique device index and name */ rproc->index = ida_simple_get(&rproc_dev_index, 0, 0, GFP_KERNEL); @@ -1370,6 +1418,7 @@ struct rproc *rproc_alloc(struct device *dev, const char *name, INIT_LIST_HEAD(&rproc->mappings); INIT_LIST_HEAD(&rproc->traces); INIT_LIST_HEAD(&rproc->rvdevs); + INIT_LIST_HEAD(&rproc->subdevs); INIT_WORK(&rproc->crash_handler, rproc_crash_handler_work); init_completion(&rproc->crash_comp); @@ -1428,8 +1477,6 @@ EXPORT_SYMBOL(rproc_put); */ int rproc_del(struct rproc *rproc) { - struct rproc_vdev *rvdev, *tmp; - if (!rproc) return -EINVAL; @@ -1441,10 +1488,6 @@ int rproc_del(struct rproc *rproc) if (rproc->auto_boot) rproc_shutdown(rproc); - /* clean up remote vdev entries */ - list_for_each_entry_safe(rvdev, tmp, &rproc->rvdevs, node) - rproc_remove_virtio_dev(rvdev); - /* the rproc is downref'ed as soon as it's removed from the klist */ mutex_lock(&rproc_list_mutex); list_del(&rproc->node); @@ -1457,6 +1500,36 @@ int rproc_del(struct rproc *rproc) EXPORT_SYMBOL(rproc_del); /** + * rproc_add_subdev() - add a subdevice to a remoteproc + * @rproc: rproc handle to add the subdevice to + * @subdev: subdev handle to register + * @probe: function to call when the rproc boots + * @remove: function to call when the rproc shuts down + */ +void rproc_add_subdev(struct rproc *rproc, + struct rproc_subdev *subdev, + int (*probe)(struct rproc_subdev *subdev), + void (*remove)(struct rproc_subdev *subdev)) +{ + subdev->probe = probe; + subdev->remove = remove; + + list_add_tail(&subdev->node, &rproc->subdevs); +} +EXPORT_SYMBOL(rproc_add_subdev); + +/** + * rproc_remove_subdev() - remove a subdevice from a remoteproc + * @rproc: rproc handle to remove the subdevice from + * @subdev: subdev handle, previously registered with rproc_add_subdev() + */ +void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev) +{ + list_del(&subdev->node); +} +EXPORT_SYMBOL(rproc_remove_subdev); + +/** * rproc_report_crash() - rproc crash reporter function * @rproc: remote processor * @type: crash type @@ -1484,6 +1557,7 @@ EXPORT_SYMBOL(rproc_report_crash); static int __init remoteproc_init(void) { + rproc_init_sysfs(); rproc_init_debugfs(); return 0; @@ -1495,6 +1569,7 @@ static void __exit remoteproc_exit(void) ida_destroy(&rproc_dev_index); rproc_exit_debugfs(); + rproc_exit_sysfs(); } module_exit(remoteproc_exit); diff --git a/drivers/remoteproc/remoteproc_debugfs.c b/drivers/remoteproc/remoteproc_debugfs.c index 374797206c79..1c122e230cec 100644 --- a/drivers/remoteproc/remoteproc_debugfs.c +++ b/drivers/remoteproc/remoteproc_debugfs.c @@ -59,75 +59,6 @@ static const struct file_operations trace_rproc_ops = { .llseek = generic_file_llseek, }; -/* - * A state-to-string lookup table, for exposing a human readable state - * via debugfs. Always keep in sync with enum rproc_state - */ -static const char * const rproc_state_string[] = { - "offline", - "suspended", - "running", - "crashed", - "invalid", -}; - -/* expose the state of the remote processor via debugfs */ -static ssize_t rproc_state_read(struct file *filp, char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct rproc *rproc = filp->private_data; - unsigned int state; - char buf[30]; - int i; - - state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state; - - i = scnprintf(buf, 30, "%.28s (%d)\n", rproc_state_string[state], - rproc->state); - - return simple_read_from_buffer(userbuf, count, ppos, buf, i); -} - -static ssize_t rproc_state_write(struct file *filp, const char __user *userbuf, - size_t count, loff_t *ppos) -{ - struct rproc *rproc = filp->private_data; - char buf[10]; - int ret; - - if (count > sizeof(buf) || count <= 0) - return -EINVAL; - - ret = copy_from_user(buf, userbuf, count); - if (ret) - return -EFAULT; - - if (buf[count - 1] == '\n') - buf[count - 1] = '\0'; - - if (!strncmp(buf, "start", count)) { - ret = rproc_boot(rproc); - if (ret) { - dev_err(&rproc->dev, "Boot failed: %d\n", ret); - return ret; - } - } else if (!strncmp(buf, "stop", count)) { - rproc_shutdown(rproc); - } else { - dev_err(&rproc->dev, "Unrecognised option: %s\n", buf); - return -EINVAL; - } - - return count; -} - -static const struct file_operations rproc_state_ops = { - .read = rproc_state_read, - .write = rproc_state_write, - .open = simple_open, - .llseek = generic_file_llseek, -}; - /* expose the name of the remote processor via debugfs */ static ssize_t rproc_name_read(struct file *filp, char __user *userbuf, size_t count, loff_t *ppos) @@ -265,8 +196,6 @@ void rproc_create_debug_dir(struct rproc *rproc) debugfs_create_file("name", 0400, rproc->dbg_dir, rproc, &rproc_name_ops); - debugfs_create_file("state", 0400, rproc->dbg_dir, - rproc, &rproc_state_ops); debugfs_create_file("recovery", 0400, rproc->dbg_dir, rproc, &rproc_recovery_ops); } diff --git a/drivers/remoteproc/remoteproc_internal.h b/drivers/remoteproc/remoteproc_internal.h index 4cf93ca2816e..1e9e5b3f021c 100644 --- a/drivers/remoteproc/remoteproc_internal.h +++ b/drivers/remoteproc/remoteproc_internal.h @@ -49,6 +49,7 @@ struct rproc_fw_ops { void rproc_release(struct kref *kref); irqreturn_t rproc_vq_interrupt(struct rproc *rproc, int vq_id); int rproc_boot_nowait(struct rproc *rproc); +void rproc_vdev_release(struct kref *ref); /* from remoteproc_virtio.c */ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id); @@ -63,6 +64,11 @@ void rproc_create_debug_dir(struct rproc *rproc); void rproc_init_debugfs(void); void rproc_exit_debugfs(void); +/* from remoteproc_sysfs.c */ +extern struct class rproc_class; +int rproc_init_sysfs(void); +void rproc_exit_sysfs(void); + void rproc_free_vring(struct rproc_vring *rvring); int rproc_alloc_vring(struct rproc_vdev *rvdev, int i); diff --git a/drivers/remoteproc/remoteproc_sysfs.c b/drivers/remoteproc/remoteproc_sysfs.c new file mode 100644 index 000000000000..bc5b0e00efb1 --- /dev/null +++ b/drivers/remoteproc/remoteproc_sysfs.c @@ -0,0 +1,151 @@ +/* + * Remote Processor Framework + * + * 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/remoteproc.h> + +#include "remoteproc_internal.h" + +#define to_rproc(d) container_of(d, struct rproc, dev) + +/* Expose the loaded / running firmware name via sysfs */ +static ssize_t firmware_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rproc *rproc = to_rproc(dev); + + return sprintf(buf, "%s\n", rproc->firmware); +} + +/* Change firmware name via sysfs */ +static ssize_t firmware_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rproc *rproc = to_rproc(dev); + char *p; + int err, len = count; + + err = mutex_lock_interruptible(&rproc->lock); + if (err) { + dev_err(dev, "can't lock rproc %s: %d\n", rproc->name, err); + return -EINVAL; + } + + if (rproc->state != RPROC_OFFLINE) { + dev_err(dev, "can't change firmware while running\n"); + err = -EBUSY; + goto out; + } + + len = strcspn(buf, "\n"); + + p = kstrndup(buf, len, GFP_KERNEL); + if (!p) { + err = -ENOMEM; + goto out; + } + + kfree(rproc->firmware); + rproc->firmware = p; +out: + mutex_unlock(&rproc->lock); + + return err ? err : count; +} +static DEVICE_ATTR_RW(firmware); + +/* + * A state-to-string lookup table, for exposing a human readable state + * via sysfs. Always keep in sync with enum rproc_state + */ +static const char * const rproc_state_string[] = { + [RPROC_OFFLINE] = "offline", + [RPROC_SUSPENDED] = "suspended", + [RPROC_RUNNING] = "running", + [RPROC_CRASHED] = "crashed", + [RPROC_LAST] = "invalid", +}; + +/* Expose the state of the remote processor via sysfs */ +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rproc *rproc = to_rproc(dev); + unsigned int state; + + state = rproc->state > RPROC_LAST ? RPROC_LAST : rproc->state; + return sprintf(buf, "%s\n", rproc_state_string[state]); +} + +/* Change remote processor state via sysfs */ +static ssize_t state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rproc *rproc = to_rproc(dev); + int ret = 0; + + if (sysfs_streq(buf, "start")) { + if (rproc->state == RPROC_RUNNING) + return -EBUSY; + + ret = rproc_boot(rproc); + if (ret) + dev_err(&rproc->dev, "Boot failed: %d\n", ret); + } else if (sysfs_streq(buf, "stop")) { + if (rproc->state != RPROC_RUNNING) + return -EINVAL; + + rproc_shutdown(rproc); + } else { + dev_err(&rproc->dev, "Unrecognised option: %s\n", buf); + ret = -EINVAL; + } + return ret ? ret : count; +} +static DEVICE_ATTR_RW(state); + +static struct attribute *rproc_attrs[] = { + &dev_attr_firmware.attr, + &dev_attr_state.attr, + NULL +}; + +static const struct attribute_group rproc_devgroup = { + .attrs = rproc_attrs +}; + +static const struct attribute_group *rproc_devgroups[] = { + &rproc_devgroup, + NULL +}; + +struct class rproc_class = { + .name = "remoteproc", + .dev_groups = rproc_devgroups, +}; + +int __init rproc_init_sysfs(void) +{ + /* create remoteproc device class for sysfs */ + int err = class_register(&rproc_class); + + if (err) + pr_err("remoteproc: unable to register class\n"); + return err; +} + +void __exit rproc_exit_sysfs(void) +{ + class_unregister(&rproc_class); +} diff --git a/drivers/remoteproc/remoteproc_virtio.c b/drivers/remoteproc/remoteproc_virtio.c index 01870a16d6d2..364411fb7734 100644 --- a/drivers/remoteproc/remoteproc_virtio.c +++ b/drivers/remoteproc/remoteproc_virtio.c @@ -79,7 +79,7 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, struct rproc_vring *rvring; struct virtqueue *vq; void *addr; - int len, size, ret; + int len, size; /* we're temporarily limited to two virtqueues per rvdev */ if (id >= ARRAY_SIZE(rvdev->vring)) @@ -88,10 +88,6 @@ static struct virtqueue *rp_find_vq(struct virtio_device *vdev, if (!name) return NULL; - ret = rproc_alloc_vring(rvdev, id); - if (ret) - return ERR_PTR(ret); - rvring = &rvdev->vring[id]; addr = rvring->va; len = rvring->len; @@ -130,7 +126,6 @@ static void __rproc_virtio_del_vqs(struct virtio_device *vdev) rvring = vq->priv; rvring->vq = NULL; vring_del_virtqueue(vq); - rproc_free_vring(rvring); } } @@ -282,14 +277,13 @@ static const struct virtio_config_ops rproc_virtio_config_ops = { * Never call this function directly; it will be called by the driver * core when needed. */ -static void rproc_vdev_release(struct device *dev) +static void rproc_virtio_dev_release(struct device *dev) { struct virtio_device *vdev = dev_to_virtio(dev); struct rproc_vdev *rvdev = vdev_to_rvdev(vdev); struct rproc *rproc = vdev_to_rproc(vdev); - list_del(&rvdev->node); - kfree(rvdev); + kref_put(&rvdev->refcount, rproc_vdev_release); put_device(&rproc->dev); } @@ -313,7 +307,7 @@ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id) vdev->id.device = id, vdev->config = &rproc_virtio_config_ops, vdev->dev.parent = dev; - vdev->dev.release = rproc_vdev_release; + vdev->dev.release = rproc_virtio_dev_release; /* * We're indirectly making a non-temporary copy of the rproc pointer @@ -325,6 +319,9 @@ int rproc_add_virtio_dev(struct rproc_vdev *rvdev, int id) */ get_device(&rproc->dev); + /* Reference the vdev and vring allocations */ + kref_get(&rvdev->refcount); + ret = register_virtio_device(vdev); if (ret) { put_device(&rproc->dev); diff --git a/drivers/remoteproc/st_remoteproc.c b/drivers/remoteproc/st_remoteproc.c index ae8963fcc8c8..da4e152e9733 100644 --- a/drivers/remoteproc/st_remoteproc.c +++ b/drivers/remoteproc/st_remoteproc.c @@ -245,8 +245,10 @@ static int st_rproc_probe(struct platform_device *pdev) goto free_rproc; enabled = st_rproc_state(pdev); - if (enabled < 0) + if (enabled < 0) { + ret = enabled; goto free_rproc; + } if (enabled) { atomic_inc(&rproc->power); diff --git a/drivers/remoteproc/st_slim_rproc.c b/drivers/remoteproc/st_slim_rproc.c new file mode 100644 index 000000000000..507716c8721f --- /dev/null +++ b/drivers/remoteproc/st_slim_rproc.c @@ -0,0 +1,364 @@ +/* + * SLIM core rproc driver + * + * Copyright (C) 2016 STMicroelectronics + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/remoteproc.h> +#include <linux/remoteproc/st_slim_rproc.h> +#include "remoteproc_internal.h" + +/* SLIM core registers */ +#define SLIM_ID_OFST 0x0 +#define SLIM_VER_OFST 0x4 + +#define SLIM_EN_OFST 0x8 +#define SLIM_EN_RUN BIT(0) + +#define SLIM_CLK_GATE_OFST 0xC +#define SLIM_CLK_GATE_DIS BIT(0) +#define SLIM_CLK_GATE_RESET BIT(2) + +#define SLIM_SLIM_PC_OFST 0x20 + +/* DMEM registers */ +#define SLIM_REV_ID_OFST 0x0 +#define SLIM_REV_ID_MIN_MASK GENMASK(15, 8) +#define SLIM_REV_ID_MIN(id) ((id & SLIM_REV_ID_MIN_MASK) >> 8) +#define SLIM_REV_ID_MAJ_MASK GENMASK(23, 16) +#define SLIM_REV_ID_MAJ(id) ((id & SLIM_REV_ID_MAJ_MASK) >> 16) + + +/* peripherals registers */ +#define SLIM_STBUS_SYNC_OFST 0xF88 +#define SLIM_STBUS_SYNC_DIS BIT(0) + +#define SLIM_INT_SET_OFST 0xFD4 +#define SLIM_INT_CLR_OFST 0xFD8 +#define SLIM_INT_MASK_OFST 0xFDC + +#define SLIM_CMD_CLR_OFST 0xFC8 +#define SLIM_CMD_MASK_OFST 0xFCC + +static const char *mem_names[ST_SLIM_MEM_MAX] = { + [ST_SLIM_DMEM] = "dmem", + [ST_SLIM_IMEM] = "imem", +}; + +static int slim_clk_get(struct st_slim_rproc *slim_rproc, struct device *dev) +{ + int clk, err; + + for (clk = 0; clk < ST_SLIM_MAX_CLK; clk++) { + slim_rproc->clks[clk] = of_clk_get(dev->of_node, clk); + if (IS_ERR(slim_rproc->clks[clk])) { + err = PTR_ERR(slim_rproc->clks[clk]); + if (err == -EPROBE_DEFER) + goto err_put_clks; + slim_rproc->clks[clk] = NULL; + break; + } + } + + return 0; + +err_put_clks: + while (--clk >= 0) + clk_put(slim_rproc->clks[clk]); + + return err; +} + +static void slim_clk_disable(struct st_slim_rproc *slim_rproc) +{ + int clk; + + for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++) + clk_disable_unprepare(slim_rproc->clks[clk]); +} + +static int slim_clk_enable(struct st_slim_rproc *slim_rproc) +{ + int clk, ret; + + for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++) { + ret = clk_prepare_enable(slim_rproc->clks[clk]); + if (ret) + goto err_disable_clks; + } + + return 0; + +err_disable_clks: + while (--clk >= 0) + clk_disable_unprepare(slim_rproc->clks[clk]); + + return ret; +} + +/* + * Remoteproc slim specific device handlers + */ +static int slim_rproc_start(struct rproc *rproc) +{ + struct device *dev = &rproc->dev; + struct st_slim_rproc *slim_rproc = rproc->priv; + unsigned long hw_id, hw_ver, fw_rev; + u32 val; + + /* disable CPU pipeline clock & reset CPU pipeline */ + val = SLIM_CLK_GATE_DIS | SLIM_CLK_GATE_RESET; + writel(val, slim_rproc->slimcore + SLIM_CLK_GATE_OFST); + + /* disable SLIM core STBus sync */ + writel(SLIM_STBUS_SYNC_DIS, slim_rproc->peri + SLIM_STBUS_SYNC_OFST); + + /* enable cpu pipeline clock */ + writel(!SLIM_CLK_GATE_DIS, + slim_rproc->slimcore + SLIM_CLK_GATE_OFST); + + /* clear int & cmd mailbox */ + writel(~0U, slim_rproc->peri + SLIM_INT_CLR_OFST); + writel(~0U, slim_rproc->peri + SLIM_CMD_CLR_OFST); + + /* enable all channels cmd & int */ + writel(~0U, slim_rproc->peri + SLIM_INT_MASK_OFST); + writel(~0U, slim_rproc->peri + SLIM_CMD_MASK_OFST); + + /* enable cpu */ + writel(SLIM_EN_RUN, slim_rproc->slimcore + SLIM_EN_OFST); + + hw_id = readl_relaxed(slim_rproc->slimcore + SLIM_ID_OFST); + hw_ver = readl_relaxed(slim_rproc->slimcore + SLIM_VER_OFST); + + fw_rev = readl(slim_rproc->mem[ST_SLIM_DMEM].cpu_addr + + SLIM_REV_ID_OFST); + + dev_info(dev, "fw rev:%ld.%ld on SLIM %ld.%ld\n", + SLIM_REV_ID_MAJ(fw_rev), SLIM_REV_ID_MIN(fw_rev), + hw_id, hw_ver); + + return 0; +} + +static int slim_rproc_stop(struct rproc *rproc) +{ + struct st_slim_rproc *slim_rproc = rproc->priv; + u32 val; + + /* mask all (cmd & int) channels */ + writel(0UL, slim_rproc->peri + SLIM_INT_MASK_OFST); + writel(0UL, slim_rproc->peri + SLIM_CMD_MASK_OFST); + + /* disable cpu pipeline clock */ + writel(SLIM_CLK_GATE_DIS, slim_rproc->slimcore + SLIM_CLK_GATE_OFST); + + writel(!SLIM_EN_RUN, slim_rproc->slimcore + SLIM_EN_OFST); + + val = readl(slim_rproc->slimcore + SLIM_EN_OFST); + if (val & SLIM_EN_RUN) + dev_warn(&rproc->dev, "Failed to disable SLIM"); + + dev_dbg(&rproc->dev, "slim stopped\n"); + + return 0; +} + +static void *slim_rproc_da_to_va(struct rproc *rproc, u64 da, int len) +{ + struct st_slim_rproc *slim_rproc = rproc->priv; + void *va = NULL; + int i; + + for (i = 0; i < ST_SLIM_MEM_MAX; i++) { + if (da != slim_rproc->mem[i].bus_addr) + continue; + + if (len <= slim_rproc->mem[i].size) { + /* __force to make sparse happy with type conversion */ + va = (__force void *)slim_rproc->mem[i].cpu_addr; + break; + } + } + + dev_dbg(&rproc->dev, "da = 0x%llx len = 0x%x va = 0x%p\n", da, len, va); + + return va; +} + +static struct rproc_ops slim_rproc_ops = { + .start = slim_rproc_start, + .stop = slim_rproc_stop, + .da_to_va = slim_rproc_da_to_va, +}; + +/* + * Firmware handler operations: sanity, boot address, load ... + */ + +static struct resource_table empty_rsc_tbl = { + .ver = 1, + .num = 0, +}; + +static struct resource_table *slim_rproc_find_rsc_table(struct rproc *rproc, + const struct firmware *fw, + int *tablesz) +{ + *tablesz = sizeof(empty_rsc_tbl); + return &empty_rsc_tbl; +} + +static struct rproc_fw_ops slim_rproc_fw_ops = { + .find_rsc_table = slim_rproc_find_rsc_table, +}; + +/** + * st_slim_rproc_alloc() - allocate and initialise slim rproc + * @pdev: Pointer to the platform_device struct + * @fw_name: Name of firmware for rproc to use + * + * Function for allocating and initialising a slim rproc for use by + * device drivers whose IP is based around the SLIM core. It + * obtains and enables any clocks required by the SLIM core and also + * ioremaps the various IO. + * + * Returns st_slim_rproc pointer or PTR_ERR() on error. + */ + +struct st_slim_rproc *st_slim_rproc_alloc(struct platform_device *pdev, + char *fw_name) +{ + struct device *dev = &pdev->dev; + struct st_slim_rproc *slim_rproc; + struct device_node *np = dev->of_node; + struct rproc *rproc; + struct resource *res; + int err, i; + const struct rproc_fw_ops *elf_ops; + + if (!fw_name) + return ERR_PTR(-EINVAL); + + if (!of_device_is_compatible(np, "st,slim-rproc")) + return ERR_PTR(-EINVAL); + + rproc = rproc_alloc(dev, np->name, &slim_rproc_ops, + fw_name, sizeof(*slim_rproc)); + if (!rproc) + return ERR_PTR(-ENOMEM); + + rproc->has_iommu = false; + + slim_rproc = rproc->priv; + slim_rproc->rproc = rproc; + + elf_ops = rproc->fw_ops; + /* Use some generic elf ops */ + slim_rproc_fw_ops.load = elf_ops->load; + slim_rproc_fw_ops.sanity_check = elf_ops->sanity_check; + + rproc->fw_ops = &slim_rproc_fw_ops; + + /* get imem and dmem */ + for (i = 0; i < ARRAY_SIZE(mem_names); i++) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + mem_names[i]); + + slim_rproc->mem[i].cpu_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(slim_rproc->mem[i].cpu_addr)) { + dev_err(&pdev->dev, "devm_ioremap_resource failed\n"); + err = PTR_ERR(slim_rproc->mem[i].cpu_addr); + goto err; + } + slim_rproc->mem[i].bus_addr = res->start; + slim_rproc->mem[i].size = resource_size(res); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "slimcore"); + slim_rproc->slimcore = devm_ioremap_resource(dev, res); + if (IS_ERR(slim_rproc->slimcore)) { + dev_err(&pdev->dev, "failed to ioremap slimcore IO\n"); + err = PTR_ERR(slim_rproc->slimcore); + goto err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "peripherals"); + slim_rproc->peri = devm_ioremap_resource(dev, res); + if (IS_ERR(slim_rproc->peri)) { + dev_err(&pdev->dev, "failed to ioremap peripherals IO\n"); + err = PTR_ERR(slim_rproc->peri); + goto err; + } + + err = slim_clk_get(slim_rproc, dev); + if (err) + goto err; + + err = slim_clk_enable(slim_rproc); + if (err) { + dev_err(dev, "Failed to enable clocks\n"); + goto err_clk_put; + } + + /* Register as a remoteproc device */ + err = rproc_add(rproc); + if (err) { + dev_err(dev, "registration of slim remoteproc failed\n"); + goto err_clk_dis; + } + + return slim_rproc; + +err_clk_dis: + slim_clk_disable(slim_rproc); +err_clk_put: + for (i = 0; i < ST_SLIM_MAX_CLK && slim_rproc->clks[i]; i++) + clk_put(slim_rproc->clks[i]); +err: + rproc_free(rproc); + return ERR_PTR(err); +} +EXPORT_SYMBOL(st_slim_rproc_alloc); + +/** + * st_slim_rproc_put() - put slim rproc resources + * @slim_rproc: Pointer to the st_slim_rproc struct + * + * Function for calling respective _put() functions on slim_rproc resources. + * + */ +void st_slim_rproc_put(struct st_slim_rproc *slim_rproc) +{ + int clk; + + if (!slim_rproc) + return; + + slim_clk_disable(slim_rproc); + + for (clk = 0; clk < ST_SLIM_MAX_CLK && slim_rproc->clks[clk]; clk++) + clk_put(slim_rproc->clks[clk]); + + rproc_del(slim_rproc->rproc); + rproc_free(slim_rproc->rproc); +} +EXPORT_SYMBOL(st_slim_rproc_put); + +MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>"); +MODULE_DESCRIPTION("STMicroelectronics SLIM core rproc driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/remoteproc/ste_modem_rproc.c b/drivers/remoteproc/ste_modem_rproc.c deleted file mode 100644 index 03d69a9a3c5b..000000000000 --- a/drivers/remoteproc/ste_modem_rproc.c +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) ST-Ericsson AB 2012 - * Author: Sjur Brændeland <sjur.brandeland@stericsson.com> - * License terms: GNU General Public License (GPL), version 2 - */ - -#include <linux/module.h> -#include <linux/dma-mapping.h> -#include <linux/remoteproc.h> -#include <linux/ste_modem_shm.h> -#include "remoteproc_internal.h" - -#define SPROC_FW_SIZE (50 * 4096) -#define SPROC_MAX_TOC_ENTRIES 32 -#define SPROC_MAX_NOTIFY_ID 14 -#define SPROC_RESOURCE_NAME "rsc-table" -#define SPROC_MODEM_NAME "ste-modem" -#define SPROC_MODEM_FIRMWARE SPROC_MODEM_NAME "-fw.bin" - -#define sproc_dbg(sproc, fmt, ...) \ - dev_dbg(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__) -#define sproc_err(sproc, fmt, ...) \ - dev_err(&sproc->mdev->pdev.dev, fmt, ##__VA_ARGS__) - -/* STE-modem control structure */ -struct sproc { - struct rproc *rproc; - struct ste_modem_device *mdev; - int error; - void *fw_addr; - size_t fw_size; - dma_addr_t fw_dma_addr; -}; - -/* STE-Modem firmware entry */ -struct ste_toc_entry { - __le32 start; - __le32 size; - __le32 flags; - __le32 entry_point; - __le32 load_addr; - char name[12]; -}; - -/* - * The Table Of Content is located at the start of the firmware image and - * at offset zero in the shared memory region. The resource table typically - * contains the initial boot image (boot strap) and other information elements - * such as remoteproc resource table. Each entry is identified by a unique - * name. - */ -struct ste_toc { - struct ste_toc_entry table[SPROC_MAX_TOC_ENTRIES]; -}; - -/* Loads the firmware to shared memory. */ -static int sproc_load_segments(struct rproc *rproc, const struct firmware *fw) -{ - struct sproc *sproc = rproc->priv; - - memcpy(sproc->fw_addr, fw->data, fw->size); - - return 0; -} - -/* Find the entry for resource table in the Table of Content */ -static const struct ste_toc_entry *sproc_find_rsc_entry(const void *data) -{ - int i; - const struct ste_toc *toc = data; - - /* Search the table for the resource table */ - for (i = 0; i < SPROC_MAX_TOC_ENTRIES && - toc->table[i].start != 0xffffffff; i++) { - if (!strncmp(toc->table[i].name, SPROC_RESOURCE_NAME, - sizeof(toc->table[i].name))) - return &toc->table[i]; - } - - return NULL; -} - -/* Find the resource table inside the remote processor's firmware. */ -static struct resource_table * -sproc_find_rsc_table(struct rproc *rproc, const struct firmware *fw, - int *tablesz) -{ - struct sproc *sproc = rproc->priv; - struct resource_table *table; - const struct ste_toc_entry *entry; - - if (!fw) - return NULL; - - entry = sproc_find_rsc_entry(fw->data); - if (!entry) { - sproc_err(sproc, "resource table not found in fw\n"); - return NULL; - } - - table = (void *)(fw->data + entry->start); - - /* sanity check size and offset of resource table */ - if (entry->start > SPROC_FW_SIZE || - entry->size > SPROC_FW_SIZE || - fw->size > SPROC_FW_SIZE || - entry->start + entry->size > fw->size || - sizeof(struct resource_table) > entry->size) { - sproc_err(sproc, "bad size of fw or resource table\n"); - return NULL; - } - - /* we don't support any version beyond the first */ - if (table->ver != 1) { - sproc_err(sproc, "unsupported fw ver: %d\n", table->ver); - return NULL; - } - - /* make sure reserved bytes are zeroes */ - if (table->reserved[0] || table->reserved[1]) { - sproc_err(sproc, "non zero reserved bytes\n"); - return NULL; - } - - /* make sure the offsets array isn't truncated */ - if (table->num > SPROC_MAX_TOC_ENTRIES || - table->num * sizeof(table->offset[0]) + - sizeof(struct resource_table) > entry->size) { - sproc_err(sproc, "resource table incomplete\n"); - return NULL; - } - - /* If the fw size has grown, release the previous fw allocation */ - if (SPROC_FW_SIZE < fw->size) { - sproc_err(sproc, "Insufficient space for fw (%d < %zd)\n", - SPROC_FW_SIZE, fw->size); - return NULL; - } - - sproc->fw_size = fw->size; - *tablesz = entry->size; - - return table; -} - -/* Find the resource table inside the remote processor's firmware. */ -static struct resource_table * -sproc_find_loaded_rsc_table(struct rproc *rproc, const struct firmware *fw) -{ - struct sproc *sproc = rproc->priv; - const struct ste_toc_entry *entry; - - if (!fw || !sproc->fw_addr) - return NULL; - - entry = sproc_find_rsc_entry(sproc->fw_addr); - if (!entry) { - sproc_err(sproc, "resource table not found in fw\n"); - return NULL; - } - - return sproc->fw_addr + entry->start; -} - -/* STE modem firmware handler operations */ -static const struct rproc_fw_ops sproc_fw_ops = { - .load = sproc_load_segments, - .find_rsc_table = sproc_find_rsc_table, - .find_loaded_rsc_table = sproc_find_loaded_rsc_table, -}; - -/* Kick the modem with specified notification id */ -static void sproc_kick(struct rproc *rproc, int vqid) -{ - struct sproc *sproc = rproc->priv; - - sproc_dbg(sproc, "kick vqid:%d\n", vqid); - - /* - * We need different notification IDs for RX and TX so add - * an offset on TX notification IDs. - */ - sproc->mdev->ops.kick(sproc->mdev, vqid + SPROC_MAX_NOTIFY_ID); -} - -/* Received a kick from a modem, kick the virtqueue */ -static void sproc_kick_callback(struct ste_modem_device *mdev, int vqid) -{ - struct sproc *sproc = mdev->drv_data; - - if (rproc_vq_interrupt(sproc->rproc, vqid) == IRQ_NONE) - sproc_dbg(sproc, "no message was found in vqid %d\n", vqid); -} - -static struct ste_modem_dev_cb sproc_dev_cb = { - .kick = sproc_kick_callback, -}; - -/* Start the STE modem */ -static int sproc_start(struct rproc *rproc) -{ - struct sproc *sproc = rproc->priv; - int i, err; - - sproc_dbg(sproc, "start ste-modem\n"); - - /* Sanity test the max_notifyid */ - if (rproc->max_notifyid > SPROC_MAX_NOTIFY_ID) { - sproc_err(sproc, "Notification IDs too high:%d\n", - rproc->max_notifyid); - return -EINVAL; - } - - /* Subscribe to notifications */ - for (i = 0; i <= rproc->max_notifyid; i++) { - err = sproc->mdev->ops.kick_subscribe(sproc->mdev, i); - if (err) { - sproc_err(sproc, - "subscription of kicks failed:%d\n", err); - return err; - } - } - - /* Request modem start-up*/ - return sproc->mdev->ops.power(sproc->mdev, true); -} - -/* Stop the STE modem */ -static int sproc_stop(struct rproc *rproc) -{ - struct sproc *sproc = rproc->priv; - - sproc_dbg(sproc, "stop ste-modem\n"); - - return sproc->mdev->ops.power(sproc->mdev, false); -} - -static struct rproc_ops sproc_ops = { - .start = sproc_start, - .stop = sproc_stop, - .kick = sproc_kick, -}; - -/* STE modem device is unregistered */ -static int sproc_drv_remove(struct platform_device *pdev) -{ - struct ste_modem_device *mdev = - container_of(pdev, struct ste_modem_device, pdev); - struct sproc *sproc = mdev->drv_data; - - sproc_dbg(sproc, "remove ste-modem\n"); - - /* Reset device callback functions */ - sproc->mdev->ops.setup(sproc->mdev, NULL); - - /* Unregister as remoteproc device */ - rproc_del(sproc->rproc); - dma_free_coherent(sproc->rproc->dev.parent, SPROC_FW_SIZE, - sproc->fw_addr, sproc->fw_dma_addr); - rproc_free(sproc->rproc); - - mdev->drv_data = NULL; - - return 0; -} - -/* Handle probe of a modem device */ -static int sproc_probe(struct platform_device *pdev) -{ - struct ste_modem_device *mdev = - container_of(pdev, struct ste_modem_device, pdev); - struct sproc *sproc; - struct rproc *rproc; - int err; - - dev_dbg(&mdev->pdev.dev, "probe ste-modem\n"); - - if (!mdev->ops.setup || !mdev->ops.kick || !mdev->ops.kick_subscribe || - !mdev->ops.power) { - dev_err(&mdev->pdev.dev, "invalid mdev ops\n"); - return -EINVAL; - } - - rproc = rproc_alloc(&mdev->pdev.dev, mdev->pdev.name, &sproc_ops, - SPROC_MODEM_FIRMWARE, sizeof(*sproc)); - if (!rproc) - return -ENOMEM; - - sproc = rproc->priv; - sproc->mdev = mdev; - sproc->rproc = rproc; - rproc->has_iommu = false; - mdev->drv_data = sproc; - - /* Provide callback functions to modem device */ - sproc->mdev->ops.setup(sproc->mdev, &sproc_dev_cb); - - /* Set the STE-modem specific firmware handler */ - rproc->fw_ops = &sproc_fw_ops; - - /* - * STE-modem requires the firmware to be located - * at the start of the shared memory region. So we need to - * reserve space for firmware at the start. - */ - sproc->fw_addr = dma_alloc_coherent(rproc->dev.parent, SPROC_FW_SIZE, - &sproc->fw_dma_addr, - GFP_KERNEL); - if (!sproc->fw_addr) { - sproc_err(sproc, "Cannot allocate memory for fw\n"); - err = -ENOMEM; - goto free_rproc; - } - - /* Register as a remoteproc device */ - err = rproc_add(rproc); - if (err) - goto free_mem; - - return 0; - -free_mem: - dma_free_coherent(rproc->dev.parent, SPROC_FW_SIZE, - sproc->fw_addr, sproc->fw_dma_addr); -free_rproc: - /* Reset device data upon error */ - mdev->drv_data = NULL; - rproc_free(rproc); - return err; -} - -static struct platform_driver sproc_driver = { - .driver = { - .name = SPROC_MODEM_NAME, - }, - .probe = sproc_probe, - .remove = sproc_drv_remove, -}; - -module_platform_driver(sproc_driver); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("STE Modem driver using the Remote Processor Framework"); diff --git a/drivers/rpmsg/qcom_smd.c b/drivers/rpmsg/qcom_smd.c index 06fef2b4c814..394a1b52e471 100644 --- a/drivers/rpmsg/qcom_smd.c +++ b/drivers/rpmsg/qcom_smd.c @@ -25,6 +25,7 @@ #include <linux/soc/qcom/smem.h> #include <linux/wait.h> #include <linux/rpmsg.h> +#include <linux/rpmsg/qcom_smd.h> #include "rpmsg_internal.h" diff --git a/include/linux/remoteproc.h b/include/linux/remoteproc.h index 930023b7c825..e2f3a3281d8f 100644 --- a/include/linux/remoteproc.h +++ b/include/linux/remoteproc.h @@ -400,6 +400,7 @@ enum rproc_crash_type { * @firmware_loading_complete: marks e/o asynchronous firmware loading * @bootaddr: address of first instruction to boot rproc with (optional) * @rvdevs: list of remote virtio devices + * @subdevs: list of subdevices, to following the running state * @notifyids: idr for dynamically assigning rproc-wide unique notify ids * @index: index of this rproc device * @crash_handler: workqueue for handling a crash @@ -407,15 +408,14 @@ enum rproc_crash_type { * @crash_comp: completion used to sync crash handler and the rproc reload * @recovery_disabled: flag that state if recovery was disabled * @max_notifyid: largest allocated notify id. - * @table_ptr: pointer to the resource table in effect - * @cached_table: copy of the resource table + * @table_ptr: our copy of the resource table * @has_iommu: flag to indicate if remote processor is behind an MMU */ struct rproc { struct list_head node; struct iommu_domain *domain; const char *name; - const char *firmware; + char *firmware; void *priv; const struct rproc_ops *ops; struct device dev; @@ -431,6 +431,7 @@ struct rproc { struct completion firmware_loading_complete; u32 bootaddr; struct list_head rvdevs; + struct list_head subdevs; struct idr notifyids; int index; struct work_struct crash_handler; @@ -439,11 +440,23 @@ struct rproc { bool recovery_disabled; int max_notifyid; struct resource_table *table_ptr; - struct resource_table *cached_table; bool has_iommu; bool auto_boot; }; +/** + * struct rproc_subdev - subdevice tied to a remoteproc + * @node: list node related to the rproc subdevs list + * @probe: probe function, called as the rproc is started + * @remove: remove function, called as the rproc is stopped + */ +struct rproc_subdev { + struct list_head node; + + int (*probe)(struct rproc_subdev *subdev); + void (*remove)(struct rproc_subdev *subdev); +}; + /* we currently support only two vrings per rvdev */ #define RVDEV_NUM_VRINGS 2 @@ -472,6 +485,9 @@ struct rproc_vring { /** * struct rproc_vdev - remoteproc state for a supported virtio device + * @refcount: reference counter for the vdev and vring allocations + * @subdev: handle for registering the vdev as a rproc subdevice + * @id: virtio device id (as in virtio_ids.h) * @node: list node * @rproc: the rproc handle * @vdev: the virio device @@ -479,6 +495,11 @@ struct rproc_vring { * @rsc_offset: offset of the vdev's resource entry */ struct rproc_vdev { + struct kref refcount; + + struct rproc_subdev subdev; + + unsigned int id; struct list_head node; struct rproc *rproc; struct virtio_device vdev; @@ -511,4 +532,11 @@ static inline struct rproc *vdev_to_rproc(struct virtio_device *vdev) return rvdev->rproc; } +void rproc_add_subdev(struct rproc *rproc, + struct rproc_subdev *subdev, + int (*probe)(struct rproc_subdev *subdev), + void (*remove)(struct rproc_subdev *subdev)); + +void rproc_remove_subdev(struct rproc *rproc, struct rproc_subdev *subdev); + #endif /* REMOTEPROC_H */ diff --git a/include/linux/remoteproc/st_slim_rproc.h b/include/linux/remoteproc/st_slim_rproc.h new file mode 100644 index 000000000000..4155556fa4b2 --- /dev/null +++ b/include/linux/remoteproc/st_slim_rproc.h @@ -0,0 +1,58 @@ +/* + * SLIM core rproc driver header + * + * Copyright (C) 2016 STMicroelectronics + * + * Author: Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#ifndef _ST_REMOTEPROC_SLIM_H +#define _ST_REMOTEPROC_SLIM_H + +#define ST_SLIM_MEM_MAX 2 +#define ST_SLIM_MAX_CLK 4 + +enum { + ST_SLIM_DMEM, + ST_SLIM_IMEM, +}; + +/** + * struct st_slim_mem - slim internal memory structure + * @cpu_addr: MPU virtual address of the memory region + * @bus_addr: Bus address used to access the memory region + * @size: Size of the memory region + */ +struct st_slim_mem { + void __iomem *cpu_addr; + phys_addr_t bus_addr; + size_t size; +}; + +/** + * struct st_slim_rproc - SLIM slim core + * @rproc: rproc handle + * @mem: slim memory information + * @slimcore: slim slimcore regs + * @peri: slim peripheral regs + * @clks: slim clocks + */ +struct st_slim_rproc { + struct rproc *rproc; + struct st_slim_mem mem[ST_SLIM_MEM_MAX]; + void __iomem *slimcore; + void __iomem *peri; + + /* st_slim_rproc private */ + struct clk *clks[ST_SLIM_MAX_CLK]; +}; + +struct st_slim_rproc *st_slim_rproc_alloc(struct platform_device *pdev, + char *fw_name); +void st_slim_rproc_put(struct st_slim_rproc *slim_rproc); + +#endif diff --git a/include/linux/rpmsg/qcom_smd.h b/include/linux/rpmsg/qcom_smd.h new file mode 100644 index 000000000000..e674b2e3074b --- /dev/null +++ b/include/linux/rpmsg/qcom_smd.h @@ -0,0 +1,33 @@ + +#ifndef _LINUX_RPMSG_QCOM_SMD_H +#define _LINUX_RPMSG_QCOM_SMD_H + +#include <linux/device.h> + +struct qcom_smd_edge; + +#if IS_ENABLED(CONFIG_RPMSG_QCOM_SMD) || IS_ENABLED(CONFIG_QCOM_SMD) + +struct qcom_smd_edge *qcom_smd_register_edge(struct device *parent, + struct device_node *node); +int qcom_smd_unregister_edge(struct qcom_smd_edge *edge); + +#else + +static inline struct qcom_smd_edge * +qcom_smd_register_edge(struct device *parent, + struct device_node *node) +{ + return ERR_PTR(-ENXIO); +} + +static inline int qcom_smd_unregister_edge(struct qcom_smd_edge *edge) +{ + /* This shouldn't be possible */ + WARN_ON(1); + return -ENXIO; +} + +#endif + +#endif |