-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the SPI driver for the Renesas RPC-IF. It's the "front end" driver using the "back end" APIs in the main driver to talk to the real hardware. We only implement the 'spi-mem' interface -- there's no need to implement the usual SPI driver methods... Based on the original patch by Mason Yang <masonccyang@mxic.com.tw>. Signed-off-by: Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> Link: https://lore.kernel.org/r/1ece0e6c-71af-f0f1-709e-571f4b0b4853@cogentembedded.com Signed-off-by: Mark Brown <broonie@kernel.org>
- Loading branch information
Sergei Shtylyov
authored and
Mark Brown
committed
Jun 15, 2020
1 parent
064e8af
commit eb8d6d4
Showing
3 changed files
with
223 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
// | ||
// RPC-IF SPI/QSPI/Octa driver | ||
// | ||
// Copyright (C) 2018 ~ 2019 Renesas Solutions Corp. | ||
// Copyright (C) 2019 Macronix International Co., Ltd. | ||
// Copyright (C) 2019 - 2020 Cogent Embedded, Inc. | ||
// | ||
|
||
#include <linux/module.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/spi/spi.h> | ||
#include <linux/spi/spi-mem.h> | ||
|
||
#include <memory/renesas-rpc-if.h> | ||
|
||
#include <asm/unaligned.h> | ||
|
||
static void rpcif_spi_mem_prepare(struct spi_device *spi_dev, | ||
const struct spi_mem_op *spi_op, | ||
u64 *offs, size_t *len) | ||
{ | ||
struct rpcif *rpc = spi_controller_get_devdata(spi_dev->controller); | ||
struct rpcif_op rpc_op = { }; | ||
|
||
rpc_op.cmd.opcode = spi_op->cmd.opcode; | ||
rpc_op.cmd.buswidth = spi_op->cmd.buswidth; | ||
|
||
if (spi_op->addr.nbytes) { | ||
rpc_op.addr.buswidth = spi_op->addr.buswidth; | ||
rpc_op.addr.nbytes = spi_op->addr.nbytes; | ||
rpc_op.addr.val = spi_op->addr.val; | ||
} | ||
|
||
if (spi_op->dummy.nbytes) { | ||
rpc_op.dummy.buswidth = spi_op->dummy.buswidth; | ||
rpc_op.dummy.ncycles = spi_op->dummy.nbytes * 8 / | ||
spi_op->dummy.buswidth; | ||
} | ||
|
||
if (spi_op->data.nbytes || (offs && len)) { | ||
rpc_op.data.buswidth = spi_op->data.buswidth; | ||
rpc_op.data.nbytes = spi_op->data.nbytes; | ||
switch (spi_op->data.dir) { | ||
case SPI_MEM_DATA_IN: | ||
rpc_op.data.dir = RPCIF_DATA_IN; | ||
rpc_op.data.buf.in = spi_op->data.buf.in; | ||
break; | ||
case SPI_MEM_DATA_OUT: | ||
rpc_op.data.dir = RPCIF_DATA_OUT; | ||
rpc_op.data.buf.out = spi_op->data.buf.out; | ||
break; | ||
case SPI_MEM_NO_DATA: | ||
rpc_op.data.dir = RPCIF_NO_DATA; | ||
break; | ||
} | ||
} else { | ||
rpc_op.data.dir = RPCIF_NO_DATA; | ||
} | ||
|
||
rpcif_prepare(rpc, &rpc_op, offs, len); | ||
} | ||
|
||
static bool rpcif_spi_mem_supports_op(struct spi_mem *mem, | ||
const struct spi_mem_op *op) | ||
{ | ||
if (!spi_mem_default_supports_op(mem, op)) | ||
return false; | ||
|
||
if (op->data.buswidth > 4 || op->addr.buswidth > 4 || | ||
op->dummy.buswidth > 4 || op->cmd.buswidth > 4 || | ||
op->addr.nbytes > 4) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
static ssize_t rpcif_spi_mem_dirmap_read(struct spi_mem_dirmap_desc *desc, | ||
u64 offs, size_t len, void *buf) | ||
{ | ||
struct rpcif *rpc = | ||
spi_controller_get_devdata(desc->mem->spi->controller); | ||
|
||
if (offs + desc->info.offset + len > U32_MAX) | ||
return -EINVAL; | ||
|
||
rpcif_spi_mem_prepare(desc->mem->spi, &desc->info.op_tmpl, &offs, &len); | ||
|
||
return rpcif_dirmap_read(rpc, offs, len, buf); | ||
} | ||
|
||
static int rpcif_spi_mem_dirmap_create(struct spi_mem_dirmap_desc *desc) | ||
{ | ||
struct rpcif *rpc = | ||
spi_controller_get_devdata(desc->mem->spi->controller); | ||
|
||
if (desc->info.offset + desc->info.length > U32_MAX) | ||
return -ENOTSUPP; | ||
|
||
if (!rpcif_spi_mem_supports_op(desc->mem, &desc->info.op_tmpl)) | ||
return -ENOTSUPP; | ||
|
||
if (!rpc->dirmap && desc->info.op_tmpl.data.dir == SPI_MEM_DATA_IN) | ||
return -ENOTSUPP; | ||
|
||
if (desc->info.op_tmpl.data.dir == SPI_MEM_DATA_OUT) | ||
return -ENOTSUPP; | ||
|
||
return 0; | ||
} | ||
|
||
static int rpcif_spi_mem_exec_op(struct spi_mem *mem, | ||
const struct spi_mem_op *op) | ||
{ | ||
struct rpcif *rpc = | ||
spi_controller_get_devdata(mem->spi->controller); | ||
|
||
rpcif_spi_mem_prepare(mem->spi, op, NULL, NULL); | ||
|
||
return rpcif_manual_xfer(rpc); | ||
} | ||
|
||
static const struct spi_controller_mem_ops rpcif_spi_mem_ops = { | ||
.supports_op = rpcif_spi_mem_supports_op, | ||
.exec_op = rpcif_spi_mem_exec_op, | ||
.dirmap_create = rpcif_spi_mem_dirmap_create, | ||
.dirmap_read = rpcif_spi_mem_dirmap_read, | ||
}; | ||
|
||
static int rpcif_spi_probe(struct platform_device *pdev) | ||
{ | ||
struct device *parent = pdev->dev.parent; | ||
struct spi_controller *ctlr; | ||
struct rpcif *rpc; | ||
int error; | ||
|
||
ctlr = spi_alloc_master(&pdev->dev, sizeof(*rpc)); | ||
if (!ctlr) | ||
return -ENOMEM; | ||
|
||
rpc = spi_controller_get_devdata(ctlr); | ||
rpcif_sw_init(rpc, parent); | ||
|
||
platform_set_drvdata(pdev, ctlr); | ||
|
||
ctlr->dev.of_node = parent->of_node; | ||
|
||
rpcif_enable_rpm(rpc); | ||
|
||
ctlr->num_chipselect = 1; | ||
ctlr->mem_ops = &rpcif_spi_mem_ops; | ||
|
||
ctlr->bits_per_word_mask = SPI_BPW_MASK(8); | ||
ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_QUAD | SPI_RX_QUAD; | ||
ctlr->flags = SPI_CONTROLLER_HALF_DUPLEX; | ||
|
||
rpcif_hw_init(rpc, false); | ||
|
||
error = spi_register_controller(ctlr); | ||
if (error) { | ||
dev_err(&pdev->dev, "spi_register_controller failed\n"); | ||
goto err_put_ctlr; | ||
} | ||
return 0; | ||
|
||
err_put_ctlr: | ||
rpcif_disable_rpm(rpc); | ||
spi_controller_put(ctlr); | ||
|
||
return error; | ||
} | ||
|
||
static int rpcif_spi_remove(struct platform_device *pdev) | ||
{ | ||
struct spi_controller *ctlr = platform_get_drvdata(pdev); | ||
struct rpcif *rpc = spi_controller_get_devdata(ctlr); | ||
|
||
spi_unregister_controller(ctlr); | ||
rpcif_disable_rpm(rpc); | ||
|
||
return 0; | ||
} | ||
|
||
#ifdef CONFIG_PM_SLEEP | ||
static int rpcif_spi_suspend(struct device *dev) | ||
{ | ||
struct spi_controller *ctlr = dev_get_drvdata(dev); | ||
|
||
return spi_controller_suspend(ctlr); | ||
} | ||
|
||
static int rpcif_spi_resume(struct device *dev) | ||
{ | ||
struct spi_controller *ctlr = dev_get_drvdata(dev); | ||
|
||
return spi_controller_resume(ctlr); | ||
} | ||
|
||
static SIMPLE_DEV_PM_OPS(rpcif_spi_pm_ops, rpcif_spi_suspend, rpcif_spi_resume); | ||
#define DEV_PM_OPS (&rpcif_spi_pm_ops) | ||
#else | ||
#define DEV_PM_OPS NULL | ||
#endif | ||
|
||
static struct platform_driver rpcif_spi_driver = { | ||
.probe = rpcif_spi_probe, | ||
.remove = rpcif_spi_remove, | ||
.driver = { | ||
.name = "rpc-if-spi", | ||
.pm = DEV_PM_OPS, | ||
}, | ||
}; | ||
module_platform_driver(rpcif_spi_driver); | ||
|
||
MODULE_DESCRIPTION("Renesas RPC-IF SPI driver"); | ||
MODULE_LICENSE("GPL v2"); |