18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss)
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM,
58c2ecf20Sopenharmony_ci * and DDR RAM to user space for applications interacting with PRUSS firmware
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or
108c2ecf20Sopenharmony_ci * modify it under the terms of the GNU General Public License as
118c2ecf20Sopenharmony_ci * published by the Free Software Foundation version 2.
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any
148c2ecf20Sopenharmony_ci * kind, whether express or implied; without even the implied warranty
158c2ecf20Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
168c2ecf20Sopenharmony_ci * GNU General Public License for more details.
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci#include <linux/device.h>
198c2ecf20Sopenharmony_ci#include <linux/module.h>
208c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
218c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
228c2ecf20Sopenharmony_ci#include <linux/uio_driver.h>
238c2ecf20Sopenharmony_ci#include <linux/platform_data/uio_pruss.h>
248c2ecf20Sopenharmony_ci#include <linux/io.h>
258c2ecf20Sopenharmony_ci#include <linux/clk.h>
268c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
278c2ecf20Sopenharmony_ci#include <linux/sizes.h>
288c2ecf20Sopenharmony_ci#include <linux/slab.h>
298c2ecf20Sopenharmony_ci#include <linux/genalloc.h>
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define DRV_NAME "pruss_uio"
328c2ecf20Sopenharmony_ci#define DRV_VERSION "1.0"
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistatic int sram_pool_sz = SZ_16K;
358c2ecf20Sopenharmony_cimodule_param(sram_pool_sz, int, 0);
368c2ecf20Sopenharmony_ciMODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate ");
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic int extram_pool_sz = SZ_256K;
398c2ecf20Sopenharmony_cimodule_param(extram_pool_sz, int, 0);
408c2ecf20Sopenharmony_ciMODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate");
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci/*
438c2ecf20Sopenharmony_ci * Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt
448c2ecf20Sopenharmony_ci * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS
458c2ecf20Sopenharmony_ci * firmware and user space application, async notification from PRU firmware
468c2ecf20Sopenharmony_ci * to user space application
478c2ecf20Sopenharmony_ci * 3	PRU_EVTOUT0
488c2ecf20Sopenharmony_ci * 4	PRU_EVTOUT1
498c2ecf20Sopenharmony_ci * 5	PRU_EVTOUT2
508c2ecf20Sopenharmony_ci * 6	PRU_EVTOUT3
518c2ecf20Sopenharmony_ci * 7	PRU_EVTOUT4
528c2ecf20Sopenharmony_ci * 8	PRU_EVTOUT5
538c2ecf20Sopenharmony_ci * 9	PRU_EVTOUT6
548c2ecf20Sopenharmony_ci * 10	PRU_EVTOUT7
558c2ecf20Sopenharmony_ci*/
568c2ecf20Sopenharmony_ci#define MAX_PRUSS_EVT	8
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#define PINTC_HIDISR	0x0038
598c2ecf20Sopenharmony_ci#define PINTC_HIPIR	0x0900
608c2ecf20Sopenharmony_ci#define HIPIR_NOPEND	0x80000000
618c2ecf20Sopenharmony_ci#define PINTC_HIER	0x1500
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistruct uio_pruss_dev {
648c2ecf20Sopenharmony_ci	struct uio_info *info;
658c2ecf20Sopenharmony_ci	struct clk *pruss_clk;
668c2ecf20Sopenharmony_ci	dma_addr_t sram_paddr;
678c2ecf20Sopenharmony_ci	dma_addr_t ddr_paddr;
688c2ecf20Sopenharmony_ci	void __iomem *prussio_vaddr;
698c2ecf20Sopenharmony_ci	unsigned long sram_vaddr;
708c2ecf20Sopenharmony_ci	void *ddr_vaddr;
718c2ecf20Sopenharmony_ci	unsigned int hostirq_start;
728c2ecf20Sopenharmony_ci	unsigned int pintc_base;
738c2ecf20Sopenharmony_ci	struct gen_pool *sram_pool;
748c2ecf20Sopenharmony_ci};
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic irqreturn_t pruss_handler(int irq, struct uio_info *info)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	struct uio_pruss_dev *gdev = info->priv;
798c2ecf20Sopenharmony_ci	int intr_bit = (irq - gdev->hostirq_start + 2);
808c2ecf20Sopenharmony_ci	int val, intr_mask = (1 << intr_bit);
818c2ecf20Sopenharmony_ci	void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base;
828c2ecf20Sopenharmony_ci	void __iomem *intren_reg = base + PINTC_HIER;
838c2ecf20Sopenharmony_ci	void __iomem *intrdis_reg = base + PINTC_HIDISR;
848c2ecf20Sopenharmony_ci	void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	val = ioread32(intren_reg);
878c2ecf20Sopenharmony_ci	/* Is interrupt enabled and active ? */
888c2ecf20Sopenharmony_ci	if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND))
898c2ecf20Sopenharmony_ci		return IRQ_NONE;
908c2ecf20Sopenharmony_ci	/* Disable interrupt */
918c2ecf20Sopenharmony_ci	iowrite32(intr_bit, intrdis_reg);
928c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	int cnt;
988c2ecf20Sopenharmony_ci	struct uio_info *p = gdev->info;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) {
1018c2ecf20Sopenharmony_ci		uio_unregister_device(p);
1028c2ecf20Sopenharmony_ci		kfree(p->name);
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci	iounmap(gdev->prussio_vaddr);
1058c2ecf20Sopenharmony_ci	if (gdev->ddr_vaddr) {
1068c2ecf20Sopenharmony_ci		dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr,
1078c2ecf20Sopenharmony_ci			gdev->ddr_paddr);
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci	if (gdev->sram_vaddr)
1108c2ecf20Sopenharmony_ci		gen_pool_free(gdev->sram_pool,
1118c2ecf20Sopenharmony_ci			      gdev->sram_vaddr,
1128c2ecf20Sopenharmony_ci			      sram_pool_sz);
1138c2ecf20Sopenharmony_ci	kfree(gdev->info);
1148c2ecf20Sopenharmony_ci	clk_disable(gdev->pruss_clk);
1158c2ecf20Sopenharmony_ci	clk_put(gdev->pruss_clk);
1168c2ecf20Sopenharmony_ci	kfree(gdev);
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int pruss_probe(struct platform_device *pdev)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	struct uio_info *p;
1228c2ecf20Sopenharmony_ci	struct uio_pruss_dev *gdev;
1238c2ecf20Sopenharmony_ci	struct resource *regs_prussio;
1248c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1258c2ecf20Sopenharmony_ci	int ret, cnt, i, len;
1268c2ecf20Sopenharmony_ci	struct uio_pruss_pdata *pdata = dev_get_platdata(dev);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	gdev = kzalloc(sizeof(struct uio_pruss_dev), GFP_KERNEL);
1298c2ecf20Sopenharmony_ci	if (!gdev)
1308c2ecf20Sopenharmony_ci		return -ENOMEM;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	gdev->info = kcalloc(MAX_PRUSS_EVT, sizeof(*p), GFP_KERNEL);
1338c2ecf20Sopenharmony_ci	if (!gdev->info) {
1348c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1358c2ecf20Sopenharmony_ci		goto err_free_gdev;
1368c2ecf20Sopenharmony_ci	}
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	/* Power on PRU in case its not done as part of boot-loader */
1398c2ecf20Sopenharmony_ci	gdev->pruss_clk = clk_get(dev, "pruss");
1408c2ecf20Sopenharmony_ci	if (IS_ERR(gdev->pruss_clk)) {
1418c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to get clock\n");
1428c2ecf20Sopenharmony_ci		ret = PTR_ERR(gdev->pruss_clk);
1438c2ecf20Sopenharmony_ci		goto err_free_info;
1448c2ecf20Sopenharmony_ci	}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	ret = clk_enable(gdev->pruss_clk);
1478c2ecf20Sopenharmony_ci	if (ret) {
1488c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to enable clock\n");
1498c2ecf20Sopenharmony_ci		goto err_clk_put;
1508c2ecf20Sopenharmony_ci	}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1538c2ecf20Sopenharmony_ci	if (!regs_prussio) {
1548c2ecf20Sopenharmony_ci		dev_err(dev, "No PRUSS I/O resource specified\n");
1558c2ecf20Sopenharmony_ci		ret = -EIO;
1568c2ecf20Sopenharmony_ci		goto err_clk_disable;
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	if (!regs_prussio->start) {
1608c2ecf20Sopenharmony_ci		dev_err(dev, "Invalid memory resource\n");
1618c2ecf20Sopenharmony_ci		ret = -EIO;
1628c2ecf20Sopenharmony_ci		goto err_clk_disable;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (pdata->sram_pool) {
1668c2ecf20Sopenharmony_ci		gdev->sram_pool = pdata->sram_pool;
1678c2ecf20Sopenharmony_ci		gdev->sram_vaddr =
1688c2ecf20Sopenharmony_ci			(unsigned long)gen_pool_dma_alloc(gdev->sram_pool,
1698c2ecf20Sopenharmony_ci					sram_pool_sz, &gdev->sram_paddr);
1708c2ecf20Sopenharmony_ci		if (!gdev->sram_vaddr) {
1718c2ecf20Sopenharmony_ci			dev_err(dev, "Could not allocate SRAM pool\n");
1728c2ecf20Sopenharmony_ci			ret = -ENOMEM;
1738c2ecf20Sopenharmony_ci			goto err_clk_disable;
1748c2ecf20Sopenharmony_ci		}
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	gdev->ddr_vaddr = dma_alloc_coherent(dev, extram_pool_sz,
1788c2ecf20Sopenharmony_ci				&(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA);
1798c2ecf20Sopenharmony_ci	if (!gdev->ddr_vaddr) {
1808c2ecf20Sopenharmony_ci		dev_err(dev, "Could not allocate external memory\n");
1818c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1828c2ecf20Sopenharmony_ci		goto err_free_sram;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	len = resource_size(regs_prussio);
1868c2ecf20Sopenharmony_ci	gdev->prussio_vaddr = ioremap(regs_prussio->start, len);
1878c2ecf20Sopenharmony_ci	if (!gdev->prussio_vaddr) {
1888c2ecf20Sopenharmony_ci		dev_err(dev, "Can't remap PRUSS I/O  address range\n");
1898c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1908c2ecf20Sopenharmony_ci		goto err_free_ddr_vaddr;
1918c2ecf20Sopenharmony_ci	}
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	gdev->pintc_base = pdata->pintc_base;
1948c2ecf20Sopenharmony_ci	gdev->hostirq_start = platform_get_irq(pdev, 0);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) {
1978c2ecf20Sopenharmony_ci		p->mem[0].addr = regs_prussio->start;
1988c2ecf20Sopenharmony_ci		p->mem[0].size = resource_size(regs_prussio);
1998c2ecf20Sopenharmony_ci		p->mem[0].memtype = UIO_MEM_PHYS;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci		p->mem[1].addr = gdev->sram_paddr;
2028c2ecf20Sopenharmony_ci		p->mem[1].size = sram_pool_sz;
2038c2ecf20Sopenharmony_ci		p->mem[1].memtype = UIO_MEM_PHYS;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci		p->mem[2].addr = gdev->ddr_paddr;
2068c2ecf20Sopenharmony_ci		p->mem[2].size = extram_pool_sz;
2078c2ecf20Sopenharmony_ci		p->mem[2].memtype = UIO_MEM_PHYS;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci		p->name = kasprintf(GFP_KERNEL, "pruss_evt%d", cnt);
2108c2ecf20Sopenharmony_ci		p->version = DRV_VERSION;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci		/* Register PRUSS IRQ lines */
2138c2ecf20Sopenharmony_ci		p->irq = gdev->hostirq_start + cnt;
2148c2ecf20Sopenharmony_ci		p->handler = pruss_handler;
2158c2ecf20Sopenharmony_ci		p->priv = gdev;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		ret = uio_register_device(dev, p);
2188c2ecf20Sopenharmony_ci		if (ret < 0) {
2198c2ecf20Sopenharmony_ci			kfree(p->name);
2208c2ecf20Sopenharmony_ci			goto err_unloop;
2218c2ecf20Sopenharmony_ci		}
2228c2ecf20Sopenharmony_ci	}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, gdev);
2258c2ecf20Sopenharmony_ci	return 0;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cierr_unloop:
2288c2ecf20Sopenharmony_ci	for (i = 0, p = gdev->info; i < cnt; i++, p++) {
2298c2ecf20Sopenharmony_ci		uio_unregister_device(p);
2308c2ecf20Sopenharmony_ci		kfree(p->name);
2318c2ecf20Sopenharmony_ci	}
2328c2ecf20Sopenharmony_ci	iounmap(gdev->prussio_vaddr);
2338c2ecf20Sopenharmony_cierr_free_ddr_vaddr:
2348c2ecf20Sopenharmony_ci	dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr,
2358c2ecf20Sopenharmony_ci			  gdev->ddr_paddr);
2368c2ecf20Sopenharmony_cierr_free_sram:
2378c2ecf20Sopenharmony_ci	if (pdata->sram_pool)
2388c2ecf20Sopenharmony_ci		gen_pool_free(gdev->sram_pool, gdev->sram_vaddr, sram_pool_sz);
2398c2ecf20Sopenharmony_cierr_clk_disable:
2408c2ecf20Sopenharmony_ci	clk_disable(gdev->pruss_clk);
2418c2ecf20Sopenharmony_cierr_clk_put:
2428c2ecf20Sopenharmony_ci	clk_put(gdev->pruss_clk);
2438c2ecf20Sopenharmony_cierr_free_info:
2448c2ecf20Sopenharmony_ci	kfree(gdev->info);
2458c2ecf20Sopenharmony_cierr_free_gdev:
2468c2ecf20Sopenharmony_ci	kfree(gdev);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	return ret;
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_cistatic int pruss_remove(struct platform_device *dev)
2528c2ecf20Sopenharmony_ci{
2538c2ecf20Sopenharmony_ci	struct uio_pruss_dev *gdev = platform_get_drvdata(dev);
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	pruss_cleanup(&dev->dev, gdev);
2568c2ecf20Sopenharmony_ci	return 0;
2578c2ecf20Sopenharmony_ci}
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_cistatic struct platform_driver pruss_driver = {
2608c2ecf20Sopenharmony_ci	.probe = pruss_probe,
2618c2ecf20Sopenharmony_ci	.remove = pruss_remove,
2628c2ecf20Sopenharmony_ci	.driver = {
2638c2ecf20Sopenharmony_ci		   .name = DRV_NAME,
2648c2ecf20Sopenharmony_ci		   },
2658c2ecf20Sopenharmony_ci};
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cimodule_platform_driver(pruss_driver);
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2708c2ecf20Sopenharmony_ciMODULE_VERSION(DRV_VERSION);
2718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>");
2728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>");
273