// 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->dev, &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->dev, &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.dev); 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.dev); } 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.dev, 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; error = rpcif_sw_init(&hyperbus->rpc, pdev->dev.parent); if (error) return error; platform_set_drvdata(pdev, hyperbus); pm_runtime_enable(hyperbus->rpc.dev); error = rpcif_hw_init(hyperbus->rpc.dev, true); if (error) goto out_disable_rpm; 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) goto out_disable_rpm; return 0; out_disable_rpm: pm_runtime_disable(hyperbus->rpc.dev); return error; } static int rpcif_hb_remove(struct platform_device *pdev) { struct rpcif_hyperbus *hyperbus = platform_get_drvdata(pdev); hyperbus_unregister_device(&hyperbus->hbdev); pm_runtime_disable(hyperbus->rpc.dev); return 0; } 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");