Commit | Line | Data |
---|---|---|
a0b25635 BS |
1 | /* |
2 | * Copyright 2011 Red Hat Inc. | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
20 | * OTHER DEALINGS IN THE SOFTWARE. | |
21 | * | |
22 | * Authors: Ben Skeggs | |
23 | */ | |
24 | ||
02a841d4 | 25 | #include <subdev/gpio.h> |
e0996aea BS |
26 | #include <subdev/bios.h> |
27 | #include <subdev/bios/gpio.h> | |
a0b25635 | 28 | |
e0996aea BS |
29 | static int |
30 | nouveau_gpio_drive(struct nouveau_gpio *gpio, | |
31 | int idx, int line, int dir, int out) | |
a0b25635 | 32 | { |
e0996aea | 33 | return gpio->drive ? gpio->drive(gpio, line, dir, out) : -ENODEV; |
a0b25635 BS |
34 | } |
35 | ||
e0996aea BS |
36 | static int |
37 | nouveau_gpio_sense(struct nouveau_gpio *gpio, int idx, int line) | |
a0b25635 | 38 | { |
e0996aea | 39 | return gpio->sense ? gpio->sense(gpio, line) : -ENODEV; |
a0b25635 BS |
40 | } |
41 | ||
e0996aea BS |
42 | static int |
43 | nouveau_gpio_find(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, | |
44 | struct dcb_gpio_func *func) | |
a0b25635 | 45 | { |
d2bcea68 BS |
46 | struct nouveau_bios *bios = nouveau_bios(gpio); |
47 | u8 ver, len; | |
48 | u16 data; | |
49 | ||
e0996aea | 50 | if (line == 0xff && tag == 0xff) |
a0b25635 BS |
51 | return -EINVAL; |
52 | ||
d2bcea68 BS |
53 | data = dcb_gpio_match(bios, idx, tag, line, &ver, &len, func); |
54 | if (data) | |
e0996aea | 55 | return 0; |
a0b25635 BS |
56 | |
57 | /* Apple iMac G4 NV18 */ | |
e0996aea BS |
58 | if (nv_device_match(nv_object(gpio), 0x0189, 0x10de, 0x0010)) { |
59 | if (tag == DCB_GPIO_TVDAC0) { | |
60 | *func = (struct dcb_gpio_func) { | |
a0b25635 BS |
61 | .func = DCB_GPIO_TVDAC0, |
62 | .line = 4, | |
63 | .log[0] = 0, | |
64 | .log[1] = 1, | |
65 | }; | |
66 | return 0; | |
67 | } | |
68 | } | |
69 | ||
70 | return -EINVAL; | |
71 | } | |
72 | ||
e0996aea BS |
73 | static int |
74 | nouveau_gpio_set(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, int state) | |
a0b25635 | 75 | { |
e0996aea | 76 | struct dcb_gpio_func func; |
a0b25635 BS |
77 | int ret; |
78 | ||
e0996aea | 79 | ret = nouveau_gpio_find(gpio, idx, tag, line, &func); |
a0b25635 | 80 | if (ret == 0) { |
e0996aea BS |
81 | int dir = !!(func.log[state] & 0x02); |
82 | int out = !!(func.log[state] & 0x01); | |
83 | ret = nouveau_gpio_drive(gpio, idx, func.line, dir, out); | |
a0b25635 BS |
84 | } |
85 | ||
86 | return ret; | |
87 | } | |
88 | ||
e0996aea BS |
89 | static int |
90 | nouveau_gpio_get(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line) | |
a0b25635 | 91 | { |
e0996aea | 92 | struct dcb_gpio_func func; |
a0b25635 BS |
93 | int ret; |
94 | ||
e0996aea | 95 | ret = nouveau_gpio_find(gpio, idx, tag, line, &func); |
a0b25635 | 96 | if (ret == 0) { |
e0996aea | 97 | ret = nouveau_gpio_sense(gpio, idx, func.line); |
a0b25635 | 98 | if (ret >= 0) |
e0996aea | 99 | ret = (ret == (func.log[1] & 1)); |
a0b25635 BS |
100 | } |
101 | ||
102 | return ret; | |
103 | } | |
104 | ||
e0996aea BS |
105 | static int |
106 | nouveau_gpio_irq(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, bool on) | |
a0b25635 | 107 | { |
e0996aea | 108 | struct dcb_gpio_func func; |
a0b25635 BS |
109 | int ret; |
110 | ||
e0996aea | 111 | ret = nouveau_gpio_find(gpio, idx, tag, line, &func); |
a0b25635 | 112 | if (ret == 0) { |
e0996aea BS |
113 | if (idx == 0 && gpio->irq_enable) |
114 | gpio->irq_enable(gpio, func.line, on); | |
a0b25635 BS |
115 | else |
116 | ret = -ENODEV; | |
117 | } | |
118 | ||
119 | return ret; | |
120 | } | |
121 | ||
122 | struct gpio_isr { | |
e0996aea | 123 | struct nouveau_gpio *gpio; |
a0b25635 BS |
124 | struct list_head head; |
125 | struct work_struct work; | |
126 | int idx; | |
e0996aea | 127 | struct dcb_gpio_func func; |
a0b25635 BS |
128 | void (*handler)(void *, int); |
129 | void *data; | |
130 | bool inhibit; | |
131 | }; | |
132 | ||
133 | static void | |
134 | nouveau_gpio_isr_bh(struct work_struct *work) | |
135 | { | |
136 | struct gpio_isr *isr = container_of(work, struct gpio_isr, work); | |
e0996aea | 137 | struct nouveau_gpio *gpio = isr->gpio; |
a0b25635 BS |
138 | unsigned long flags; |
139 | int state; | |
140 | ||
e0996aea BS |
141 | state = nouveau_gpio_get(gpio, isr->idx, isr->func.func, |
142 | isr->func.line); | |
a0b25635 BS |
143 | if (state >= 0) |
144 | isr->handler(isr->data, state); | |
145 | ||
e0996aea | 146 | spin_lock_irqsave(&gpio->lock, flags); |
a0b25635 | 147 | isr->inhibit = false; |
e0996aea | 148 | spin_unlock_irqrestore(&gpio->lock, flags); |
a0b25635 BS |
149 | } |
150 | ||
e0996aea BS |
151 | static void |
152 | nouveau_gpio_isr_run(struct nouveau_gpio *gpio, int idx, u32 line_mask) | |
a0b25635 | 153 | { |
a0b25635 BS |
154 | struct gpio_isr *isr; |
155 | ||
156 | if (idx != 0) | |
157 | return; | |
158 | ||
e0996aea BS |
159 | spin_lock(&gpio->lock); |
160 | list_for_each_entry(isr, &gpio->isr, head) { | |
a0b25635 BS |
161 | if (line_mask & (1 << isr->func.line)) { |
162 | if (isr->inhibit) | |
163 | continue; | |
164 | isr->inhibit = true; | |
165 | schedule_work(&isr->work); | |
166 | } | |
167 | } | |
e0996aea | 168 | spin_unlock(&gpio->lock); |
a0b25635 BS |
169 | } |
170 | ||
e0996aea BS |
171 | static int |
172 | nouveau_gpio_isr_add(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, | |
a0b25635 BS |
173 | void (*handler)(void *, int), void *data) |
174 | { | |
a0b25635 BS |
175 | struct gpio_isr *isr; |
176 | unsigned long flags; | |
177 | int ret; | |
178 | ||
179 | isr = kzalloc(sizeof(*isr), GFP_KERNEL); | |
180 | if (!isr) | |
181 | return -ENOMEM; | |
182 | ||
e0996aea | 183 | ret = nouveau_gpio_find(gpio, idx, tag, line, &isr->func); |
a0b25635 BS |
184 | if (ret) { |
185 | kfree(isr); | |
186 | return ret; | |
187 | } | |
188 | ||
189 | INIT_WORK(&isr->work, nouveau_gpio_isr_bh); | |
e0996aea | 190 | isr->gpio = gpio; |
a0b25635 BS |
191 | isr->handler = handler; |
192 | isr->data = data; | |
193 | isr->idx = idx; | |
194 | ||
e0996aea BS |
195 | spin_lock_irqsave(&gpio->lock, flags); |
196 | list_add(&isr->head, &gpio->isr); | |
197 | spin_unlock_irqrestore(&gpio->lock, flags); | |
a0b25635 BS |
198 | return 0; |
199 | } | |
200 | ||
e0996aea BS |
201 | static void |
202 | nouveau_gpio_isr_del(struct nouveau_gpio *gpio, int idx, u8 tag, u8 line, | |
a0b25635 BS |
203 | void (*handler)(void *, int), void *data) |
204 | { | |
a0b25635 | 205 | struct gpio_isr *isr, *tmp; |
e0996aea | 206 | struct dcb_gpio_func func; |
a0b25635 BS |
207 | unsigned long flags; |
208 | LIST_HEAD(tofree); | |
209 | int ret; | |
210 | ||
e0996aea | 211 | ret = nouveau_gpio_find(gpio, idx, tag, line, &func); |
a0b25635 | 212 | if (ret == 0) { |
e0996aea BS |
213 | spin_lock_irqsave(&gpio->lock, flags); |
214 | list_for_each_entry_safe(isr, tmp, &gpio->isr, head) { | |
a0b25635 BS |
215 | if (memcmp(&isr->func, &func, sizeof(func)) || |
216 | isr->idx != idx || | |
217 | isr->handler != handler || isr->data != data) | |
218 | continue; | |
e0996aea | 219 | list_move_tail(&isr->head, &tofree); |
a0b25635 | 220 | } |
e0996aea | 221 | spin_unlock_irqrestore(&gpio->lock, flags); |
a0b25635 BS |
222 | |
223 | list_for_each_entry_safe(isr, tmp, &tofree, head) { | |
612a9aab | 224 | flush_work(&isr->work); |
a0b25635 BS |
225 | kfree(isr); |
226 | } | |
227 | } | |
228 | } | |
229 | ||
230 | int | |
e0996aea BS |
231 | nouveau_gpio_create_(struct nouveau_object *parent, |
232 | struct nouveau_object *engine, | |
233 | struct nouveau_oclass *oclass, int length, void **pobject) | |
a0b25635 | 234 | { |
e0996aea BS |
235 | struct nouveau_gpio *gpio; |
236 | int ret; | |
a0b25635 | 237 | |
e0996aea BS |
238 | ret = nouveau_subdev_create_(parent, engine, oclass, 0, "GPIO", "gpio", |
239 | length, pobject); | |
240 | gpio = *pobject; | |
241 | if (ret) | |
242 | return ret; | |
a0b25635 | 243 | |
e0996aea BS |
244 | gpio->find = nouveau_gpio_find; |
245 | gpio->set = nouveau_gpio_set; | |
246 | gpio->get = nouveau_gpio_get; | |
247 | gpio->irq = nouveau_gpio_irq; | |
248 | gpio->isr_run = nouveau_gpio_isr_run; | |
249 | gpio->isr_add = nouveau_gpio_isr_add; | |
250 | gpio->isr_del = nouveau_gpio_isr_del; | |
251 | INIT_LIST_HEAD(&gpio->isr); | |
252 | spin_lock_init(&gpio->lock); | |
253 | return 0; | |
a0b25635 BS |
254 | } |
255 | ||
e0996aea BS |
256 | static struct dmi_system_id gpio_reset_ids[] = { |
257 | { | |
258 | .ident = "Apple Macbook 10,1", | |
259 | .matches = { | |
260 | DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), | |
261 | DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro10,1"), | |
262 | } | |
263 | }, | |
264 | { } | |
265 | }; | |
a0b25635 BS |
266 | |
267 | int | |
e0996aea | 268 | nouveau_gpio_init(struct nouveau_gpio *gpio) |
a0b25635 | 269 | { |
e0996aea BS |
270 | int ret = nouveau_subdev_init(&gpio->base); |
271 | if (ret == 0 && gpio->reset) { | |
272 | if (dmi_check_system(gpio_reset_ids)) | |
1ed73166 | 273 | gpio->reset(gpio, DCB_GPIO_UNUSED); |
a0b25635 | 274 | } |
e0996aea | 275 | return ret; |
a0b25635 | 276 | } |