1 // SPDX-License-Identifier: GPL-2.0-only
3 * TI LP8788 MFD - interrupt handler
5 * Copyright 2012 Texas Instruments
7 * Author: Milo(Woogyom) Kim <milo.kim@ti.com>
10 #include <linux/delay.h>
11 #include <linux/err.h>
12 #include <linux/interrupt.h>
13 #include <linux/irq.h>
14 #include <linux/irqdomain.h>
15 #include <linux/device.h>
16 #include <linux/mfd/lp8788.h>
17 #include <linux/module.h>
18 #include <linux/slab.h>
20 /* register address */
21 #define LP8788_INT_1 0x00
22 #define LP8788_INTEN_1 0x03
24 #define BASE_INTEN_ADDR LP8788_INTEN_1
29 * struct lp8788_irq_data
30 * @lp : used for accessing to lp8788 registers
31 * @irq_lock : mutex for enabling/disabling the interrupt
32 * @domain : IRQ domain for handling nested interrupt
33 * @enabled : status of enabled interrupt
35 struct lp8788_irq_data
{
37 struct mutex irq_lock
;
38 struct irq_domain
*domain
;
39 int enabled
[LP8788_INT_MAX
];
42 static inline u8
_irq_to_addr(enum lp8788_int_id id
)
47 static inline u8
_irq_to_enable_addr(enum lp8788_int_id id
)
49 return _irq_to_addr(id
) + BASE_INTEN_ADDR
;
52 static inline u8
_irq_to_mask(enum lp8788_int_id id
)
54 return 1 << (id
% SIZE_REG
);
57 static inline u8
_irq_to_val(enum lp8788_int_id id
, int enable
)
59 return enable
<< (id
% SIZE_REG
);
62 static void lp8788_irq_enable(struct irq_data
*data
)
64 struct lp8788_irq_data
*irqd
= irq_data_get_irq_chip_data(data
);
66 irqd
->enabled
[data
->hwirq
] = 1;
69 static void lp8788_irq_disable(struct irq_data
*data
)
71 struct lp8788_irq_data
*irqd
= irq_data_get_irq_chip_data(data
);
73 irqd
->enabled
[data
->hwirq
] = 0;
76 static void lp8788_irq_bus_lock(struct irq_data
*data
)
78 struct lp8788_irq_data
*irqd
= irq_data_get_irq_chip_data(data
);
80 mutex_lock(&irqd
->irq_lock
);
83 static void lp8788_irq_bus_sync_unlock(struct irq_data
*data
)
85 struct lp8788_irq_data
*irqd
= irq_data_get_irq_chip_data(data
);
86 enum lp8788_int_id irq
= data
->hwirq
;
89 addr
= _irq_to_enable_addr(irq
);
90 mask
= _irq_to_mask(irq
);
91 val
= _irq_to_val(irq
, irqd
->enabled
[irq
]);
93 lp8788_update_bits(irqd
->lp
, addr
, mask
, val
);
95 mutex_unlock(&irqd
->irq_lock
);
98 static struct irq_chip lp8788_irq_chip
= {
100 .irq_enable
= lp8788_irq_enable
,
101 .irq_disable
= lp8788_irq_disable
,
102 .irq_bus_lock
= lp8788_irq_bus_lock
,
103 .irq_bus_sync_unlock
= lp8788_irq_bus_sync_unlock
,
106 static irqreturn_t
lp8788_irq_handler(int irq
, void *ptr
)
108 struct lp8788_irq_data
*irqd
= ptr
;
109 struct lp8788
*lp
= irqd
->lp
;
110 u8 status
[NUM_REGS
], addr
, mask
;
111 bool handled
= false;
114 if (lp8788_read_multi_bytes(lp
, LP8788_INT_1
, status
, NUM_REGS
))
117 for (i
= 0 ; i
< LP8788_INT_MAX
; i
++) {
118 addr
= _irq_to_addr(i
);
119 mask
= _irq_to_mask(i
);
121 /* reporting only if the irq is enabled */
122 if (status
[addr
] & mask
) {
123 handle_nested_irq(irq_find_mapping(irqd
->domain
, i
));
128 return handled
? IRQ_HANDLED
: IRQ_NONE
;
131 static int lp8788_irq_map(struct irq_domain
*d
, unsigned int virq
,
132 irq_hw_number_t hwirq
)
134 struct lp8788_irq_data
*irqd
= d
->host_data
;
135 struct irq_chip
*chip
= &lp8788_irq_chip
;
137 irq_set_chip_data(virq
, irqd
);
138 irq_set_chip_and_handler(virq
, chip
, handle_edge_irq
);
139 irq_set_nested_thread(virq
, 1);
140 irq_set_noprobe(virq
);
145 static const struct irq_domain_ops lp8788_domain_ops
= {
146 .map
= lp8788_irq_map
,
149 int lp8788_irq_init(struct lp8788
*lp
, int irq
)
151 struct lp8788_irq_data
*irqd
;
155 dev_warn(lp
->dev
, "invalid irq number: %d\n", irq
);
159 irqd
= devm_kzalloc(lp
->dev
, sizeof(*irqd
), GFP_KERNEL
);
164 irqd
->domain
= irq_domain_add_linear(lp
->dev
->of_node
, LP8788_INT_MAX
,
165 &lp8788_domain_ops
, irqd
);
167 dev_err(lp
->dev
, "failed to add irq domain err\n");
171 lp
->irqdm
= irqd
->domain
;
172 mutex_init(&irqd
->irq_lock
);
174 ret
= request_threaded_irq(irq
, NULL
, lp8788_irq_handler
,
175 IRQF_TRIGGER_FALLING
| IRQF_ONESHOT
,
178 irq_domain_remove(lp
->irqdm
);
179 dev_err(lp
->dev
, "failed to create a thread for IRQ_N\n");
188 void lp8788_irq_exit(struct lp8788
*lp
)
191 free_irq(lp
->irq
, lp
->irqdm
);
193 irq_domain_remove(lp
->irqdm
);