| 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_drm_bridge_alloc(dev, struct cros_ec_anx7688, bridge, |
| 107 | &cros_ec_anx7688_bridge_funcs); |
| 108 | if (IS_ERR(ptr: anx7688)) |
| 109 | return PTR_ERR(ptr: anx7688); |
| 110 | |
| 111 | anx7688->client = client; |
| 112 | i2c_set_clientdata(client, data: anx7688); |
| 113 | |
| 114 | anx7688->regmap = devm_regmap_init_i2c(client, &cros_ec_anx7688_regmap_config); |
| 115 | if (IS_ERR(ptr: anx7688->regmap)) { |
| 116 | ret = PTR_ERR(ptr: anx7688->regmap); |
| 117 | dev_err(dev, "regmap i2c init failed: %d\n" , ret); |
| 118 | return ret; |
| 119 | } |
| 120 | |
| 121 | /* Read both vendor and device id (4 bytes). */ |
| 122 | ret = regmap_bulk_read(map: anx7688->regmap, ANX7688_VENDOR_ID_REG, |
| 123 | val: buffer, val_count: 4); |
| 124 | if (ret) { |
| 125 | dev_err(dev, "Failed to read chip vendor/device id\n" ); |
| 126 | return ret; |
| 127 | } |
| 128 | |
| 129 | vendor = (u16)buffer[1] << 8 | buffer[0]; |
| 130 | device = (u16)buffer[3] << 8 | buffer[2]; |
| 131 | if (vendor != ANX7688_VENDOR_ID || device != ANX7688_DEVICE_ID) { |
| 132 | dev_err(dev, "Invalid vendor/device id %04x/%04x\n" , |
| 133 | vendor, device); |
| 134 | return -ENODEV; |
| 135 | } |
| 136 | |
| 137 | ret = regmap_bulk_read(map: anx7688->regmap, ANX7688_FW_VERSION_REG, |
| 138 | val: buffer, val_count: 2); |
| 139 | if (ret) { |
| 140 | dev_err(dev, "Failed to read firmware version\n" ); |
| 141 | return ret; |
| 142 | } |
| 143 | |
| 144 | fw_version = (u16)buffer[0] << 8 | buffer[1]; |
| 145 | dev_info(dev, "ANX7688 firmware version 0x%04x\n" , fw_version); |
| 146 | |
| 147 | anx7688->bridge.of_node = dev->of_node; |
| 148 | |
| 149 | /* FW version >= 0.85 supports bandwidth/lane count registers */ |
| 150 | if (fw_version >= ANX7688_MINIMUM_FW_VERSION) |
| 151 | anx7688->filter = true; |
| 152 | else |
| 153 | /* Warn, but not fail, for backwards compatibility */ |
| 154 | DRM_WARN("Old ANX7688 FW version (0x%04x), not filtering\n" , |
| 155 | fw_version); |
| 156 | |
| 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 | |