Commit 9113ba38 authored by Richard Weinberger's avatar Richard Weinberger
Browse files

Merge tag 'cfi/for-5.10' of gitolite.kernel.org:pub/scm/linux/kernel/git/mtd/linux into mtd/next

HyperBus changes

* DMA support for TI's AM654 HyperBus controller driver.
* HyperBus frontend driver for Renesas RPC-IF driver.
parents 80510e25 5de15b61
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -22,4 +22,11 @@ config HBMC_AM654
	 This is the driver for HyperBus controller on TI's AM65x and
	 other SoCs

config RPCIF_HYPERBUS
	tristate "Renesas RPC-IF HyperBus driver"
	depends on RENESAS_RPCIF || COMPILE_TEST
	depends on MTD_CFI_BE_BYTE_SWAP
	help
	  This option includes Renesas RPC-IF HyperBus support.

endif # MTD_HYPERBUS
+1 −0
Original line number Diff line number Diff line
@@ -2,3 +2,4 @@

obj-$(CONFIG_MTD_HYPERBUS)	+= hyperbus-core.o
obj-$(CONFIG_HBMC_AM654)	+= hbmc-am654.o
obj-$(CONFIG_RPCIF_HYPERBUS)	+= rpc-if.o
+128 −16
Original line number Diff line number Diff line
@@ -3,6 +3,10 @@
// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
// Author: Vignesh Raghavendra <vigneshr@ti.com>

#include <linux/completion.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -13,11 +17,18 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/sched/task_stack.h>
#include <linux/types.h>

#define AM654_HBMC_CALIB_COUNT 25

struct am654_hbmc_device_priv {
	struct completion rx_dma_complete;
	phys_addr_t device_base;
	struct hyperbus_ctlr *ctlr;
	struct dma_chan *rx_chan;
};

struct am654_hbmc_priv {
	struct hyperbus_ctlr ctlr;
	struct hyperbus_device hbdev;
@@ -52,13 +63,103 @@ static int am654_hbmc_calibrate(struct hyperbus_device *hbdev)
	return ret;
}

static void am654_hbmc_dma_callback(void *param)
{
	struct am654_hbmc_device_priv *priv = param;

	complete(&priv->rx_dma_complete);
}

static int am654_hbmc_dma_read(struct am654_hbmc_device_priv *priv, void *to,
			       unsigned long from, ssize_t len)

{
	enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
	struct dma_chan *rx_chan = priv->rx_chan;
	struct dma_async_tx_descriptor *tx;
	dma_addr_t dma_dst, dma_src;
	dma_cookie_t cookie;
	int ret;

	if (!priv->rx_chan || !virt_addr_valid(to) || object_is_on_stack(to))
		return -EINVAL;

	dma_dst = dma_map_single(rx_chan->device->dev, to, len, DMA_FROM_DEVICE);
	if (dma_mapping_error(rx_chan->device->dev, dma_dst)) {
		dev_dbg(priv->ctlr->dev, "DMA mapping failed\n");
		return -EIO;
	}

	dma_src = priv->device_base + from;
	tx = dmaengine_prep_dma_memcpy(rx_chan, dma_dst, dma_src, len, flags);
	if (!tx) {
		dev_err(priv->ctlr->dev, "device_prep_dma_memcpy error\n");
		ret = -EIO;
		goto unmap_dma;
	}

	reinit_completion(&priv->rx_dma_complete);
	tx->callback = am654_hbmc_dma_callback;
	tx->callback_param = priv;
	cookie = dmaengine_submit(tx);

	ret = dma_submit_error(cookie);
	if (ret) {
		dev_err(priv->ctlr->dev, "dma_submit_error %d\n", cookie);
		goto unmap_dma;
	}

	dma_async_issue_pending(rx_chan);
	if (!wait_for_completion_timeout(&priv->rx_dma_complete,  msecs_to_jiffies(len + 1000))) {
		dmaengine_terminate_sync(rx_chan);
		dev_err(priv->ctlr->dev, "DMA wait_for_completion_timeout\n");
		ret = -ETIMEDOUT;
	}

unmap_dma:
	dma_unmap_single(rx_chan->device->dev, dma_dst, len, DMA_FROM_DEVICE);
	return ret;
}

static void am654_hbmc_read(struct hyperbus_device *hbdev, void *to,
			    unsigned long from, ssize_t len)
{
	struct am654_hbmc_device_priv *priv = hbdev->priv;

	if (len < SZ_1K || am654_hbmc_dma_read(priv, to, from, len))
		memcpy_fromio(to, hbdev->map.virt + from, len);
}

static const struct hyperbus_ops am654_hbmc_ops = {
	.calibrate = am654_hbmc_calibrate,
	.copy_from = am654_hbmc_read,
};

static int am654_hbmc_request_mmap_dma(struct am654_hbmc_device_priv *priv)
{
	struct dma_chan *rx_chan;
	dma_cap_mask_t mask;

	dma_cap_zero(mask);
	dma_cap_set(DMA_MEMCPY, mask);

	rx_chan = dma_request_chan_by_mask(&mask);
	if (IS_ERR(rx_chan)) {
		if (PTR_ERR(rx_chan) == -EPROBE_DEFER)
			return -EPROBE_DEFER;
		dev_dbg(priv->ctlr->dev, "No DMA channel available\n");
		return 0;
	}
	priv->rx_chan = rx_chan;
	init_completion(&priv->rx_dma_complete);

	return 0;
}

static int am654_hbmc_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct am654_hbmc_device_priv *dev_priv;
	struct device *dev = &pdev->dev;
	struct am654_hbmc_priv *priv;
	struct resource res;
@@ -70,7 +171,8 @@ static int am654_hbmc_probe(struct platform_device *pdev)

	platform_set_drvdata(pdev, priv);

	ret = of_address_to_resource(np, 0, &res);
	priv->hbdev.np = of_get_next_child(np, NULL);
	ret = of_address_to_resource(priv->hbdev.np, 0, &res);
	if (ret)
		return ret;

@@ -88,13 +190,6 @@ static int am654_hbmc_probe(struct platform_device *pdev)
		priv->mux_ctrl = control;
	}

	pm_runtime_enable(dev);
	ret = pm_runtime_get_sync(dev);
	if (ret < 0) {
		pm_runtime_put_noidle(dev);
		goto disable_pm;
	}

	priv->hbdev.map.size = resource_size(&res);
	priv->hbdev.map.virt = devm_ioremap_resource(dev, &res);
	if (IS_ERR(priv->hbdev.map.virt))
@@ -103,17 +198,32 @@ static int am654_hbmc_probe(struct platform_device *pdev)
	priv->ctlr.dev = dev;
	priv->ctlr.ops = &am654_hbmc_ops;
	priv->hbdev.ctlr = &priv->ctlr;
	priv->hbdev.np = of_get_next_child(dev->of_node, NULL);

	dev_priv = devm_kzalloc(dev, sizeof(*dev_priv), GFP_KERNEL);
	if (!dev_priv) {
		ret = -ENOMEM;
		goto disable_mux;
	}

	priv->hbdev.priv = dev_priv;
	dev_priv->device_base = res.start;
	dev_priv->ctlr = &priv->ctlr;

	ret = am654_hbmc_request_mmap_dma(dev_priv);
	if (ret)
		goto disable_mux;

	ret = hyperbus_register_device(&priv->hbdev);
	if (ret) {
		dev_err(dev, "failed to register controller\n");
		pm_runtime_put_sync(&pdev->dev);
		goto disable_pm;
		goto release_dma;
	}

	return 0;
disable_pm:
	pm_runtime_disable(dev);
release_dma:
	if (dev_priv->rx_chan)
		dma_release_channel(dev_priv->rx_chan);
disable_mux:
	if (priv->mux_ctrl)
		mux_control_deselect(priv->mux_ctrl);
	return ret;
@@ -122,13 +232,15 @@ disable_pm:
static int am654_hbmc_remove(struct platform_device *pdev)
{
	struct am654_hbmc_priv *priv = platform_get_drvdata(pdev);
	struct am654_hbmc_device_priv *dev_priv = priv->hbdev.priv;
	int ret;

	ret = hyperbus_unregister_device(&priv->hbdev);
	if (priv->mux_ctrl)
		mux_control_deselect(priv->mux_ctrl);
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);

	if (dev_priv->rx_chan)
		dma_release_channel(dev_priv->rx_chan);

	return ret;
}
+170 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Linux driver for RPC-IF HyperFlash
 *
 * Copyright (C) 2019-2020 Cogent Embedded, Inc.
 */

#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtd/hyperbus.h>
#include <linux/mtd/mtd.h>
#include <linux/mux/consumer.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/types.h>

#include <memory/renesas-rpc-if.h>

struct	rpcif_hyperbus {
	struct rpcif rpc;
	struct hyperbus_ctlr ctlr;
	struct hyperbus_device hbdev;
};

static const struct rpcif_op rpcif_op_tmpl = {
	.cmd = {
		.buswidth = 8,
		.ddr = true,
	},
	.ocmd = {
		.buswidth = 8,
		.ddr = true,
	},
	.addr = {
		.nbytes = 1,
		.buswidth = 8,
		.ddr = true,
	},
	.data = {
		.buswidth = 8,
		.ddr = true,
	},
};

static void rpcif_hb_prepare_read(struct rpcif *rpc, void *to,
				  unsigned long from, ssize_t len)
{
	struct rpcif_op op = rpcif_op_tmpl;

	op.cmd.opcode = HYPERBUS_RW_READ | HYPERBUS_AS_MEM;
	op.addr.val = from >> 1;
	op.dummy.buswidth = 1;
	op.dummy.ncycles = 15;
	op.data.dir = RPCIF_DATA_IN;
	op.data.nbytes = len;
	op.data.buf.in = to;

	rpcif_prepare(rpc, &op, NULL, NULL);
}

static void rpcif_hb_prepare_write(struct rpcif *rpc, unsigned long to,
				   void *from, ssize_t len)
{
	struct rpcif_op op = rpcif_op_tmpl;

	op.cmd.opcode = HYPERBUS_RW_WRITE | HYPERBUS_AS_MEM;
	op.addr.val = to >> 1;
	op.data.dir = RPCIF_DATA_OUT;
	op.data.nbytes = len;
	op.data.buf.out = from;

	rpcif_prepare(rpc, &op, NULL, NULL);
}

static u16 rpcif_hb_read16(struct hyperbus_device *hbdev, unsigned long addr)
{
	struct rpcif_hyperbus *hyperbus =
		container_of(hbdev, struct rpcif_hyperbus, hbdev);
	map_word data;

	rpcif_hb_prepare_read(&hyperbus->rpc, &data, addr, 2);

	rpcif_manual_xfer(&hyperbus->rpc);

	return data.x[0];
}

static void rpcif_hb_write16(struct hyperbus_device *hbdev, unsigned long addr,
			     u16 data)
{
	struct rpcif_hyperbus *hyperbus =
		container_of(hbdev, struct rpcif_hyperbus, hbdev);

	rpcif_hb_prepare_write(&hyperbus->rpc, addr, &data, 2);

	rpcif_manual_xfer(&hyperbus->rpc);
}

static void rpcif_hb_copy_from(struct hyperbus_device *hbdev, void *to,
			       unsigned long from, ssize_t len)
{
	struct rpcif_hyperbus *hyperbus =
		container_of(hbdev, struct rpcif_hyperbus, hbdev);

	rpcif_hb_prepare_read(&hyperbus->rpc, to, from, len);

	rpcif_dirmap_read(&hyperbus->rpc, from, len, to);
}

static const struct hyperbus_ops rpcif_hb_ops = {
	.read16 = rpcif_hb_read16,
	.write16 = rpcif_hb_write16,
	.copy_from = rpcif_hb_copy_from,
};

static int rpcif_hb_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct rpcif_hyperbus *hyperbus;
	int error;

	hyperbus = devm_kzalloc(dev, sizeof(*hyperbus), GFP_KERNEL);
	if (!hyperbus)
		return -ENOMEM;

	rpcif_sw_init(&hyperbus->rpc, pdev->dev.parent);

	platform_set_drvdata(pdev, hyperbus);

	rpcif_enable_rpm(&hyperbus->rpc);

	rpcif_hw_init(&hyperbus->rpc, true);

	hyperbus->hbdev.map.size = hyperbus->rpc.size;
	hyperbus->hbdev.map.virt = hyperbus->rpc.dirmap;

	hyperbus->ctlr.dev = dev;
	hyperbus->ctlr.ops = &rpcif_hb_ops;
	hyperbus->hbdev.ctlr = &hyperbus->ctlr;
	hyperbus->hbdev.np = of_get_next_child(pdev->dev.parent->of_node, NULL);
	error = hyperbus_register_device(&hyperbus->hbdev);
	if (error)
		rpcif_disable_rpm(&hyperbus->rpc);

	return error;
}

static int rpcif_hb_remove(struct platform_device *pdev)
{
	struct rpcif_hyperbus *hyperbus = platform_get_drvdata(pdev);
	int error = hyperbus_unregister_device(&hyperbus->hbdev);
	struct rpcif *rpc = dev_get_drvdata(pdev->dev.parent);

	rpcif_disable_rpm(rpc);
	return error;
}

static struct platform_driver rpcif_platform_driver = {
	.probe	= rpcif_hb_probe,
	.remove	= rpcif_hb_remove,
	.driver	= {
		.name	= "rpc-if-hyperflash",
	},
};

module_platform_driver(rpcif_platform_driver);

MODULE_DESCRIPTION("Renesas RPC-IF HyperFlash driver");
MODULE_LICENSE("GPL v2");
+13 −0
Original line number Diff line number Diff line
@@ -8,6 +8,17 @@

#include <linux/mtd/map.h>

/* HyperBus command bits */
#define HYPERBUS_RW	0x80	/* R/W# */
#define HYPERBUS_RW_WRITE 0
#define HYPERBUS_RW_READ 0x80
#define HYPERBUS_AS	0x40	/* Address Space */
#define HYPERBUS_AS_MEM	0
#define HYPERBUS_AS_REG	0x40
#define HYPERBUS_BT	0x20	/* Burst Type */
#define HYPERBUS_BT_WRAPPED 0
#define HYPERBUS_BT_LINEAR 0x20

enum hyperbus_memtype {
	HYPERFLASH,
	HYPERRAM,
@@ -20,6 +31,7 @@ enum hyperbus_memtype {
 * @mtd: pointer to MTD struct
 * @ctlr: pointer to HyperBus controller struct
 * @memtype: type of memory device: HyperFlash or HyperRAM
 * @priv: pointer to controller specific per device private data
 */

struct hyperbus_device {
@@ -28,6 +40,7 @@ struct hyperbus_device {
	struct mtd_info *mtd;
	struct hyperbus_ctlr *ctlr;
	enum hyperbus_memtype memtype;
	void *priv;
};

/**