Skip to content

Commit f22d5d1

Browse files
cnphoondinguyen702
authored andcommitted
FogBugz #178225: Add Altera interrupt latency counter driver
Adding Altera interrupt latency counter driver support. This driver works together with the Altera interrupt latency driver soft IP to measure the time from the interrupt being asserted to the execution of the interrupt service routine. This driver and soft ip supports for both edge and level interrupt. V2: - Update fifo-depth property name in device tree - Update binding document to add sysfs path and use case - Update includes header in alphabetical order - Remove global variables - Rename kfifo stucts naming to avoid confusion - Validate offset return value - Changing read and write register functions - Update on misc coding styles and format V3: - Update coding styles - Update irq_request function - Changing print out to dev_dbg() in ISR - Fix ISR return values V4: - Remove stray line Signed-off-by: Phoon Chee Nouk <[email protected]>
1 parent 381d92a commit f22d5d1

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Altera Interrupt Latency Counter soft IP
2+
Altera Interrupt Latency Counter IP core driver provides a sysfs interface
3+
for user to obtain interrupt latency values from Altera Interrupt Latency
4+
Counter soft IP.
5+
6+
The sysfs interface is located at path,
7+
/sys/bus/platform/devices/{addr}.ilc/ilc_data/{int_#}
8+
with
9+
- {addr} = the base address of the soft ip
10+
- {int_#} = the interrupt number
11+
12+
Example use case:
13+
# cat /sys/bus/platform/devices/c0010000.ilc/ilc_data/40
14+
15+
Required properties:
16+
- compatible :
17+
- "altr,ilc-1.0"
18+
- reg :
19+
- physical base address of the soft ip and length of memory mapped region
20+
- interrupt-parent :
21+
- interrupt source phandle similiar to the interrupt source node
22+
- interrupts :
23+
-interrupt number. The interrupt specifier format depends on the interrupt
24+
controller parent
25+
26+
Altera specific properties:
27+
- altr,sw-fifo-depth :
28+
- define software fifo depth needed to record latency values
29+
30+
Note:
31+
- For edge triggered interrupt, the order of loading the ILC driver relative
32+
to driver of the actual interrupt source affects the meaning of the ILC
33+
values. If the ILC driver is loaded first, then the count values represent
34+
the time to the start of the interrupt handler of the of the interrupt source.
35+
If the order is switched, then the counts represent the time to finish the
36+
interrupt handler for the interrupt source.
37+
38+
- The driver for the interrupt source must be changed to request a shared irq.
39+
40+
Example:
41+
interrupt_latency_counter_0: intc@0x10000000 {
42+
compatible = "altr,ilc-1.0";
43+
reg = <0x10000000 0x00000100>;
44+
interrupt-parent = < &interrupt_parent >;
45+
interrupts = < 0 1 4 >;
46+
altr,sw-fifo-depth = < 32 >;
47+
};
48+
49+

drivers/misc/altera_ilc.c

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/*
2+
* Copyright (C) 2014 Altera Corporation. All rights reserved
3+
*
4+
* This program is free software; you can redistribute it and/or modify it
5+
* under the terms and conditions of the GNU General Public License,
6+
* version 2, as published by the Free Software Foundation.
7+
*
8+
* This program is distributed in the hope it will be useful, but WITHOUT
9+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11+
* more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
#include <linux/device.h>
18+
#include <linux/interrupt.h>
19+
#include <linux/io.h>
20+
#include <linux/kernel.h>
21+
#include <linux/kfifo.h>
22+
#include <linux/module.h>
23+
#include <linux/of.h>
24+
#include <linux/platform_device.h>
25+
#include <linux/sysfs.h>
26+
27+
#define DRV_NAME "altera_ilc"
28+
#define CTRL_REG 0x80
29+
#define FREQ_REG 0x84
30+
#define STP_REG 0x88
31+
#define VLD_REG 0x8C
32+
#define ILC_MAX_PORTS 32
33+
#define ILC_FIFO_DEFAULT 32
34+
#define ILC_ENABLE 0x01
35+
#define CHAR_SIZE 10
36+
#define POLL_INTERVAL 1
37+
#define GET_PORT_COUNT(_val) ((_val & 0x7C) >> 2)
38+
#define GET_VLD_BIT(_val, _offset) (((_val) >> _offset) & 0x1)
39+
40+
struct altera_ilc {
41+
struct platform_device *pdev;
42+
void __iomem *regs;
43+
unsigned int port_count;
44+
unsigned int irq;
45+
unsigned int channel_offset;
46+
unsigned int interrupt_channels[ILC_MAX_PORTS];
47+
struct kfifo kfifos[ILC_MAX_PORTS];
48+
struct device_attribute dev_attr[ILC_MAX_PORTS];
49+
struct delayed_work ilc_work;
50+
char sysfs[ILC_MAX_PORTS][CHAR_SIZE];
51+
u32 fifo_depth;
52+
};
53+
54+
static int ilc_irq_lookup(struct altera_ilc *ilc, int irq)
55+
{
56+
int i;
57+
for (i = 0; i < ilc->port_count; i++) {
58+
if (irq == platform_get_irq(ilc->pdev, i))
59+
return i;
60+
}
61+
return -EPERM;
62+
}
63+
64+
static ssize_t ilc_show_counter(struct device *dev,
65+
struct device_attribute *attr, char *buf)
66+
{
67+
int ret, i, id, fifo_len;
68+
unsigned int fifo_buf[ILC_MAX_PORTS];
69+
char temp[10];
70+
struct altera_ilc *ilc = dev_get_drvdata(dev);
71+
72+
fifo_len = 0;
73+
ret = kstrtouint(attr->attr.name, 0, &id);
74+
75+
for (i = 0; i < ilc->port_count; i++) {
76+
if (id == (ilc->interrupt_channels[i])) {
77+
/*Check for kfifo length*/
78+
fifo_len = kfifo_len(&ilc->kfifos[i])
79+
/sizeof(unsigned int);
80+
if (fifo_len <= 0) {
81+
dev_info(&ilc->pdev->dev, "Fifo for interrupt %s is empty\n",
82+
attr->attr.name);
83+
return 0;
84+
}
85+
/*Read from kfifo*/
86+
ret = kfifo_out(&ilc->kfifos[i], &fifo_buf,
87+
kfifo_len(&ilc->kfifos[i]));
88+
}
89+
}
90+
91+
for (i = 0; i < fifo_len; i++) {
92+
sprintf(temp, "%u\n", fifo_buf[i]);
93+
strcat(buf, temp);
94+
}
95+
96+
strcat(buf, "\0");
97+
98+
return strlen(buf);
99+
}
100+
101+
static struct attribute *altera_ilc_attrs[ILC_MAX_PORTS];
102+
103+
struct attribute_group altera_ilc_attr_group = {
104+
.name = "ilc_data",
105+
.attrs = altera_ilc_attrs,
106+
};
107+
108+
static void ilc_work(struct work_struct *work)
109+
{
110+
unsigned int ilc_value, ret, offset, stp_reg;
111+
struct altera_ilc *ilc =
112+
container_of(work, struct altera_ilc, ilc_work.work);
113+
114+
offset = ilc_irq_lookup(ilc, ilc->irq);
115+
if (offset < 0) {
116+
dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n");
117+
return;
118+
}
119+
120+
if (GET_VLD_BIT(readl(ilc->regs + VLD_REG), offset)) {
121+
/*Read counter register*/
122+
ilc_value = readl(ilc->regs + (offset) * 4);
123+
124+
/*Putting value into kfifo*/
125+
ret = kfifo_in((&ilc->kfifos[offset]),
126+
(unsigned int *)&ilc_value, sizeof(ilc_value));
127+
128+
/*Clearing stop register*/
129+
stp_reg = readl(ilc->regs + STP_REG);
130+
writel((!(0x1 << offset))&stp_reg, ilc->regs + STP_REG);
131+
132+
return;
133+
}
134+
135+
/*Start workqueue to poll data valid*/
136+
schedule_delayed_work(&ilc->ilc_work, msecs_to_jiffies(POLL_INTERVAL));
137+
}
138+
139+
static irqreturn_t ilc_interrupt_handler(int irq, void *p)
140+
{
141+
unsigned int offset, stp_reg;
142+
143+
struct altera_ilc *ilc = (struct altera_ilc *)p;
144+
145+
/*Update ILC struct*/
146+
ilc->irq = irq;
147+
148+
dev_dbg(&ilc->pdev->dev, "Interrupt %u triggered\n",
149+
ilc->irq);
150+
151+
offset = ilc_irq_lookup(ilc, irq);
152+
if (offset < 0) {
153+
dev_err(&ilc->pdev->dev, "Unable to lookup irq number\n");
154+
return IRQ_RETVAL(IRQ_NONE);
155+
}
156+
157+
/*Setting stop register*/
158+
stp_reg = readl(ilc->regs + STP_REG);
159+
writel((0x1 << offset)|stp_reg, ilc->regs + STP_REG);
160+
161+
/*Start workqueue to poll data valid*/
162+
schedule_delayed_work(&ilc->ilc_work, 0);
163+
164+
return IRQ_RETVAL(IRQ_NONE);
165+
}
166+
167+
static int altera_ilc_probe(struct platform_device *pdev)
168+
{
169+
struct altera_ilc *ilc;
170+
struct resource *regs;
171+
struct device_node *np = pdev->dev.of_node;
172+
int ret, i;
173+
174+
ilc = devm_kzalloc(&pdev->dev, sizeof(struct altera_ilc),
175+
GFP_KERNEL);
176+
if (!ilc)
177+
return -ENOMEM;
178+
179+
ilc->pdev = pdev;
180+
181+
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
182+
if (!regs)
183+
return -ENXIO;
184+
185+
ilc->regs = devm_ioremap_resource(&pdev->dev, regs);
186+
if (!ilc->regs)
187+
return -EADDRNOTAVAIL;
188+
189+
ilc->port_count = GET_PORT_COUNT(readl(ilc->regs + CTRL_REG));
190+
if (ilc->port_count <= 0) {
191+
dev_warn(&pdev->dev, "No interrupt connected to ILC\n");
192+
return -EPERM;
193+
}
194+
195+
/*Check for fifo depth*/
196+
ret = of_property_read_u32(np, "altr,sw-fifo-depth",
197+
&(ilc->fifo_depth));
198+
if (ret) {
199+
dev_warn(&pdev->dev, "Fifo depth undefined\n");
200+
dev_warn(&pdev->dev, "Setting fifo depth to default value (32)\n");
201+
ilc->fifo_depth = ILC_FIFO_DEFAULT;
202+
}
203+
204+
/*Initialize Kfifo*/
205+
for (i = 0; i < ilc->port_count; i++) {
206+
ret = kfifo_alloc(&ilc->kfifos[i], (ilc->fifo_depth *
207+
sizeof(unsigned int)), GFP_KERNEL);
208+
if (ret) {
209+
dev_err(&pdev->dev, "Kfifo failed to initialize\n");
210+
return ret;
211+
}
212+
}
213+
214+
/*Register each of the IRQs*/
215+
for (i = 0; i < ilc->port_count; i++) {
216+
ilc->interrupt_channels[i] = platform_get_irq(pdev, i);
217+
218+
ret = devm_request_irq(&pdev->dev, (ilc->interrupt_channels[i]),
219+
ilc_interrupt_handler, IRQF_SHARED, "ilc_0",
220+
(void *)(ilc));
221+
222+
if (ret < 0)
223+
dev_warn(&pdev->dev, "Failed to register interrupt handler");
224+
}
225+
226+
/*Setup sysfs interface*/
227+
for (i = 0; (i < ilc->port_count); i++) {
228+
sprintf(ilc->sysfs[i], "%d", (ilc->interrupt_channels[i]));
229+
ilc->dev_attr[i].attr.name = ilc->sysfs[i];
230+
ilc->dev_attr[i].attr.mode = S_IRUGO;
231+
ilc->dev_attr[i].show = ilc_show_counter;
232+
altera_ilc_attrs[i] = &ilc->dev_attr[i].attr;
233+
altera_ilc_attrs[i+1] = NULL;
234+
}
235+
ret = sysfs_create_group(&pdev->dev.kobj, &altera_ilc_attr_group);
236+
237+
/*Initialize workqueue*/
238+
INIT_DELAYED_WORK(&ilc->ilc_work, ilc_work);
239+
240+
/*Global enable ILC softIP*/
241+
writel(ILC_ENABLE, ilc->regs + CTRL_REG);
242+
243+
platform_set_drvdata(pdev, ilc);
244+
245+
dev_info(&pdev->dev, "Driver successfully loaded\n");
246+
247+
return 0;
248+
}
249+
250+
static int altera_ilc_remove(struct platform_device *pdev)
251+
{
252+
int i;
253+
struct altera_ilc *ilc = platform_get_drvdata(pdev);
254+
255+
/*Remove sysfs interface*/
256+
sysfs_remove_group(&pdev->dev.kobj, &altera_ilc_attr_group);
257+
258+
/*Free up kfifo memory*/
259+
for (i = 0; i < ilc->port_count; i++)
260+
kfifo_free(&ilc->kfifos[i]);
261+
262+
platform_set_drvdata(pdev, NULL);
263+
return 0;
264+
}
265+
266+
static const struct of_device_id altera_ilc_match[] = {
267+
{ .compatible = "altr,ilc-1.0" },
268+
{ /* Sentinel */ }
269+
};
270+
271+
MODULE_DEVICE_TABLE(of, altera_ilc_match);
272+
273+
static struct platform_driver altera_ilc_platform_driver = {
274+
.driver = {
275+
.name = DRV_NAME,
276+
.owner = THIS_MODULE,
277+
.of_match_table = of_match_ptr(altera_ilc_match),
278+
},
279+
.remove = altera_ilc_remove,
280+
};
281+
282+
static int __init altera_ilc_init(void)
283+
{
284+
return platform_driver_probe(&altera_ilc_platform_driver,
285+
altera_ilc_probe);
286+
}
287+
288+
static void __exit altera_ilc_exit(void)
289+
{
290+
platform_driver_unregister(&altera_ilc_platform_driver);
291+
}
292+
293+
module_init(altera_ilc_init);
294+
module_exit(altera_ilc_exit);
295+
296+
MODULE_AUTHOR("Chee Nouk Phoon <[email protected]>");
297+
MODULE_LICENSE("GPL v2");
298+
MODULE_DESCRIPTION("Altera Interrupt Latency Counter Driver");
299+
MODULE_ALIAS("platform:" DRV_NAME);

0 commit comments

Comments
 (0)