1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * CrOS EC ANX7688 HDMI->DP bridge driver |
4 | * |
5 | * Copyright 2020 Google LLC |
6 | */ |
7 | |
8 | #include <drm/drm_bridge.h> |
9 | #include <drm/drm_print.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/module.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/types.h> |
14 | |
15 | /* Register addresses */ |
16 | #define ANX7688_VENDOR_ID_REG 0x00 |
17 | #define ANX7688_DEVICE_ID_REG 0x02 |
18 | |
19 | #define ANX7688_FW_VERSION_REG 0x80 |
20 | |
21 | #define ANX7688_DP_BANDWIDTH_REG 0x85 |
22 | #define ANX7688_DP_LANE_COUNT_REG 0x86 |
23 | |
24 | #define ANX7688_VENDOR_ID 0x1f29 |
25 | #define ANX7688_DEVICE_ID 0x7688 |
26 | |
27 | /* First supported firmware version (0.85) */ |
28 | #define ANX7688_MINIMUM_FW_VERSION 0x0085 |
29 | |
30 | static const struct regmap_config cros_ec_anx7688_regmap_config = { |
31 | .reg_bits = 8, |
32 | .val_bits = 8, |
33 | }; |
34 | |
35 | struct cros_ec_anx7688 { |
36 | struct i2c_client *client; |
37 | struct regmap *regmap; |
38 | struct drm_bridge bridge; |
39 | bool filter; |
40 | }; |
41 | |
42 | static inline struct cros_ec_anx7688 * |
43 | bridge_to_cros_ec_anx7688(struct drm_bridge *bridge) |
44 | { |
45 | return container_of(bridge, struct cros_ec_anx7688, bridge); |
46 | } |
47 | |
48 | static bool cros_ec_anx7688_bridge_mode_fixup(struct drm_bridge *bridge, |
49 | const struct drm_display_mode *mode, |
50 | struct drm_display_mode *adjusted_mode) |
51 | { |
52 | struct cros_ec_anx7688 *anx = bridge_to_cros_ec_anx7688(bridge); |
53 | int totalbw, requiredbw; |
54 | u8 dpbw, lanecount; |
55 | u8 regs[2]; |
56 | int ret; |
57 | |
58 | if (!anx->filter) |
59 | return true; |
60 | |
61 | /* Read both regs 0x85 (bandwidth) and 0x86 (lane count). */ |
62 | ret = regmap_bulk_read(map: anx->regmap, ANX7688_DP_BANDWIDTH_REG, val: regs, val_count: 2); |
63 | if (ret < 0) { |
64 | DRM_ERROR("Failed to read bandwidth/lane count\n" ); |
65 | return false; |
66 | } |
67 | dpbw = regs[0]; |
68 | lanecount = regs[1]; |
69 | |
70 | /* Maximum 0x19 bandwidth (6.75 Gbps Turbo mode), 2 lanes */ |
71 | if (dpbw > 0x19 || lanecount > 2) { |
72 | DRM_ERROR("Invalid bandwidth/lane count (%02x/%d)\n" , dpbw, |
73 | lanecount); |
74 | return false; |
75 | } |
76 | |
77 | /* Compute available bandwidth (kHz) */ |
78 | totalbw = dpbw * lanecount * 270000 * 8 / 10; |
79 | |
80 | /* Required bandwidth (8 bpc, kHz) */ |
81 | requiredbw = mode->clock * 8 * 3; |
82 | |
83 | DRM_DEBUG_KMS("DP bandwidth: %d kHz (%02x/%d); mode requires %d Khz\n" , |
84 | totalbw, dpbw, lanecount, requiredbw); |
85 | |
86 | if (totalbw == 0) { |
87 | DRM_ERROR("Bandwidth/lane count are 0, not rejecting modes\n" ); |
88 | return true; |
89 | } |
90 | |
91 | return totalbw >= requiredbw; |
92 | } |
93 | |
94 | static const struct drm_bridge_funcs cros_ec_anx7688_bridge_funcs = { |
95 | .mode_fixup = cros_ec_anx7688_bridge_mode_fixup, |
96 | }; |
97 | |
98 | static int cros_ec_anx7688_bridge_probe(struct i2c_client *client) |
99 | { |
100 | struct device *dev = &client->dev; |
101 | struct cros_ec_anx7688 *anx7688; |
102 | u16 vendor, device, fw_version; |
103 | u8 buffer[4]; |
104 | int ret; |
105 | |
106 | anx7688 = devm_kzalloc(dev, size: sizeof(*anx7688), GFP_KERNEL); |
107 | if (!anx7688) |
108 | return -ENOMEM; |
109 | |
110 | anx7688->client = client; |
111 | i2c_set_clientdata(client, data: anx7688); |
112 | |
113 | anx7688->regmap = devm_regmap_init_i2c(client, &cros_ec_anx7688_regmap_config); |
114 | if (IS_ERR(ptr: anx7688->regmap)) { |
115 | ret = PTR_ERR(ptr: anx7688->regmap); |
116 | dev_err(dev, "regmap i2c init failed: %d\n" , ret); |
117 | return ret; |
118 | } |
119 | |
120 | /* Read both vendor and device id (4 bytes). */ |
121 | ret = regmap_bulk_read(map: anx7688->regmap, ANX7688_VENDOR_ID_REG, |
122 | val: buffer, val_count: 4); |
123 | if (ret) { |
124 | dev_err(dev, "Failed to read chip vendor/device id\n" ); |
125 | return ret; |
126 | } |
127 | |
128 | vendor = (u16)buffer[1] << 8 | buffer[0]; |
129 | device = (u16)buffer[3] << 8 | buffer[2]; |
130 | if (vendor != ANX7688_VENDOR_ID || device != ANX7688_DEVICE_ID) { |
131 | dev_err(dev, "Invalid vendor/device id %04x/%04x\n" , |
132 | vendor, device); |
133 | return -ENODEV; |
134 | } |
135 | |
136 | ret = regmap_bulk_read(map: anx7688->regmap, ANX7688_FW_VERSION_REG, |
137 | val: buffer, val_count: 2); |
138 | if (ret) { |
139 | dev_err(dev, "Failed to read firmware version\n" ); |
140 | return ret; |
141 | } |
142 | |
143 | fw_version = (u16)buffer[0] << 8 | buffer[1]; |
144 | dev_info(dev, "ANX7688 firmware version 0x%04x\n" , fw_version); |
145 | |
146 | anx7688->bridge.of_node = dev->of_node; |
147 | |
148 | /* FW version >= 0.85 supports bandwidth/lane count registers */ |
149 | if (fw_version >= ANX7688_MINIMUM_FW_VERSION) |
150 | anx7688->filter = true; |
151 | else |
152 | /* Warn, but not fail, for backwards compatibility */ |
153 | DRM_WARN("Old ANX7688 FW version (0x%04x), not filtering\n" , |
154 | fw_version); |
155 | |
156 | anx7688->bridge.funcs = &cros_ec_anx7688_bridge_funcs; |
157 | drm_bridge_add(bridge: &anx7688->bridge); |
158 | |
159 | return 0; |
160 | } |
161 | |
162 | static void cros_ec_anx7688_bridge_remove(struct i2c_client *client) |
163 | { |
164 | struct cros_ec_anx7688 *anx7688 = i2c_get_clientdata(client); |
165 | |
166 | drm_bridge_remove(bridge: &anx7688->bridge); |
167 | } |
168 | |
169 | static const struct of_device_id cros_ec_anx7688_bridge_match_table[] = { |
170 | { .compatible = "google,cros-ec-anx7688" }, |
171 | { } |
172 | }; |
173 | MODULE_DEVICE_TABLE(of, cros_ec_anx7688_bridge_match_table); |
174 | |
175 | static struct i2c_driver cros_ec_anx7688_bridge_driver = { |
176 | .probe = cros_ec_anx7688_bridge_probe, |
177 | .remove = cros_ec_anx7688_bridge_remove, |
178 | .driver = { |
179 | .name = "cros-ec-anx7688-bridge" , |
180 | .of_match_table = cros_ec_anx7688_bridge_match_table, |
181 | }, |
182 | }; |
183 | |
184 | module_i2c_driver(cros_ec_anx7688_bridge_driver); |
185 | |
186 | MODULE_DESCRIPTION("ChromeOS EC ANX7688 HDMI->DP bridge driver" ); |
187 | MODULE_AUTHOR("Nicolas Boichat <drinkcat@chromium.org>" ); |
188 | MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>" ); |
189 | MODULE_LICENSE("GPL" ); |
190 | |