1 /*
2  * Intel Reference Systems cplds
3  *
4  * Copyright (C) 2014 Robert Jarzmik
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Cplds motherboard driver, supporting lubbock and mainstone SoC board.
12  */
13 
14 #include <linux/bitops.h>
15 #include <linux/gpio.h>
16 #include <linux/gpio/consumer.h>
17 #include <linux/interrupt.h>
18 #include <linux/io.h>
19 #include <linux/irq.h>
20 #include <linux/irqdomain.h>
21 #include <linux/mfd/core.h>
22 #include <linux/module.h>
23 #include <linux/of_platform.h>
24 
25 #define FPGA_IRQ_MASK_EN 0x0
26 #define FPGA_IRQ_SET_CLR 0x10
27 
28 #define CPLDS_NB_IRQ	32
29 
30 struct cplds {
31 	void __iomem *base;
32 	int irq;
33 	unsigned int irq_mask;
34 	struct gpio_desc *gpio0;
35 	struct irq_domain *irqdomain;
36 };
37 
cplds_irq_handler(int in_irq,void * d)38 static irqreturn_t cplds_irq_handler(int in_irq, void *d)
39 {
40 	struct cplds *fpga = d;
41 	unsigned long pending;
42 	unsigned int bit;
43 
44 	do {
45 		pending = readl(fpga->base + FPGA_IRQ_SET_CLR) & fpga->irq_mask;
46 		for_each_set_bit(bit, &pending, CPLDS_NB_IRQ) {
47 			generic_handle_irq(irq_find_mapping(fpga->irqdomain,
48 							    bit));
49 		}
50 	} while (pending);
51 
52 	return IRQ_HANDLED;
53 }
54 
cplds_irq_mask(struct irq_data * d)55 static void cplds_irq_mask(struct irq_data *d)
56 {
57 	struct cplds *fpga = irq_data_get_irq_chip_data(d);
58 	unsigned int cplds_irq = irqd_to_hwirq(d);
59 	unsigned int bit = BIT(cplds_irq);
60 
61 	fpga->irq_mask &= ~bit;
62 	writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
63 }
64 
cplds_irq_unmask(struct irq_data * d)65 static void cplds_irq_unmask(struct irq_data *d)
66 {
67 	struct cplds *fpga = irq_data_get_irq_chip_data(d);
68 	unsigned int cplds_irq = irqd_to_hwirq(d);
69 	unsigned int set, bit = BIT(cplds_irq);
70 
71 	set = readl(fpga->base + FPGA_IRQ_SET_CLR);
72 	writel(set & ~bit, fpga->base + FPGA_IRQ_SET_CLR);
73 
74 	fpga->irq_mask |= bit;
75 	writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
76 }
77 
78 static struct irq_chip cplds_irq_chip = {
79 	.name		= "pxa_cplds",
80 	.irq_ack	= cplds_irq_mask,
81 	.irq_mask	= cplds_irq_mask,
82 	.irq_unmask	= cplds_irq_unmask,
83 	.flags		= IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE,
84 };
85 
cplds_irq_domain_map(struct irq_domain * d,unsigned int irq,irq_hw_number_t hwirq)86 static int cplds_irq_domain_map(struct irq_domain *d, unsigned int irq,
87 				   irq_hw_number_t hwirq)
88 {
89 	struct cplds *fpga = d->host_data;
90 
91 	irq_set_chip_and_handler(irq, &cplds_irq_chip, handle_level_irq);
92 	irq_set_chip_data(irq, fpga);
93 
94 	return 0;
95 }
96 
97 static const struct irq_domain_ops cplds_irq_domain_ops = {
98 	.xlate = irq_domain_xlate_twocell,
99 	.map = cplds_irq_domain_map,
100 };
101 
cplds_resume(struct platform_device * pdev)102 static int cplds_resume(struct platform_device *pdev)
103 {
104 	struct cplds *fpga = platform_get_drvdata(pdev);
105 
106 	writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
107 
108 	return 0;
109 }
110 
cplds_probe(struct platform_device * pdev)111 static int cplds_probe(struct platform_device *pdev)
112 {
113 	struct resource *res;
114 	struct cplds *fpga;
115 	int ret;
116 	int base_irq;
117 	unsigned long irqflags = 0;
118 
119 	fpga = devm_kzalloc(&pdev->dev, sizeof(*fpga), GFP_KERNEL);
120 	if (!fpga)
121 		return -ENOMEM;
122 
123 	fpga->irq = platform_get_irq(pdev, 0);
124 	if (fpga->irq <= 0)
125 		return fpga->irq;
126 
127 	base_irq = platform_get_irq(pdev, 1);
128 	if (base_irq < 0)
129 		base_irq = 0;
130 
131 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
132 	fpga->base = devm_ioremap_resource(&pdev->dev, res);
133 	if (IS_ERR(fpga->base))
134 		return PTR_ERR(fpga->base);
135 
136 	platform_set_drvdata(pdev, fpga);
137 
138 	writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN);
139 	writel(0, fpga->base + FPGA_IRQ_SET_CLR);
140 
141 	irqflags = irq_get_trigger_type(fpga->irq);
142 	ret = devm_request_irq(&pdev->dev, fpga->irq, cplds_irq_handler,
143 			       irqflags, dev_name(&pdev->dev), fpga);
144 	if (ret == -ENOSYS)
145 		return -EPROBE_DEFER;
146 
147 	if (ret) {
148 		dev_err(&pdev->dev, "couldn't request main irq%d: %d\n",
149 			fpga->irq, ret);
150 		return ret;
151 	}
152 
153 	irq_set_irq_wake(fpga->irq, 1);
154 	fpga->irqdomain = irq_domain_add_linear(pdev->dev.of_node,
155 					       CPLDS_NB_IRQ,
156 					       &cplds_irq_domain_ops, fpga);
157 	if (!fpga->irqdomain)
158 		return -ENODEV;
159 
160 	if (base_irq) {
161 		ret = irq_create_strict_mappings(fpga->irqdomain, base_irq, 0,
162 						 CPLDS_NB_IRQ);
163 		if (ret) {
164 			dev_err(&pdev->dev, "couldn't create the irq mapping %d..%d\n",
165 				base_irq, base_irq + CPLDS_NB_IRQ);
166 			return ret;
167 		}
168 	}
169 
170 	return 0;
171 }
172 
cplds_remove(struct platform_device * pdev)173 static int cplds_remove(struct platform_device *pdev)
174 {
175 	struct cplds *fpga = platform_get_drvdata(pdev);
176 
177 	irq_set_chip_and_handler(fpga->irq, NULL, NULL);
178 
179 	return 0;
180 }
181 
182 static const struct of_device_id cplds_id_table[] = {
183 	{ .compatible = "intel,lubbock-cplds-irqs", },
184 	{ .compatible = "intel,mainstone-cplds-irqs", },
185 	{ }
186 };
187 MODULE_DEVICE_TABLE(of, cplds_id_table);
188 
189 static struct platform_driver cplds_driver = {
190 	.driver		= {
191 		.name	= "pxa_cplds_irqs",
192 		.of_match_table = of_match_ptr(cplds_id_table),
193 	},
194 	.probe		= cplds_probe,
195 	.remove		= cplds_remove,
196 	.resume		= cplds_resume,
197 };
198 
199 module_platform_driver(cplds_driver);
200 
201 MODULE_DESCRIPTION("PXA Cplds interrupts driver");
202 MODULE_AUTHOR("Robert Jarzmik <robert.jarzmik@free.fr>");
203 MODULE_LICENSE("GPL");
204