]>
Commit | Line | Data |
---|---|---|
9e8f664e VB |
1 | /* |
2 | * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. | |
3 | * | |
4 | * SPDX-License-Identifier: GPL-2.0+ | |
5 | */ | |
6 | ||
7 | /* | |
8 | * This file is a driver for Parade dP<->LVDS bridges. The original submission | |
9 | * is for the ps8625 chip. | |
10 | */ | |
11 | #include <config.h> | |
12 | #include <common.h> | |
13 | #include <i2c.h> | |
14 | #include <fdtdec.h> | |
15 | ||
16 | /* | |
17 | * Initialization of the chip is a process of writing certaing values into | |
18 | * certain registers over i2c bus. The chip in fact responds to a range of | |
19 | * addresses on the i2c bus, so for each written value three parameters are | |
20 | * required: i2c address, register address and the actual value. | |
21 | * | |
22 | * The base address is derived from the device tree, only address offset is | |
23 | * stored in the table below. | |
24 | */ | |
25 | /** | |
26 | * struct reg_data() - data for a parade register write | |
27 | * | |
28 | * @addr_off offset from the i2c base address for parade | |
29 | * @reg_addr register address to write | |
30 | * @value value to be written | |
31 | */ | |
32 | struct reg_data { | |
33 | uint8_t addr_off; | |
34 | uint8_t reg; | |
35 | uint8_t value; | |
36 | } _packed; | |
37 | ||
38 | #define END_OF_TABLE 0xff /* Ficticious offset */ | |
39 | ||
40 | static const struct reg_data parade_values[] = { | |
41 | {0x02, 0xa1, 0x01}, /* HPD low */ | |
42 | /* | |
43 | * SW setting | |
44 | * [1:0] SW output 1.2V voltage is lower to 96% | |
45 | */ | |
46 | {0x04, 0x14, 0x01}, | |
47 | /* | |
48 | * RCO SS setting | |
49 | * [5:4] = b01 0.5%, b10 1%, b11 1.5% | |
50 | */ | |
51 | {0x04, 0xe3, 0x20}, | |
52 | {0x04, 0xe2, 0x80}, /* [7] RCO SS enable */ | |
53 | /* | |
54 | * RPHY Setting | |
55 | * [3:2] CDR tune wait cycle before | |
56 | * measure for fine tune b00: 1us, | |
57 | * 01: 0.5us, 10:2us, 11:4us. | |
58 | */ | |
59 | {0x04, 0x8a, 0x0c}, | |
60 | {0x04, 0x89, 0x08}, /* [3] RFD always on */ | |
61 | /* | |
62 | * CTN lock in/out: | |
63 | * 20000ppm/80000ppm. Lock out 2 | |
64 | * times. | |
65 | */ | |
66 | {0x04, 0x71, 0x2d}, | |
67 | /* | |
68 | * 2.7G CDR settings | |
69 | * NOF=40LSB for HBR CDR setting | |
70 | */ | |
71 | {0x04, 0x7d, 0x07}, | |
72 | {0x04, 0x7b, 0x00}, /* [1:0] Fmin=+4bands */ | |
73 | {0x04, 0x7a, 0xfd}, /* [7:5] DCO_FTRNG=+-40% */ | |
74 | /* | |
75 | * 1.62G CDR settings | |
76 | * [5:2]NOF=64LSB [1:0]DCO scale is 2/5 | |
77 | */ | |
78 | {0x04, 0xc0, 0x12}, | |
79 | {0x04, 0xc1, 0x92}, /* Gitune=-37% */ | |
80 | {0x04, 0xc2, 0x1c}, /* Fbstep=100% */ | |
81 | {0x04, 0x32, 0x80}, /* [7] LOS signal disable */ | |
82 | /* | |
83 | * RPIO Setting | |
84 | * [7:4] LVDS driver bias current : | |
85 | * 75% (250mV swing) | |
86 | */ | |
87 | {0x04, 0x00, 0xb0}, | |
88 | /* | |
89 | * [7:6] Right-bar GPIO output strength is 8mA | |
90 | */ | |
91 | {0x04, 0x15, 0x40}, | |
92 | /* EQ Training State Machine Setting */ | |
93 | {0x04, 0x54, 0x10}, /* RCO calibration start */ | |
94 | /* [4:0] MAX_LANE_COUNT set to one lane */ | |
95 | {0x01, 0x02, 0x81}, | |
96 | /* [4:0] LANE_COUNT_SET set to one lane */ | |
97 | {0x01, 0x21, 0x81}, | |
98 | {0x00, 0x52, 0x20}, | |
99 | {0x00, 0xf1, 0x03}, /* HPD CP toggle enable */ | |
100 | {0x00, 0x62, 0x41}, | |
101 | /* Counter number, add 1ms counter delay */ | |
102 | {0x00, 0xf6, 0x01}, | |
103 | /* | |
104 | * [6]PWM function control by | |
105 | * DPCD0040f[7], default is PWM | |
106 | * block always works. | |
107 | */ | |
108 | {0x00, 0x77, 0x06}, | |
109 | /* | |
110 | * 04h Adjust VTotal tolerance to | |
111 | * fix the 30Hz no display issue | |
112 | */ | |
113 | {0x00, 0x4c, 0x04}, | |
114 | /* DPCD00400='h00, Parade OUI = 'h001cf8 */ | |
115 | {0x01, 0xc0, 0x00}, | |
116 | {0x01, 0xc1, 0x1c}, /* DPCD00401='h1c */ | |
117 | {0x01, 0xc2, 0xf8}, /* DPCD00402='hf8 */ | |
118 | /* | |
119 | * DPCD403~408 = ASCII code | |
120 | * D2SLV5='h4432534c5635 | |
121 | */ | |
122 | {0x01, 0xc3, 0x44}, | |
123 | {0x01, 0xc4, 0x32}, /* DPCD404 */ | |
124 | {0x01, 0xc5, 0x53}, /* DPCD405 */ | |
125 | {0x01, 0xc6, 0x4c}, /* DPCD406 */ | |
126 | {0x01, 0xc7, 0x56}, /* DPCD407 */ | |
127 | {0x01, 0xc8, 0x35}, /* DPCD408 */ | |
128 | /* | |
129 | * DPCD40A, Initial Code major revision | |
130 | * '01' | |
131 | */ | |
132 | {0x01, 0xca, 0x01}, | |
133 | /* DPCD40B, Initial Code minor revision '05' */ | |
134 | {0x01, 0xcb, 0x05}, | |
135 | /* DPCD720, Select internal PWM */ | |
136 | {0x01, 0xa5, 0xa0}, | |
137 | /* | |
138 | * FFh for 100% PWM of brightness, 0h for 0% | |
139 | * brightness | |
140 | */ | |
141 | {0x01, 0xa7, 0xff}, | |
142 | /* | |
143 | * Set LVDS output as 6bit-VESA mapping, | |
144 | * single LVDS channel | |
145 | */ | |
146 | {0x01, 0xcc, 0x13}, | |
147 | /* Enable SSC set by register */ | |
148 | {0x02, 0xb1, 0x20}, | |
149 | /* | |
150 | * Set SSC enabled and +/-1% central | |
151 | * spreading | |
152 | */ | |
153 | {0x04, 0x10, 0x16}, | |
154 | /* MPU Clock source: LC => RCO */ | |
155 | {0x04, 0x59, 0x60}, | |
156 | {0x04, 0x54, 0x14}, /* LC -> RCO */ | |
157 | {0x02, 0xa1, 0x91}, /* HPD high */ | |
158 | {END_OF_TABLE} | |
159 | }; | |
160 | ||
161 | /** | |
162 | * Write values table into the Parade eDP bridge | |
163 | * | |
164 | * @return 0 on success, non-0 on failure | |
165 | */ | |
166 | ||
167 | static int parade_write_regs(int base_addr, const struct reg_data *table) | |
168 | { | |
169 | int ret = 0; | |
170 | ||
171 | while (!ret && (table->addr_off != END_OF_TABLE)) { | |
172 | ret = i2c_write(base_addr + table->addr_off, | |
173 | table->reg, 1, | |
174 | (uint8_t *)&table->value, | |
175 | sizeof(table->value)); | |
176 | table++; | |
177 | } | |
178 | return ret; | |
179 | } | |
180 | ||
181 | int parade_init(const void *blob) | |
182 | { | |
183 | int bus, old_bus; | |
184 | int parent; | |
185 | int node; | |
186 | int addr; | |
187 | int ret; | |
188 | ||
189 | node = fdtdec_next_compatible(blob, 0, COMPAT_PARADE_PS8625); | |
190 | if (node < 0) | |
191 | return 0; | |
192 | ||
193 | parent = fdt_parent_offset(blob, node); | |
194 | if (parent < 0) { | |
195 | debug("%s: Could not find parent i2c node\n", __func__); | |
196 | return -1; | |
197 | } | |
198 | addr = fdtdec_get_int(blob, node, "reg", -1); | |
199 | if (addr < 0) { | |
200 | debug("%s: Could not find i2c address\n", __func__); | |
201 | return -1; | |
202 | } | |
203 | ||
204 | bus = i2c_get_bus_num_fdt(parent); | |
205 | old_bus = i2c_get_bus_num(); | |
206 | ||
207 | debug("%s: Using i2c bus %d\n", __func__, bus); | |
208 | ||
209 | /* | |
210 | * TODO(sjg@chromium.org): Hmmm we seem to need some sort of delay | |
211 | * here. | |
212 | */ | |
213 | mdelay(40); | |
214 | i2c_set_bus_num(bus); | |
215 | ret = parade_write_regs(addr, parade_values); | |
216 | ||
217 | i2c_set_bus_num(old_bus); | |
218 | ||
219 | return ret; | |
220 | } |