diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-10-17 19:45:42 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-10-17 19:45:42 +0200 |
commit | 6f78b9acf04fbf9ede7f4265e7282f9fb39d2c8c (patch) | |
tree | 7b26efb213f30302a6f579a8b11f8a44c8574a1f /drivers/mtd/hyperbus | |
parent | Merge tag 'thermal-v5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/gi... (diff) | |
parent | mtd: hyperbus: Fix build failure when only RPCIF_HYPERBUS is enabled (diff) | |
download | linux-6f78b9acf04fbf9ede7f4265e7282f9fb39d2c8c.tar.xz linux-6f78b9acf04fbf9ede7f4265e7282f9fb39d2c8c.zip |
Merge tag 'mtd/for-5.10' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux
Pull MTD updates from Richard Weinberger:
"NAND core changes:
- Drop useless 'depends on' in Kconfig
- Add an extra level in the Kconfig hierarchy
- Trivial spellings
- Dynamic allocation of the interface configurations
- Dropping the default ONFI timing mode
- Various cleanup (types, structures, naming, comments)
- Hide the chip->data_interface indirection
- Add the generic rb-gpios property
- Add the ->choose_interface_config() hook
- Introduce nand_choose_best_sdr_timings()
- Use default values for tPROG_max and tBERS_max
- Avoid redefining tR_max and tCCS_min
- Add a helper to find the closest ONFI mode
- bcm63xx MTD parsers: simplify CFE detection
Raw NAND controller drivers changes:
- fsl-upm: Deprecation of specific DT properties
- fsl_upm: Driver rework and cleanup in favor of ->exec_op()
- Ingenic: Cleanup ARRAY_SIZE() vs sizeof() use
- brcmnand: ECC error handling on EDU transfers
- brcmnand: Don't default to EDU transfers
- qcom: Set BAM mode only if not set already
- qcom: Avoid write to unavailable register
- gpio: Driver rework in favor of ->exec_op()
- tango: ->exec_op() conversion
- mtk: ->exec_op() conversion
Raw NAND chip drivers changes:
- toshiba: Implement ->choose_interface_config() for TH58NVG2S3HBAI4
- toshiba: Implement ->choose_interface_config() for TC58NVG0S3E
- toshiba: Implement ->choose_interface_config() for TC58TEG5DCLTA00
- hynix: Implement ->choose_interface_config() for H27UCG8T2ATR-BC
HyperBus changes:
- DMA support for TI's AM654 HyperBus controller driver.
- HyperBus frontend driver for Renesas RPC-IF driver.
SPI NOR core changes:
- Support for Winbond w25q64jwm flash
- Enable 4K sector support for mx25l12805d
SPI NOR controller drivers changes:
- intel-spi Add Alder Lake-S PCI ID
MTD Core changes:
- mtdoops: Don't run panic write twice
- mtdconcat: Correctly handle panic write
- Use DEFINE_SHOW_ATTRIBUTE"
* tag 'mtd/for-5.10' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (76 commits)
mtd: hyperbus: Fix build failure when only RPCIF_HYPERBUS is enabled
mtd: hyperbus: add Renesas RPC-IF driver
Revert "mtd: spi-nor: Prefer asynchronous probe"
mtd: parsers: bcm63xx: Do not make it modular
mtd: spear_smi: Enable compile testing
mtd: maps: vmu-flash: fix typos for struct memcard
mtd: physmap: Add Baikal-T1 physically mapped ROM support
mtd: maps: vmu-flash: simplify the return expression of probe_maple_vmu
mtd: onenand: simplify the return expression of onenand_transfer_auto_oob
mtd: rawnand: cadence: remove a redundant dev_err call
mtd: rawnand: ams-delta: Fix non-OF build warning
mtd: rawnand: Don't overwrite the error code from nand_set_ecc_soft_ops()
mtd: rawnand: Introduce nand_set_ecc_on_host_ops()
mtd: rawnand: atmel: Check return values for nand_read_data_op
mtd: rawnand: vf610: Remove unused function vf610_nfc_transfer_size()
mtd: rawnand: qcom: Simplify with dev_err_probe()
mtd: rawnand: marvell: Fix and update kerneldoc
mtd: rawnand: marvell: Simplify with dev_err_probe()
mtd: rawnand: gpmi: Simplify with dev_err_probe()
mtd: rawnand: atmel: Simplify with dev_err_probe()
...
Diffstat (limited to 'drivers/mtd/hyperbus')
-rw-r--r-- | drivers/mtd/hyperbus/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mtd/hyperbus/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/hyperbus/hbmc-am654.c | 144 | ||||
-rw-r--r-- | drivers/mtd/hyperbus/rpc-if.c | 170 |
4 files changed, 306 insertions, 16 deletions
diff --git a/drivers/mtd/hyperbus/Kconfig b/drivers/mtd/hyperbus/Kconfig index a4d8968d133d..46c7e407e378 100644 --- a/drivers/mtd/hyperbus/Kconfig +++ b/drivers/mtd/hyperbus/Kconfig @@ -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 + depends on MTD_CFI_BE_BYTE_SWAP + help + This option includes Renesas RPC-IF HyperBus support. + endif # MTD_HYPERBUS diff --git a/drivers/mtd/hyperbus/Makefile b/drivers/mtd/hyperbus/Makefile index 8a936e066f48..5fc2b5124542 100644 --- a/drivers/mtd/hyperbus/Makefile +++ b/drivers/mtd/hyperbus/Makefile @@ -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 diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c index e0e33f6bf513..a3439b791eeb 100644 --- a/drivers/mtd/hyperbus/hbmc-am654.c +++ b/drivers/mtd/hyperbus/hbmc-am654.c @@ -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; } diff --git a/drivers/mtd/hyperbus/rpc-if.c b/drivers/mtd/hyperbus/rpc-if.c new file mode 100644 index 000000000000..ecb050ba95cd --- /dev/null +++ b/drivers/mtd/hyperbus/rpc-if.c @@ -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"); |