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, Xilinx 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_config_data {
  22        const char *name;
  23};
  24
  25struct xlnx_pr_decoupler_data {
  26        const struct xlnx_config_data *ipconfig;
  27        void __iomem *io_base;
  28        struct clk *clk;
  29};
  30
  31static inline void xlnx_pr_decoupler_write(struct xlnx_pr_decoupler_data *d,
  32                                           u32 offset, u32 val)
  33{
  34        writel(val, d->io_base + offset);
  35}
  36
  37static inline u32 xlnx_pr_decouple_read(const struct xlnx_pr_decoupler_data *d,
  38                                        u32 offset)
  39{
  40        return readl(d->io_base + offset);
  41}
  42
  43static int xlnx_pr_decoupler_enable_set(struct fpga_bridge *bridge, bool enable)
  44{
  45        int err;
  46        struct xlnx_pr_decoupler_data *priv = bridge->priv;
  47
  48        err = clk_enable(priv->clk);
  49        if (err)
  50                return err;
  51
  52        if (enable)
  53                xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_COUPLE);
  54        else
  55                xlnx_pr_decoupler_write(priv, CTRL_OFFSET, CTRL_CMD_DECOUPLE);
  56
  57        clk_disable(priv->clk);
  58
  59        return 0;
  60}
  61
  62static int xlnx_pr_decoupler_enable_show(struct fpga_bridge *bridge)
  63{
  64        const struct xlnx_pr_decoupler_data *priv = bridge->priv;
  65        u32 status;
  66        int err;
  67
  68        err = clk_enable(priv->clk);
  69        if (err)
  70                return err;
  71
  72        status = readl(priv->io_base);
  73
  74        clk_disable(priv->clk);
  75
  76        return !status;
  77}
  78
  79static const struct fpga_bridge_ops xlnx_pr_decoupler_br_ops = {
  80        .enable_set = xlnx_pr_decoupler_enable_set,
  81        .enable_show = xlnx_pr_decoupler_enable_show,
  82};
  83
  84#ifdef CONFIG_OF
  85static const struct xlnx_config_data decoupler_config = {
  86        .name = "Xilinx PR Decoupler",
  87};
  88
  89static const struct xlnx_config_data shutdown_config = {
  90        .name = "Xilinx DFX AXI Shutdown Manager",
  91};
  92
  93static const struct of_device_id xlnx_pr_decoupler_of_match[] = {
  94        { .compatible = "xlnx,pr-decoupler-1.00", .data = &decoupler_config },
  95        { .compatible = "xlnx,pr-decoupler", .data = &decoupler_config },
  96        { .compatible = "xlnx,dfx-axi-shutdown-manager-1.00",
  97                                        .data = &shutdown_config },
  98        { .compatible = "xlnx,dfx-axi-shutdown-manager",
  99                                        .data = &shutdown_config },
 100        {},
 101};
 102MODULE_DEVICE_TABLE(of, xlnx_pr_decoupler_of_match);
 103#endif
 104
 105static int xlnx_pr_decoupler_probe(struct platform_device *pdev)
 106{
 107        struct device_node *np = pdev->dev.of_node;
 108        struct xlnx_pr_decoupler_data *priv;
 109        struct fpga_bridge *br;
 110        int err;
 111        struct resource *res;
 112
 113        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 114        if (!priv)
 115                return -ENOMEM;
 116
 117        if (np) {
 118                const struct of_device_id *match;
 119
 120                match = of_match_node(xlnx_pr_decoupler_of_match, np);
 121                if (match && match->data)
 122                        priv->ipconfig = match->data;
 123        }
 124
 125        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 126        priv->io_base = devm_ioremap_resource(&pdev->dev, res);
 127        if (IS_ERR(priv->io_base))
 128                return PTR_ERR(priv->io_base);
 129
 130        priv->clk = devm_clk_get(&pdev->dev, "aclk");
 131        if (IS_ERR(priv->clk))
 132                return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk),
 133                                     "input clock not found\n");
 134
 135        err = clk_prepare_enable(priv->clk);
 136        if (err) {
 137                dev_err(&pdev->dev, "unable to enable clock\n");
 138                return err;
 139        }
 140
 141        clk_disable(priv->clk);
 142
 143        br = devm_fpga_bridge_create(&pdev->dev, priv->ipconfig->name,
 144                                     &xlnx_pr_decoupler_br_ops, priv);
 145        if (!br) {
 146                err = -ENOMEM;
 147                goto err_clk;
 148        }
 149
 150        platform_set_drvdata(pdev, br);
 151
 152        err = fpga_bridge_register(br);
 153        if (err) {
 154                dev_err(&pdev->dev, "unable to register %s",
 155                        priv->ipconfig->name);
 156                goto err_clk;
 157        }
 158
 159        return 0;
 160
 161err_clk:
 162        clk_unprepare(priv->clk);
 163
 164        return err;
 165}
 166
 167static int xlnx_pr_decoupler_remove(struct platform_device *pdev)
 168{
 169        struct fpga_bridge *bridge = platform_get_drvdata(pdev);
 170        struct xlnx_pr_decoupler_data *p = bridge->priv;
 171
 172        fpga_bridge_unregister(bridge);
 173
 174        clk_unprepare(p->clk);
 175
 176        return 0;
 177}
 178
 179static struct platform_driver xlnx_pr_decoupler_driver = {
 180        .probe = xlnx_pr_decoupler_probe,
 181        .remove = xlnx_pr_decoupler_remove,
 182        .driver = {
 183                .name = "xlnx_pr_decoupler",
 184                .of_match_table = of_match_ptr(xlnx_pr_decoupler_of_match),
 185        },
 186};
 187
 188module_platform_driver(xlnx_pr_decoupler_driver);
 189
 190MODULE_DESCRIPTION("Xilinx Partial Reconfiguration Decoupler");
 191MODULE_AUTHOR("Moritz Fischer <mdf@kernel.org>");
 192MODULE_AUTHOR("Michal Simek <michal.simek@xilinx.com>");
 193MODULE_LICENSE("GPL v2");
 194