linux/drivers/fpga/xilinx-pr-decoupler.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Copyright (c) 2017, National Instruments Corp.
   4 * Copyright (c) 2017, Xilix Inc
   5 *
   6 * FPGA Bridge Driver for the Xilinx LogiCORE Partial Reconfiguration
   7 * Decoupler IP Core.
   8 */
   9
  10#include <linux/clk.h>
  11#include <linux/io.h>
  12#include <linux/kernel.h>
  13#include <linux/of_device.h>
  14#include <linux/module.h>
  15#include <linux/fpga/fpga-bridge.h>
  16
  17#define CTRL_CMD_DECOUPLE       BIT(0)
  18#define CTRL_CMD_COUPLE         0
  19#define CTRL_OFFSET             0
  20
  21struct xlnx_pr_decoupler_data {
  22        void __iomem *io_base;
  23        struct clk *clk;
  24};
  25
  26static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d,
  27                                           u32 offset, u32 val)
  28{
  29        writel(val, d->io_base + offset);
  30}
  31
  32static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d,
  33                                        u32 offset)
  34{
  35        return readl(d->io_base + offset);
  36}
  37
  38static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable)
  39{
  40        int err;
  41        struct xlnx_pr_decoupler_data *priv = bridge->priv;
  42
  43        err = clk_enable(priv->clk);
  44        if (err)
  45                return err;
  46
  47        if (enable)
  48                xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE);
  49        else
  50                xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE);
  51
  52        clk_disable(priv->clk);
  53
  54        return 0;
  55}
  56
  57static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge)
  58{
  59        const struct xlnx_pr_decoupler_data *priv = bridge->priv;
  60        u32 status;
  61        int err;
  62
  63        err = clk_enable(priv->clk);
  64        if (err)
  65                return err;
  66
  67        status = readl(priv->io_base);
  68
  69        clk_disable(priv->clk);
  70
  71        return !status;
  72}
  73
  74static const struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = {
  75        .enable_set = xlnx_pr_decoupler_enable_set,
  76        .enable_show = xlnx_pr_decoupler_enable_show,
  77};
  78
  79static const struct of_device_id xlnx_pr_decoupler_of_match[] = {
  80        { .compatible = "xlnx,pr-decoupler-1.00", },
  81        { .compatible = "xlnx,pr-decoupler", },
  82        {},
  83};
  84MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match);
  85
  86static int xlnx_pr_decoupler_probe(struct platform_device *pdev)
  87{
  88        struct xlnx_pr_decoupler_data *priv;
  89        struct fpga_bridge *br;
  90        int err;
  91        struct resource *res;
  92
  93        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
  94        if (!priv)
  95                return -ENOMEM;
  96
  97        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  98        priv->io_base = devm_ioremap_resource(&pdev->dev, res);
  99        if (IS_ERR(priv->io_base))
 100                return PTR_ERR(priv->io_base);
 101
 102        priv->clk = devm_clk_get(&pdev->dev, "aclk");
 103        if (IS_ERR(priv->clk)) {
 104                dev_err(&pdev->dev, "input clock not found\n");
 105                return PTR_ERR(priv->clk);
 106        }
 107
 108        err = clk_prepare_enable(priv->clk);
 109        if (err) {
 110                dev_err(&pdev->dev, "unable to enable clock\n");
 111                return err;
 112        }
 113
 114        clk_disable(priv->clk);
 115
 116        br = devm_fpga_bridge_create(&pdev->dev, "Xilinx PR Decoupler",
 117                                     &xlnx_pr_decoupler_br_ops, priv);
 118        if (!br) {
 119                err = -ENOMEM;
 120                goto err_clk;
 121        }
 122
 123        platform_set_drvdata(pdev, br);
 124
 125        err = fpga_bridge_register(br);
 126        if (err) {
 127                dev_err(&pdev->dev, "unable to register Xilinx PR Decoupler");
 128                goto err_clk;
 129        }
 130
 131        return 0;
 132
 133err_clk:
 134        clk_unprepare(priv->clk);
 135
 136        return err;
 137}
 138
 139static int xlnx_pr_decoupler_remove(struct platform_device *pdev)
 140{
 141        struct fpga_bridge *bridge = platform_get_drvdata(pdev);
 142        struct xlnx_pr_decoupler_data *p = bridge->priv;
 143
 144        fpga_bridge_unregister(bridge);
 145
 146        clk_unprepare(p->clk);
 147
 148        return 0;
 149}
 150
 151static struct platform_driver xlnx_pr_decoupler_driver = {
 152        .probe = xlnx_pr_decoupler_probe,
 153        .remove = xlnx_pr_decoupler_remove,
 154        .driver = {
 155                .name = "xlnx_pr_decoupler",
 156                .of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match),
 157        },
 158};
 159
 160module_platform_driver(xlnx_pr_decoupler_driver);
 161
 162MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler");
 163MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>");
 164MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>");
 165MODULE_LICENSE("GPL v2");
 166