1 | /************************************************************************** |
2 | |
3 | Copyright © 2006 Dave Airlie |
4 | |
5 | All Rights Reserved. |
6 | |
7 | Permission is hereby granted, free of charge, to any person obtaining a |
8 | copy of this software and associated documentation files (the |
9 | "Software"), to deal in the Software without restriction, including |
10 | without limitation the rights to use, copy, modify, merge, publish, |
11 | distribute, sub license, and/or sell copies of the Software, and to |
12 | permit persons to whom the Software is furnished to do so, subject to |
13 | the following conditions: |
14 | |
15 | The above copyright notice and this permission notice (including the |
16 | next paragraph) shall be included in all copies or substantial portions |
17 | of the Software. |
18 | |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. |
22 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
23 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
26 | |
27 | **************************************************************************/ |
28 | |
29 | #include "intel_display_types.h" |
30 | #include "intel_dvo_dev.h" |
31 | |
32 | #define CH7xxx_REG_VID 0x4a |
33 | #define CH7xxx_REG_DID 0x4b |
34 | |
35 | #define CH7011_VID 0x83 /* 7010 as well */ |
36 | #define CH7010B_VID 0x05 |
37 | #define CH7009A_VID 0x84 |
38 | #define CH7009B_VID 0x85 |
39 | #define CH7301_VID 0x95 |
40 | |
41 | #define CH7xxx_VID 0x84 |
42 | #define CH7xxx_DID 0x17 |
43 | #define CH7010_DID 0x16 |
44 | |
45 | #define CH7xxx_NUM_REGS 0x4c |
46 | |
47 | #define CH7xxx_CM 0x1c |
48 | #define CH7xxx_CM_XCM (1<<0) |
49 | #define CH7xxx_CM_MCP (1<<2) |
50 | #define CH7xxx_INPUT_CLOCK 0x1d |
51 | #define CH7xxx_GPIO 0x1e |
52 | #define CH7xxx_GPIO_HPIR (1<<3) |
53 | |
54 | #define CH7xxx_IDF 0x1f |
55 | #define CH7xxx_IDF_IBS (1<<7) |
56 | #define CH7xxx_IDF_DES (1<<6) |
57 | #define CH7xxx_IDF_HSP (1<<3) |
58 | #define CH7xxx_IDF_VSP (1<<4) |
59 | |
60 | #define CH7xxx_CONNECTION_DETECT 0x20 |
61 | #define CH7xxx_CDET_DVI (1<<5) |
62 | |
63 | #define CH7xxx_DAC_CNTL 0x21 |
64 | #define CH7xxx_SYNCO_MASK (3 << 3) |
65 | #define CH7xxx_SYNCO_VGA_HSYNC (1 << 3) |
66 | |
67 | #define CH7xxx_CLOCK_OUTPUT 0x22 |
68 | #define CH7xxx_BCOEN (1 << 4) |
69 | #define CH7xxx_BCOP (1 << 3) |
70 | #define CH7xxx_BCO_MASK (7 << 0) |
71 | #define CH7xxx_BCO_VGA_VSYNC (6 << 0) |
72 | |
73 | #define CH7301_HOTPLUG 0x23 |
74 | #define CH7xxx_TCTL 0x31 |
75 | #define CH7xxx_TVCO 0x32 |
76 | #define CH7xxx_TPCP 0x33 |
77 | #define CH7xxx_TPD 0x34 |
78 | #define CH7xxx_TPVT 0x35 |
79 | #define CH7xxx_TLPF 0x36 |
80 | #define CH7xxx_TCT 0x37 |
81 | #define CH7301_TEST_PATTERN 0x48 |
82 | |
83 | #define CH7xxx_PM 0x49 |
84 | #define CH7xxx_PM_FPD (1<<0) |
85 | #define CH7301_PM_DACPD0 (1<<1) |
86 | #define CH7301_PM_DACPD1 (1<<2) |
87 | #define CH7301_PM_DACPD2 (1<<3) |
88 | #define CH7xxx_PM_DVIL (1<<6) |
89 | #define CH7xxx_PM_DVIP (1<<7) |
90 | |
91 | #define CH7301_SYNC_POLARITY 0x56 |
92 | #define CH7301_SYNC_RGB_YUV (1<<0) |
93 | #define CH7301_SYNC_POL_DVI (1<<5) |
94 | |
95 | /** @file |
96 | * driver for the Chrontel 7xxx DVI chip over DVO. |
97 | */ |
98 | |
99 | static struct ch7xxx_id_struct { |
100 | u8 vid; |
101 | char *name; |
102 | } ch7xxx_ids[] = { |
103 | { CH7011_VID, "CH7011" }, |
104 | { CH7010B_VID, "CH7010B" }, |
105 | { CH7009A_VID, "CH7009A" }, |
106 | { CH7009B_VID, "CH7009B" }, |
107 | { CH7301_VID, "CH7301" }, |
108 | }; |
109 | |
110 | static struct ch7xxx_did_struct { |
111 | u8 did; |
112 | char *name; |
113 | } ch7xxx_dids[] = { |
114 | { CH7xxx_DID, "CH7XXX" }, |
115 | { CH7010_DID, "CH7010B" }, |
116 | }; |
117 | |
118 | struct ch7xxx_priv { |
119 | bool quiet; |
120 | }; |
121 | |
122 | static char *ch7xxx_get_id(u8 vid) |
123 | { |
124 | int i; |
125 | |
126 | for (i = 0; i < ARRAY_SIZE(ch7xxx_ids); i++) { |
127 | if (ch7xxx_ids[i].vid == vid) |
128 | return ch7xxx_ids[i].name; |
129 | } |
130 | |
131 | return NULL; |
132 | } |
133 | |
134 | static char *ch7xxx_get_did(u8 did) |
135 | { |
136 | int i; |
137 | |
138 | for (i = 0; i < ARRAY_SIZE(ch7xxx_dids); i++) { |
139 | if (ch7xxx_dids[i].did == did) |
140 | return ch7xxx_dids[i].name; |
141 | } |
142 | |
143 | return NULL; |
144 | } |
145 | |
146 | /** Reads an 8 bit register */ |
147 | static bool ch7xxx_readb(struct intel_dvo_device *dvo, int addr, u8 *ch) |
148 | { |
149 | struct ch7xxx_priv *ch7xxx = dvo->dev_priv; |
150 | struct i2c_adapter *adapter = dvo->i2c_bus; |
151 | u8 out_buf[2]; |
152 | u8 in_buf[2]; |
153 | |
154 | struct i2c_msg msgs[] = { |
155 | { |
156 | .addr = dvo->slave_addr, |
157 | .flags = 0, |
158 | .len = 1, |
159 | .buf = out_buf, |
160 | }, |
161 | { |
162 | .addr = dvo->slave_addr, |
163 | .flags = I2C_M_RD, |
164 | .len = 1, |
165 | .buf = in_buf, |
166 | } |
167 | }; |
168 | |
169 | out_buf[0] = addr; |
170 | out_buf[1] = 0; |
171 | |
172 | if (i2c_transfer(adap: adapter, msgs, num: 2) == 2) { |
173 | *ch = in_buf[0]; |
174 | return true; |
175 | } |
176 | |
177 | if (!ch7xxx->quiet) { |
178 | DRM_DEBUG_KMS("Unable to read register 0x%02x from %s:%02x.\n" , |
179 | addr, adapter->name, dvo->slave_addr); |
180 | } |
181 | return false; |
182 | } |
183 | |
184 | /** Writes an 8 bit register */ |
185 | static bool ch7xxx_writeb(struct intel_dvo_device *dvo, int addr, u8 ch) |
186 | { |
187 | struct ch7xxx_priv *ch7xxx = dvo->dev_priv; |
188 | struct i2c_adapter *adapter = dvo->i2c_bus; |
189 | u8 out_buf[2]; |
190 | struct i2c_msg msg = { |
191 | .addr = dvo->slave_addr, |
192 | .flags = 0, |
193 | .len = 2, |
194 | .buf = out_buf, |
195 | }; |
196 | |
197 | out_buf[0] = addr; |
198 | out_buf[1] = ch; |
199 | |
200 | if (i2c_transfer(adap: adapter, msgs: &msg, num: 1) == 1) |
201 | return true; |
202 | |
203 | if (!ch7xxx->quiet) { |
204 | DRM_DEBUG_KMS("Unable to write register 0x%02x to %s:%d.\n" , |
205 | addr, adapter->name, dvo->slave_addr); |
206 | } |
207 | |
208 | return false; |
209 | } |
210 | |
211 | static bool ch7xxx_init(struct intel_dvo_device *dvo, |
212 | struct i2c_adapter *adapter) |
213 | { |
214 | /* this will detect the CH7xxx chip on the specified i2c bus */ |
215 | struct ch7xxx_priv *ch7xxx; |
216 | u8 vendor, device; |
217 | char *name, *devid; |
218 | |
219 | ch7xxx = kzalloc(size: sizeof(*ch7xxx), GFP_KERNEL); |
220 | if (ch7xxx == NULL) |
221 | return false; |
222 | |
223 | dvo->i2c_bus = adapter; |
224 | dvo->dev_priv = ch7xxx; |
225 | ch7xxx->quiet = true; |
226 | |
227 | if (!ch7xxx_readb(dvo, CH7xxx_REG_VID, ch: &vendor)) |
228 | goto out; |
229 | |
230 | name = ch7xxx_get_id(vid: vendor); |
231 | if (!name) { |
232 | DRM_DEBUG_KMS("ch7xxx not detected; got VID 0x%02x from %s slave %d.\n" , |
233 | vendor, adapter->name, dvo->slave_addr); |
234 | goto out; |
235 | } |
236 | |
237 | |
238 | if (!ch7xxx_readb(dvo, CH7xxx_REG_DID, ch: &device)) |
239 | goto out; |
240 | |
241 | devid = ch7xxx_get_did(did: device); |
242 | if (!devid) { |
243 | DRM_DEBUG_KMS("ch7xxx not detected; got DID 0x%02x from %s slave %d.\n" , |
244 | device, adapter->name, dvo->slave_addr); |
245 | goto out; |
246 | } |
247 | |
248 | ch7xxx->quiet = false; |
249 | DRM_DEBUG_KMS("Detected %s chipset, vendor/device ID 0x%02x/0x%02x\n" , |
250 | name, vendor, device); |
251 | return true; |
252 | out: |
253 | kfree(objp: ch7xxx); |
254 | return false; |
255 | } |
256 | |
257 | static enum drm_connector_status ch7xxx_detect(struct intel_dvo_device *dvo) |
258 | { |
259 | u8 cdet, orig_pm, pm; |
260 | |
261 | ch7xxx_readb(dvo, CH7xxx_PM, ch: &orig_pm); |
262 | |
263 | pm = orig_pm; |
264 | pm &= ~CH7xxx_PM_FPD; |
265 | pm |= CH7xxx_PM_DVIL | CH7xxx_PM_DVIP; |
266 | |
267 | ch7xxx_writeb(dvo, CH7xxx_PM, ch: pm); |
268 | |
269 | ch7xxx_readb(dvo, CH7xxx_CONNECTION_DETECT, ch: &cdet); |
270 | |
271 | ch7xxx_writeb(dvo, CH7xxx_PM, ch: orig_pm); |
272 | |
273 | if (cdet & CH7xxx_CDET_DVI) |
274 | return connector_status_connected; |
275 | return connector_status_disconnected; |
276 | } |
277 | |
278 | static enum drm_mode_status ch7xxx_mode_valid(struct intel_dvo_device *dvo, |
279 | struct drm_display_mode *mode) |
280 | { |
281 | if (mode->clock > 165000) |
282 | return MODE_CLOCK_HIGH; |
283 | |
284 | return MODE_OK; |
285 | } |
286 | |
287 | static void ch7xxx_mode_set(struct intel_dvo_device *dvo, |
288 | const struct drm_display_mode *mode, |
289 | const struct drm_display_mode *adjusted_mode) |
290 | { |
291 | u8 tvco, tpcp, tpd, tlpf, idf; |
292 | |
293 | if (mode->clock <= 65000) { |
294 | tvco = 0x23; |
295 | tpcp = 0x08; |
296 | tpd = 0x16; |
297 | tlpf = 0x60; |
298 | } else { |
299 | tvco = 0x2d; |
300 | tpcp = 0x06; |
301 | tpd = 0x26; |
302 | tlpf = 0xa0; |
303 | } |
304 | |
305 | ch7xxx_writeb(dvo, CH7xxx_TCTL, ch: 0x00); |
306 | ch7xxx_writeb(dvo, CH7xxx_TVCO, ch: tvco); |
307 | ch7xxx_writeb(dvo, CH7xxx_TPCP, ch: tpcp); |
308 | ch7xxx_writeb(dvo, CH7xxx_TPD, ch: tpd); |
309 | ch7xxx_writeb(dvo, CH7xxx_TPVT, ch: 0x30); |
310 | ch7xxx_writeb(dvo, CH7xxx_TLPF, ch: tlpf); |
311 | ch7xxx_writeb(dvo, CH7xxx_TCT, ch: 0x00); |
312 | |
313 | ch7xxx_readb(dvo, CH7xxx_IDF, ch: &idf); |
314 | |
315 | idf |= CH7xxx_IDF_IBS; |
316 | |
317 | idf &= ~(CH7xxx_IDF_HSP | CH7xxx_IDF_VSP); |
318 | if (mode->flags & DRM_MODE_FLAG_PHSYNC) |
319 | idf |= CH7xxx_IDF_HSP; |
320 | |
321 | if (mode->flags & DRM_MODE_FLAG_PVSYNC) |
322 | idf |= CH7xxx_IDF_VSP; |
323 | |
324 | ch7xxx_writeb(dvo, CH7xxx_IDF, ch: idf); |
325 | |
326 | ch7xxx_writeb(dvo, CH7xxx_DAC_CNTL, |
327 | CH7xxx_SYNCO_VGA_HSYNC); |
328 | ch7xxx_writeb(dvo, CH7xxx_CLOCK_OUTPUT, |
329 | CH7xxx_BCOEN | CH7xxx_BCO_VGA_VSYNC); |
330 | } |
331 | |
332 | /* set the CH7xxx power state */ |
333 | static void ch7xxx_dpms(struct intel_dvo_device *dvo, bool enable) |
334 | { |
335 | if (enable) |
336 | ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_DVIL | CH7xxx_PM_DVIP); |
337 | else |
338 | ch7xxx_writeb(dvo, CH7xxx_PM, CH7xxx_PM_FPD); |
339 | } |
340 | |
341 | static bool ch7xxx_get_hw_state(struct intel_dvo_device *dvo) |
342 | { |
343 | u8 val; |
344 | |
345 | ch7xxx_readb(dvo, CH7xxx_PM, ch: &val); |
346 | |
347 | if (val & (CH7xxx_PM_DVIL | CH7xxx_PM_DVIP)) |
348 | return true; |
349 | else |
350 | return false; |
351 | } |
352 | |
353 | static void ch7xxx_dump_regs(struct intel_dvo_device *dvo) |
354 | { |
355 | int i; |
356 | |
357 | for (i = 0; i < CH7xxx_NUM_REGS; i++) { |
358 | u8 val; |
359 | if ((i % 8) == 0) |
360 | DRM_DEBUG_KMS("\n %02X: " , i); |
361 | ch7xxx_readb(dvo, addr: i, ch: &val); |
362 | DRM_DEBUG_KMS("%02X " , val); |
363 | } |
364 | } |
365 | |
366 | static void ch7xxx_destroy(struct intel_dvo_device *dvo) |
367 | { |
368 | struct ch7xxx_priv *ch7xxx = dvo->dev_priv; |
369 | |
370 | if (ch7xxx) { |
371 | kfree(objp: ch7xxx); |
372 | dvo->dev_priv = NULL; |
373 | } |
374 | } |
375 | |
376 | const struct intel_dvo_dev_ops ch7xxx_ops = { |
377 | .init = ch7xxx_init, |
378 | .detect = ch7xxx_detect, |
379 | .mode_valid = ch7xxx_mode_valid, |
380 | .mode_set = ch7xxx_mode_set, |
381 | .dpms = ch7xxx_dpms, |
382 | .get_hw_state = ch7xxx_get_hw_state, |
383 | .dump_regs = ch7xxx_dump_regs, |
384 | .destroy = ch7xxx_destroy, |
385 | }; |
386 | |