]>
Commit | Line | Data |
---|---|---|
b8407c9e BS |
1 | /* |
2 | * Copyright 2014 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 | ||
25 | #include <subdev/i2c.h> | |
26 | ||
27 | #include "outpdp.h" | |
28 | #include "conn.h" | |
bb7ef1ec | 29 | #include "dport.h" |
b8407c9e | 30 | |
55f083c3 BS |
31 | int |
32 | nvkm_output_dp_train(struct nvkm_output *base, u32 datarate, bool wait) | |
33 | { | |
34 | struct nvkm_output_dp *outp = (void *)base; | |
35 | bool retrain = true; | |
36 | u8 link[2], stat[3]; | |
0713b451 | 37 | u32 linkrate; |
55f083c3 BS |
38 | int ret, i; |
39 | ||
40 | /* check that the link is trained at a high enough rate */ | |
41 | ret = nv_rdaux(outp->base.edid, DPCD_LC00_LINK_BW_SET, link, 2); | |
42 | if (ret) { | |
43 | DBG("failed to read link config, assuming no sink\n"); | |
44 | goto done; | |
45 | } | |
46 | ||
0713b451 BS |
47 | linkrate = link[0] * 27000 * (link[1] & DPCD_LC01_LANE_COUNT_SET); |
48 | linkrate = (linkrate * 8) / 10; /* 8B/10B coding overhead */ | |
49 | datarate = (datarate + 9) / 10; /* -> decakilobits */ | |
50 | if (linkrate < datarate) { | |
55f083c3 BS |
51 | DBG("link not trained at sufficient rate\n"); |
52 | goto done; | |
53 | } | |
54 | ||
55 | /* check that link is still trained */ | |
56 | ret = nv_rdaux(outp->base.edid, DPCD_LS02, stat, 3); | |
57 | if (ret) { | |
58 | DBG("failed to read link status, assuming no sink\n"); | |
59 | goto done; | |
60 | } | |
61 | ||
62 | if (stat[2] & DPCD_LS04_INTERLANE_ALIGN_DONE) { | |
63 | for (i = 0; i < (link[1] & DPCD_LC01_LANE_COUNT_SET); i++) { | |
64 | u8 lane = (stat[i >> 1] >> ((i & 1) * 4)) & 0x0f; | |
65 | if (!(lane & DPCD_LS02_LANE0_CR_DONE) || | |
66 | !(lane & DPCD_LS02_LANE0_CHANNEL_EQ_DONE) || | |
67 | !(lane & DPCD_LS02_LANE0_SYMBOL_LOCKED)) { | |
68 | DBG("lane %d not equalised\n", lane); | |
69 | goto done; | |
70 | } | |
71 | } | |
72 | retrain = false; | |
73 | } else { | |
74 | DBG("no inter-lane alignment\n"); | |
75 | } | |
76 | ||
77 | done: | |
78 | if (retrain || !atomic_read(&outp->lt.done)) { | |
79 | /* no sink, but still need to configure source */ | |
80 | if (outp->dpcd[DPCD_RC00_DPCD_REV] == 0x00) { | |
81 | outp->dpcd[DPCD_RC01_MAX_LINK_RATE] = | |
82 | outp->base.info.dpconf.link_bw; | |
83 | outp->dpcd[DPCD_RC02] = | |
84 | outp->base.info.dpconf.link_nr; | |
85 | } | |
86 | atomic_set(&outp->lt.done, 0); | |
87 | schedule_work(&outp->lt.work); | |
88 | } else { | |
89 | nouveau_event_get(outp->irq); | |
90 | } | |
91 | ||
92 | if (wait) { | |
93 | if (!wait_event_timeout(outp->lt.wait, | |
94 | atomic_read(&outp->lt.done), | |
95 | msecs_to_jiffies(2000))) | |
96 | ret = -ETIMEDOUT; | |
97 | } | |
98 | ||
99 | return ret; | |
100 | } | |
101 | ||
bb7ef1ec BS |
102 | static void |
103 | nvkm_output_dp_enable(struct nvkm_output_dp *outp, bool present) | |
b8407c9e | 104 | { |
bb7ef1ec BS |
105 | struct nouveau_i2c_port *port = outp->base.edid; |
106 | if (present) { | |
107 | if (!outp->present) { | |
108 | nouveau_i2c(port)->acquire_pad(port, 0); | |
109 | DBG("aux power -> always\n"); | |
110 | outp->present = true; | |
111 | } | |
55f083c3 | 112 | nvkm_output_dp_train(&outp->base, 0, true); |
bb7ef1ec BS |
113 | } else { |
114 | if (outp->present) { | |
115 | nouveau_i2c(port)->release_pad(port); | |
116 | DBG("aux power -> demand\n"); | |
117 | outp->present = false; | |
118 | } | |
55f083c3 | 119 | atomic_set(&outp->lt.done, 0); |
bb7ef1ec BS |
120 | } |
121 | } | |
122 | ||
123 | static void | |
124 | nvkm_output_dp_detect(struct nvkm_output_dp *outp) | |
125 | { | |
126 | struct nouveau_i2c_port *port = outp->base.edid; | |
127 | int ret = nouveau_i2c(port)->acquire_pad(port, 0); | |
128 | if (ret == 0) { | |
129 | ret = nv_rdaux(outp->base.edid, DPCD_RC00_DPCD_REV, | |
130 | outp->dpcd, sizeof(outp->dpcd)); | |
131 | nvkm_output_dp_enable(outp, ret == 0); | |
132 | nouveau_i2c(port)->release_pad(port); | |
133 | } | |
134 | } | |
135 | ||
136 | static void | |
137 | nvkm_output_dp_service_work(struct work_struct *work) | |
138 | { | |
139 | struct nvkm_output_dp *outp = container_of(work, typeof(*outp), work); | |
140 | struct nouveau_disp *disp = nouveau_disp(outp); | |
141 | int type = atomic_xchg(&outp->pending, 0); | |
142 | u32 send = 0; | |
143 | ||
144 | if (type & (NVKM_I2C_PLUG | NVKM_I2C_UNPLUG)) { | |
145 | nvkm_output_dp_detect(outp); | |
146 | if (type & NVKM_I2C_UNPLUG) | |
147 | send |= NVKM_HPD_UNPLUG; | |
148 | if (type & NVKM_I2C_PLUG) | |
149 | send |= NVKM_HPD_PLUG; | |
150 | nouveau_event_get(outp->base.conn->hpd.event); | |
151 | } | |
152 | ||
153 | if (type & NVKM_I2C_IRQ) { | |
55f083c3 | 154 | nvkm_output_dp_train(&outp->base, 0, true); |
bb7ef1ec BS |
155 | send |= NVKM_HPD_IRQ; |
156 | } | |
157 | ||
158 | nouveau_event_trigger(disp->hpd, send, outp->base.info.connector); | |
b8407c9e BS |
159 | } |
160 | ||
161 | static int | |
bb7ef1ec | 162 | nvkm_output_dp_service(void *data, u32 type, int index) |
b8407c9e BS |
163 | { |
164 | struct nvkm_output_dp *outp = data; | |
165 | DBG("HPD: %d\n", type); | |
bb7ef1ec BS |
166 | atomic_or(type, &outp->pending); |
167 | schedule_work(&outp->work); | |
168 | return NVKM_EVENT_DROP; | |
b8407c9e BS |
169 | } |
170 | ||
171 | int | |
172 | _nvkm_output_dp_fini(struct nouveau_object *object, bool suspend) | |
173 | { | |
174 | struct nvkm_output_dp *outp = (void *)object; | |
175 | nouveau_event_put(outp->irq); | |
bb7ef1ec | 176 | nvkm_output_dp_enable(outp, false); |
b8407c9e BS |
177 | return nvkm_output_fini(&outp->base, suspend); |
178 | } | |
179 | ||
180 | int | |
181 | _nvkm_output_dp_init(struct nouveau_object *object) | |
182 | { | |
183 | struct nvkm_output_dp *outp = (void *)object; | |
bb7ef1ec | 184 | nvkm_output_dp_detect(outp); |
b8407c9e BS |
185 | return nvkm_output_init(&outp->base); |
186 | } | |
187 | ||
188 | void | |
189 | _nvkm_output_dp_dtor(struct nouveau_object *object) | |
190 | { | |
191 | struct nvkm_output_dp *outp = (void *)object; | |
192 | nouveau_event_ref(NULL, &outp->irq); | |
193 | nvkm_output_destroy(&outp->base); | |
194 | } | |
195 | ||
196 | int | |
197 | nvkm_output_dp_create_(struct nouveau_object *parent, | |
198 | struct nouveau_object *engine, | |
199 | struct nouveau_oclass *oclass, | |
200 | struct dcb_output *info, int index, | |
201 | int length, void **pobject) | |
202 | { | |
203 | struct nouveau_bios *bios = nouveau_bios(parent); | |
204 | struct nouveau_i2c *i2c = nouveau_i2c(parent); | |
205 | struct nvkm_output_dp *outp; | |
206 | u8 hdr, cnt, len; | |
207 | u32 data; | |
208 | int ret; | |
209 | ||
210 | ret = nvkm_output_create_(parent, engine, oclass, info, index, | |
211 | length, pobject); | |
212 | outp = *pobject; | |
213 | if (ret) | |
214 | return ret; | |
215 | ||
216 | nouveau_event_ref(NULL, &outp->base.conn->hpd.event); | |
217 | ||
218 | /* access to the aux channel is not optional... */ | |
219 | if (!outp->base.edid) { | |
220 | ERR("aux channel not found\n"); | |
221 | return -ENODEV; | |
222 | } | |
223 | ||
224 | /* nor is the bios data for this output... */ | |
225 | data = nvbios_dpout_match(bios, outp->base.info.hasht, | |
226 | outp->base.info.hashm, &outp->version, | |
227 | &hdr, &cnt, &len, &outp->info); | |
228 | if (!data) { | |
229 | ERR("no bios dp data\n"); | |
230 | return -ENODEV; | |
231 | } | |
232 | ||
233 | DBG("bios dp %02x %02x %02x %02x\n", outp->version, hdr, cnt, len); | |
234 | ||
55f083c3 BS |
235 | /* link training */ |
236 | INIT_WORK(&outp->lt.work, nouveau_dp_train); | |
237 | init_waitqueue_head(&outp->lt.wait); | |
238 | atomic_set(&outp->lt.done, 0); | |
239 | ||
b8407c9e BS |
240 | /* link maintenance */ |
241 | ret = nouveau_event_new(i2c->ntfy, NVKM_I2C_IRQ, outp->base.edid->index, | |
242 | nvkm_output_dp_service, outp, &outp->irq); | |
243 | if (ret) { | |
244 | ERR("error monitoring aux irq event: %d\n", ret); | |
245 | return ret; | |
246 | } | |
247 | ||
bb7ef1ec BS |
248 | INIT_WORK(&outp->work, nvkm_output_dp_service_work); |
249 | ||
b8407c9e BS |
250 | /* hotplug detect, replaces gpio-based mechanism with aux events */ |
251 | ret = nouveau_event_new(i2c->ntfy, NVKM_I2C_PLUG | NVKM_I2C_UNPLUG, | |
252 | outp->base.edid->index, | |
bb7ef1ec | 253 | nvkm_output_dp_service, outp, |
b8407c9e BS |
254 | &outp->base.conn->hpd.event); |
255 | if (ret) { | |
256 | ERR("error monitoring aux hpd events: %d\n", ret); | |
257 | return ret; | |
258 | } | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | int | |
264 | _nvkm_output_dp_ctor(struct nouveau_object *parent, | |
265 | struct nouveau_object *engine, | |
266 | struct nouveau_oclass *oclass, void *info, u32 index, | |
267 | struct nouveau_object **pobject) | |
268 | { | |
269 | struct nvkm_output_dp *outp; | |
270 | int ret; | |
271 | ||
272 | ret = nvkm_output_dp_create(parent, engine, oclass, info, index, &outp); | |
273 | *pobject = nv_object(outp); | |
274 | if (ret) | |
275 | return ret; | |
276 | ||
277 | return 0; | |
278 | } |