1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Aspeed 24XX/25XX I2C Interrupt Controller. |
4 | * |
5 | * Copyright (C) 2012-2017 ASPEED Technology Inc. |
6 | * Copyright 2017 IBM Corporation |
7 | * Copyright 2017 Google, Inc. |
8 | */ |
9 | |
10 | #include <linux/irq.h> |
11 | #include <linux/irqchip.h> |
12 | #include <linux/irqchip/chained_irq.h> |
13 | #include <linux/irqdomain.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/of_irq.h> |
16 | #include <linux/io.h> |
17 | |
18 | |
19 | #define ASPEED_I2C_IC_NUM_BUS 14 |
20 | |
21 | struct aspeed_i2c_ic { |
22 | void __iomem *base; |
23 | int parent_irq; |
24 | struct irq_domain *irq_domain; |
25 | }; |
26 | |
27 | /* |
28 | * The aspeed chip provides a single hardware interrupt for all of the I2C |
29 | * busses, so we use a dummy interrupt chip to translate this single interrupt |
30 | * into multiple interrupts, each associated with a single I2C bus. |
31 | */ |
32 | static void aspeed_i2c_ic_irq_handler(struct irq_desc *desc) |
33 | { |
34 | struct aspeed_i2c_ic *i2c_ic = irq_desc_get_handler_data(desc); |
35 | struct irq_chip *chip = irq_desc_get_chip(desc); |
36 | unsigned long bit, status; |
37 | |
38 | chained_irq_enter(chip, desc); |
39 | status = readl(addr: i2c_ic->base); |
40 | for_each_set_bit(bit, &status, ASPEED_I2C_IC_NUM_BUS) |
41 | generic_handle_domain_irq(domain: i2c_ic->irq_domain, hwirq: bit); |
42 | |
43 | chained_irq_exit(chip, desc); |
44 | } |
45 | |
46 | /* |
47 | * Set simple handler and mark IRQ as valid. Nothing interesting to do here |
48 | * since we are using a dummy interrupt chip. |
49 | */ |
50 | static int aspeed_i2c_ic_map_irq_domain(struct irq_domain *domain, |
51 | unsigned int irq, irq_hw_number_t hwirq) |
52 | { |
53 | irq_set_chip_and_handler(irq, chip: &dummy_irq_chip, handle: handle_simple_irq); |
54 | irq_set_chip_data(irq, data: domain->host_data); |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static const struct irq_domain_ops aspeed_i2c_ic_irq_domain_ops = { |
60 | .map = aspeed_i2c_ic_map_irq_domain, |
61 | }; |
62 | |
63 | static int __init aspeed_i2c_ic_of_init(struct device_node *node, |
64 | struct device_node *parent) |
65 | { |
66 | struct aspeed_i2c_ic *i2c_ic; |
67 | int ret = 0; |
68 | |
69 | i2c_ic = kzalloc(size: sizeof(*i2c_ic), GFP_KERNEL); |
70 | if (!i2c_ic) |
71 | return -ENOMEM; |
72 | |
73 | i2c_ic->base = of_iomap(node, index: 0); |
74 | if (!i2c_ic->base) { |
75 | ret = -ENOMEM; |
76 | goto err_free_ic; |
77 | } |
78 | |
79 | i2c_ic->parent_irq = irq_of_parse_and_map(node, index: 0); |
80 | if (!i2c_ic->parent_irq) { |
81 | ret = -EINVAL; |
82 | goto err_iounmap; |
83 | } |
84 | |
85 | i2c_ic->irq_domain = irq_domain_add_linear(of_node: node, ASPEED_I2C_IC_NUM_BUS, |
86 | ops: &aspeed_i2c_ic_irq_domain_ops, |
87 | NULL); |
88 | if (!i2c_ic->irq_domain) { |
89 | ret = -ENOMEM; |
90 | goto err_iounmap; |
91 | } |
92 | |
93 | i2c_ic->irq_domain->name = "aspeed-i2c-domain" ; |
94 | |
95 | irq_set_chained_handler_and_data(irq: i2c_ic->parent_irq, |
96 | handle: aspeed_i2c_ic_irq_handler, data: i2c_ic); |
97 | |
98 | pr_info("i2c controller registered, irq %d\n" , i2c_ic->parent_irq); |
99 | |
100 | return 0; |
101 | |
102 | err_iounmap: |
103 | iounmap(addr: i2c_ic->base); |
104 | err_free_ic: |
105 | kfree(objp: i2c_ic); |
106 | return ret; |
107 | } |
108 | |
109 | IRQCHIP_DECLARE(ast2400_i2c_ic, "aspeed,ast2400-i2c-ic" , aspeed_i2c_ic_of_init); |
110 | IRQCHIP_DECLARE(ast2500_i2c_ic, "aspeed,ast2500-i2c-ic" , aspeed_i2c_ic_of_init); |
111 | |