Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / arch / arm / plat-pxa / ssp.c
diff --git a/kernel/arch/arm/plat-pxa/ssp.c b/kernel/arch/arm/plat-pxa/ssp.c
new file mode 100644 (file)
index 0000000..ad9529c
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ *  linux/arch/arm/mach-pxa/ssp.c
+ *
+ *  based on linux/arch/arm/mach-sa1100/ssp.c by Russell King
+ *
+ *  Copyright (C) 2003 Russell King.
+ *  Copyright (C) 2003 Wolfson Microelectronics PLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  PXA2xx SSP driver.  This provides the generic core for simple
+ *  IO-based SSP applications and allows easy port setup for DMA access.
+ *
+ *  Author: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/spi/pxa2xx_spi.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <asm/irq.h>
+#include <mach/hardware.h>
+
+static DEFINE_MUTEX(ssp_lock);
+static LIST_HEAD(ssp_list);
+
+struct ssp_device *pxa_ssp_request(int port, const char *label)
+{
+       struct ssp_device *ssp = NULL;
+
+       mutex_lock(&ssp_lock);
+
+       list_for_each_entry(ssp, &ssp_list, node) {
+               if (ssp->port_id == port && ssp->use_count == 0) {
+                       ssp->use_count++;
+                       ssp->label = label;
+                       break;
+               }
+       }
+
+       mutex_unlock(&ssp_lock);
+
+       if (&ssp->node == &ssp_list)
+               return NULL;
+
+       return ssp;
+}
+EXPORT_SYMBOL(pxa_ssp_request);
+
+struct ssp_device *pxa_ssp_request_of(const struct device_node *of_node,
+                                     const char *label)
+{
+       struct ssp_device *ssp = NULL;
+
+       mutex_lock(&ssp_lock);
+
+       list_for_each_entry(ssp, &ssp_list, node) {
+               if (ssp->of_node == of_node && ssp->use_count == 0) {
+                       ssp->use_count++;
+                       ssp->label = label;
+                       break;
+               }
+       }
+
+       mutex_unlock(&ssp_lock);
+
+       if (&ssp->node == &ssp_list)
+               return NULL;
+
+       return ssp;
+}
+EXPORT_SYMBOL(pxa_ssp_request_of);
+
+void pxa_ssp_free(struct ssp_device *ssp)
+{
+       mutex_lock(&ssp_lock);
+       if (ssp->use_count) {
+               ssp->use_count--;
+               ssp->label = NULL;
+       } else
+               dev_err(&ssp->pdev->dev, "device already free\n");
+       mutex_unlock(&ssp_lock);
+}
+EXPORT_SYMBOL(pxa_ssp_free);
+
+#ifdef CONFIG_OF
+static const struct of_device_id pxa_ssp_of_ids[] = {
+       { .compatible = "mrvl,pxa25x-ssp",      .data = (void *) PXA25x_SSP },
+       { .compatible = "mvrl,pxa25x-nssp",     .data = (void *) PXA25x_NSSP },
+       { .compatible = "mrvl,pxa27x-ssp",      .data = (void *) PXA27x_SSP },
+       { .compatible = "mrvl,pxa3xx-ssp",      .data = (void *) PXA3xx_SSP },
+       { .compatible = "mvrl,pxa168-ssp",      .data = (void *) PXA168_SSP },
+       { .compatible = "mrvl,pxa910-ssp",      .data = (void *) PXA910_SSP },
+       { .compatible = "mrvl,ce4100-ssp",      .data = (void *) CE4100_SSP },
+       { .compatible = "mrvl,lpss-ssp",        .data = (void *) LPSS_SSP },
+       { },
+};
+MODULE_DEVICE_TABLE(of, pxa_ssp_of_ids);
+#endif
+
+static int pxa_ssp_probe(struct platform_device *pdev)
+{
+       struct resource *res;
+       struct ssp_device *ssp;
+       struct device *dev = &pdev->dev;
+
+       ssp = devm_kzalloc(dev, sizeof(struct ssp_device), GFP_KERNEL);
+       if (ssp == NULL)
+               return -ENOMEM;
+
+       ssp->pdev = pdev;
+
+       ssp->clk = devm_clk_get(dev, NULL);
+       if (IS_ERR(ssp->clk))
+               return PTR_ERR(ssp->clk);
+
+       if (dev->of_node) {
+               struct of_phandle_args dma_spec;
+               struct device_node *np = dev->of_node;
+               int ret;
+
+               /*
+                * FIXME: we should allocate the DMA channel from this
+                * context and pass the channel down to the ssp users.
+                * For now, we lookup the rx and tx indices manually
+                */
+
+               /* rx */
+               ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells",
+                                                0, &dma_spec);
+
+               if (ret) {
+                       dev_err(dev, "Can't parse dmas property\n");
+                       return -ENODEV;
+               }
+               ssp->drcmr_rx = dma_spec.args[0];
+               of_node_put(dma_spec.np);
+
+               /* tx */
+               ret = of_parse_phandle_with_args(np, "dmas", "#dma-cells",
+                                                1, &dma_spec);
+               if (ret) {
+                       dev_err(dev, "Can't parse dmas property\n");
+                       return -ENODEV;
+               }
+               ssp->drcmr_tx = dma_spec.args[0];
+               of_node_put(dma_spec.np);
+       } else {
+               res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+               if (res == NULL) {
+                       dev_err(dev, "no SSP RX DRCMR defined\n");
+                       return -ENODEV;
+               }
+               ssp->drcmr_rx = res->start;
+
+               res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+               if (res == NULL) {
+                       dev_err(dev, "no SSP TX DRCMR defined\n");
+                       return -ENODEV;
+               }
+               ssp->drcmr_tx = res->start;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res == NULL) {
+               dev_err(dev, "no memory resource defined\n");
+               return -ENODEV;
+       }
+
+       res = devm_request_mem_region(dev, res->start, resource_size(res),
+                                     pdev->name);
+       if (res == NULL) {
+               dev_err(dev, "failed to request memory resource\n");
+               return -EBUSY;
+       }
+
+       ssp->phys_base = res->start;
+
+       ssp->mmio_base = devm_ioremap(dev, res->start, resource_size(res));
+       if (ssp->mmio_base == NULL) {
+               dev_err(dev, "failed to ioremap() registers\n");
+               return -ENODEV;
+       }
+
+       ssp->irq = platform_get_irq(pdev, 0);
+       if (ssp->irq < 0) {
+               dev_err(dev, "no IRQ resource defined\n");
+               return -ENODEV;
+       }
+
+       if (dev->of_node) {
+               const struct of_device_id *id =
+                       of_match_device(of_match_ptr(pxa_ssp_of_ids), dev);
+               ssp->type = (int) id->data;
+       } else {
+               const struct platform_device_id *id =
+                       platform_get_device_id(pdev);
+               ssp->type = (int) id->driver_data;
+
+               /* PXA2xx/3xx SSP ports starts from 1 and the internal pdev->id
+                * starts from 0, do a translation here
+                */
+               ssp->port_id = pdev->id + 1;
+       }
+
+       ssp->use_count = 0;
+       ssp->of_node = dev->of_node;
+
+       mutex_lock(&ssp_lock);
+       list_add(&ssp->node, &ssp_list);
+       mutex_unlock(&ssp_lock);
+
+       platform_set_drvdata(pdev, ssp);
+
+       return 0;
+}
+
+static int pxa_ssp_remove(struct platform_device *pdev)
+{
+       struct resource *res;
+       struct ssp_device *ssp;
+
+       ssp = platform_get_drvdata(pdev);
+       if (ssp == NULL)
+               return -ENODEV;
+
+       iounmap(ssp->mmio_base);
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       release_mem_region(res->start, resource_size(res));
+
+       clk_put(ssp->clk);
+
+       mutex_lock(&ssp_lock);
+       list_del(&ssp->node);
+       mutex_unlock(&ssp_lock);
+
+       kfree(ssp);
+       return 0;
+}
+
+static const struct platform_device_id ssp_id_table[] = {
+       { "pxa25x-ssp",         PXA25x_SSP },
+       { "pxa25x-nssp",        PXA25x_NSSP },
+       { "pxa27x-ssp",         PXA27x_SSP },
+       { "pxa3xx-ssp",         PXA3xx_SSP },
+       { "pxa168-ssp",         PXA168_SSP },
+       { "pxa910-ssp",         PXA910_SSP },
+       { },
+};
+
+static struct platform_driver pxa_ssp_driver = {
+       .probe          = pxa_ssp_probe,
+       .remove         = pxa_ssp_remove,
+       .driver         = {
+               .name           = "pxa2xx-ssp",
+               .of_match_table = of_match_ptr(pxa_ssp_of_ids),
+       },
+       .id_table       = ssp_id_table,
+};
+
+static int __init pxa_ssp_init(void)
+{
+       return platform_driver_register(&pxa_ssp_driver);
+}
+
+static void __exit pxa_ssp_exit(void)
+{
+       platform_driver_unregister(&pxa_ssp_driver);
+}
+
+arch_initcall(pxa_ssp_init);
+module_exit(pxa_ssp_exit);
+
+MODULE_DESCRIPTION("PXA SSP driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");