@@ -135,4 +135,12 @@ config UIO_MF624
If you compile this as a module, it will be called uio_mf624.
+config UIO_XGENE_QMTM
+ tristate "Applied Micro X-Gene QMTM driver"
+ depends on OF
+ help
+ Userspace I/O interface for the X-Gene QMTM. The userspace part of
+ this driver will be available for download from the Applied Micro
+ web site (http://www.apm.com/).
+
endif
@@ -8,3 +8,4 @@ obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o
obj-$(CONFIG_UIO_NETX) += uio_netx.o
obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o
obj-$(CONFIG_UIO_MF624) += uio_mf624.o
+obj-$(CONFIG_UIO_XGENE_QMTM) += uio_xgene_qmtm.o
new file mode 100644
@@ -0,0 +1,271 @@
+/*
+ * X-Gene Queue Manager Traffic Manager (QMTM) UIO driver (uio_xgene_qmtm)
+ *
+ * This driver exports QMTM CSRs, Fabric and memory for queues to user-space
+ *
+ * Copyright (C) 2014 Applied Micro - http://www.apm.com/
+ * Copyright (C) 2014 Linaro Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uio_driver.h>
+
+#define DRV_NAME "qmtm_uio"
+#define DRV_VERSION "1.0"
+
+#define QMTM_CFG_MEM_RAM_SHUTDOWN 0x0000d070
+
+#define QMTM_DEFAULT_QSIZE 65536
+
+struct uio_qmtm_dev {
+ struct uio_info *info;
+ struct clk *qmtm_clk;
+};
+
+/* QMTM CSR read/write routine */
+static inline void qmtm_csr_write(struct uio_qmtm_dev *qmtm_dev, u32 offset,
+ u32 data)
+{
+ void __iomem *addr = qmtm_dev->info->mem[0].internal_addr;
+
+ writel(data, addr + offset);
+}
+
+static inline u32 qmtm_csr_read(struct uio_qmtm_dev *qmtm_dev, u32 offset)
+{
+ void __iomem *addr = qmtm_dev->info->mem[0].internal_addr;
+
+ return readl(addr + offset);
+}
+
+static int qmtm_reset(struct uio_qmtm_dev *qmtm_dev)
+{
+ u32 val;
+ int wait = 1000;
+
+ /* reset the internal memory of the device */
+ qmtm_csr_write(qmtm_dev, QMTM_CFG_MEM_RAM_SHUTDOWN, 0);
+
+ /* check whether device internal memory is out of reset or not */
+ while (wait--) {
+ val = qmtm_csr_read(qmtm_dev, QMTM_CFG_MEM_RAM_SHUTDOWN);
+
+ if (val != 0xffffffff)
+ return 0;
+
+ udelay(1);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int qmtm_probe(struct platform_device *pdev)
+{
+ struct uio_info *info;
+ struct uio_qmtm_dev *qmtm_dev;
+ struct resource *csr;
+ struct resource *fabric;
+ struct resource qpool;
+ unsigned int num_queues;
+ unsigned int devid;
+ phandle qpool_phandle;
+ struct device_node *qpool_node;
+ int ret;
+
+ qmtm_dev = devm_kzalloc(&pdev->dev, sizeof(struct uio_qmtm_dev),
+ GFP_KERNEL);
+ if (!qmtm_dev)
+ return -ENOMEM;
+
+ qmtm_dev->info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!qmtm_dev->info)
+ return -ENOMEM;
+
+ /* Power on qmtm in case its not done as part of boot-loader */
+ qmtm_dev->qmtm_clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(qmtm_dev->qmtm_clk)) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ ret = PTR_ERR(qmtm_dev->qmtm_clk);
+ return ret;
+ }
+
+ csr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!csr) {
+ ret = -ENODEV;
+ dev_err(&pdev->dev, "No QMTM CSR resource specified\n");
+ goto out_err;
+ }
+
+ if (!csr->start) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "Invalid CSR resource\n");
+ goto out_err;
+ }
+
+ fabric = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!fabric) {
+ ret = -ENODEV;
+ dev_err(&pdev->dev, "No QMTM Fabric resource specified\n");
+ goto out_err;
+ }
+
+ if (!fabric->start) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "Invalid Fabric resource\n");
+ goto out_err;
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node, "qpool-memory",
+ &qpool_phandle);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "No qpool-memory resource specified\n");
+ goto out_err;
+ }
+
+ qpool_node = of_find_node_by_phandle(qpool_phandle);
+ if (IS_ERR_OR_NULL(qpool_node)) {
+ ret = PTR_ERR(qpool_node);
+ dev_err(&pdev->dev, "Failed to get node by phandle\n");
+ goto out_err;
+ }
+
+ ret = of_address_to_resource(qpool_node, 0, &qpool);
+
+ of_node_put(qpool_node);
+
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to get qpool resource from node\n");
+ goto out_err;
+ }
+
+ if (!qpool.start) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "Invalid qpool resource\n");
+ goto out_err;
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node, "num-queues",
+ &num_queues);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "No num-queues resource specified\n");
+ goto out_err;
+ }
+
+ /* check whether sufficient memory is provided for the given queues */
+ if (num_queues * QMTM_DEFAULT_QSIZE > resource_size(&qpool)) {
+ ret = -ENOSPC;
+ dev_err(&pdev->dev, "Insufficient Qpool for the given queues\n");
+ goto out_err;
+ }
+
+ ret = of_property_read_u32(pdev->dev.of_node, "devid", &devid);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "No devid resource specified\n");
+ goto out_err;
+ }
+
+ info = qmtm_dev->info;
+ info->mem[0].name = "csr";
+ info->mem[0].addr = csr->start;
+ info->mem[0].size = resource_size(csr);
+ info->mem[0].memtype = UIO_MEM_PHYS;
+ info->mem[0].internal_addr = devm_ioremap_resource(&pdev->dev, csr);
+
+ if (IS_ERR(info->mem[0].internal_addr)) {
+ ret = PTR_ERR(info->mem[0].internal_addr);
+ dev_err(&pdev->dev, "Failed to ioremap CSR region\n");
+ goto out_err;
+ }
+
+ info->mem[1].name = "fabric";
+ info->mem[1].addr = fabric->start;
+ info->mem[1].size = resource_size(fabric);
+ info->mem[1].memtype = UIO_MEM_PHYS;
+
+ info->mem[2].name = "qpool";
+ info->mem[2].addr = qpool.start;
+ info->mem[2].size = resource_size(&qpool);
+ info->mem[2].memtype = UIO_MEM_PHYS_CACHE;
+
+ info->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "qmtm%d", devid);
+ info->version = DRV_VERSION;
+
+ info->priv = qmtm_dev;
+
+ /* enable the clock */
+ clk_prepare_enable(qmtm_dev->qmtm_clk);
+
+ /* get the qmtm out of reset */
+ ret = qmtm_reset(qmtm_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to reset QMTM\n");
+ goto out_clk;
+ }
+
+ /* register with uio framework */
+ ret = uio_register_device(&pdev->dev, info);
+ if (ret < 0)
+ goto out_clk;
+
+ platform_set_drvdata(pdev, qmtm_dev);
+ return 0;
+
+out_clk:
+ clk_disable_unprepare(qmtm_dev->qmtm_clk);
+
+out_err:
+ return ret;
+}
+
+static int qmtm_remove(struct platform_device *pdev)
+{
+ struct uio_qmtm_dev *qmtm_dev = platform_get_drvdata(pdev);
+ struct uio_info *info = qmtm_dev->info;
+
+ uio_unregister_device(info);
+
+ clk_disable_unprepare(qmtm_dev->qmtm_clk);
+
+ return 0;
+}
+
+static struct of_device_id qmtm_match[] = {
+ {.compatible = "apm,xgene-qmtm",},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, qmtm_match);
+
+static struct platform_driver qmtm_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = qmtm_match,
+ },
+ .probe = qmtm_probe,
+ .remove = qmtm_remove,
+};
+
+module_platform_driver(qmtm_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(DRV_VERSION);
+MODULE_AUTHOR("Ankit Jindal <ankit.jindal@linaro.org>");
+MODULE_AUTHOR("Tushar Jagad <tushar.jagad@linaro.org>");