HyperBus changes

* DMA support for TI's AM654 HyperBus controller driver.
 * HyperBus frontend driver for Renesas RPC-IF driver.
 -----BEGIN PGP SIGNATURE-----
 
 iQFEBAABCAAuFiEEyRC2zAhGcGjrhiNExEYeRXyRFuMFAl+ARxIQHHZpZ25lc2hy
 QHRpLmNvbQAKCRDERh5FfJEW42A7B/4jHMZ2OZlOEWVros7Yf1ceEBb9V2zskTDT
 APmPQzFKDix7OeHRnNQRqHEKLQwceUQaYAxRBBlKU7tpHVvr0+F/scELTgvOOZz9
 BjLt0hC/CcjhiIUykWu6g+VW2U0YZGIJlWiMdEHiTi7dV3e1tGNaOTf9QJPD5Glq
 7XGMg9lfQEKxg5yg9BQ3Jq1n9N9scuwwmsmKcjbjeelMt9CWRVNTvPla8If4+eN3
 yoYk3AQagZCYoLpTUS8JthZ6pOehO41/Gn+y4gi9nAuHiqAj4C1sbcJJkvJuMcBY
 rzRHpu8JZv+lHGnPQNxf2a/UYdBDlpDeRVft4asK8TXADl4/kHEg
 =b30x
 -----END PGP SIGNATURE-----

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.
This commit is contained in:
Richard Weinberger 2020-10-11 22:08:21 +02:00
commit 9113ba38fb
5 changed files with 319 additions and 16 deletions

View file

@ -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

View file

@ -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

View file

@ -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 @@ static int am654_hbmc_probe(struct platform_device *pdev)
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;
}

View file

@ -0,0 +1,170 @@
// 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");

View file

@ -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;
};
/**