summaryrefslogtreecommitdiffstats
path: root/drivers/mfd/lp8788-irq.c
blob: c84ded5f8ece0bcf0d13326eb96d137bd934a0b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
 * TI LP8788 MFD - interrupt handler
 *
 * Copyright 2012 Texas Instruments
 *
 * Author: Milo(Woogyom) Kim <milo.kim@ti.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/device.h>
#include <linux/mfd/lp8788.h>
#include <linux/module.h>
#include <linux/slab.h>

/* register address */
#define LP8788_INT_1			0x00
#define LP8788_INTEN_1			0x03

#define BASE_INTEN_ADDR			LP8788_INTEN_1
#define SIZE_REG			8
#define NUM_REGS			3

/*
 * struct lp8788_irq_data
 * @lp               : used for accessing to lp8788 registers
 * @irq_lock         : mutex for enabling/disabling the interrupt
 * @domain           : IRQ domain for handling nested interrupt
 * @enabled          : status of enabled interrupt
 */
struct lp8788_irq_data {
	struct lp8788 *lp;
	struct mutex irq_lock;
	struct irq_domain *domain;
	int enabled[LP8788_INT_MAX];
};

static inline u8 _irq_to_addr(enum lp8788_int_id id)
{
	return id / SIZE_REG;
}

static inline u8 _irq_to_enable_addr(enum lp8788_int_id id)
{
	return _irq_to_addr(id) + BASE_INTEN_ADDR;
}

static inline u8 _irq_to_mask(enum lp8788_int_id id)
{
	return 1 << (id % SIZE_REG);
}

static inline u8 _irq_to_val(enum lp8788_int_id id, int enable)
{
	return enable << (id % SIZE_REG);
}

static void lp8788_irq_enable(struct irq_data *data)
{
	struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
	irqd->enabled[data->hwirq] = 1;
}

static void lp8788_irq_disable(struct irq_data *data)
{
	struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
	irqd->enabled[data->hwirq] = 0;
}

static void lp8788_irq_bus_lock(struct irq_data *data)
{
	struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);

	mutex_lock(&irqd->irq_lock);
}

static void lp8788_irq_bus_sync_unlock(struct irq_data *data)
{
	struct lp8788_irq_data *irqd = irq_data_get_irq_chip_data(data);
	enum lp8788_int_id irq = data->hwirq;
	u8 addr, mask, val;

	addr = _irq_to_enable_addr(irq);
	mask = _irq_to_mask(irq);
	val = _irq_to_val(irq, irqd->enabled[irq]);

	lp8788_update_bits(irqd->lp, addr, mask, val);

	mutex_unlock(&irqd->irq_lock);
}

static struct irq_chip lp8788_irq_chip = {
	.name			= "lp8788",
	.irq_enable		= lp8788_irq_enable,
	.irq_disable		= lp8788_irq_disable,
	.irq_bus_lock		= lp8788_irq_bus_lock,
	.irq_bus_sync_unlock	= lp8788_irq_bus_sync_unlock,
};

static irqreturn_t lp8788_irq_handler(int irq, void *ptr)
{
	struct lp8788_irq_data *irqd = ptr;
	struct lp8788 *lp = irqd->lp;
	u8 status[NUM_REGS], addr, mask;
	bool handled;
	int i;

	if (lp8788_read_multi_bytes(lp, LP8788_INT_1, status, NUM_REGS))
		return IRQ_NONE;

	for (i = 0 ; i < LP8788_INT_MAX ; i++) {
		addr = _irq_to_addr(i);
		mask = _irq_to_mask(i);

		/* reporting only if the irq is enabled */
		if (status[addr] & mask) {
			handle_nested_irq(irq_find_mapping(irqd->domain, i));
			handled = true;
		}
	}

	return handled ? IRQ_HANDLED : IRQ_NONE;
}

static int lp8788_irq_map(struct irq_domain *d, unsigned int virq,
			irq_hw_number_t hwirq)
{
	struct lp8788_irq_data *irqd = d->host_data;
	struct irq_chip *chip = &lp8788_irq_chip;

	irq_set_chip_data(virq, irqd);
	irq_set_chip_and_handler(virq, chip, handle_edge_irq);
	irq_set_nested_thread(virq, 1);

#ifdef CONFIG_ARM
	set_irq_flags(virq, IRQF_VALID);
#else
	irq_set_noprobe(virq);
#endif

	return 0;
}

static struct irq_domain_ops lp8788_domain_ops = {
	.map = lp8788_irq_map,
};

int lp8788_irq_init(struct lp8788 *lp, int irq)
{
	struct lp8788_irq_data *irqd;
	int ret;

	if (irq <= 0) {
		dev_warn(lp->dev, "invalid irq number: %d\n", irq);
		return 0;
	}

	irqd = devm_kzalloc(lp->dev, sizeof(*irqd), GFP_KERNEL);
	if (!irqd)
		return -ENOMEM;

	irqd->lp = lp;
	irqd->domain = irq_domain_add_linear(lp->dev->of_node, LP8788_INT_MAX,
					&lp8788_domain_ops, irqd);
	if (!irqd->domain) {
		dev_err(lp->dev, "failed to add irq domain err\n");
		return -EINVAL;
	}

	lp->irqdm = irqd->domain;
	mutex_init(&irqd->irq_lock);

	ret = request_threaded_irq(irq, NULL, lp8788_irq_handler,
				IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
				"lp8788-irq", irqd);
	if (ret) {
		dev_err(lp->dev, "failed to create a thread for IRQ_N\n");
		return ret;
	}

	lp->irq = irq;

	return 0;
}

void lp8788_irq_exit(struct lp8788 *lp)
{
	if (lp->irq)
		free_irq(lp->irq, lp->irqdm);
}