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 30struct 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 38static 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 pending = readl(fpga->base + FPGA_IRQ_SET_CLR) & fpga->irq_mask; 45 for_each_set_bit(bit, &pending, CPLDS_NB_IRQ) 46 generic_handle_irq(irq_find_mapping(fpga->irqdomain, bit)); 47 48 return IRQ_HANDLED; 49} 50 51static void cplds_irq_mask_ack(struct irq_data *d) 52{ 53 struct cplds *fpga = irq_data_get_irq_chip_data(d); 54 unsigned int cplds_irq = irqd_to_hwirq(d); 55 unsigned int set, bit = BIT(cplds_irq); 56 57 fpga->irq_mask &= ~bit; 58 writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN); 59 set = readl(fpga->base + FPGA_IRQ_SET_CLR); 60 writel(set & ~bit, fpga->base + FPGA_IRQ_SET_CLR); 61} 62 63static void cplds_irq_unmask(struct irq_data *d) 64{ 65 struct cplds *fpga = irq_data_get_irq_chip_data(d); 66 unsigned int cplds_irq = irqd_to_hwirq(d); 67 unsigned int bit = BIT(cplds_irq); 68 69 fpga->irq_mask |= bit; 70 writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN); 71} 72 73static struct irq_chip cplds_irq_chip = { 74 .name = "pxa_cplds", 75 .irq_mask_ack = cplds_irq_mask_ack, 76 .irq_unmask = cplds_irq_unmask, 77 .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE, 78}; 79 80static int cplds_irq_domain_map(struct irq_domain *d, unsigned int irq, 81 irq_hw_number_t hwirq) 82{ 83 struct cplds *fpga = d->host_data; 84 85 irq_set_chip_and_handler(irq, &cplds_irq_chip, handle_level_irq); 86 irq_set_chip_data(irq, fpga); 87 88 return 0; 89} 90 91static const struct irq_domain_ops cplds_irq_domain_ops = { 92 .xlate = irq_domain_xlate_twocell, 93 .map = cplds_irq_domain_map, 94}; 95 96static int cplds_resume(struct platform_device *pdev) 97{ 98 struct cplds *fpga = platform_get_drvdata(pdev); 99 100 writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN); 101 102 return 0; 103} 104 105static int cplds_probe(struct platform_device *pdev) 106{ 107 struct resource *res; 108 struct cplds *fpga; 109 int ret; 110 int base_irq; 111 unsigned long irqflags = 0; 112 113 fpga = devm_kzalloc(&pdev->dev, sizeof(*fpga), GFP_KERNEL); 114 if (!fpga) 115 return -ENOMEM; 116 117 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 118 if (res) { 119 fpga->irq = (unsigned int)res->start; 120 irqflags = res->flags; 121 } 122 if (!fpga->irq) 123 return -ENODEV; 124 125 base_irq = platform_get_irq(pdev, 1); 126 if (base_irq < 0) 127 base_irq = 0; 128 129 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 130 fpga->base = devm_ioremap_resource(&pdev->dev, res); 131 if (IS_ERR(fpga->base)) 132 return PTR_ERR(fpga->base); 133 134 platform_set_drvdata(pdev, fpga); 135 136 writel(fpga->irq_mask, fpga->base + FPGA_IRQ_MASK_EN); 137 writel(0, fpga->base + FPGA_IRQ_SET_CLR); 138 139 ret = devm_request_irq(&pdev->dev, fpga->irq, cplds_irq_handler, 140 irqflags, dev_name(&pdev->dev), fpga); 141 if (ret == -ENOSYS) 142 return -EPROBE_DEFER; 143 144 if (ret) { 145 dev_err(&pdev->dev, "couldn't request main irq%d: %d\n", 146 fpga->irq, ret); 147 return ret; 148 } 149 150 irq_set_irq_wake(fpga->irq, 1); 151 fpga->irqdomain = irq_domain_add_linear(pdev->dev.of_node, 152 CPLDS_NB_IRQ, 153 &cplds_irq_domain_ops, fpga); 154 if (!fpga->irqdomain) 155 return -ENODEV; 156 157 if (base_irq) { 158 ret = irq_create_strict_mappings(fpga->irqdomain, base_irq, 0, 159 CPLDS_NB_IRQ); 160 if (ret) { 161 dev_err(&pdev->dev, "couldn't create the irq mapping %d..%d\n", 162 base_irq, base_irq + CPLDS_NB_IRQ); 163 return ret; 164 } 165 } 166 167 return 0; 168} 169 170static int cplds_remove(struct platform_device *pdev) 171{ 172 struct cplds *fpga = platform_get_drvdata(pdev); 173 174 irq_set_chip_and_handler(fpga->irq, NULL, NULL); 175 176 return 0; 177} 178 179static const struct of_device_id cplds_id_table[] = { 180 { .compatible = "intel,lubbock-cplds-irqs", }, 181 { .compatible = "intel,mainstone-cplds-irqs", }, 182 { } 183}; 184MODULE_DEVICE_TABLE(of, cplds_id_table); 185 186static struct platform_driver cplds_driver = { 187 .driver = { 188 .name = "pxa_cplds_irqs", 189 .of_match_table = of_match_ptr(cplds_id_table), 190 }, 191 .probe = cplds_probe, 192 .remove = cplds_remove, 193 .resume = cplds_resume, 194}; 195 196module_platform_driver(cplds_driver); 197 198MODULE_DESCRIPTION("PXA Cplds interrupts driver"); 199MODULE_AUTHOR("Robert Jarzmik <robert.jarzmik@free.fr>"); 200MODULE_LICENSE("GPL"); 201