Commit | Line | Data |
---|---|---|
6ee73861 | 1 | /* |
7dcd060c | 2 | * Copyright 2013 Red Hat Inc. |
6ee73861 BS |
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 | ||
7dcd060c | 25 | #include <core/option.h> |
e0cd3608 | 26 | |
5effecd4 BS |
27 | #include <subdev/bios.h> |
28 | #include <subdev/bios/dcb.h> | |
29 | #include <subdev/bios/i2c.h> | |
7dcd060c | 30 | #include <subdev/vga.h> |
6ee73861 | 31 | |
c26fe843 BS |
32 | #include "priv.h" |
33 | ||
7dcd060c BS |
34 | /****************************************************************************** |
35 | * interface to linux i2c bit-banging algorithm | |
36 | *****************************************************************************/ | |
37 | ||
38 | #ifdef CONFIG_NOUVEAU_I2C_INTERNAL_DEFAULT | |
39 | #define CSTMSEL true | |
40 | #else | |
41 | #define CSTMSEL false | |
42 | #endif | |
43 | ||
44 | static int | |
45 | nouveau_i2c_pre_xfer(struct i2c_adapter *adap) | |
4196faa8 | 46 | { |
7dcd060c BS |
47 | struct i2c_algo_bit_data *bit = adap->algo_data; |
48 | struct nouveau_i2c_port *port = bit->data; | |
49 | if (port->func->acquire) | |
50 | port->func->acquire(port); | |
51 | return 0; | |
52 | } | |
4196faa8 | 53 | |
7dcd060c BS |
54 | static void |
55 | nouveau_i2c_setscl(void *data, int state) | |
56 | { | |
57 | struct nouveau_i2c_port *port = data; | |
58 | port->func->drive_scl(port, state); | |
59 | } | |
4196faa8 | 60 | |
7dcd060c BS |
61 | static void |
62 | nouveau_i2c_setsda(void *data, int state) | |
63 | { | |
64 | struct nouveau_i2c_port *port = data; | |
65 | port->func->drive_sda(port, state); | |
4196faa8 BS |
66 | } |
67 | ||
7dcd060c BS |
68 | static int |
69 | nouveau_i2c_getscl(void *data) | |
4196faa8 | 70 | { |
7dcd060c BS |
71 | struct nouveau_i2c_port *port = data; |
72 | return port->func->sense_scl(port); | |
73 | } | |
4196faa8 | 74 | |
7dcd060c BS |
75 | static int |
76 | nouveau_i2c_getsda(void *data) | |
77 | { | |
78 | struct nouveau_i2c_port *port = data; | |
79 | return port->func->sense_sda(port); | |
80 | } | |
4196faa8 | 81 | |
7dcd060c BS |
82 | /****************************************************************************** |
83 | * base i2c "port" class implementation | |
84 | *****************************************************************************/ | |
85 | ||
86 | void | |
87 | _nouveau_i2c_port_dtor(struct nouveau_object *object) | |
88 | { | |
89 | struct nouveau_i2c_port *port = (void *)object; | |
90 | i2c_del_adapter(&port->adapter); | |
91 | nouveau_object_destroy(&port->base); | |
4196faa8 BS |
92 | } |
93 | ||
7dcd060c BS |
94 | int |
95 | nouveau_i2c_port_create_(struct nouveau_object *parent, | |
96 | struct nouveau_object *engine, | |
97 | struct nouveau_oclass *oclass, u8 index, | |
98 | const struct i2c_algorithm *algo, | |
c865534f | 99 | const struct nouveau_i2c_func *func, |
7dcd060c | 100 | int size, void **pobject) |
4196faa8 | 101 | { |
7dcd060c BS |
102 | struct nouveau_device *device = nv_device(parent); |
103 | struct nouveau_i2c *i2c = (void *)engine; | |
104 | struct nouveau_i2c_port *port; | |
105 | int ret; | |
106 | ||
107 | ret = nouveau_object_create_(parent, engine, oclass, 0, size, pobject); | |
108 | port = *pobject; | |
109 | if (ret) | |
110 | return ret; | |
111 | ||
112 | snprintf(port->adapter.name, sizeof(port->adapter.name), | |
113 | "nouveau-%s-%d", device->name, index); | |
114 | port->adapter.owner = THIS_MODULE; | |
420b9469 | 115 | port->adapter.dev.parent = nv_device_base(device); |
7dcd060c | 116 | port->index = index; |
c865534f | 117 | port->func = func; |
7dcd060c BS |
118 | |
119 | if ( algo == &nouveau_i2c_bit_algo && | |
120 | !nouveau_boolopt(device->cfgopt, "NvI2C", CSTMSEL)) { | |
121 | struct i2c_algo_bit_data *bit; | |
122 | ||
123 | bit = kzalloc(sizeof(*bit), GFP_KERNEL); | |
124 | if (!bit) | |
125 | return -ENOMEM; | |
126 | ||
127 | bit->udelay = 10; | |
128 | bit->timeout = usecs_to_jiffies(2200); | |
129 | bit->data = port; | |
130 | bit->pre_xfer = nouveau_i2c_pre_xfer; | |
131 | bit->setsda = nouveau_i2c_setsda; | |
132 | bit->setscl = nouveau_i2c_setscl; | |
133 | bit->getsda = nouveau_i2c_getsda; | |
134 | bit->getscl = nouveau_i2c_getscl; | |
135 | ||
136 | port->adapter.algo_data = bit; | |
137 | ret = i2c_bit_add_bus(&port->adapter); | |
138 | } else { | |
139 | port->adapter.algo_data = port; | |
140 | port->adapter.algo = algo; | |
141 | ret = i2c_add_adapter(&port->adapter); | |
142 | } | |
143 | ||
d395f1e4 | 144 | if (ret == 0) |
7dcd060c | 145 | list_add_tail(&port->head, &i2c->ports); |
7dcd060c | 146 | return ret; |
4196faa8 BS |
147 | } |
148 | ||
7dcd060c BS |
149 | /****************************************************************************** |
150 | * base i2c subdev class implementation | |
151 | *****************************************************************************/ | |
152 | ||
4196faa8 BS |
153 | static struct nouveau_i2c_port * |
154 | nouveau_i2c_find(struct nouveau_i2c *i2c, u8 index) | |
155 | { | |
156 | struct nouveau_bios *bios = nouveau_bios(i2c); | |
157 | struct nouveau_i2c_port *port; | |
158 | ||
159 | if (index == NV_I2C_DEFAULT(0) || | |
160 | index == NV_I2C_DEFAULT(1)) { | |
161 | u8 ver, hdr, cnt, len; | |
162 | u16 i2c = dcb_i2c_table(bios, &ver, &hdr, &cnt, &len); | |
163 | if (i2c && ver >= 0x30) { | |
164 | u8 auxidx = nv_ro08(bios, i2c + 4); | |
165 | if (index == NV_I2C_DEFAULT(0)) | |
166 | index = (auxidx & 0x0f) >> 0; | |
167 | else | |
168 | index = (auxidx & 0xf0) >> 4; | |
169 | } else { | |
170 | index = 2; | |
171 | } | |
172 | } | |
173 | ||
174 | list_for_each_entry(port, &i2c->ports, head) { | |
175 | if (port->index == index) | |
df3ef6a1 | 176 | return port; |
4196faa8 BS |
177 | } |
178 | ||
df3ef6a1 | 179 | return NULL; |
4196faa8 BS |
180 | } |
181 | ||
548ddb6d BS |
182 | static struct nouveau_i2c_port * |
183 | nouveau_i2c_find_type(struct nouveau_i2c *i2c, u16 type) | |
184 | { | |
185 | struct nouveau_i2c_port *port; | |
186 | ||
187 | list_for_each_entry(port, &i2c->ports, head) { | |
7dcd060c | 188 | if (nv_hclass(port) == type) |
548ddb6d BS |
189 | return port; |
190 | } | |
191 | ||
192 | return NULL; | |
193 | } | |
194 | ||
4196faa8 BS |
195 | static int |
196 | nouveau_i2c_identify(struct nouveau_i2c *i2c, int index, const char *what, | |
9e2b734f | 197 | struct nouveau_i2c_board_info *info, |
4196faa8 | 198 | bool (*match)(struct nouveau_i2c_port *, |
fdd239ac | 199 | struct i2c_board_info *, void *), void *data) |
6ee73861 | 200 | { |
4196faa8 BS |
201 | struct nouveau_i2c_port *port = nouveau_i2c_find(i2c, index); |
202 | int i; | |
203 | ||
204 | if (!port) { | |
205 | nv_debug(i2c, "no bus when probing %s on %d\n", what, index); | |
206 | return -ENODEV; | |
207 | } | |
208 | ||
209 | nv_debug(i2c, "probing %ss on bus: %d\n", what, port->index); | |
9e2b734f MP |
210 | for (i = 0; info[i].dev.addr; i++) { |
211 | u8 orig_udelay = 0; | |
212 | ||
213 | if ((port->adapter.algo == &i2c_bit_algo) && | |
214 | (info[i].udelay != 0)) { | |
215 | struct i2c_algo_bit_data *algo = port->adapter.algo_data; | |
216 | nv_debug(i2c, "using custom udelay %d instead of %d\n", | |
217 | info[i].udelay, algo->udelay); | |
218 | orig_udelay = algo->udelay; | |
219 | algo->udelay = info[i].udelay; | |
220 | } | |
221 | ||
222 | if (nv_probe_i2c(port, info[i].dev.addr) && | |
fdd239ac | 223 | (!match || match(port, &info[i].dev, data))) { |
9e2b734f MP |
224 | nv_info(i2c, "detected %s: %s\n", what, |
225 | info[i].dev.type); | |
4196faa8 BS |
226 | return i; |
227 | } | |
9e2b734f MP |
228 | |
229 | if (orig_udelay) { | |
230 | struct i2c_algo_bit_data *algo = port->adapter.algo_data; | |
231 | algo->udelay = orig_udelay; | |
232 | } | |
4196faa8 BS |
233 | } |
234 | ||
235 | nv_debug(i2c, "no devices found.\n"); | |
236 | return -ENODEV; | |
237 | } | |
238 | ||
7dcd060c BS |
239 | int |
240 | _nouveau_i2c_fini(struct nouveau_object *object, bool suspend) | |
4196faa8 | 241 | { |
7dcd060c BS |
242 | struct nouveau_i2c *i2c = (void *)object; |
243 | struct nouveau_i2c_port *port; | |
244 | int ret; | |
4196faa8 | 245 | |
7dcd060c BS |
246 | list_for_each_entry(port, &i2c->ports, head) { |
247 | ret = nv_ofuncs(port)->fini(nv_object(port), suspend); | |
248 | if (ret && suspend) | |
249 | goto fail; | |
2bdb06e3 | 250 | } |
4196faa8 | 251 | |
7dcd060c BS |
252 | return nouveau_subdev_fini(&i2c->base, suspend); |
253 | fail: | |
254 | list_for_each_entry_continue_reverse(port, &i2c->ports, head) { | |
255 | nv_ofuncs(port)->init(nv_object(port)); | |
2bdb06e3 | 256 | } |
7dcd060c BS |
257 | |
258 | return ret; | |
6ee73861 BS |
259 | } |
260 | ||
4196faa8 | 261 | int |
7dcd060c | 262 | _nouveau_i2c_init(struct nouveau_object *object) |
eeb3ca12 | 263 | { |
7dcd060c BS |
264 | struct nouveau_i2c *i2c = (void *)object; |
265 | struct nouveau_i2c_port *port; | |
266 | int ret; | |
267 | ||
268 | ret = nouveau_subdev_init(&i2c->base); | |
269 | if (ret == 0) { | |
270 | list_for_each_entry(port, &i2c->ports, head) { | |
271 | ret = nv_ofuncs(port)->init(nv_object(port)); | |
272 | if (ret) | |
273 | goto fail; | |
274 | } | |
2bdb06e3 | 275 | } |
4196faa8 | 276 | |
7dcd060c BS |
277 | return ret; |
278 | fail: | |
279 | list_for_each_entry_continue_reverse(port, &i2c->ports, head) { | |
280 | nv_ofuncs(port)->fini(nv_object(port), false); | |
2bdb06e3 | 281 | } |
4196faa8 | 282 | |
7dcd060c | 283 | return ret; |
eeb3ca12 | 284 | } |
6ee73861 | 285 | |
7dcd060c BS |
286 | void |
287 | _nouveau_i2c_dtor(struct nouveau_object *object) | |
df3ef6a1 | 288 | { |
7dcd060c BS |
289 | struct nouveau_i2c *i2c = (void *)object; |
290 | struct nouveau_i2c_port *port, *temp; | |
291 | ||
292 | list_for_each_entry_safe(port, temp, &i2c->ports, head) { | |
293 | nouveau_object_ref(NULL, (struct nouveau_object **)&port); | |
df3ef6a1 BS |
294 | } |
295 | ||
7dcd060c | 296 | nouveau_subdev_destroy(&i2c->base); |
df3ef6a1 BS |
297 | } |
298 | ||
5effecd4 BS |
299 | static struct nouveau_oclass * |
300 | nouveau_i2c_extdev_sclass[] = { | |
301 | nouveau_anx9805_sclass, | |
302 | }; | |
303 | ||
7dcd060c BS |
304 | int |
305 | nouveau_i2c_create_(struct nouveau_object *parent, | |
306 | struct nouveau_object *engine, | |
307 | struct nouveau_oclass *oclass, | |
7dcd060c | 308 | int length, void **pobject) |
6ee73861 | 309 | { |
c26fe843 | 310 | const struct nouveau_i2c_impl *impl = (void *)oclass; |
4196faa8 | 311 | struct nouveau_bios *bios = nouveau_bios(parent); |
4196faa8 | 312 | struct nouveau_i2c *i2c; |
7dcd060c | 313 | struct nouveau_object *object; |
4196faa8 | 314 | struct dcb_i2c_entry info; |
5effecd4 BS |
315 | int ret, i, j, index = -1; |
316 | struct dcb_output outp; | |
317 | u8 ver, hdr; | |
318 | u32 data; | |
4196faa8 BS |
319 | |
320 | ret = nouveau_subdev_create(parent, engine, oclass, 0, | |
321 | "I2C", "i2c", &i2c); | |
322 | *pobject = nv_object(i2c); | |
323 | if (ret) | |
324 | return ret; | |
325 | ||
326 | i2c->find = nouveau_i2c_find; | |
548ddb6d | 327 | i2c->find_type = nouveau_i2c_find_type; |
4196faa8 BS |
328 | i2c->identify = nouveau_i2c_identify; |
329 | INIT_LIST_HEAD(&i2c->ports); | |
330 | ||
7dcd060c | 331 | while (!dcb_i2c_parse(bios, ++index, &info)) { |
4196faa8 BS |
332 | if (info.type == DCB_I2C_UNUSED) |
333 | continue; | |
6ee73861 | 334 | |
c26fe843 | 335 | oclass = impl->sclass; |
7dcd060c BS |
336 | do { |
337 | ret = -EINVAL; | |
338 | if (oclass->handle == info.type) { | |
339 | ret = nouveau_object_ctor(*pobject, *pobject, | |
340 | oclass, &info, | |
341 | index, &object); | |
4196faa8 | 342 | } |
7dcd060c | 343 | } while (ret && (++oclass)->handle); |
6ee73861 BS |
344 | } |
345 | ||
5effecd4 BS |
346 | /* in addition to the busses specified in the i2c table, there |
347 | * may be ddc/aux channels hiding behind external tmds/dp/etc | |
348 | * transmitters. | |
349 | */ | |
350 | index = ((index + 0x0f) / 0x10) * 0x10; | |
351 | i = -1; | |
352 | while ((data = dcb_outp_parse(bios, ++i, &ver, &hdr, &outp))) { | |
353 | if (!outp.location || !outp.extdev) | |
354 | continue; | |
355 | ||
356 | switch (outp.type) { | |
357 | case DCB_OUTPUT_TMDS: | |
358 | info.type = NV_I2C_TYPE_EXTDDC(outp.extdev); | |
359 | break; | |
360 | case DCB_OUTPUT_DP: | |
361 | info.type = NV_I2C_TYPE_EXTAUX(outp.extdev); | |
362 | break; | |
363 | default: | |
364 | continue; | |
365 | } | |
366 | ||
367 | ret = -ENODEV; | |
368 | j = -1; | |
369 | while (ret && ++j < ARRAY_SIZE(nouveau_i2c_extdev_sclass)) { | |
370 | parent = nv_object(i2c->find(i2c, outp.i2c_index)); | |
371 | oclass = nouveau_i2c_extdev_sclass[j]; | |
372 | do { | |
373 | if (oclass->handle != info.type) | |
374 | continue; | |
375 | ret = nouveau_object_ctor(parent, *pobject, | |
376 | oclass, NULL, | |
377 | index++, &object); | |
378 | } while (ret && (++oclass)->handle); | |
379 | } | |
380 | } | |
381 | ||
6ee73861 BS |
382 | return 0; |
383 | } | |
c26fe843 BS |
384 | |
385 | int | |
386 | _nouveau_i2c_ctor(struct nouveau_object *parent, struct nouveau_object *engine, | |
387 | struct nouveau_oclass *oclass, void *data, u32 size, | |
388 | struct nouveau_object **pobject) | |
389 | { | |
390 | struct nouveau_i2c *i2c; | |
391 | int ret; | |
392 | ||
393 | ret = nouveau_i2c_create(parent, engine, oclass, &i2c); | |
394 | *pobject = nv_object(i2c); | |
395 | if (ret) | |
396 | return ret; | |
397 | ||
398 | return 0; | |
399 | } |