18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/ 48c2ecf20Sopenharmony_ci// Author: Vignesh Raghavendra <vigneshr@ti.com> 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/completion.h> 78c2ecf20Sopenharmony_ci#include <linux/dma-direction.h> 88c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 98c2ecf20Sopenharmony_ci#include <linux/dmaengine.h> 108c2ecf20Sopenharmony_ci#include <linux/err.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/mtd/cfi.h> 148c2ecf20Sopenharmony_ci#include <linux/mtd/hyperbus.h> 158c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 168c2ecf20Sopenharmony_ci#include <linux/mux/consumer.h> 178c2ecf20Sopenharmony_ci#include <linux/of.h> 188c2ecf20Sopenharmony_ci#include <linux/of_address.h> 198c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 208c2ecf20Sopenharmony_ci#include <linux/sched/task_stack.h> 218c2ecf20Sopenharmony_ci#include <linux/types.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#define AM654_HBMC_CALIB_COUNT 25 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistruct am654_hbmc_device_priv { 268c2ecf20Sopenharmony_ci struct completion rx_dma_complete; 278c2ecf20Sopenharmony_ci phys_addr_t device_base; 288c2ecf20Sopenharmony_ci struct hyperbus_ctlr *ctlr; 298c2ecf20Sopenharmony_ci struct dma_chan *rx_chan; 308c2ecf20Sopenharmony_ci}; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct am654_hbmc_priv { 338c2ecf20Sopenharmony_ci struct hyperbus_ctlr ctlr; 348c2ecf20Sopenharmony_ci struct hyperbus_device hbdev; 358c2ecf20Sopenharmony_ci struct mux_control *mux_ctrl; 368c2ecf20Sopenharmony_ci}; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic int am654_hbmc_calibrate(struct hyperbus_device *hbdev) 398c2ecf20Sopenharmony_ci{ 408c2ecf20Sopenharmony_ci struct map_info *map = &hbdev->map; 418c2ecf20Sopenharmony_ci struct cfi_private cfi; 428c2ecf20Sopenharmony_ci int count = AM654_HBMC_CALIB_COUNT; 438c2ecf20Sopenharmony_ci int pass_count = 0; 448c2ecf20Sopenharmony_ci int ret; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci cfi.interleave = 1; 478c2ecf20Sopenharmony_ci cfi.device_type = CFI_DEVICETYPE_X16; 488c2ecf20Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, 0, map, &cfi, cfi.device_type, NULL); 498c2ecf20Sopenharmony_ci cfi_send_gen_cmd(0x98, 0x55, 0, map, &cfi, cfi.device_type, NULL); 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci while (count--) { 528c2ecf20Sopenharmony_ci ret = cfi_qry_present(map, 0, &cfi); 538c2ecf20Sopenharmony_ci if (ret) 548c2ecf20Sopenharmony_ci pass_count++; 558c2ecf20Sopenharmony_ci else 568c2ecf20Sopenharmony_ci pass_count = 0; 578c2ecf20Sopenharmony_ci if (pass_count == 5) 588c2ecf20Sopenharmony_ci break; 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci cfi_qry_mode_off(0, map, &cfi); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci return ret; 648c2ecf20Sopenharmony_ci} 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistatic void am654_hbmc_dma_callback(void *param) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci struct am654_hbmc_device_priv *priv = param; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci complete(&priv->rx_dma_complete); 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic int am654_hbmc_dma_read(struct am654_hbmc_device_priv *priv, void *to, 748c2ecf20Sopenharmony_ci unsigned long from, ssize_t len) 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci enum dma_ctrl_flags flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; 788c2ecf20Sopenharmony_ci struct dma_chan *rx_chan = priv->rx_chan; 798c2ecf20Sopenharmony_ci struct dma_async_tx_descriptor *tx; 808c2ecf20Sopenharmony_ci dma_addr_t dma_dst, dma_src; 818c2ecf20Sopenharmony_ci dma_cookie_t cookie; 828c2ecf20Sopenharmony_ci int ret; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci if (!priv->rx_chan || !virt_addr_valid(to) || object_is_on_stack(to)) 858c2ecf20Sopenharmony_ci return -EINVAL; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci dma_dst = dma_map_single(rx_chan->device->dev, to, len, DMA_FROM_DEVICE); 888c2ecf20Sopenharmony_ci if (dma_mapping_error(rx_chan->device->dev, dma_dst)) { 898c2ecf20Sopenharmony_ci dev_dbg(priv->ctlr->dev, "DMA mapping failed\n"); 908c2ecf20Sopenharmony_ci return -EIO; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci dma_src = priv->device_base + from; 948c2ecf20Sopenharmony_ci tx = dmaengine_prep_dma_memcpy(rx_chan, dma_dst, dma_src, len, flags); 958c2ecf20Sopenharmony_ci if (!tx) { 968c2ecf20Sopenharmony_ci dev_err(priv->ctlr->dev, "device_prep_dma_memcpy error\n"); 978c2ecf20Sopenharmony_ci ret = -EIO; 988c2ecf20Sopenharmony_ci goto unmap_dma; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci reinit_completion(&priv->rx_dma_complete); 1028c2ecf20Sopenharmony_ci tx->callback = am654_hbmc_dma_callback; 1038c2ecf20Sopenharmony_ci tx->callback_param = priv; 1048c2ecf20Sopenharmony_ci cookie = dmaengine_submit(tx); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci ret = dma_submit_error(cookie); 1078c2ecf20Sopenharmony_ci if (ret) { 1088c2ecf20Sopenharmony_ci dev_err(priv->ctlr->dev, "dma_submit_error %d\n", cookie); 1098c2ecf20Sopenharmony_ci goto unmap_dma; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci dma_async_issue_pending(rx_chan); 1138c2ecf20Sopenharmony_ci if (!wait_for_completion_timeout(&priv->rx_dma_complete, msecs_to_jiffies(len + 1000))) { 1148c2ecf20Sopenharmony_ci dmaengine_terminate_sync(rx_chan); 1158c2ecf20Sopenharmony_ci dev_err(priv->ctlr->dev, "DMA wait_for_completion_timeout\n"); 1168c2ecf20Sopenharmony_ci ret = -ETIMEDOUT; 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ciunmap_dma: 1208c2ecf20Sopenharmony_ci dma_unmap_single(rx_chan->device->dev, dma_dst, len, DMA_FROM_DEVICE); 1218c2ecf20Sopenharmony_ci return ret; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic void am654_hbmc_read(struct hyperbus_device *hbdev, void *to, 1258c2ecf20Sopenharmony_ci unsigned long from, ssize_t len) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct am654_hbmc_device_priv *priv = hbdev->priv; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci if (len < SZ_1K || am654_hbmc_dma_read(priv, to, from, len)) 1308c2ecf20Sopenharmony_ci memcpy_fromio(to, hbdev->map.virt + from, len); 1318c2ecf20Sopenharmony_ci} 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_cistatic const struct hyperbus_ops am654_hbmc_ops = { 1348c2ecf20Sopenharmony_ci .calibrate = am654_hbmc_calibrate, 1358c2ecf20Sopenharmony_ci .copy_from = am654_hbmc_read, 1368c2ecf20Sopenharmony_ci}; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic int am654_hbmc_request_mmap_dma(struct am654_hbmc_device_priv *priv) 1398c2ecf20Sopenharmony_ci{ 1408c2ecf20Sopenharmony_ci struct dma_chan *rx_chan; 1418c2ecf20Sopenharmony_ci dma_cap_mask_t mask; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci dma_cap_zero(mask); 1448c2ecf20Sopenharmony_ci dma_cap_set(DMA_MEMCPY, mask); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci rx_chan = dma_request_chan_by_mask(&mask); 1478c2ecf20Sopenharmony_ci if (IS_ERR(rx_chan)) { 1488c2ecf20Sopenharmony_ci if (PTR_ERR(rx_chan) == -EPROBE_DEFER) 1498c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1508c2ecf20Sopenharmony_ci dev_dbg(priv->ctlr->dev, "No DMA channel available\n"); 1518c2ecf20Sopenharmony_ci return 0; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci priv->rx_chan = rx_chan; 1548c2ecf20Sopenharmony_ci init_completion(&priv->rx_dma_complete); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int am654_hbmc_probe(struct platform_device *pdev) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1628c2ecf20Sopenharmony_ci struct am654_hbmc_device_priv *dev_priv; 1638c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1648c2ecf20Sopenharmony_ci struct am654_hbmc_priv *priv; 1658c2ecf20Sopenharmony_ci struct resource res; 1668c2ecf20Sopenharmony_ci int ret; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 1698c2ecf20Sopenharmony_ci if (!priv) 1708c2ecf20Sopenharmony_ci return -ENOMEM; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci priv->hbdev.np = of_get_next_child(np, NULL); 1758c2ecf20Sopenharmony_ci ret = of_address_to_resource(priv->hbdev.np, 0, &res); 1768c2ecf20Sopenharmony_ci if (ret) 1778c2ecf20Sopenharmony_ci return ret; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci if (of_property_read_bool(dev->of_node, "mux-controls")) { 1808c2ecf20Sopenharmony_ci struct mux_control *control = devm_mux_control_get(dev, NULL); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (IS_ERR(control)) 1838c2ecf20Sopenharmony_ci return PTR_ERR(control); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci ret = mux_control_select(control, 1); 1868c2ecf20Sopenharmony_ci if (ret) { 1878c2ecf20Sopenharmony_ci dev_err(dev, "Failed to select HBMC mux\n"); 1888c2ecf20Sopenharmony_ci return ret; 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci priv->mux_ctrl = control; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci priv->hbdev.map.size = resource_size(&res); 1948c2ecf20Sopenharmony_ci priv->hbdev.map.virt = devm_ioremap_resource(dev, &res); 1958c2ecf20Sopenharmony_ci if (IS_ERR(priv->hbdev.map.virt)) 1968c2ecf20Sopenharmony_ci return PTR_ERR(priv->hbdev.map.virt); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci priv->ctlr.dev = dev; 1998c2ecf20Sopenharmony_ci priv->ctlr.ops = &am654_hbmc_ops; 2008c2ecf20Sopenharmony_ci priv->hbdev.ctlr = &priv->ctlr; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci dev_priv = devm_kzalloc(dev, sizeof(*dev_priv), GFP_KERNEL); 2038c2ecf20Sopenharmony_ci if (!dev_priv) { 2048c2ecf20Sopenharmony_ci ret = -ENOMEM; 2058c2ecf20Sopenharmony_ci goto disable_mux; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci priv->hbdev.priv = dev_priv; 2098c2ecf20Sopenharmony_ci dev_priv->device_base = res.start; 2108c2ecf20Sopenharmony_ci dev_priv->ctlr = &priv->ctlr; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci ret = am654_hbmc_request_mmap_dma(dev_priv); 2138c2ecf20Sopenharmony_ci if (ret) 2148c2ecf20Sopenharmony_ci goto disable_mux; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci ret = hyperbus_register_device(&priv->hbdev); 2178c2ecf20Sopenharmony_ci if (ret) { 2188c2ecf20Sopenharmony_ci dev_err(dev, "failed to register controller\n"); 2198c2ecf20Sopenharmony_ci goto release_dma; 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci return 0; 2238c2ecf20Sopenharmony_cirelease_dma: 2248c2ecf20Sopenharmony_ci if (dev_priv->rx_chan) 2258c2ecf20Sopenharmony_ci dma_release_channel(dev_priv->rx_chan); 2268c2ecf20Sopenharmony_cidisable_mux: 2278c2ecf20Sopenharmony_ci if (priv->mux_ctrl) 2288c2ecf20Sopenharmony_ci mux_control_deselect(priv->mux_ctrl); 2298c2ecf20Sopenharmony_ci return ret; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int am654_hbmc_remove(struct platform_device *pdev) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct am654_hbmc_priv *priv = platform_get_drvdata(pdev); 2358c2ecf20Sopenharmony_ci struct am654_hbmc_device_priv *dev_priv = priv->hbdev.priv; 2368c2ecf20Sopenharmony_ci int ret; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci ret = hyperbus_unregister_device(&priv->hbdev); 2398c2ecf20Sopenharmony_ci if (priv->mux_ctrl) 2408c2ecf20Sopenharmony_ci mux_control_deselect(priv->mux_ctrl); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci if (dev_priv->rx_chan) 2438c2ecf20Sopenharmony_ci dma_release_channel(dev_priv->rx_chan); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci return ret; 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic const struct of_device_id am654_hbmc_dt_ids[] = { 2498c2ecf20Sopenharmony_ci { 2508c2ecf20Sopenharmony_ci .compatible = "ti,am654-hbmc", 2518c2ecf20Sopenharmony_ci }, 2528c2ecf20Sopenharmony_ci { /* end of table */ } 2538c2ecf20Sopenharmony_ci}; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, am654_hbmc_dt_ids); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic struct platform_driver am654_hbmc_platform_driver = { 2588c2ecf20Sopenharmony_ci .probe = am654_hbmc_probe, 2598c2ecf20Sopenharmony_ci .remove = am654_hbmc_remove, 2608c2ecf20Sopenharmony_ci .driver = { 2618c2ecf20Sopenharmony_ci .name = "hbmc-am654", 2628c2ecf20Sopenharmony_ci .of_match_table = am654_hbmc_dt_ids, 2638c2ecf20Sopenharmony_ci }, 2648c2ecf20Sopenharmony_ci}; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cimodule_platform_driver(am654_hbmc_platform_driver); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HBMC driver for AM654 SoC"); 2698c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2708c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:hbmc-am654"); 2718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>"); 272