* [PATCH v10 11/11] ARM: multi_v7_defconfig: Enable STi and simple-card drivers.
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This patch enables the STi ALSA drivers found on STi platforms
as well as the simple-card driver which is a dependency to have
working sound.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
Cc: arnaud.pouliquen at st.com
Cc: broonie at kernel.org
---
arch/arm/configs/multi_v7_defconfig | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 46f174e..5d92092 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -644,6 +644,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
--
1.9.1
^ permalink raw reply related
* [PATCH v10 10/11] ARM: multi_v7_defconfig: Enable STi FDMA driver
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This DMA controller is found on all STi chipsets.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
---
arch/arm/configs/multi_v7_defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 33f7a8a..46f174e 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -784,6 +784,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
--
1.9.1
^ permalink raw reply related
* [PATCH v10 09/11] ARM: multi_v7_defconfig: Enable st_remoteproc driver.
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
The st231 remote coprocessors are found on all STi chipsets.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
arch/arm/configs/multi_v7_defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index c94611c..33f7a8a 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -818,6 +818,7 @@ 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
--
1.9.1
^ permalink raw reply related
* [PATCH v10 08/11] ARM: multi_v7_defconfig: Enable remoteproc core
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
Now that remoteproc core is selectable it needs to be enabled
in the multi_v7 build.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
arch/arm/configs/multi_v7_defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm/configs/multi_v7_defconfig b/arch/arm/configs/multi_v7_defconfig
index 3ca4974..c94611c 100644
--- a/arch/arm/configs/multi_v7_defconfig
+++ b/arch/arm/configs/multi_v7_defconfig
@@ -817,6 +817,7 @@ CONFIG_HWSPINLOCK_QCOM=y
CONFIG_ROCKCHIP_IOMMU=y
CONFIG_TEGRA_IOMMU_GART=y
CONFIG_TEGRA_IOMMU_SMMU=y
+CONFIG_REMOTEPROC=m
CONFIG_PM_DEVFREQ=y
CONFIG_ARM_TEGRA_DEVFREQ=m
CONFIG_MEMORY=y
--
1.9.1
^ permalink raw reply related
* [PATCH v10 07/11] MAINTAINERS: Add FDMA driver files to STi section.
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This patch adds the FDMA driver files to the STi
section of the maintainers file.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
---
MAINTAINERS | 1 +
1 file changed, 1 insertion(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9924036..f71a1d1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1749,6 +1749,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/
--
1.9.1
^ permalink raw reply related
* [PATCH v10 06/11] dmaengine: st_fdma: Add STMicroelectronics FDMA engine driver support
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This patch adds support for the Flexible Direct Memory Access (FDMA) core
driver. The FDMA is a slim core CPU with a dedicated firmware.
It is a general purpose DMA controller capable of supporting 16
independent DMA channels. Data moves maybe from memory to memory
or between memory and paced latency critical real time targets and it
is found on al STi based chipsets.
Signed-off-by: Ludovic Barre <ludovic.barre@st.com>
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
drivers/dma/Kconfig | 13 +
drivers/dma/Makefile | 1 +
drivers/dma/st_fdma.c | 899 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 913 insertions(+)
create mode 100644 drivers/dma/st_fdma.c
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 739f797..8322d37 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -437,6 +437,19 @@ config STE_DMA40
help
Support for ST-Ericsson DMA40 controller
+config ST_FDMA
+ tristate "ST FDMA dmaengine support"
+ depends on ARCH_STI
+ 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
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index e4dc9ca..a4fa336 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 0000000..515e1d4
--- /dev/null
+++ b/drivers/dma/st_fdma.c
@@ -0,0 +1,899 @@
+/*
+ * 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)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+
+ if (!np)
+ goto err;
+
+ ret = of_property_read_u32(np, "dma-channels", &fdev->nr_channels);
+ if (ret)
+ goto err;
+
+ snprintf(fdev->fw_name, FW_NAME_SIZE, "fdma_%s_%d.elf",
+ drvdata->name, drvdata->id);
+
+err:
+ return ret;
+}
+#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 (!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);
--
1.9.1
^ permalink raw reply related
* [PATCH v10 05/11] dmaengine: st_fdma: Add STMicroelectronics FDMA driver header file
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This header file will also be used by the dma xbar driver in the
future.
Signed-off-by: Ludovic Barre <ludovic.barre@st.com>
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
drivers/dma/st_fdma.h | 249 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 249 insertions(+)
create mode 100644 drivers/dma/st_fdma.h
diff --git a/drivers/dma/st_fdma.h b/drivers/dma/st_fdma.h
new file mode 100644
index 0000000..c58e00d
--- /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 */
--
1.9.1
^ permalink raw reply related
* [PATCH v10 04/11] dmaengine: st_fdma: Add STMicroelectronics FDMA DT binding documentation
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This patch adds the DT binding documentation for the FDMA constroller
found on STi based chipsets from STMicroelectronics.
Signed-off-by: Ludovic Barre <ludovic.barre@st.com>
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Rob Herring <robh@kernel.org>
---
Documentation/devicetree/bindings/dma/st_fdma.txt | 87 +++++++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 Documentation/devicetree/bindings/dma/st_fdma.txt
diff --git a/Documentation/devicetree/bindings/dma/st_fdma.txt b/Documentation/devicetree/bindings/dma/st_fdma.txt
new file mode 100644
index 0000000..495d853
--- /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 at 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 at 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";
+ };
--
1.9.1
^ permalink raw reply related
* [PATCH v10 03/11] remoteproc: Update Kconfig setup to 'depends on REMOTEPROC'
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
Make REMOTEPROC core a selectable kconfig option, and update
remoteproc client drivers to 'depends on' the core. This avoids
some nasty Kconfig recursive dependency issues. Also when using
menuconfig client drivers will be hidden until the core has been
enabled.
Documentation/kbuild/kconfig-language.txt:
Note:
select should be used with care. select will force
a symbol to a value without visiting the dependencies.
By abusing select you are able to select a symbol FOO even
if FOO depends on BAR that is not set.
In general use select only for non-visible symbols
(no prompts anywhere) and for symbols with no dependencies.
That will limit the usefulness but on the other hand avoid
the illegal configurations all over.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
drivers/remoteproc/Kconfig | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index a7bedc6..decdcbe 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
@@ -34,7 +35,7 @@ config OMAP_REMOTEPROC
config STE_MODEM_RPROC
tristate "STE-Modem remoteproc support"
depends on HAS_DMA
- select REMOTEPROC
+ depends on REMOTEPROC
default n
help
Say y or m here to support STE-Modem shared memory driver.
@@ -44,7 +45,7 @@ config STE_MODEM_RPROC
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 +58,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
help
Say y here to support DA8xx/OMAP-L13x remote processors via the
@@ -84,9 +85,9 @@ 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
help
Say y here to support the Qualcomm Peripherial Image Loader for the
Hexagon V5 based remote processors.
@@ -94,7 +95,7 @@ config QCOM_Q6V5_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.
@@ -102,6 +103,8 @@ config ST_REMOTEPROC
config ST_SLIM_REMOTEPROC
tristate
- select REMOTEPROC
+ depends on REMOTEPROC
+
+endif # REMOTEPROC
endmenu
--
1.9.1
^ permalink raw reply related
* [PATCH v10 02/11] MAINTAINERS: Add st slim core rproc driver to STi section.
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
This patch adds the slim core rproc driver to the STi section
of the MAINTAINERS file.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
---
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index f593300..9924036 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1759,6 +1759,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
@@ -1767,6 +1768,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>
--
1.9.1
^ permalink raw reply related
* [PATCH v10 01/11] remoteproc: st_slim_rproc: add a slimcore rproc driver
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475931154-1021-1-git-send-email-peter.griffin@linaro.org>
slim core is used as a basis for many IPs in the STi
chipsets such as fdma and demux. To avoid duplicating
the elf loading code in each device driver a slim
rproc driver has been created.
This driver is designed to be used by other device drivers
such as fdma, or demux whose IP is based around a slim core.
The device driver can call slim_rproc_alloc() to allocate
a slim rproc and slim_rproc_put() when finished.
This driver takes care of ioremapping the slim
registers (dmem, imem, slimcore, peripherals), whose offsets
and sizes can change between IP's. It also obtains and enables
any clocks used by the device. This approach avoids having
a double mapping of the registers as slim_rproc does not register
its own platform device. It also maps well to device tree
abstraction as it allows us to have one dt node for the whole
device.
All of the generic rproc elf loading code can be reused, and
we provide start() stop() hooks to start and stop the slim
core once the firmware has been loaded. This has been tested
successfully with fdma driver.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
---
drivers/remoteproc/Kconfig | 4 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/st_slim_rproc.c | 364 +++++++++++++++++++++++++++++++
include/linux/remoteproc/st_slim_rproc.h | 58 +++++
4 files changed, 427 insertions(+)
create mode 100644 drivers/remoteproc/st_slim_rproc.c
create mode 100644 include/linux/remoteproc/st_slim_rproc.h
diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
index 1a8bf76a..a7bedc6 100644
--- a/drivers/remoteproc/Kconfig
+++ b/drivers/remoteproc/Kconfig
@@ -100,4 +100,8 @@ config ST_REMOTEPROC
processor framework.
This can be either built-in or a loadable module.
+config ST_SLIM_REMOTEPROC
+ tristate
+ select REMOTEPROC
+
endmenu
diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
index 92d3758..db1dae7 100644
--- a/drivers/remoteproc/Makefile
+++ b/drivers/remoteproc/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_DA8XX_REMOTEPROC) += da8xx_remoteproc.o
obj-$(CONFIG_QCOM_MDT_LOADER) += qcom_mdt_loader.o
obj-$(CONFIG_QCOM_Q6V5_PIL) += qcom_q6v5_pil.o
obj-$(CONFIG_ST_REMOTEPROC) += st_remoteproc.o
+obj-$(CONFIG_ST_SLIM_REMOTEPROC) += st_slim_rproc.o
diff --git a/drivers/remoteproc/st_slim_rproc.c b/drivers/remoteproc/st_slim_rproc.c
new file mode 100644
index 0000000..1484e97
--- /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_put(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_put(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/include/linux/remoteproc/st_slim_rproc.h b/include/linux/remoteproc/st_slim_rproc.h
new file mode 100644
index 0000000..4155556
--- /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
--
1.9.1
^ permalink raw reply related
* [PATCH v10 00/11] Add support for FDMA DMA controller and slim core rproc found on STi chipsets
From: Peter Griffin @ 2016-10-08 12:52 UTC (permalink / raw)
To: linux-arm-kernel
Hi Vinod and Bjorn,
This patchset adds support for the Flexible Direct Memory Access (FDMA) core
found on STi chipsets from STMicroelectronics. The FDMA is a slim core CPU
with a dedicated firmware. It is a general purpose DMA controller supporting
16 independent channels and data can be moved from memory to memory or between
memory and paced latency critical real time targets.
[..]
V10 series updates remoteproc kconfig subsystem to use 'depends on' rather
than select for the remoteproc core. Remoteproc client drivers now need to
'depends on' for the core part.
This avoids the Kconfig recursive dependency errors and reliance on the DRM
patch included with the v9 series. Patches are also included for enabling the
remoteproc core in the multi_v7 defconfig. All patches for DRM subsystem have
been removed.
Note there is no longer a depedency with the DRM_VIRTIO_GPU driver in this series,
only remoteproc and dmaengine subsystems.
regards,
Peter.
Changes since v9:
- Update remoteproc Kconfig to 'depends on'
- Add remoteproc core to multi_v7 defconfig
- Remove drm patches
- rebase v4.8
Changes since v8:
- Add MODULE_ALIAS (Vinod)
- devm_kzalloc to devm_kcalloc (Vinod)
- quisce tasklet initialised by vchan_init() (Vinod)
- Don't make SLIM rproc user selectable (Bjorn)
- slim_rproc: Ensure clocks enabled before firmware load (Peter)
- Various code style nits / commit message change (Lee)
- Separate patch for '\n' kconfig removal (Vinod)
- Fix clock bug when booting without clk_ignore_unused param
Changes since v7:
- Rebase on v4.8-rc3 (Peter)
- Double check that len is <= mem[i].size (Bjorn)
- Remove if (!fw) checks (Bjorn)
- Omit reference from fw_ops struct (Bjorn)
- Call rproc_del() before rproc_put() (Bjorn)
- Fix some odd line breaks (Bjorn)
- Rename SLIM_* and slim_* with st prefix (Bjorn)
Changes since v6:
- Fix recursive Kconfig warning (Patrice)
- Fix various Intel zero day compiler warnings
- Remove all changes related to late firmware load (now relies on initramfs) (Peter)
Changes since v5:
- Remove optional rsc table and go back to dummy resource table (Bjorn)
- Retry rproc_fw_config_virtio() from rproc_boot() if previously failed (Peter)
- Fixup some kbuild warnings
- rebase on v4.7-rc5
- Remove some patches which were merged via ASoC tree
Changes since v4:
- Make rsc table optional in remoteproc (Peter)
- Various fixups to STi audio DT nodes (Arnaud)
- Bulk rename of xp70 to slim (Peter)
- Update my email to @linaro.org (Lee)
- rebase on v4.7-rc2 (Peter)
- Don't make ST_SLIM_REMOTEPROC user selectable (Bjorn)
- -EPROBE_DEFER if rproc_boot fails when allocating DMA channel (Arnaud / Peter)
- Drop some unnecessary headers (Vinod / Bjorn)
- Change to writel now we have several mappings (Bjorn)
- Remove io_res, rproc, and some unused structure fields / #define (Bjorn)
- put clks in error path, also put clks before rproc_put() (Bjorn)
- Make enum less generic (Bjorn)
- Make slim_rproc_alloc() return a st_slim_rproc reference (Bjorn)
- Alphabetical naming in Kconfig & Makefile (Vinod)
- Add FDMA prefix to REQ_CTRL* (Vinod)
- Print ret on some error paths (Vinod)
- Add some acked-by (Peter)
Changes since v3:
- Remove elf loading code from fdma driver (Vinod)
- Remove fdma_ prefix for clock names (Arnd)
- Make _xlate use dma_get_any_channel rather than request_channel (Arnd)
- Make a common function for _prep_ routines (Vinod)
- Make driver depend on COMPILE_TEST (Arnd)
- Remove unnecessary st_fdma_filter_fn (Arnd)
- Enable FDMA as a module (Arnd)
- Drop fdma_ clock prefix (Arnd)
- Fix description as well as example for st, prefix (Arnd)
- Remove string concatenation from fdma register macros to ease grep'ability (Arnd)
- Add a XP70 rproc driver for ELF firmware loading and start/stop control (Peter)
- Add myself as a author of the driver (Peter)
Changes since v2:
- Change to dma-controller (Arnd)
- Remove platform data header file and simplifiy code (Arnd)
- Remove FW_LOADER_USER_HELPER_FALLBACK and rework firmware loading to device config (Vinod)
- Use SET_RUNTIME_PM_OPS helpers (Vinod)
- Remove fdma-id dt prop and use compatibles to generate different fdma firmware names (Arnd / Lee)
- Add sti-asoc-card DT nodes and pinmux config for uniperif player & reader (Peter)
- Update sti-asoc-card DT binding documentation (Peter)
- Enable STi audio drivers in multi_v7_defconfig (Peter)
Changes since v1:
- split into smaller patches for easier / faster review (Vinod)
- new fill_hw_mode() with common code (Vinod)
- new config_reqctrl() called from *_prep() instead of device_config cb (Vinod)
- fdma-xbar support removed (Peter)
- rework firmware name mechanism so fwname isn't in DT (Peter / Lee)
- st_fdma_seg_to_mem can be static (Paul)
- EXPORT_SYMBOL st_fdma_filter_fn not required (Paul)
- s/channel/channels (vinod)
- better describe "Must be <3>" (vinod)
- sizeof(*ehdr) (vinod)
- print values on error debug (vinod)
- empty line (Vinod)
- Update to -EIO (Vinod)
- Make st_fdma tristate (Paul)
- Remove __exit tag from .remove (Maxime)
- Update MAINTAINERS rule to fdma* (Lee)
- Unit address should match reg property (Lee)
Peter Griffin (11):
remoteproc: st_slim_rproc: add a slimcore rproc driver
MAINTAINERS: Add st slim core rproc driver to STi section.
remoteproc: Update Kconfig setup to 'depends on REMOTEPROC'
dmaengine: st_fdma: Add STMicroelectronics FDMA DT binding
documentation
dmaengine: st_fdma: Add STMicroelectronics FDMA driver header file
dmaengine: st_fdma: Add STMicroelectronics FDMA engine driver support
MAINTAINERS: Add FDMA driver files to STi section.
ARM: multi_v7_defconfig: Enable remoteproc core
ARM: multi_v7_defconfig: Enable st_remoteproc driver.
ARM: multi_v7_defconfig: Enable STi FDMA driver
ARM: multi_v7_defconfig: Enable STi and simple-card drivers.
Documentation/devicetree/bindings/dma/st_fdma.txt | 87 +++
MAINTAINERS | 3 +
arch/arm/configs/multi_v7_defconfig | 6 +
drivers/dma/Kconfig | 13 +
drivers/dma/Makefile | 1 +
drivers/dma/st_fdma.c | 899 ++++++++++++++++++++++
drivers/dma/st_fdma.h | 249 ++++++
drivers/remoteproc/Kconfig | 23 +-
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/st_slim_rproc.c | 364 +++++++++
include/linux/remoteproc/st_slim_rproc.h | 58 ++
11 files changed, 1696 insertions(+), 8 deletions(-)
create mode 100644 Documentation/devicetree/bindings/dma/st_fdma.txt
create mode 100644 drivers/dma/st_fdma.c
create mode 100644 drivers/dma/st_fdma.h
create mode 100644 drivers/remoteproc/st_slim_rproc.c
create mode 100644 include/linux/remoteproc/st_slim_rproc.h
--
1.9.1
^ permalink raw reply
* [PATCH] clk: nxp: clk-lpc18xx-ccu: Unmap region obtained by of_iomap
From: Joachim Eastwood @ 2016-10-08 10:58 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474367987-15808-1-git-send-email-arvind.yadav.cs@gmail.com>
Hi Arvind,
On 20 September 2016 at 12:39, Arvind Yadav <arvind.yadav.cs@gmail.com> wrote:
> From: Arvind Yadav <arvind.yadav.cs@gmail.com>
>
> Free memory mapping, if lpc18xx_ccu_init is not successful.
>
> Signed-off-by: Arvind Yadav <arvind.yadav.cs@gmail.com>
Acked-by: Joachim Eastwood <manabian@gmail.com>
One comment below:
> ---
> drivers/clk/nxp/clk-lpc18xx-ccu.c | 5 ++++-
> 1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/clk/nxp/clk-lpc18xx-ccu.c b/drivers/clk/nxp/clk-lpc18xx-ccu.c
> index f7136b9..27781b4 100644
> --- a/drivers/clk/nxp/clk-lpc18xx-ccu.c
> +++ b/drivers/clk/nxp/clk-lpc18xx-ccu.c
> @@ -277,12 +277,15 @@ static void __init lpc18xx_ccu_init(struct device_node *np)
> }
>
> clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
> - if (!clk_data)
> + if (!clk_data) {
> + iounmap(reg_base);
> return;
> + }
You could also move the kzalloc() at the beginning of the function to
save a few lines. I am fine either way.
>
> clk_data->num = of_property_count_strings(np, "clock-names");
> clk_data->name = kcalloc(clk_data->num, sizeof(char *), GFP_KERNEL);
> if (!clk_data->name) {
> + iounmap(reg_base);
> kfree(clk_data);
> return;
> }
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH 1/3] firmware: add lpc18xx boot rom driver
From: Joachim Eastwood @ 2016-10-08 10:49 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9fec6e3b-fcd9-2251-c4c9-5b3f4fdc3c70@mleia.com>
Hi Vladimir,
On 7 October 2016 at 03:42, Vladimir Zapolskiy <vz@mleia.com> wrote:
> Hi Joachim,
>
> On 13.09.2016 22:51, Joachim Eastwood wrote:
>> Firmware driver for the boot ROM found on all NXP LPC18xx/43xx
>> devices. This driver makes it possible to retrieve device specific
>> information from either the ROM via API calls or from OTP memory.
>>
>> The boot ROM contains several APIs for on-chip devices. Note that not
>> all APIs are available on all devices. The API to retrieve device
>> information and internal Flash programming (IAP) is only available on
>> devices with Flash. Flashless devices retrieve device information from
>> OTP memory. The CHIPID register in CREG (syscon) is used to check if
>> IAP is available.
>>
>> For now this driver is only used to expose device information via a
>> 'SoC device'. Linux API for the IAP and OTP will be added later. These
>> two APIs will be used by a Flash MTD driver and a OTP NVMEM driver to
>> program the memory.
>>
>> Signed-off-by: Joachim Eastwood <manabian@gmail.com>
>
> please feel free to add to the series my
>
> Tested-by: Vladimir Zapolskiy <vz@mleia.com>
> Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
>
> I tested the change on a board powered by LPC4357.
Thanks!
> Nevertheless I have some nitpicks for your consideration.
>
> And the first one is that I'm not totally convinced that drivers/firmware
> is the proper place for the driver, I tend to think that it might be
> a good time to create drivers/soc/nxp/
As the driver stand now, I agree with you. But I do plan to add
firmware functionally later for Flash programming which will be used
for a MTD driver and OTP programming for the OTP nvmem driver. This is
also stated in the cover letter.
See this patch for the Flash ROM API:
https://github.com/manabian/linux-lpc/commit/f33b03e7b47515a1d48b42f8bb1476ad5c67025b
The reason why I want to merge a simple driver now is:
i) The driver useful for SoC identification
ii ) It's easier to review and the Flash API is really a separate thing
iii ) The Flash API can be reviewed/merged together with its user (MTD-driver)
>> ---
>> drivers/firmware/Kconfig | 12 ++
>> drivers/firmware/Makefile | 1 +
>> drivers/firmware/nxp_lpc_boot_rom.c | 411 ++++++++++++++++++++++++++++++++++++
>> 3 files changed, 424 insertions(+)
>> create mode 100644 drivers/firmware/nxp_lpc_boot_rom.c
>>
>
> [snip]
>
>> +/* LPC18xx/43xx CREG (syscon) defines */
>> +#define LPC18XX_CREG_CHIPID 0x200
>> +#define LPC18XX_FLASH_CHIPID0 0x4284e02b
>> +#define LPC18XX_FLASH_CHIPID1 0x7284e02b
>> +#define LPC18XX_FLASHLESS_CHIPID0 0x5284e02b
>> +#define LPC18XX_FLASHLESS_CHIPID1 0x6284e02b
>> +#define LPC43XX_FLASH_CHIPID0 0x4906002b
>> +#define LPC43XX_FLASH_CHIPID1 0x7906002b
>> +#define LPC43XX_FLASHLESS_CHIPID0 0x5906002b
>> +#define LPC43XX_FLASHLESS_CHIPID1 0x6906002b
>> +
>> +#define NXP_PART_LPC(_num, _id0, _id1, _sz0, _sz1) \
>> + { \
>> + .name = "LPC"#_num, \
>> + .id[0] = _id0, .id[1] = _id1, \
>> + .flash_size[0] = _sz0 * 1024, \
>> + .flash_size[1] = _sz1 * 1024, \
>> + }
>> +
>> +
>
> checkpatch complains:
>
> CHECK: Please don't use multiple blank lines
> #145: FILE: drivers/firmware/nxp_lpc_boot_rom.c:75:
I'll fix up.
>
>> +struct nxp_lpc_part {
>> + const char *name;
>> + u16 flash_size[2];
>
> flash_size[2] is set by NXP_PART_LPC() macro but unused, however
> in my build environment (W=1 and sparse checks) I get tons of
> legitimate compile time warnings:
flash_size is currently unused, but will be used by the MTD via the
Flash ROM API. I only kept in this patch to avoid the churn later, but
if it causes warnings I'll remove it.
> drivers/firmware/nxp_lpc_boot_rom.c:93:9: warning: cast truncates bits from constant value (80000 becomes 0)
> drivers/firmware/nxp_lpc_boot_rom.c:93:9: warning: cast truncates bits from constant value (80000 becomes 0)
> drivers/firmware/nxp_lpc_boot_rom.c:94:9: warning: cast truncates bits from constant value (80000 becomes 0)
> drivers/firmware/nxp_lpc_boot_rom.c:94:9: warning: cast truncates bits from constant value (80000 becomes 0)
> ....
>
> drivers/firmware/nxp_lpc_boot_rom.c:93:2: warning: large integer implicitly truncated to unsigned type [-Woverflow]
> NXP_PART_LPC(1857, 0xf001d830, 0x00, 512, 512),
> ^
> drivers/firmware/nxp_lpc_boot_rom.c:93:2: warning: large integer implicitly truncated to unsigned type [-Woverflow]
> drivers/firmware/nxp_lpc_boot_rom.c:94:2: warning: large integer implicitly truncated to unsigned type [-Woverflow]
> NXP_PART_LPC(18S57, 0xf001d860, 0x00, 512, 512),
> ^
> ....
>
> and so on.
>
> Please consider either to store flash size in kB or change storage type to u32.
This was a last minute change and I didn't notice before sending it. I
have already fixed this up in my tree.
>> + u32 id[2];
>> +};
>> +
>> +static const struct nxp_lpc_part nxp_lpc_parts[] = {
>> + /* LPC18xx Flashless parts */
>> + NXP_PART_LPC(1850, 0xf000d830, 0x00, 0, 0),
>> + NXP_PART_LPC(18S50, 0xf000d860, 0x00, 0, 0),
>> + NXP_PART_LPC(1830, 0xf000da30, 0x00, 0, 0),
>> + NXP_PART_LPC(18S30, 0xf000da60, 0x00, 0, 0),
>> + NXP_PART_LPC(1820, 0xf00adb3c, 0x00, 0, 0),
>> + NXP_PART_LPC(18S20, 0xf00adb6c, 0x00, 0, 0),
>> + NXP_PART_LPC(1810, 0xf00b5b3f, 0x00, 0, 0),
>> + NXP_PART_LPC(18S10, 0xf00b5b6f, 0x00, 0, 0),
>> + /* LPC18xx Flash parts */
>> + NXP_PART_LPC(1857, 0xf001d830, 0x00, 512, 512),
>> + NXP_PART_LPC(18S57, 0xf001d860, 0x00, 512, 512),
>> + NXP_PART_LPC(1853, 0xf001d830, 0x44, 256, 256),
>> + NXP_PART_LPC(1837, 0xf001da30, 0x00, 512, 512),
>> + NXP_PART_LPC(18S37, 0xf001d860, 0x00, 512, 512),
>> + NXP_PART_LPC(1833, 0xf001da30, 0x44, 256, 256),
>> + NXP_PART_LPC(1827, 0xf001db3c, 0x00, 512, 512),
>> + NXP_PART_LPC(1825, 0xf001db3c, 0x22, 384, 384),
>> + NXP_PART_LPC(1823, 0xf00bdb3c, 0x44, 256, 256),
>> + NXP_PART_LPC(1822, 0xf00bdb3c, 0x80, 512, 0),
>> + NXP_PART_LPC(1817, 0xf001db3f, 0x00, 512, 512),
>> + NXP_PART_LPC(1815, 0xf001db3f, 0x22, 384, 384),
>> + NXP_PART_LPC(1813, 0xf00bdb3f, 0x44, 256, 256),
>> + NXP_PART_LPC(1812, 0xf00bdb3f, 0x80, 512, 0),
>> + /* LPC43xx Flashless parts */
>> + NXP_PART_LPC(4370, 0x00000030, 0x00, 0, 0), /* LBGA256 */
>> + NXP_PART_LPC(4370, 0x00000230, 0x00, 0, 0), /* TFBGA100 */
>> + NXP_PART_LPC(43S70, 0x00000060, 0x00, 0, 0),
>> + NXP_PART_LPC(4350, 0xa0000830, 0x00, 0, 0),
>> + NXP_PART_LPC(43S50, 0xa0000860, 0x00, 0, 0),
>> + NXP_PART_LPC(4330, 0xa0000a30, 0x00, 0, 0),
>> + NXP_PART_LPC(43S30, 0xa0000a60, 0x00, 0, 0),
>> + NXP_PART_LPC(4320, 0xa000cb3c, 0x00, 0, 0),
>> + NXP_PART_LPC(43S20, 0xa000cb6c, 0x00, 0, 0),
>> + NXP_PART_LPC(4310, 0xa00acb3f, 0x00, 0, 0),
>> + /* LPC43xx parts with Flash */
>> + NXP_PART_LPC(4367, 0x8001c030, 0x00, 512, 512),
>> + NXP_PART_LPC(43S67, 0x8001c060, 0x00, 512, 512),
>> + NXP_PART_LPC(4357, 0xa001c830, 0x00, 512, 512),
>> + NXP_PART_LPC(43S57, 0xa001c860, 0x00, 512, 512), /* LBGA256 */
>> + NXP_PART_LPC(43S57, 0xa001ca60, 0x00, 512, 512), /* LQFP208 */
>> + NXP_PART_LPC(4353, 0xa001c830, 0x44, 256, 256),
>> + NXP_PART_LPC(4337, 0xa001ca30, 0x00, 512, 512),
>> + NXP_PART_LPC(43S37, 0xa001ca60, 0x00, 512, 512),
>> + NXP_PART_LPC(4333, 0xa001ca30, 0x44, 256, 256),
>> + NXP_PART_LPC(4327, 0xa001cb3c, 0x00, 512, 512),
>> + NXP_PART_LPC(4325, 0xa001cb3c, 0x22, 384, 384),
>> + NXP_PART_LPC(4323, 0xa00bcb3c, 0x44, 256, 256),
>> + NXP_PART_LPC(4322, 0xa00bcb3c, 0x80, 512, 0),
>> + NXP_PART_LPC(4317, 0xa001cb3f, 0x00, 512, 512),
>> + NXP_PART_LPC(4315, 0xa001cb3f, 0x22, 384, 384),
>> + NXP_PART_LPC(4313, 0xa00bcb3f, 0x44, 256, 256),
>> + NXP_PART_LPC(4312, 0xa00bcb3f, 0x80, 512, 0),
>> +};
>> +
>> +struct iap_rom {
>> + void (*entry)(u32 *, u32 *);
>> +};
>
> Here it might be simpler to declare a typedef instead of a struct.
This is done using a struct to keep it consistent with the other ROM
APIs that will be added later. The IAP is kinda special since it only
has one function while most other APIs have several functions.
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH 3/3] ARM: dts: lpc18xx: add boot rom node
From: Joachim Eastwood @ 2016-10-08 10:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <7d4f9e48-e93a-db9e-fba0-e1b64e7056d7@mleia.com>
Hi Vladimir,
On 7 October 2016 at 03:50, Vladimir Zapolskiy <vz@mleia.com> wrote:
> Hi Joachim,
>
> On 13.09.2016 22:51, Joachim Eastwood wrote:
>> Add node for the boot ROM found on all NXP LPC18xx/43xx devices.
>>
>> Signed-off-by: Joachim Eastwood <manabian@gmail.com>
>> ---
>> arch/arm/boot/dts/lpc18xx.dtsi | 8 ++++++++
>> 1 file changed, 8 insertions(+)
>>
>> diff --git a/arch/arm/boot/dts/lpc18xx.dtsi b/arch/arm/boot/dts/lpc18xx.dtsi
>> index 631e6f6c..9f94f3e 100644
>> --- a/arch/arm/boot/dts/lpc18xx.dtsi
>> +++ b/arch/arm/boot/dts/lpc18xx.dtsi
>> @@ -81,6 +81,14 @@
>> status = "disabled";
>> };
>>
>> + boot_rom: firmware at 10400000 {
>> + compatible = "nxp,lpc1850-boot-rom";
>> + reg = <0x10400000 0x10000>;
>> + syscon = <&creg>;
>> + nvmem-cells = <&part_id>;
>
> nitpicking, this change has a compile time dependency on yours
> "nvmem: add lpc18xx OTP memory driver" series, I haven't noticed
> this info stated, but it may be an overlook on my part.
It's not really a hard dependency and I will ensure that the DTS
changes comes in the correct order.
>> + nvmem-cell-names = "PartID";
>> + };
>> +
>> dmac: dma-controller at 40002000 {
>> compatible = "arm,pl080", "arm,primecell";
>> arm,primecell-periphid = <0x00041080>;
>>
>
> Tested-by: Vladimir Zapolskiy <vz@mleia.com>
> Reviewed-by: Vladimir Zapolskiy <vz@mleia.com>
Thanks for testing and going through the changes.
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH 1/3] nvmem: add NXP LPC18xx OTP driver
From: Joachim Eastwood @ 2016-10-08 10:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <8ad4d5fb-0672-fbd7-6076-a91c89dee132@mleia.com>
Hi Vladimir,
On 7 October 2016 at 03:44, Vladimir Zapolskiy <vz@mleia.com> wrote:
> Hi Joachim, Srinivas,
>
> On 13.09.2016 19:12, Joachim Eastwood wrote:
>> Add simple read only driver for the internal OTP (One Time Programmable)
>> memory found on all NXP LPC18xx and LPC43xx devices.
>>
>> The OTP memory is split into 4 banks each with 4 32-bits word. Some of
>> the banks contain predefined data while others are for general purpose
>> and user programmable via the OTP API in ROM. Note that writing to the
>> OTP memory is not yet supported.
>>
>> Signed-off-by: Joachim Eastwood <manabian@gmail.com>
>> ---
>
> please feel free to add my
>
> Tested-by: Vladimir Zapolskiy <vz@mleia.com>
Thanks for testing!
regards,
Joachim Eastwood
^ permalink raw reply
* [PATCH] ARM: dts: pxa: fix gpio0 and gpio1 interrupts
From: Robert Jarzmik @ 2016-10-08 9:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474874531-26703-1-git-send-email-robert.jarzmik@free.fr>
Robert Jarzmik <robert.jarzmik@free.fr> writes:
> Since gpio-pxa was redesigned to differenciate gpio0, gpio1 and the
> gpio-mux interrupt as in the hardware IP, the device-tree description
> should be amended so that interrupts from gpio0 and gpio1 can be mapped
> to consumers.
>
> This is especially true on lubbock and mainstone devices where gpio0 is
> multiplexed on pxa_cplds for ethernet, sa1111, usb udc, and other
> devices.
>
> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Applied to pxa/dt.
Cheers.
--
Robert
^ permalink raw reply
* [PATCH 7/10] mmc: sdhci-xenon: Add support to PHYs of Marvell Xenon SDHC
From: Ziji Hu @ 2016-10-08 9:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <25146173-d98b-f346-b333-4d7466960496@rock-chips.com>
Hi Shawn,
On 2016/10/8 10:44, Shawn Lin wrote:
> ? 2016/10/7 23:22, Gregory CLEMENT ??:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Marvell Xenon eMMC/SD/SDIO Host Controller contains PHY.
>> Three types of PHYs are supported.
>>
>> Add support to multiple types of PHYs init and configuration.
>> Add register definitions of PHYs.
>>
>> Signed-off-by: Hu Ziji <huziji@marvell.com>
>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> ---
>> MAINTAINERS | 1 +-
>> drivers/mmc/host/Makefile | 2 +-
>> drivers/mmc/host/sdhci-xenon-phy.c | 1141 +++++++++++++++++++++++++++++-
>> drivers/mmc/host/sdhci-xenon-phy.h | 157 ++++-
>> drivers/mmc/host/sdhci-xenon.c | 4 +-
>> drivers/mmc/host/sdhci-xenon.h | 17 +-
>> 6 files changed, 1321 insertions(+), 1 deletion(-)
>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.c
>> create mode 100644 drivers/mmc/host/sdhci-xenon-phy.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 859420e5dfd3..b5673c2ee5f2 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -7583,6 +7583,7 @@ M: Ziji Hu <huziji@marvell.com>
>> L: linux-mmc at vger.kernel.org
>> S: Supported
>> F: drivers/mmc/host/sdhci-xenon.*
>> +F: drivers/mmc/host/sdhci-xenon-phy.*
>
> drivers/mmc/host/sdhci-xenon* shoube enough
>
>> F: Documentation/devicetree/bindings/mmc/marvell,sdhci-xenon.txt
>>
>> MATROX FRAMEBUFFER DRIVER
>> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
>> index 75eaf743486c..4f2854556ff7 100644
>> --- a/drivers/mmc/host/Makefile
>> +++ b/drivers/mmc/host/Makefile
>> @@ -82,4 +82,4 @@ ifeq ($(CONFIG_CB710_DEBUG),y)
>> endif
>>
>> obj-$(CONFIG_MMC_SDHCI_XENON) += sdhci-xenon-driver.o
>> -sdhci-xenon-driver-y += sdhci-xenon.o
>> +sdhci-xenon-driver-y += sdhci-xenon.o sdhci-xenon-phy.o
>> diff --git a/drivers/mmc/host/sdhci-xenon-phy.c b/drivers/mmc/host/sdhci-xenon-phy.c
>> new file mode 100644
>> index 000000000000..4eb8fea1bec9
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon-phy.c
>
> Well, it's legit to use phy API and move your phy
> operations to PHY subsystem. :)
>
Actually we tried to put the PHY code into Linux PHY framework.
But it cannot fit in Linux common PHY framework.
Our Xenon SDHC PHY register is a part of Xenon SDHC register set.
Besides, during MMC initialization, MMC sequence has to call several PHY functions to complete timing setting.
In those PHY setting functions, they have to access SDHC register and know current MMC setting, such as bus width, clock frequency and speed mode.
As a result, we have to implement PHY under MMC directory.
Thank you.
Best regards,
Hu Ziji
>> @@ -0,0 +1,1141 @@
>> +/*
>> + * PHY support for Xenon SDHC
>> + *
>> + * Copyright (C) 2016 Marvell, All Rights Reserved.
>> + *
>> + * Author: Hu Ziji <huziji@marvell.com>
>> + * Date: 2016-8-24
>> + *
>> + * 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 version 2.
>> + */
>> +
>> +#include <linux/slab.h>
>> +#include <linux/delay.h>
>> +#include <linux/of_address.h>
>> +#include <linux/mmc/host.h>
>> +#include <linux/mmc/mmc.h>
>> +#include <linux/mmc/card.h>
>> +#include <linux/mmc/sdio.h>
>> +
>> +#include "sdhci.h"
>> +#include "sdhci-pltfm.h"
>> +#include "sdhci-xenon.h"
>> +
>> +static const char * const phy_types[] = {
>> + "sdh phy",
>> + "emmc 5.0 phy",
>> + "emmc 5.1 phy"
>> +};
>> +
>> +enum phy_type_enum {
>> + SDH_PHY,
>> + EMMC_5_0_PHY,
>> + EMMC_5_1_PHY,
>> + NR_PHY_TYPES
>> +};
>> +
>> +struct soc_pad_ctrl_table {
>> + const char *soc;
>> + void (*set_soc_pad)(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +};
>> +
>> +struct soc_pad_ctrl {
>> + /* Register address of SOC PHY PAD ctrl */
>> + void __iomem *reg;
>> + /* SOC PHY PAD ctrl type */
>> + enum soc_pad_ctrl_type pad_type;
>> + /* SOC specific operation to set SOC PHY PAD */
>> + void (*set_soc_pad)(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +};
>> +
>> +static struct xenon_emmc_phy_regs xenon_emmc_5_0_phy_regs = {
>> + .timing_adj = EMMC_5_0_PHY_TIMING_ADJUST,
>> + .func_ctrl = EMMC_5_0_PHY_FUNC_CONTROL,
>> + .pad_ctrl = EMMC_5_0_PHY_PAD_CONTROL,
>> + .pad_ctrl2 = EMMC_5_0_PHY_PAD_CONTROL2,
>> + .dll_ctrl = EMMC_5_0_PHY_DLL_CONTROL,
>> + .logic_timing_adj = EMMC_5_0_PHY_LOGIC_TIMING_ADJUST,
>> + .delay_mask = EMMC_5_0_PHY_FIXED_DELAY_MASK,
>> + .dll_update = DLL_UPDATE_STROBE_5_0,
>> +};
>> +
>> +static struct xenon_emmc_phy_regs xenon_emmc_5_1_phy_regs = {
>> + .timing_adj = EMMC_PHY_TIMING_ADJUST,
>> + .func_ctrl = EMMC_PHY_FUNC_CONTROL,
>> + .pad_ctrl = EMMC_PHY_PAD_CONTROL,
>> + .pad_ctrl2 = EMMC_PHY_PAD_CONTROL2,
>> + .dll_ctrl = EMMC_PHY_DLL_CONTROL,
>> + .logic_timing_adj = EMMC_PHY_LOGIC_TIMING_ADJUST,
>> + .delay_mask = EMMC_PHY_FIXED_DELAY_MASK,
>> + .dll_update = DLL_UPDATE,
>> +};
>> +
>> +static int xenon_delay_adj_test(struct mmc_card *card);
>> +
>> +/*
>> + * eMMC PHY configuration and operations
>> + */
>> +struct emmc_phy_params {
>> + bool slow_mode;
>> +
>> + u8 znr;
>> + u8 zpr;
>> +
>> + /* Nr of consecutive Sampling Points of a Valid Sampling Window */
>> + u8 nr_tun_times;
>> + /* Divider for calculating Tuning Step */
>> + u8 tun_step_divider;
>> +
>> + struct soc_pad_ctrl pad_ctrl;
>> +};
>> +
>> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card);
>> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card);
>> +static void xenon_emmc_phy_set(struct sdhci_host *host,
>> + unsigned char timing);
>> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +
>> +static const struct xenon_phy_ops emmc_phy_ops = {
>> + .strobe_delay_adj = xenon_emmc_phy_strobe_delay_adj,
>> + .fix_sampl_delay_adj = xenon_emmc_phy_fix_sampl_delay_adj,
>> + .phy_set = xenon_emmc_phy_set,
>> + .set_soc_pad = xenon_emmc_set_soc_pad,
>> +};
>> +
>> +static int alloc_emmc_phy(struct sdhci_xenon_priv *priv)
>> +{
>> + struct emmc_phy_params *params;
>> +
>> + params = kzalloc(sizeof(*params), GFP_KERNEL);
>> + if (!params)
>> + return -ENOMEM;
>> +
>> + priv->phy_params = params;
>> + priv->phy_ops = &emmc_phy_ops;
>> + if (priv->phy_type == EMMC_5_0_PHY)
>> + priv->emmc_phy_regs = &xenon_emmc_5_0_phy_regs;
>> + else
>> + priv->emmc_phy_regs = &xenon_emmc_5_1_phy_regs;
>> +
>> + return 0;
>> +}
>> +
>> +static int xenon_emmc_phy_init(struct sdhci_host *host)
>> +{
>> + u32 reg;
>> + u32 wait, clock;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> +
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg |= PHY_INITIALIZAION;
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> +
>> + /* Add duration of FC_SYNC_RST */
>> + wait = ((reg >> FC_SYNC_RST_DURATION_SHIFT) &
>> + FC_SYNC_RST_DURATION_MASK);
>> + /* Add interval between FC_SYNC_EN and FC_SYNC_RST */
>> + wait += ((reg >> FC_SYNC_RST_EN_DURATION_SHIFT) &
>> + FC_SYNC_RST_EN_DURATION_MASK);
>> + /* Add duration of asserting FC_SYNC_EN */
>> + wait += ((reg >> FC_SYNC_EN_DURATION_SHIFT) &
>> + FC_SYNC_EN_DURATION_MASK);
>> + /* Add duration of waiting for PHY */
>> + wait += ((reg >> WAIT_CYCLE_BEFORE_USING_SHIFT) &
>> + WAIT_CYCLE_BEFORE_USING_MASK);
>> + /* 4 addtional bus clock and 4 AXI bus clock are required */
>> + wait += 8;
>> + wait <<= 20;
>> +
>> + clock = host->clock;
>> + if (!clock)
>> + /* Use the possibly slowest bus frequency value */
>> + clock = LOWEST_SDCLK_FREQ;
>> + /* get the wait time */
>> + wait /= clock;
>> + wait++;
>> + /* wait for host eMMC PHY init completes */
>> + udelay(wait);
>> +
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= PHY_INITIALIZAION;
>> + if (reg) {
>> + dev_err(mmc_dev(host->mmc), "eMMC PHY init cannot complete after %d us\n",
>> + wait);
>> + return -ETIMEDOUT;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +#define ARMADA_3700_SOC_PAD_1_8V 0x1
>> +#define ARMADA_3700_SOC_PAD_3_3V 0x0
>> +
>> +static void armada_3700_soc_pad_voltage_set(struct sdhci_host *host,
>> + unsigned char signal_voltage)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> +
>> + if (params->pad_ctrl.pad_type == SOC_PAD_FIXED_1_8V) {
>> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
>> + } else if (params->pad_ctrl.pad_type == SOC_PAD_SD) {
>> + if (signal_voltage == MMC_SIGNAL_VOLTAGE_180)
>> + writel(ARMADA_3700_SOC_PAD_1_8V, params->pad_ctrl.reg);
>> + else if (signal_voltage == MMC_SIGNAL_VOLTAGE_330)
>> + writel(ARMADA_3700_SOC_PAD_3_3V, params->pad_ctrl.reg);
>> + }
>> +}
>> +
>> +static void xenon_emmc_set_soc_pad(struct sdhci_host *host,
>> + unsigned char signal_voltage)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> +
>> + if (!params->pad_ctrl.reg)
>> + return;
>> +
>> + if (params->pad_ctrl.set_soc_pad)
>> + params->pad_ctrl.set_soc_pad(host, signal_voltage);
>> +}
>> +
>> +static int emmc_phy_set_fix_sampl_delay(struct sdhci_host *host,
>> + unsigned int delay,
>> + bool invert,
>> + bool delay_90_degree)
>> +{
>> + u32 reg;
>> + unsigned long flags;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + int ret = 0;
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + /* Setup Sampling fix delay */
>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>> + reg &= ~phy_regs->delay_mask;
>> + reg |= delay & phy_regs->delay_mask;
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> +
>> + if (priv->phy_type == EMMC_5_0_PHY) {
>> + /* set 90 degree phase if necessary */
>> + reg &= ~DELAY_90_DEGREE_MASK_EMMC5;
>> + reg |= (delay_90_degree << DELAY_90_DEGREE_SHIFT_EMMC5);
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> + }
>> +
>> + /* Disable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + if (priv->phy_type == EMMC_5_1_PHY) {
>> + /* set 90 degree phase if necessary */
>> + reg = sdhci_readl(host, EMMC_PHY_FUNC_CONTROL);
>> + reg &= ~ASYNC_DDRMODE_MASK;
>> + reg |= (delay_90_degree << ASYNC_DDRMODE_SHIFT);
>> + sdhci_writel(host, reg, EMMC_PHY_FUNC_CONTROL);
>> + }
>> +
>> + /* Setup Inversion of Sampling edge */
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= ~SAMPL_INV_QSP_PHASE_SELECT;
>> + reg |= (invert << SAMPL_INV_QSP_PHASE_SELECT_SHIFT);
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> +
>> + /* Enable SD internal clock */
>> + ret = enable_xenon_internal_clk(host);
>> + if (ret)
>> + goto out;
>> +
>> + /* Enable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg |= SDHCI_CLOCK_CARD_EN;
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + /*
>> + * Has to re-initialize eMMC PHY here to active PHY
>> + * because later get status cmd will be issued.
>> + */
>> + ret = xenon_emmc_phy_init(host);
>> +
>> +out:
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return ret;
>> +}
>> +
>> +static int emmc_phy_do_fix_sampl_delay(struct sdhci_host *host,
>> + struct mmc_card *card,
>> + unsigned int delay,
>> + bool invert, bool quarter)
>> +{
>> + int ret;
>> +
>> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
>> +
>> + ret = xenon_delay_adj_test(card);
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "fail when sampling fix delay = %d, phase = %d degree\n",
>> + delay, invert * 180 + quarter * 90);
>> + return -1;
>> + }
>> + return 0;
>> +}
>> +
>> +static int xenon_emmc_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + enum sampl_fix_delay_phase phase;
>> + int idx, nr_pair;
>> + int ret;
>> + unsigned int delay;
>> + unsigned int min_delay, max_delay;
>> + bool invert, quarter;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + u32 coarse_step, fine_step;
>> + const enum sampl_fix_delay_phase delay_edge[] = {
>> + PHASE_0_DEGREE,
>> + PHASE_180_DEGREE,
>> + PHASE_90_DEGREE,
>> + PHASE_270_DEGREE
>> + };
>> +
>> + coarse_step = phy_regs->delay_mask >> 1;
>> + fine_step = coarse_step >> 2;
>> +
>> + nr_pair = ARRAY_SIZE(delay_edge);
>> +
>> + for (idx = 0; idx < nr_pair; idx++) {
>> + phase = delay_edge[idx];
>> + invert = (phase & 0x2) ? true : false;
>> + quarter = (phase & 0x1) ? true : false;
>> +
>> + /* increase delay value to get fix delay */
>> + for (min_delay = 0;
>> + min_delay <= phy_regs->delay_mask;
>> + min_delay += coarse_step) {
>> + ret = emmc_phy_do_fix_sampl_delay(host, card, min_delay,
>> + invert, quarter);
>> + if (!ret)
>> + break;
>> + }
>> +
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "Fail to set Sampling Fixed Delay with phase = %d degree\n",
>> + phase * 90);
>> + continue;
>> + }
>> +
>> + for (max_delay = min_delay + fine_step;
>> + max_delay < phy_regs->delay_mask;
>> + max_delay += fine_step) {
>> + ret = emmc_phy_do_fix_sampl_delay(host, card, max_delay,
>> + invert, quarter);
>> + if (ret) {
>> + max_delay -= fine_step;
>> + break;
>> + }
>> + }
>> +
>> + if (!ret) {
>> + ret = emmc_phy_do_fix_sampl_delay(host, card,
>> + phy_regs->delay_mask,
>> + invert, quarter);
>> + if (!ret)
>> + max_delay = phy_regs->delay_mask;
>> + }
>> +
>> + /*
>> + * Sampling Fixed Delay line window should be large enough,
>> + * thus the sampling point (the middle of the window)
>> + * can work when environment varies.
>> + * However, there is no clear conclusion how large the window
>> + * should be.
>> + */
>> + if ((max_delay - min_delay) <=
>> + EMMC_PHY_FIXED_DELAY_WINDOW_MIN) {
>> + dev_info(mmc_dev(host->mmc),
>> + "The window size %d with phase = %d degree is too small\n",
>> + max_delay - min_delay, phase * 90);
>> + continue;
>> + }
>> +
>> + delay = (min_delay + max_delay) / 2;
>> + emmc_phy_set_fix_sampl_delay(host, delay, invert, quarter);
>> + dev_dbg(mmc_dev(host->mmc),
>> + "sampling fix delay = %d with phase = %d degree\n",
>> + delay, phase * 90);
>> + return 0;
>> + }
>> +
>> + return -EIO;
>> +}
>> +
>> +static int xenon_emmc_phy_enable_dll(struct sdhci_host *host)
>> +{
>> + u32 reg;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + u8 timeout;
>> +
>> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
>> + return -EINVAL;
>> +
>> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
>> + if (reg & DLL_ENABLE)
>> + return 0;
>> +
>> + /* Enable DLL */
>> + reg = sdhci_readl(host, phy_regs->dll_ctrl);
>> + reg |= (DLL_ENABLE | DLL_FAST_LOCK);
>> +
>> + /*
>> + * Set Phase as 90 degree, which is most common value.
>> + * Might set another value if necessary.
>> + * The granularity is 1 degree.
>> + */
>> + reg &= ~((DLL_PHASE_MASK << DLL_PHSEL0_SHIFT) |
>> + (DLL_PHASE_MASK << DLL_PHSEL1_SHIFT));
>> + reg |= ((DLL_PHASE_90_DEGREE << DLL_PHSEL0_SHIFT) |
>> + (DLL_PHASE_90_DEGREE << DLL_PHSEL1_SHIFT));
>> +
>> + reg &= ~DLL_BYPASS_EN;
>> + reg |= phy_regs->dll_update;
>> + if (priv->phy_type == EMMC_5_1_PHY)
>> + reg &= ~DLL_REFCLK_SEL;
>> + sdhci_writel(host, reg, phy_regs->dll_ctrl);
>> +
>> + /* Wait max 32 ms */
>> + timeout = 32;
>> + while (!(sdhci_readw(host, SDHC_SLOT_EXT_PRESENT_STATE) & LOCK_STATE)) {
>> + if (!timeout) {
>> + dev_err(mmc_dev(host->mmc), "Wait for DLL Lock time-out\n");
>> + return -ETIMEDOUT;
>> + }
>> + timeout--;
>> + mdelay(1);
>> + }
>> + return 0;
>> +}
>> +
>> +static int __emmc_phy_config_tuning(struct sdhci_host *host)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> + u32 reg, tuning_step;
>> + int ret;
>> + unsigned long flags;
>> +
>> + if (WARN_ON(host->clock <= MMC_HIGH_52_MAX_DTR))
>> + return -EINVAL;
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + ret = xenon_emmc_phy_enable_dll(host);
>> + if (ret) {
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return ret;
>> + }
>> +
>> + reg = sdhci_readl(host, SDHC_SLOT_DLL_CUR_DLY_VAL);
>> + tuning_step = reg / params->tun_step_divider;
>> + if (unlikely(tuning_step > TUNING_STEP_MASK)) {
>> + dev_warn(mmc_dev(host->mmc),
>> + "HS200 TUNING_STEP %d is larger than MAX value\n",
>> + tuning_step);
>> + tuning_step = TUNING_STEP_MASK;
>> + }
>> +
>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>> + reg &= ~(TUN_CONSECUTIVE_TIMES_MASK << TUN_CONSECUTIVE_TIMES_SHIFT);
>> + reg |= (params->nr_tun_times << TUN_CONSECUTIVE_TIMES_SHIFT);
>> + reg &= ~(TUNING_STEP_MASK << TUNING_STEP_SHIFT);
>> + reg |= (tuning_step << TUNING_STEP_SHIFT);
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> +
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return 0;
>> +}
>> +
>> +static int xenon_emmc_phy_config_tuning(struct sdhci_host *host)
>> +{
>> + return __emmc_phy_config_tuning(host);
>> +}
>> +
>> +static void xenon_emmc_phy_strobe_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + u32 reg;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + unsigned long flags;
>> +
>> + if (host->clock <= MMC_HIGH_52_MAX_DTR)
>> + return;
>> +
>> + dev_dbg(mmc_dev(host->mmc), "starts HS400 strobe delay adjustment\n");
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + xenon_emmc_phy_enable_dll(host);
>> +
>> + /* Enable SDHC Data Strobe */
>> + reg = sdhci_readl(host, SDHC_SLOT_EMMC_CTRL);
>> + reg |= ENABLE_DATA_STROBE;
>> + sdhci_writel(host, reg, SDHC_SLOT_EMMC_CTRL);
>> +
>> + /* Set Data Strobe Pull down */
>> + if (priv->phy_type == EMMC_5_0_PHY) {
>> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
>> + reg |= EMMC5_FC_QSP_PD;
>> + reg &= ~EMMC5_FC_QSP_PU;
>> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
>> + } else {
>> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
>> + reg |= EMMC5_1_FC_QSP_PD;
>> + reg &= ~EMMC5_1_FC_QSP_PU;
>> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
>> + }
>> + spin_unlock_irqrestore(&host->lock, flags);
>> +}
>> +
>> +#define LOGIC_TIMING_VALUE 0x00AA8977
>> +
>> +static void xenon_emmc_phy_set(struct sdhci_host *host,
>> + unsigned char timing)
>> +{
>> + u32 reg;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + struct emmc_phy_params *params = priv->phy_params;
>> + struct xenon_emmc_phy_regs *phy_regs = priv->emmc_phy_regs;
>> + struct mmc_card *card = priv->card_candidate;
>> + unsigned long flags;
>> +
>> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting starts\n");
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + /* Setup pad, set bit[28] and bits[26:24] */
>> + reg = sdhci_readl(host, phy_regs->pad_ctrl);
>> + reg |= (FC_DQ_RECEN | FC_CMD_RECEN | FC_QSP_RECEN | OEN_QSN);
>> + /*
>> + * All FC_XX_RECEIVCE should be set as CMOS Type
>> + */
>> + reg |= FC_ALL_CMOS_RECEIVER;
>> + sdhci_writel(host, reg, phy_regs->pad_ctrl);
>> +
>> + /* Set CMD and DQ Pull Up */
>> + if (priv->phy_type == EMMC_5_0_PHY) {
>> + reg = sdhci_readl(host, EMMC_5_0_PHY_PAD_CONTROL);
>> + reg |= (EMMC5_FC_CMD_PU | EMMC5_FC_DQ_PU);
>> + reg &= ~(EMMC5_FC_CMD_PD | EMMC5_FC_DQ_PD);
>> + sdhci_writel(host, reg, EMMC_5_0_PHY_PAD_CONTROL);
>> + } else {
>> + reg = sdhci_readl(host, EMMC_PHY_PAD_CONTROL1);
>> + reg |= (EMMC5_1_FC_CMD_PU | EMMC5_1_FC_DQ_PU);
>> + reg &= ~(EMMC5_1_FC_CMD_PD | EMMC5_1_FC_DQ_PD);
>> + sdhci_writel(host, reg, EMMC_PHY_PAD_CONTROL1);
>> + }
>> +
>> + if ((timing == MMC_TIMING_LEGACY) || !card)
>> + goto phy_init;
>> +
>> + /*
>> + * FIXME: should depends on the specific board timing.
>> + */
>> + if ((timing == MMC_TIMING_MMC_HS400) ||
>> + (timing == MMC_TIMING_MMC_HS200) ||
>> + (timing == MMC_TIMING_UHS_SDR50) ||
>> + (timing == MMC_TIMING_UHS_SDR104) ||
>> + (timing == MMC_TIMING_UHS_DDR50) ||
>> + (timing == MMC_TIMING_UHS_SDR25) ||
>> + (timing == MMC_TIMING_MMC_DDR52)) {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= ~OUTPUT_QSN_PHASE_SELECT;
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + }
>> +
>> + /*
>> + * If SDIO card, set SDIO Mode
>> + * Otherwise, clear SDIO Mode and Slow Mode
>> + */
>> + if (mmc_card_sdio(card)) {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg |= TIMING_ADJUST_SDIO_MODE;
>> +
>> + if ((timing == MMC_TIMING_UHS_SDR25) ||
>> + (timing == MMC_TIMING_UHS_SDR12) ||
>> + (timing == MMC_TIMING_SD_HS) ||
>> + (timing == MMC_TIMING_LEGACY))
>> + reg |= TIMING_ADJUST_SLOW_MODE;
>> +
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + } else {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg &= ~(TIMING_ADJUST_SDIO_MODE | TIMING_ADJUST_SLOW_MODE);
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + }
>> +
>> + if (((timing == MMC_TIMING_UHS_SDR50) ||
>> + (timing == MMC_TIMING_UHS_SDR25) ||
>> + (timing == MMC_TIMING_UHS_SDR12) ||
>> + (timing == MMC_TIMING_SD_HS) ||
>> + (timing == MMC_TIMING_MMC_HS) ||
>> + (timing == MMC_TIMING_LEGACY)) && params->slow_mode) {
>> + reg = sdhci_readl(host, phy_regs->timing_adj);
>> + reg |= TIMING_ADJUST_SLOW_MODE;
>> + sdhci_writel(host, reg, phy_regs->timing_adj);
>> + }
>> +
>> + /*
>> + * Set preferred ZNR and ZPR value
>> + * The ZNR and ZPR value vary between different boards.
>> + * Define them both in sdhci-xenon-emmc-phy.h.
>> + */
>> + reg = sdhci_readl(host, phy_regs->pad_ctrl2);
>> + reg &= ~((ZNR_MASK << ZNR_SHIFT) | ZPR_MASK);
>> + reg |= ((params->znr << ZNR_SHIFT) | params->zpr);
>> + sdhci_writel(host, reg, phy_regs->pad_ctrl2);
>> +
>> + /*
>> + * When setting EMMC_PHY_FUNC_CONTROL register,
>> + * SD clock should be disabled
>> + */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg &= ~SDHCI_CLOCK_CARD_EN;
>> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + if ((timing == MMC_TIMING_UHS_DDR50) ||
>> + (timing == MMC_TIMING_MMC_HS400) ||
>> + (timing == MMC_TIMING_MMC_DDR52)) {
>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>> + reg |= (DQ_DDR_MODE_MASK << DQ_DDR_MODE_SHIFT) | CMD_DDR_MODE;
>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>> + }
>> +
>> + if (timing == MMC_TIMING_MMC_HS400) {
>> + reg = sdhci_readl(host, phy_regs->func_ctrl);
>> + reg &= ~DQ_ASYNC_MODE;
>> + sdhci_writel(host, reg, phy_regs->func_ctrl);
>> + }
>> +
>> + /* Enable bus clock */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg |= SDHCI_CLOCK_CARD_EN;
>> + sdhci_writew(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + if (timing == MMC_TIMING_MMC_HS400)
>> + /* Hardware team recommend a value for HS400 */
>> + sdhci_writel(host, LOGIC_TIMING_VALUE,
>> + phy_regs->logic_timing_adj);
>> +
>> +phy_init:
>> + xenon_emmc_phy_init(host);
>> +
>> + spin_unlock_irqrestore(&host->lock, flags);
>> +
>> + dev_dbg(mmc_dev(host->mmc), "eMMC PHY setting completes\n");
>> +}
>> +
>> +static int get_dt_pad_ctrl_data(struct sdhci_host *host,
>> + struct device_node *np,
>> + struct emmc_phy_params *params)
>> +{
>> + int ret = 0;
>> + const char *name;
>> + struct resource iomem;
>> +
>> + if (of_device_is_compatible(np, "marvell,armada-3700-sdhci"))
>> + params->pad_ctrl.set_soc_pad = armada_3700_soc_pad_voltage_set;
>> + else
>> + return 0;
>> +
>> + if (of_address_to_resource(np, 1, &iomem)) {
>> + dev_err(mmc_dev(host->mmc), "Unable to find SOC PAD ctrl register address for %s\n",
>> + np->name);
>> + return -EINVAL;
>> + }
>> +
>> + params->pad_ctrl.reg = devm_ioremap_resource(mmc_dev(host->mmc),
>> + &iomem);
>> + if (IS_ERR(params->pad_ctrl.reg)) {
>> + dev_err(mmc_dev(host->mmc), "Unable to get SOC PHY PAD ctrl regiser for %s\n",
>> + np->name);
>> + return PTR_ERR(params->pad_ctrl.reg);
>> + }
>> +
>> + ret = of_property_read_string(np, "xenon,pad-type", &name);
>> + if (ret) {
>> + dev_err(mmc_dev(host->mmc), "Unable to determine SOC PHY PAD ctrl type\n");
>> + return ret;
>> + }
>> + if (!strcmp(name, "sd")) {
>> + params->pad_ctrl.pad_type = SOC_PAD_SD;
>> + } else if (!strcmp(name, "fixed-1-8v")) {
>> + params->pad_ctrl.pad_type = SOC_PAD_FIXED_1_8V;
>> + } else {
>> + dev_err(mmc_dev(host->mmc), "Unsupported SOC PHY PAD ctrl type %s\n",
>> + name);
>> + return -EINVAL;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int emmc_phy_parse_param_dt(struct sdhci_host *host,
>> + struct device_node *np,
>> + struct emmc_phy_params *params)
>> +{
>> + u32 value;
>> +
>> + if (of_property_read_bool(np, "xenon,phy-slow-mode"))
>> + params->slow_mode = true;
>> + else
>> + params->slow_mode = false;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-znr", &value))
>> + params->znr = value & ZNR_MASK;
>> + else
>> + params->znr = ZNR_DEF_VALUE;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-zpr", &value))
>> + params->zpr = value & ZPR_MASK;
>> + else
>> + params->zpr = ZPR_DEF_VALUE;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-nr-tun-times", &value))
>> + params->nr_tun_times = value & TUN_CONSECUTIVE_TIMES_MASK;
>> + else
>> + params->nr_tun_times = TUN_CONSECUTIVE_TIMES;
>> +
>> + if (!of_property_read_u32(np, "xenon,phy-tun-step-divider", &value))
>> + params->tun_step_divider = value & 0xFF;
>> + else
>> + params->tun_step_divider = TUNING_STEP_DIVIDER;
>> +
>> + return get_dt_pad_ctrl_data(host, np, params);
>> +}
>> +
>> +/*
>> + * SDH PHY configuration and operations
>> + */
>> +static int xenon_sdh_phy_set_fix_sampl_delay(struct sdhci_host *host,
>> + unsigned int delay, bool invert)
>> +{
>> + u32 reg;
>> + unsigned long flags;
>> + int ret;
>> +
>> + if (invert)
>> + invert = 0x1;
>> + else
>> + invert = 0x0;
>> +
>> + spin_lock_irqsave(&host->lock, flags);
>> +
>> + /* Disable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg &= ~(SDHCI_CLOCK_CARD_EN | SDHCI_CLOCK_INT_EN);
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + /* Setup Sampling fix delay */
>> + reg = sdhci_readl(host, SDHC_SLOT_OP_STATUS_CTRL);
>> + reg &= ~(SDH_PHY_FIXED_DELAY_MASK |
>> + (0x1 << FORCE_SEL_INVERSE_CLK_SHIFT));
>> + reg |= ((delay & SDH_PHY_FIXED_DELAY_MASK) |
>> + (invert << FORCE_SEL_INVERSE_CLK_SHIFT));
>> + sdhci_writel(host, reg, SDHC_SLOT_OP_STATUS_CTRL);
>> +
>> + /* Enable SD internal clock */
>> + ret = enable_xenon_internal_clk(host);
>> +
>> + /* Enable SDCLK */
>> + reg = sdhci_readl(host, SDHCI_CLOCK_CONTROL);
>> + reg |= SDHCI_CLOCK_CARD_EN;
>> + sdhci_writel(host, reg, SDHCI_CLOCK_CONTROL);
>> +
>> + udelay(200);
>> +
>> + spin_unlock_irqrestore(&host->lock, flags);
>> + return ret;
>> +}
>> +
>> +static int sdh_phy_do_fix_sampl_delay(struct sdhci_host *host,
>> + struct mmc_card *card,
>> + unsigned int delay, bool invert)
>> +{
>> + int ret;
>> +
>> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, invert);
>> +
>> + ret = xenon_delay_adj_test(card);
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "fail when sampling fix delay = %d, phase = %d degree\n",
>> + delay, invert * 180);
>> + return -1;
>> + }
>> + return 0;
>> +}
>> +
>> +#define SDH_PHY_COARSE_FIX_DELAY (SDH_PHY_FIXED_DELAY_MASK / 2)
>> +#define SDH_PHY_FINE_FIX_DELAY (SDH_PHY_COARSE_FIX_DELAY / 4)
>> +
>> +static int xenon_sdh_phy_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + u32 reg;
>> + bool dll_enable = false;
>> + unsigned int min_delay, max_delay, delay;
>> + const bool sampl_edge[] = {
>> + false,
>> + true,
>> + };
>> + int i, nr;
>> + int ret;
>> +
>> + if (host->clock > HIGH_SPEED_MAX_DTR) {
>> + /* Enable DLL when SDCLK is higher than 50MHz */
>> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_CTRL);
>> + if (!(reg & SDH_PHY_ENABLE_DLL)) {
>> + reg |= (SDH_PHY_ENABLE_DLL | SDH_PHY_FAST_LOCK_EN);
>> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_CTRL);
>> + mdelay(1);
>> +
>> + reg = sdhci_readl(host, SDH_PHY_SLOT_DLL_PHASE_SEL);
>> + reg |= SDH_PHY_DLL_UPDATE_TUNING;
>> + sdhci_writel(host, reg, SDH_PHY_SLOT_DLL_PHASE_SEL);
>> + }
>> + dll_enable = true;
>> + }
>> +
>> + nr = dll_enable ? ARRAY_SIZE(sampl_edge) : 1;
>> + for (i = 0; i < nr; i++) {
>> + for (min_delay = 0; min_delay <= SDH_PHY_FIXED_DELAY_MASK;
>> + min_delay += SDH_PHY_COARSE_FIX_DELAY) {
>> + ret = sdh_phy_do_fix_sampl_delay(host, card, min_delay,
>> + sampl_edge[i]);
>> + if (!ret)
>> + break;
>> + }
>> +
>> + if (ret) {
>> + dev_dbg(mmc_dev(host->mmc),
>> + "Fail to set Fixed Sampling Delay with %s edge\n",
>> + sampl_edge[i] ? "negative" : "positive");
>> + continue;
>> + }
>> +
>> + for (max_delay = min_delay + SDH_PHY_FINE_FIX_DELAY;
>> + max_delay < SDH_PHY_FIXED_DELAY_MASK;
>> + max_delay += SDH_PHY_FINE_FIX_DELAY) {
>> + ret = sdh_phy_do_fix_sampl_delay(host, card, max_delay,
>> + sampl_edge[i]);
>> + if (ret) {
>> + max_delay -= SDH_PHY_FINE_FIX_DELAY;
>> + break;
>> + }
>> + }
>> +
>> + if (!ret) {
>> + delay = SDH_PHY_FIXED_DELAY_MASK;
>> + ret = sdh_phy_do_fix_sampl_delay(host, card, delay,
>> + sampl_edge[i]);
>> + if (!ret)
>> + max_delay = SDH_PHY_FIXED_DELAY_MASK;
>> + }
>> +
>> + if ((max_delay - min_delay) <= SDH_PHY_FIXED_DELAY_WINDOW_MIN) {
>> + dev_info(mmc_dev(host->mmc),
>> + "The window size %d with %s edge is too small\n",
>> + max_delay - min_delay,
>> + sampl_edge[i] ? "negative" : "positive");
>> + continue;
>> + }
>> +
>> + delay = (min_delay + max_delay) / 2;
>> + xenon_sdh_phy_set_fix_sampl_delay(host, delay, sampl_edge[i]);
>> + dev_dbg(mmc_dev(host->mmc), "sampling fix delay = %d with %s edge\n",
>> + delay, sampl_edge[i] ? "negative" : "positive");
>> + return 0;
>> + }
>> + return -EIO;
>> +}
>> +
>> +static const struct xenon_phy_ops sdh_phy_ops = {
>> + .fix_sampl_delay_adj = xenon_sdh_phy_fix_sampl_delay_adj,
>> +};
>> +
>> +static int alloc_sdh_phy(struct sdhci_xenon_priv *priv)
>> +{
>> + priv->phy_params = NULL;
>> + priv->phy_ops = &sdh_phy_ops;
>> + return 0;
>> +}
>> +
>> +/*
>> + * Common functions for all PHYs
>> + */
>> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
>> + unsigned char signal_voltage)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (priv->phy_ops->set_soc_pad)
>> + priv->phy_ops->set_soc_pad(host, signal_voltage);
>> +}
>> +
>> +static int __xenon_emmc_delay_adj_test(struct mmc_card *card)
>> +{
>> + int err;
>> + u8 *ext_csd = NULL;
>> +
>> + err = mmc_get_ext_csd(card, &ext_csd);
>> + kfree(ext_csd);
>> +
>> + return err;
>> +}
>> +
>> +static int __xenon_sdio_delay_adj_test(struct mmc_card *card)
>> +{
>> + struct mmc_command cmd = {0};
>> + int err;
>> +
>> + cmd.opcode = SD_IO_RW_DIRECT;
>> + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC;
>> +
>> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
>> + if (err)
>> + return err;
>> +
>> + if (cmd.resp[0] & R5_ERROR)
>> + return -EIO;
>> + if (cmd.resp[0] & R5_FUNCTION_NUMBER)
>> + return -EINVAL;
>> + if (cmd.resp[0] & R5_OUT_OF_RANGE)
>> + return -ERANGE;
>> + return 0;
>> +}
>> +
>> +static int __xenon_sd_delay_adj_test(struct mmc_card *card)
>> +{
>> + struct mmc_command cmd = {0};
>> + int err;
>> +
>> + cmd.opcode = MMC_SEND_STATUS;
>> + cmd.arg = card->rca << 16;
>> + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
>> +
>> + err = mmc_wait_for_cmd(card->host, &cmd, 0);
>> + return err;
>> +}
>> +
>> +static int xenon_delay_adj_test(struct mmc_card *card)
>> +{
>> + WARN_ON(!card);
>> + WARN_ON(!card->host);
>> +
>> + if (mmc_card_mmc(card))
>> + return __xenon_emmc_delay_adj_test(card);
>> + else if (mmc_card_sd(card))
>> + return __xenon_sd_delay_adj_test(card);
>> + else if (mmc_card_sdio(card))
>> + return __xenon_sdio_delay_adj_test(card);
>> + else
>> + return -EINVAL;
>> +}
>> +
>> +static void xenon_phy_set(struct sdhci_host *host, unsigned char timing)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (priv->phy_ops->phy_set)
>> + priv->phy_ops->phy_set(host, timing);
>> +}
>> +
>> +static void xenon_hs400_strobe_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (WARN_ON(!mmc_card_hs400(card)))
>> + return;
>> +
>> + /* Enable the DLL to automatically adjust HS400 strobe delay.
>> + */
>> + if (priv->phy_ops->strobe_delay_adj)
>> + priv->phy_ops->strobe_delay_adj(host, card);
>> +}
>> +
>> +static int xenon_fix_sampl_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (priv->phy_ops->fix_sampl_delay_adj)
>> + return priv->phy_ops->fix_sampl_delay_adj(host, card);
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * xenon_delay_adj should not be called inside IRQ context,
>> + * either Hard IRQ or Softirq.
>> + */
>> +static int xenon_hs_delay_adj(struct sdhci_host *host,
>> + struct mmc_card *card)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + int ret = 0;
>> +
>> + if (WARN_ON(host->clock <= DEFAULT_SDCLK_FREQ))
>> + return -EINVAL;
>> +
>> + if (mmc_card_hs400(card)) {
>> + xenon_hs400_strobe_delay_adj(host, card);
>> + return 0;
>> + }
>> +
>> + if (((priv->phy_type == EMMC_5_1_PHY) ||
>> + (priv->phy_type == EMMC_5_0_PHY)) &&
>> + (mmc_card_hs200(card) ||
>> + (host->timing == MMC_TIMING_UHS_SDR104))) {
>> + ret = xenon_emmc_phy_config_tuning(host);
>> + if (!ret)
>> + return 0;
>> + }
>> +
>> + ret = xenon_fix_sampl_delay_adj(host, card);
>> + if (ret)
>> + dev_err(mmc_dev(host->mmc), "fails sampling fixed delay adjustment\n");
>> + return ret;
>> +}
>> +
>> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios)
>> +{
>> + struct mmc_host *mmc = host->mmc;
>> + struct mmc_card *card;
>> + int ret = 0;
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (!host->clock) {
>> + priv->clock = 0;
>> + return 0;
>> + }
>> +
>> + /*
>> + * The timing, frequency or bus width is changed,
>> + * better to set eMMC PHY based on current setting
>> + * and adjust Xenon SDHC delay.
>> + */
>> + if ((host->clock == priv->clock) &&
>> + (ios->bus_width == priv->bus_width) &&
>> + (ios->timing == priv->timing))
>> + return 0;
>> +
>> + xenon_phy_set(host, ios->timing);
>> +
>> + /* Update the record */
>> + priv->bus_width = ios->bus_width;
>> + /* Temp stage from HS200 to HS400 */
>> + if (((priv->timing == MMC_TIMING_MMC_HS200) &&
>> + (ios->timing == MMC_TIMING_MMC_HS)) ||
>> + ((ios->timing == MMC_TIMING_MMC_HS) &&
>> + (priv->clock > host->clock))) {
>> + priv->timing = ios->timing;
>> + priv->clock = host->clock;
>> + return 0;
>> + }
>> + priv->timing = ios->timing;
>> + priv->clock = host->clock;
>> +
>> + /* Legacy mode is a special case */
>> + if (ios->timing == MMC_TIMING_LEGACY)
>> + return 0;
>> +
>> + card = priv->card_candidate;
>> + if (unlikely(!card)) {
>> + dev_warn(mmc_dev(mmc), "card is not present\n");
>> + return -EINVAL;
>> + }
>> +
>> + if (host->clock > DEFAULT_SDCLK_FREQ)
>> + ret = xenon_hs_delay_adj(host, card);
>> + return ret;
>> +}
>> +
>> +static int add_xenon_phy(struct device_node *np, struct sdhci_host *host,
>> + const char *phy_name)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_xenon_priv *priv = sdhci_pltfm_priv(pltfm_host);
>> + int i, ret;
>> +
>> + for (i = 0; i < NR_PHY_TYPES; i++) {
>> + if (!strcmp(phy_name, phy_types[i])) {
>> + priv->phy_type = i;
>> + break;
>> + }
>> + }
>> + if (i == NR_PHY_TYPES) {
>> + dev_err(mmc_dev(host->mmc),
>> + "Unable to determine PHY name %s. Use default eMMC 5.1 PHY\n",
>> + phy_name);
>> + priv->phy_type = EMMC_5_1_PHY;
>> + }
>> +
>> + if (priv->phy_type == SDH_PHY) {
>> + return alloc_sdh_phy(priv);
>> + } else if ((priv->phy_type == EMMC_5_0_PHY) ||
>> + (priv->phy_type == EMMC_5_1_PHY)) {
>> + ret = alloc_emmc_phy(priv);
>> + if (ret)
>> + return ret;
>> + return emmc_phy_parse_param_dt(host, np, priv->phy_params);
>> + }
>> +
>> + return -EINVAL;
>> +}
>> +
>> +int xenon_phy_parse_dt(struct device_node *np, struct sdhci_host *host)
>> +{
>> + const char *phy_type = NULL;
>> +
>> + if (!of_property_read_string(np, "xenon,phy-type", &phy_type))
>> + return add_xenon_phy(np, host, phy_type);
>> +
>> + dev_err(mmc_dev(host->mmc), "Fail to get Xenon PHY type. Use default eMMC 5.1 PHY\n");
>> + return add_xenon_phy(np, host, "emmc 5.1 phy");
>> +}
>> diff --git a/drivers/mmc/host/sdhci-xenon-phy.h b/drivers/mmc/host/sdhci-xenon-phy.h
>> new file mode 100644
>> index 000000000000..4373c71d3b7b
>> --- /dev/null
>> +++ b/drivers/mmc/host/sdhci-xenon-phy.h
>> @@ -0,0 +1,157 @@
>> +/* linux/drivers/mmc/host/sdhci-xenon-phy.h
>> + *
>> + * Author: Hu Ziji <huziji@marvell.com>
>> + * Date: 2016-8-24
>> + *
>> + * Copyright (C) 2016 Marvell, 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 as published by
>> + * the Free Software Foundation; either version 2 of the License, or (at
>> + * your option) any later version.
>> + */
>> +#ifndef SDHCI_XENON_PHY_H_
>> +#define SDHCI_XENON_PHY_H_
>> +
>> +#include <linux/types.h>
>> +#include "sdhci.h"
>> +
>> +/* Register base for eMMC PHY 5.0 Version */
>> +#define EMMC_5_0_PHY_REG_BASE 0x0160
>> +/* Register base for eMMC PHY 5.1 Version */
>> +#define EMMC_PHY_REG_BASE 0x0170
>> +
>> +#define EMMC_PHY_TIMING_ADJUST EMMC_PHY_REG_BASE
>> +#define EMMC_5_0_PHY_TIMING_ADJUST EMMC_5_0_PHY_REG_BASE
>> +#define TIMING_ADJUST_SLOW_MODE BIT(29)
>> +#define TIMING_ADJUST_SDIO_MODE BIT(28)
>> +#define OUTPUT_QSN_PHASE_SELECT BIT(17)
>> +#define SAMPL_INV_QSP_PHASE_SELECT BIT(18)
>> +#define SAMPL_INV_QSP_PHASE_SELECT_SHIFT 18
>> +#define PHY_INITIALIZAION BIT(31)
>> +#define WAIT_CYCLE_BEFORE_USING_MASK 0xF
>> +#define WAIT_CYCLE_BEFORE_USING_SHIFT 12
>> +#define FC_SYNC_EN_DURATION_MASK 0xF
>> +#define FC_SYNC_EN_DURATION_SHIFT 8
>> +#define FC_SYNC_RST_EN_DURATION_MASK 0xF
>> +#define FC_SYNC_RST_EN_DURATION_SHIFT 4
>> +#define FC_SYNC_RST_DURATION_MASK 0xF
>> +#define FC_SYNC_RST_DURATION_SHIFT 0
>> +
>> +#define EMMC_PHY_FUNC_CONTROL (EMMC_PHY_REG_BASE + 0x4)
>> +#define EMMC_5_0_PHY_FUNC_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x4)
>> +#define ASYNC_DDRMODE_MASK BIT(23)
>> +#define ASYNC_DDRMODE_SHIFT 23
>> +#define CMD_DDR_MODE BIT(16)
>> +#define DQ_DDR_MODE_SHIFT 8
>> +#define DQ_DDR_MODE_MASK 0xFF
>> +#define DQ_ASYNC_MODE BIT(4)
>> +
>> +#define EMMC_PHY_PAD_CONTROL (EMMC_PHY_REG_BASE + 0x8)
>> +#define EMMC_5_0_PHY_PAD_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x8)
>> +#define REC_EN_SHIFT 24
>> +#define REC_EN_MASK 0xF
>> +#define FC_DQ_RECEN BIT(24)
>> +#define FC_CMD_RECEN BIT(25)
>> +#define FC_QSP_RECEN BIT(26)
>> +#define FC_QSN_RECEN BIT(27)
>> +#define OEN_QSN BIT(28)
>> +#define AUTO_RECEN_CTRL BIT(30)
>> +#define FC_ALL_CMOS_RECEIVER 0xF000
>> +
>> +#define EMMC5_FC_QSP_PD BIT(18)
>> +#define EMMC5_FC_QSP_PU BIT(22)
>> +#define EMMC5_FC_CMD_PD BIT(17)
>> +#define EMMC5_FC_CMD_PU BIT(21)
>> +#define EMMC5_FC_DQ_PD BIT(16)
>> +#define EMMC5_FC_DQ_PU BIT(20)
>> +
>> +#define EMMC_PHY_PAD_CONTROL1 (EMMC_PHY_REG_BASE + 0xC)
>> +#define EMMC5_1_FC_QSP_PD BIT(9)
>> +#define EMMC5_1_FC_QSP_PU BIT(25)
>> +#define EMMC5_1_FC_CMD_PD BIT(8)
>> +#define EMMC5_1_FC_CMD_PU BIT(24)
>> +#define EMMC5_1_FC_DQ_PD 0xFF
>> +#define EMMC5_1_FC_DQ_PU (0xFF << 16)
>> +
>> +#define EMMC_PHY_PAD_CONTROL2 (EMMC_PHY_REG_BASE + 0x10)
>> +#define EMMC_5_0_PHY_PAD_CONTROL2 (EMMC_5_0_PHY_REG_BASE + 0xC)
>> +#define ZNR_MASK 0x1F
>> +#define ZNR_SHIFT 8
>> +#define ZPR_MASK 0x1F
>> +/* Perferred ZNR and ZPR value vary between different boards.
>> + * The specific ZNR and ZPR value should be defined here
>> + * according to board actual timing.
>> + */
>> +#define ZNR_DEF_VALUE 0xF
>> +#define ZPR_DEF_VALUE 0xF
>> +
>> +#define EMMC_PHY_DLL_CONTROL (EMMC_PHY_REG_BASE + 0x14)
>> +#define EMMC_5_0_PHY_DLL_CONTROL (EMMC_5_0_PHY_REG_BASE + 0x10)
>> +#define DLL_ENABLE BIT(31)
>> +#define DLL_UPDATE_STROBE_5_0 BIT(30)
>> +#define DLL_REFCLK_SEL BIT(30)
>> +#define DLL_UPDATE BIT(23)
>> +#define DLL_PHSEL1_SHIFT 24
>> +#define DLL_PHSEL0_SHIFT 16
>> +#define DLL_PHASE_MASK 0x3F
>> +#define DLL_PHASE_90_DEGREE 0x1F
>> +#define DLL_FAST_LOCK BIT(5)
>> +#define DLL_GAIN2X BIT(3)
>> +#define DLL_BYPASS_EN BIT(0)
>> +
>> +#define EMMC_5_0_PHY_LOGIC_TIMING_ADJUST (EMMC_5_0_PHY_REG_BASE + 0x14)
>> +#define EMMC_PHY_LOGIC_TIMING_ADJUST (EMMC_PHY_REG_BASE + 0x18)
>> +
>> +enum sampl_fix_delay_phase {
>> + PHASE_0_DEGREE = 0x0,
>> + PHASE_90_DEGREE = 0x1,
>> + PHASE_180_DEGREE = 0x2,
>> + PHASE_270_DEGREE = 0x3,
>> +};
>> +
>> +#define SDH_PHY_SLOT_DLL_CTRL (0x0138)
>> +#define SDH_PHY_ENABLE_DLL BIT(1)
>> +#define SDH_PHY_FAST_LOCK_EN BIT(5)
>> +
>> +#define SDH_PHY_SLOT_DLL_PHASE_SEL (0x013C)
>> +#define SDH_PHY_DLL_UPDATE_TUNING BIT(15)
>> +
>> +enum soc_pad_ctrl_type {
>> + SOC_PAD_SD,
>> + SOC_PAD_FIXED_1_8V,
>> +};
>> +
>> +/*
>> + * List offset of PHY registers and some special register values
>> + * in eMMC PHY 5.0 or eMMC PHY 5.1
>> + */
>> +struct xenon_emmc_phy_regs {
>> + /* Offset of Timing Adjust register */
>> + u16 timing_adj;
>> + /* Offset of Func Control register */
>> + u16 func_ctrl;
>> + /* Offset of Pad Control register */
>> + u16 pad_ctrl;
>> + /* Offset of Pad Control register */
>> + u16 pad_ctrl2;
>> + /* Offset of DLL Control register */
>> + u16 dll_ctrl;
>> + /* Offset of Logic Timing Adjust register */
>> + u16 logic_timing_adj;
>> + /* Max value of eMMC Fixed Sampling Delay */
>> + u32 delay_mask;
>> + /* DLL Update Enable bit */
>> + u32 dll_update;
>> +};
>> +
>> +struct xenon_phy_ops {
>> + void (*strobe_delay_adj)(struct sdhci_host *host,
>> + struct mmc_card *card);
>> + int (*fix_sampl_delay_adj)(struct sdhci_host *host,
>> + struct mmc_card *card);
>> + void (*phy_set)(struct sdhci_host *host, unsigned char timing);
>> + void (*set_soc_pad)(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> +};
>> +#endif
>> diff --git a/drivers/mmc/host/sdhci-xenon.c b/drivers/mmc/host/sdhci-xenon.c
>> index 03ba183494d3..4d7d871544fc 100644
>> --- a/drivers/mmc/host/sdhci-xenon.c
>> +++ b/drivers/mmc/host/sdhci-xenon.c
>> @@ -224,6 +224,7 @@ static void xenon_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
>> spin_unlock_irqrestore(&host->lock, flags);
>>
>> sdhci_set_ios(mmc, ios);
>> + xenon_phy_adj(host, ios);
>>
>> if (host->clock > DEFAULT_SDCLK_FREQ) {
>> spin_lock_irqsave(&host->lock, flags);
>> @@ -309,6 +310,8 @@ static int xenon_start_signal_voltage_switch(struct mmc_host *mmc,
>> */
>> enable_xenon_internal_clk(host);
>>
>> + xenon_soc_pad_ctrl(host, ios->signal_voltage);
>> +
>> if (priv->card_candidate) {
>> if (mmc_card_mmc(priv->card_candidate))
>> return xenon_emmc_signal_voltage_switch(mmc, ios);
>> @@ -453,6 +456,7 @@ static int xenon_probe_dt(struct platform_device *pdev)
>> sdhci_writel(host, reg, SDHC_SYS_EXT_OP_CTRL);
>> }
>>
>> + err = xenon_phy_parse_dt(np, host);
>> return err;
>> }
>>
>> diff --git a/drivers/mmc/host/sdhci-xenon.h b/drivers/mmc/host/sdhci-xenon.h
>> index c2370493fbe8..06e5261a563c 100644
>> --- a/drivers/mmc/host/sdhci-xenon.h
>> +++ b/drivers/mmc/host/sdhci-xenon.h
>> @@ -15,6 +15,7 @@
>> #include <linux/mmc/card.h>
>> #include <linux/of.h>
>> #include "sdhci.h"
>> +#include "sdhci-xenon-phy.h"
>>
>> /* Register Offset of SD Host Controller SOCP self-defined register */
>> #define SDHC_SYS_CFG_INFO 0x0104
>> @@ -76,6 +77,7 @@
>> #define MMC_TIMING_FAKE 0xFF
>>
>> #define DEFAULT_SDCLK_FREQ (400000)
>> +#define LOWEST_SDCLK_FREQ (100000)
>>
>> /* Xenon specific Mode Select value */
>> #define XENON_SDHCI_CTRL_HS200 0x5
>> @@ -97,6 +99,15 @@ struct sdhci_xenon_priv {
>> /* Slot idx */
>> u8 slot_idx;
>>
>> + int phy_type;
>> + /*
>> + * Contains board-specific PHY parameters
>> + * passed from device tree.
>> + */
>> + void *phy_params;
>> + const struct xenon_phy_ops *phy_ops;
>> + struct xenon_emmc_phy_regs *emmc_phy_regs;
>> +
>> /*
>> * When initializing card, Xenon has to determine card type and
>> * adjust Sampling Fixed delay.
>> @@ -131,4 +142,10 @@ static inline int enable_xenon_internal_clk(struct sdhci_host *host)
>>
>> return 0;
>> }
>> +
>> +int xenon_phy_adj(struct sdhci_host *host, struct mmc_ios *ios);
>> +int xenon_phy_parse_dt(struct device_node *np,
>> + struct sdhci_host *host);
>> +void xenon_soc_pad_ctrl(struct sdhci_host *host,
>> + unsigned char signal_voltage);
>> #endif
>>
>
>
^ permalink raw reply
* [PATCH] ARM: pxa: pxa_cplds: honor probe deferral
From: Robert Jarzmik @ 2016-10-08 9:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1474874488-26533-1-git-send-email-robert.jarzmik@free.fr>
Robert Jarzmik <robert.jarzmik@free.fr> writes:
> The method to acquire the input interrupt was working in a platform data
> based board, but was failing in a device-tree one when the gpio
> controller was probed after pxa_cplds.
>
> Use platform_get_irq() to benefit from the probe deferral
> mechanism. Moreover, as seen in dm9000.c development, platform_get_irq()
> doesn't honor the irq type IO resource (ie. edge rising for example),
> and it must be passed again at irq request in a not device-tree build,
> hence the irq_get_trigger_type() call.
>
> Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr>
Applied to pxa/for-next.
Cheers.
--
Robert
^ permalink raw reply
* [PATCH] Specify all interrupts for the GPIO controller.
From: Robert Jarzmik @ 2016-10-08 9:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1475489412-9758-1-git-send-email-vijaykumar@zilogic.com>
Vijay Kumar B <vijaykumar@zilogic.com> writes:
> The PXA GPIO controller has 3 interrupt outputs, this needs to be
> indicated in the DTS file. Without this mainstone's CPLD interrupt 0
> will not be raised to the processor.
>
> Signed-off-by: Vijay Kumar B. <vijaykumar@zilogic.com>
> Reviewed-by: Deepak S. <deepak@zilogic.com>
> ---
> arch/arm/boot/dts/pxa2xx.dtsi | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm/boot/dts/pxa2xx.dtsi b/arch/arm/boot/dts/pxa2xx.dtsi
> index 5e5af07..9ca2e5b 100644
> --- a/arch/arm/boot/dts/pxa2xx.dtsi
> +++ b/arch/arm/boot/dts/pxa2xx.dtsi
> @@ -54,8 +54,8 @@
> reg = <0x40e00000 0x10000>;
> gpio-controller;
> #gpio-cells = <0x2>;
> - interrupts = <10>;
> - interrupt-names = "gpio_mux";
> + interrupts = <8 9 10>;
> + interrupt-names = "gpio0", "gpio1", "gpio_mux";
> interrupt-controller;
> #interrupt-cells = <0x2>;
> ranges;
Hi Vijay,
A patch was already submitted in september for this in there :
https://mail-archive.com/linux-kernel at vger.kernel.org/msg1239445.html
Cheers.
--
Robert
^ permalink raw reply
* [PATCH] clk: core: add __init decoration for CLK_OF_DECLARE_DRIVER function
From: Shawn Guo @ 2016-10-08 8:59 UTC (permalink / raw)
To: linux-arm-kernel
The new introduced macro CLK_OF_DECLARE_DRIVER is usually used to
declare clock driver init functions, which are mostly decorated with
__init. Add __init decoration for CLK_OF_DECLARE_DRIVER function to
avoid causing section mismatch warnings on client clock drivers.
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
---
include/linux/clk-provider.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index af596381fa0f..a428aec36ace 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -785,7 +785,7 @@ struct clk_hw_onecell_data {
* routines, one at of_clk_init(), and one at platform device probe
*/
#define CLK_OF_DECLARE_DRIVER(name, compat, fn) \
- static void name##_of_clk_init_driver(struct device_node *np) \
+ static void __init name##_of_clk_init_driver(struct device_node *np) \
{ \
of_node_clear_flag(np, OF_POPULATED); \
fn(np); \
--
1.9.1
^ permalink raw reply related
* [PATCH v7 0/8] power: add power sequence library
From: Peter Chen @ 2016-10-08 8:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20160926062649.GB25395@b29397-desktop>
On Mon, Sep 26, 2016 at 02:26:49PM +0800, Peter Chen wrote:
> On Tue, Sep 20, 2016 at 11:36:39AM +0800, Peter Chen wrote:
> > Hi all,
> >
> > This is a follow-up for my last power sequence framework patch set [1].
> > According to Rob Herring and Ulf Hansson's comments[2]. The kinds of
> > power sequence instances will be added at postcore_initcall, the match
> > criteria is compatible string first, if the compatible string is not
> > matched between dts and library, it will try to use generic power sequence.
> >
> > The host driver just needs to call of_pwrseq_on/of_pwrseq_off
> > if only one power sequence instance is needed, for more power sequences
> > are used, using of_pwrseq_on_list/of_pwrseq_off_list instead (eg, USB hub driver).
> >
> > In future, if there are special power sequence requirements, the special
> > power sequence library can be created.
> >
> > This patch set is tested on i.mx6 sabresx evk using a dts change, I use
> > two hot-plug devices to simulate this use case, the related binding
> > change is updated at patch [1/6], The udoo board changes were tested
> > using my last power sequence patch set.[3]
> >
> > Except for hard-wired MMC and USB devices, I find the USB ULPI PHY also
> > need to power on itself before it can be found by ULPI bus.
> >
> > [1] http://www.spinics.net/lists/linux-usb/msg142755.html
> > [2] http://www.spinics.net/lists/linux-usb/msg143106.html
> > [3] http://www.spinics.net/lists/linux-usb/msg142815.html
> >
> > Changes for v7:
> > - Create kinds of power sequence instance at postcore_initcall, and match
> > the instance with node using compatible string, the beneit of this is
> > the host driver doesn't need to consider which pwrseq instance needs
> > to be used, and pwrseq core will match it, however, it eats some memories
> > if less power sequence instances are used. [Patch 2/8]
> > - Add pwrseq_compatible_sample.c to test match pwrseq using device_id. [Patch 2/8]
> > - Fix the comments Vaibhav Hiremath adds for error path for clock and do not
> > use device_node for parameters at pwrseq_on. [Patch 2/8]
> > - Simplify the caller to use power sequence, follows Alan's commnets [Patch 4/8]
> > - Tested three pwrseq instances together using both specific compatible string and
> > generic libraries.
> >
>
> Hi Vaibhav, would you please test if this series can support your case,
> you can add one instance like pwrseq_compatible_sample? If you are busy
> now, but think this series can satisfy your requirement, please ack it.
> I will delete pwrseq_compatible_sample, and only submit the generic one
> at next revision, you can add it later, thanks.
>
ping...
Vaibhav, if you have no more comments, I will delete the
pwrseq_compatible_sample, and send v8.
Peter
> Peter
>
> > Changes for v6:
> > - Add Matthias Kaehlcke's Reviewed-by and Tested-by. (patch [2/6])
> > - Change chipidea core of_node assignment for coming user. (patch [5/6])
> > - Applies Joshua Clayton's three dts changes for two boards,
> > the USB device's reg has only #address-cells, but without #size-cells.
> >
> > Changes for v5:
> > - Delete pwrseq_register/pwrseq_unregister, which is useless currently
> > - Fix the linker error when the pwrseq user is compiled as module
> >
> > Changes for v4:
> > - Create the patch on next-20160722
> > - Fix the of_node is not NULL after chipidea driver is unbinded [Patch 5/6]
> > - Using more friendly wait method for reset gpio [Patch 2/6]
> > - Support multiple input clocks [Patch 2/6]
> > - Add Rob Herring's ack for DT changes
> > - Add Joshua Clayton's Tested-by
> >
> > Changes for v3:
> > - Delete "power-sequence" property at binding-doc, and change related code
> > at both library and user code.
> > - Change binding-doc example node name with Rob's comments
> > - of_get_named_gpio_flags only gets the gpio, but without setting gpio flags,
> > add additional code request gpio with proper gpio flags
> > - Add Philipp Zabel's Ack and MAINTAINER's entry
> >
> > Changes for v2:
> > - Delete "pwrseq" prefix and clock-names for properties at dt binding
> > - Should use structure not but its pointer for kzalloc
> > - Since chipidea core has no of_node, let core's of_node equals glue
> > layer's at core's probe
> >
> > Joshua Clayton (2):
> > ARM: dts: imx6qdl: Enable usb node children with <reg>
> > ARM: dts: imx6q-evi: Fix onboard hub reset line
> >
> > Peter Chen (6):
> > binding-doc: power: pwrseq-generic: add binding doc for generic power
> > sequence library
> > power: add power sequence library
> > binding-doc: usb: usb-device: add optional properties for power
> > sequence
> > usb: core: add power sequence handling for USB devices
> > usb: chipidea: let chipidea core device of_node equal's glue layer
> > device of_node
> > ARM: dts: imx6qdl-udoo.dtsi: fix onboard USB HUB property
> >
> > .../bindings/power/pwrseq/pwrseq-generic.txt | 48 ++++++
> > .../devicetree/bindings/usb/usb-device.txt | 10 +-
> > MAINTAINERS | 9 +
> > arch/arm/boot/dts/imx6q-evi.dts | 25 +--
> > arch/arm/boot/dts/imx6qdl-udoo.dtsi | 26 ++-
> > arch/arm/boot/dts/imx6qdl.dtsi | 6 +
> > drivers/power/Kconfig | 1 +
> > drivers/power/Makefile | 1 +
> > drivers/power/pwrseq/Kconfig | 45 +++++
> > drivers/power/pwrseq/Makefile | 3 +
> > drivers/power/pwrseq/core.c | 190 +++++++++++++++++++++
> > drivers/power/pwrseq/pwrseq_compatible_sample.c | 178 +++++++++++++++++++
> > drivers/power/pwrseq/pwrseq_generic.c | 177 +++++++++++++++++++
> > drivers/usb/chipidea/core.c | 27 ++-
> > drivers/usb/core/hub.c | 41 ++++-
> > drivers/usb/core/hub.h | 1 +
> > include/linux/power/pwrseq.h | 73 ++++++++
> > 17 files changed, 820 insertions(+), 41 deletions(-)
> > create mode 100644 Documentation/devicetree/bindings/power/pwrseq/pwrseq-generic.txt
> > create mode 100644 drivers/power/pwrseq/Kconfig
> > create mode 100644 drivers/power/pwrseq/Makefile
> > create mode 100644 drivers/power/pwrseq/core.c
> > create mode 100644 drivers/power/pwrseq/pwrseq_compatible_sample.c
> > create mode 100644 drivers/power/pwrseq/pwrseq_generic.c
> > create mode 100644 include/linux/power/pwrseq.h
> >
> > --
> > 2.7.4
> >
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> > the body of a message to majordomo at vger.kernel.org
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
>
> --
>
> Best Regards,
> Peter Chen
--
Best Regards,
Peter Chen
^ permalink raw reply
* [PATCH] drm/virtio: kconfig: Fixup white space.
From: Peter Griffin @ 2016-10-08 7:55 UTC (permalink / raw)
To: linux-arm-kernel
Use tabs instead of spaces.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Acked-by: Lee Jones <lee.jones@linaro.org>
---
drivers/gpu/drm/virtio/Kconfig | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/virtio/Kconfig b/drivers/gpu/drm/virtio/Kconfig
index e1afc3d..81d1807 100644
--- a/drivers/gpu/drm/virtio/Kconfig
+++ b/drivers/gpu/drm/virtio/Kconfig
@@ -1,10 +1,10 @@
config DRM_VIRTIO_GPU
tristate "Virtio GPU driver"
depends on DRM && VIRTIO
- select DRM_KMS_HELPER
- select DRM_TTM
+ select DRM_KMS_HELPER
+ select DRM_TTM
help
This is the virtual GPU driver for virtio. It can be used with
- QEMU based VMMs (like KVM or Xen).
+ QEMU based VMMs (like KVM or Xen).
If unsure say M.
--
1.9.1
^ permalink raw reply related
* [PATCH 2/10] mmc: sdhci: Export sdhci_start_signal_voltage_switch() in sdhci.c
From: Ziji Hu @ 2016-10-08 6:26 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9bfe6e9a-c06e-481a-b5ea-af58820093bf@rock-chips.com>
Hi Shawn,
On 2016/10/8 10:40, Shawn Lin wrote:
> Hi,
>
> ? 2016/10/7 23:22, Gregory CLEMENT ??:
>> From: Ziji Hu <huziji@marvell.com>
>>
>> Export sdhci_start_signal_voltage_switch() from sdhci.c.
>> Thus vendor sdhci driver can implement its own signal voltage
>> switch routine.
>>
>
> You can overwtite this callback within your driver itself.
> That is what other sdhci variant drivers did, so patch 1-3 are
> unnecessary.
Thanks a lot for your reply.
Our SDHC driver just requests some pre- and post- operations besides common standard SDHC functions.
Overwriting those common functions costs too much.
Thank you.
Best regards,
Hu Ziji
>
>> Signed-off-by: Hu Ziji <huziji@marvell.com>
>> Reviewed-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> Signed-off-by: Gregory CLEMENT <gregory.clement@free-electrons.com>
>> ---
>> drivers/mmc/host/sdhci.c | 5 +++--
>> drivers/mmc/host/sdhci.h | 2 ++
>> 2 files changed, 5 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
>> index d4bb818c52d5..2250ea22231f 100644
>> --- a/drivers/mmc/host/sdhci.c
>> +++ b/drivers/mmc/host/sdhci.c
>> @@ -1828,8 +1828,8 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable)
>> spin_unlock_irqrestore(&host->lock, flags);
>> }
>>
>> -static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> - struct mmc_ios *ios)
>> +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> + struct mmc_ios *ios)
>> {
>> struct sdhci_host *host = mmc_priv(mmc);
>> u16 ctrl;
>> @@ -1921,6 +1921,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> return 0;
>> }
>> }
>> +EXPORT_SYMBOL_GPL(sdhci_start_signal_voltage_switch);
>>
>> static int sdhci_card_busy(struct mmc_host *mmc)
>> {
>> diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
>> index 21dc80b8ae3d..c38ab65b9a97 100644
>> --- a/drivers/mmc/host/sdhci.h
>> +++ b/drivers/mmc/host/sdhci.h
>> @@ -687,6 +687,8 @@ void sdhci_set_bus_width(struct sdhci_host *host, int width);
>> void sdhci_reset(struct sdhci_host *host, u8 mask);
>> void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing);
>> void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
>> +int sdhci_start_signal_voltage_switch(struct mmc_host *mmc,
>> + struct mmc_ios *ios);
>>
>> #ifdef CONFIG_PM
>> extern int sdhci_suspend_host(struct sdhci_host *host);
>>
>
>
^ permalink raw reply
* [PATCH 2/3] arm64: hw_breakpoint: Handle inexact watchpoint addresses
From: Pratyush Anand @ 2016-10-08 5:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAJt8pk9WOhebSXpbahbbLscqJcY1OOqHiA_9hHC0zmHruRPxWQ@mail.gmail.com>
Hi Pavel,
On Fri, Oct 7, 2016 at 10:54 PM, Pavel Labath <labath@google.com> wrote:
> On 7 October 2016 at 09:38, Pratyush Anand <panand@redhat.com> wrote:
>>
>>
>> IIUC, then you see an issue when an address watched is not the base
>> address accessed by the instruction. For example, if an address 'a+8'
>> is watched and an instruction accesses instruction from a to a +16. I
>> tried to reproduce the issue with mustang using your test-case in
>> patch3 (after couple of syntax modifcations for resolving compilation
>> issue with gcc). All the test case did pass with existing code in
>> v4.8. I noticed that, watchpoint exception is generated if any of the
>> sub-location accessed from a single instruction is watched, provided
>> watchdpoint watches either a byte, half word, word or double word
>> from the base.
>>
>>
>> So, either I must be missing something or the problem is not related
>> to all arm64 platform.
>
>
> Hello Pratyush,
>
> Thank you for looking into this.
>
> The thing is, I have observed different behavior here depending on the
> exact hardware used. I don't have the exact parameters with me now,
> but I can look it up next week.
>
> The thing is that the spec is imprecise about what exact address the
> hardware can report for the watchpoint hit. I presume that is
> deliberate to give some leeway to implementers. The spec says the
> address can be anywhere in the range from the lowest memory address
> accessed by the instruction to the highest address watched by the
> watchpoint,
I think, my patches should be able to take care of the above condition.
> but most hardware seems to be stricter than that and
> return an address that fits inside the watched range.
>
> On chip 1, I observed the behavior where the hardware would
> consistently report an address out of range of the watchpoint and we
> would just spin it in a loop.
>
> On chip 2, I observed the behavior where the hardware would report an
> out-of-range address for the first two dozen (~) iterations, after
> which it would "give up" and report an address that we were happy
> with. I don't really have an explanation for this - I can only assume
> that some external event like a reschedule to a different core caused
> some internal state of the hardware to be reset and cause it to report
> a different (better?) address instead. In the case where this was
> happening, it had no observable effects on userspace - it did not see
> the fact that we had re-executed the offending instruction a dozen
> times and as far as it was concerned, the watchpoint functionality
> worked perfectly. You can check whether this is happening in your case
> by instrumenting the code to print the reported address whenever it
> enters `watchpoint_handler`.
>
Yes, I had done that. In my case it was always starting memory address
accessed by the instruction. e.g. if a strb writes to 0x400023, I
would get addr=0x400023 in watchpoint_handler(), if a str writes to
0x400020-0x400027, I would get addr=0x400020.
> (I am sorry about the test errors. I was compiling the test case with
> an android gcc - I'll make sure to check it with a vanilla linux gcc
> also.)
>
your test cases are helpful. I think, I would use them to build
further test cases.
> >
>> However, I did notice that it does not work if we watch an address
>> which is at some offset from address programmed. For example, it works
>> when byte_mask is 0x3, but it does not work if byte_mask if 0x2 (which
>> is supported by hardware).
>>
>> I do have some patches to resolve that.
>>
>> https://github.com/pratyushanand/linux/commits/perf/upstream_arm64_devel
>>
>> I will send them for review comment after some testing.
>
> I am looking forward to these patches - they were the next on my list
> to look into after I got this resolved. :)
>
> However: Are sure about 0x2 not being a valid byte mask? According to
It is a valid mask indeed, and you should be able to use all those
mask after my patches.
> my reading of the armv8 spec (section D7.3.11, "DBGWCR<n>_EL1, Debug
> Watchpoint Control Registers, n = 0 - 15") it should be fine.
> ====
> The valid values for BAS are 0b0000000, or a binary number all of
> whose set bits are contiguous. All other values are reserved and must
> not be used by software.
> ====
> So, 0x2 (as well as 0x6, 0xC, 0xE) should be fine as it has a
> contiguous sequence of set bit(s). I haven't tried yet whether any
> hardware actually handles that correctly, but I was certainly hoping
> we would be able to watch more precise memory regions.
I have tested couple of them, and I think my patches should help with
using any contiguous bit mask acceptable by spec.
~Pratyush
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox