1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss) |
4 | * |
5 | * This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM, |
6 | * and DDR RAM to user space for applications interacting with PRUSS firmware |
7 | * |
8 | * Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/ |
9 | */ |
10 | #include <linux/device.h> |
11 | #include <linux/module.h> |
12 | #include <linux/moduleparam.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/uio_driver.h> |
15 | #include <linux/platform_data/uio_pruss.h> |
16 | #include <linux/io.h> |
17 | #include <linux/clk.h> |
18 | #include <linux/dma-mapping.h> |
19 | #include <linux/sizes.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/genalloc.h> |
22 | |
23 | #define DRV_NAME "pruss_uio" |
24 | #define DRV_VERSION "1.0" |
25 | |
26 | static int sram_pool_sz = SZ_16K; |
27 | module_param(sram_pool_sz, int, 0); |
28 | MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate " ); |
29 | |
30 | static int = SZ_256K; |
31 | module_param(extram_pool_sz, int, 0); |
32 | MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate" ); |
33 | |
34 | /* |
35 | * Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt |
36 | * events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS |
37 | * firmware and user space application, async notification from PRU firmware |
38 | * to user space application |
39 | * 3 PRU_EVTOUT0 |
40 | * 4 PRU_EVTOUT1 |
41 | * 5 PRU_EVTOUT2 |
42 | * 6 PRU_EVTOUT3 |
43 | * 7 PRU_EVTOUT4 |
44 | * 8 PRU_EVTOUT5 |
45 | * 9 PRU_EVTOUT6 |
46 | * 10 PRU_EVTOUT7 |
47 | */ |
48 | #define MAX_PRUSS_EVT 8 |
49 | |
50 | #define PINTC_HIDISR 0x0038 |
51 | #define PINTC_HIPIR 0x0900 |
52 | #define HIPIR_NOPEND 0x80000000 |
53 | #define PINTC_HIER 0x1500 |
54 | |
55 | struct uio_pruss_dev { |
56 | struct uio_info *info; |
57 | struct clk *pruss_clk; |
58 | dma_addr_t sram_paddr; |
59 | dma_addr_t ddr_paddr; |
60 | void __iomem *prussio_vaddr; |
61 | unsigned long sram_vaddr; |
62 | void *ddr_vaddr; |
63 | unsigned int hostirq_start; |
64 | unsigned int pintc_base; |
65 | struct gen_pool *sram_pool; |
66 | }; |
67 | |
68 | static irqreturn_t pruss_handler(int irq, struct uio_info *info) |
69 | { |
70 | struct uio_pruss_dev *gdev = info->priv; |
71 | int intr_bit = (irq - gdev->hostirq_start + 2); |
72 | int val, intr_mask = (1 << intr_bit); |
73 | void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base; |
74 | void __iomem *intren_reg = base + PINTC_HIER; |
75 | void __iomem *intrdis_reg = base + PINTC_HIDISR; |
76 | void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2); |
77 | |
78 | val = ioread32(intren_reg); |
79 | /* Is interrupt enabled and active ? */ |
80 | if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND)) |
81 | return IRQ_NONE; |
82 | /* Disable interrupt */ |
83 | iowrite32(intr_bit, intrdis_reg); |
84 | return IRQ_HANDLED; |
85 | } |
86 | |
87 | static void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev) |
88 | { |
89 | int cnt; |
90 | struct uio_info *p = gdev->info; |
91 | |
92 | for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) { |
93 | uio_unregister_device(info: p); |
94 | } |
95 | iounmap(addr: gdev->prussio_vaddr); |
96 | if (gdev->ddr_vaddr) { |
97 | dma_free_coherent(dev, size: extram_pool_sz, cpu_addr: gdev->ddr_vaddr, |
98 | dma_handle: gdev->ddr_paddr); |
99 | } |
100 | if (gdev->sram_vaddr) |
101 | gen_pool_free(pool: gdev->sram_pool, |
102 | addr: gdev->sram_vaddr, |
103 | size: sram_pool_sz); |
104 | clk_disable(clk: gdev->pruss_clk); |
105 | } |
106 | |
107 | static int pruss_probe(struct platform_device *pdev) |
108 | { |
109 | struct uio_info *p; |
110 | struct uio_pruss_dev *gdev; |
111 | struct resource *regs_prussio; |
112 | struct device *dev = &pdev->dev; |
113 | int ret, cnt, i, len; |
114 | struct uio_pruss_pdata *pdata = dev_get_platdata(dev); |
115 | |
116 | gdev = devm_kzalloc(dev, size: sizeof(struct uio_pruss_dev), GFP_KERNEL); |
117 | if (!gdev) |
118 | return -ENOMEM; |
119 | |
120 | gdev->info = devm_kcalloc(dev, MAX_PRUSS_EVT, size: sizeof(*p), GFP_KERNEL); |
121 | if (!gdev->info) |
122 | return -ENOMEM; |
123 | |
124 | /* Power on PRU in case its not done as part of boot-loader */ |
125 | gdev->pruss_clk = devm_clk_get(dev, id: "pruss" ); |
126 | if (IS_ERR(ptr: gdev->pruss_clk)) { |
127 | dev_err(dev, "Failed to get clock\n" ); |
128 | return PTR_ERR(ptr: gdev->pruss_clk); |
129 | } |
130 | |
131 | ret = clk_enable(clk: gdev->pruss_clk); |
132 | if (ret) { |
133 | dev_err(dev, "Failed to enable clock\n" ); |
134 | return ret; |
135 | } |
136 | |
137 | regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
138 | if (!regs_prussio) { |
139 | dev_err(dev, "No PRUSS I/O resource specified\n" ); |
140 | ret = -EIO; |
141 | goto err_clk_disable; |
142 | } |
143 | |
144 | if (!regs_prussio->start) { |
145 | dev_err(dev, "Invalid memory resource\n" ); |
146 | ret = -EIO; |
147 | goto err_clk_disable; |
148 | } |
149 | |
150 | if (pdata->sram_pool) { |
151 | gdev->sram_pool = pdata->sram_pool; |
152 | gdev->sram_vaddr = |
153 | (unsigned long)gen_pool_dma_alloc(pool: gdev->sram_pool, |
154 | size: sram_pool_sz, dma: &gdev->sram_paddr); |
155 | if (!gdev->sram_vaddr) { |
156 | dev_err(dev, "Could not allocate SRAM pool\n" ); |
157 | ret = -ENOMEM; |
158 | goto err_clk_disable; |
159 | } |
160 | } |
161 | |
162 | gdev->ddr_vaddr = dma_alloc_coherent(dev, size: extram_pool_sz, |
163 | dma_handle: &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); |
164 | if (!gdev->ddr_vaddr) { |
165 | dev_err(dev, "Could not allocate external memory\n" ); |
166 | ret = -ENOMEM; |
167 | goto err_free_sram; |
168 | } |
169 | |
170 | len = resource_size(res: regs_prussio); |
171 | gdev->prussio_vaddr = ioremap(offset: regs_prussio->start, size: len); |
172 | if (!gdev->prussio_vaddr) { |
173 | dev_err(dev, "Can't remap PRUSS I/O address range\n" ); |
174 | ret = -ENOMEM; |
175 | goto err_free_ddr_vaddr; |
176 | } |
177 | |
178 | ret = platform_get_irq(pdev, 0); |
179 | if (ret < 0) |
180 | goto err_unmap; |
181 | |
182 | gdev->hostirq_start = ret; |
183 | gdev->pintc_base = pdata->pintc_base; |
184 | |
185 | for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) { |
186 | p->mem[0].addr = regs_prussio->start; |
187 | p->mem[0].size = resource_size(res: regs_prussio); |
188 | p->mem[0].memtype = UIO_MEM_PHYS; |
189 | |
190 | p->mem[1].addr = gdev->sram_paddr; |
191 | p->mem[1].size = sram_pool_sz; |
192 | p->mem[1].memtype = UIO_MEM_PHYS; |
193 | |
194 | p->mem[2].addr = gdev->ddr_paddr; |
195 | p->mem[2].size = extram_pool_sz; |
196 | p->mem[2].memtype = UIO_MEM_PHYS; |
197 | |
198 | p->name = devm_kasprintf(dev, GFP_KERNEL, fmt: "pruss_evt%d" , cnt); |
199 | p->version = DRV_VERSION; |
200 | |
201 | /* Register PRUSS IRQ lines */ |
202 | p->irq = gdev->hostirq_start + cnt; |
203 | p->handler = pruss_handler; |
204 | p->priv = gdev; |
205 | |
206 | ret = uio_register_device(dev, p); |
207 | if (ret < 0) |
208 | goto err_unloop; |
209 | } |
210 | |
211 | platform_set_drvdata(pdev, data: gdev); |
212 | return 0; |
213 | |
214 | err_unloop: |
215 | for (i = 0, p = gdev->info; i < cnt; i++, p++) { |
216 | uio_unregister_device(info: p); |
217 | } |
218 | err_unmap: |
219 | iounmap(addr: gdev->prussio_vaddr); |
220 | err_free_ddr_vaddr: |
221 | dma_free_coherent(dev, size: extram_pool_sz, cpu_addr: gdev->ddr_vaddr, |
222 | dma_handle: gdev->ddr_paddr); |
223 | err_free_sram: |
224 | if (pdata->sram_pool) |
225 | gen_pool_free(pool: gdev->sram_pool, addr: gdev->sram_vaddr, size: sram_pool_sz); |
226 | err_clk_disable: |
227 | clk_disable(clk: gdev->pruss_clk); |
228 | |
229 | return ret; |
230 | } |
231 | |
232 | static int pruss_remove(struct platform_device *dev) |
233 | { |
234 | struct uio_pruss_dev *gdev = platform_get_drvdata(pdev: dev); |
235 | |
236 | pruss_cleanup(dev: &dev->dev, gdev); |
237 | return 0; |
238 | } |
239 | |
240 | static struct platform_driver pruss_driver = { |
241 | .probe = pruss_probe, |
242 | .remove = pruss_remove, |
243 | .driver = { |
244 | .name = DRV_NAME, |
245 | }, |
246 | }; |
247 | |
248 | module_platform_driver(pruss_driver); |
249 | |
250 | MODULE_LICENSE("GPL v2" ); |
251 | MODULE_VERSION(DRV_VERSION); |
252 | MODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>" ); |
253 | MODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>" ); |
254 | |