usb_mux.c (13104B)
1 /* 2 * Copyright (C) 2021 System76 3 * Copyright (C) 2021 Jimmy Cassis <KernelOops@outlook.com> 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 #include "usb_mux.h" 20 21 #include <stdbool.h> 22 23 #include "i2c_master.h" 24 #include "wait.h" 25 26 #define REG_PF1_CTL 0xBF800C04 27 #define REG_PIO64_OEN 0xBF800908 28 #define REG_PIO64_OUT 0xBF800928 29 #define REG_VID 0xBF803000 30 #define REG_PRT_SWAP 0xBF8030FA 31 #define REG_USB3_HUB_VID 0xBFD2E548 32 #define REG_RUNTIME_FLAGS2 0xBFD23408 33 #define REG_I2S_FEAT_SEL 0xBFD23412 34 35 struct USB7206 { 36 uint8_t addr; 37 }; 38 39 struct USB7206 usb_hub = {.addr = 0x2D}; 40 41 // Perform USB7206 register access. 42 // Returns zero on success or a negative number on error. 43 i2c_status_t usb7206_register_access(struct USB7206* self) { 44 uint8_t register_access[3] = { 45 0x99, 46 0x37, 47 0x00, 48 }; 49 50 return i2c_transmit(self->addr << 1, register_access, sizeof(register_access), I2C_TIMEOUT); 51 } 52 53 // Read data from USB7206 register region. 54 // Returns number of bytes read on success or a negative number on error. 55 i2c_status_t usb7206_read_reg(struct USB7206* self, uint32_t addr, uint8_t* data, int length) { 56 i2c_status_t status; 57 58 uint8_t register_read[9] = { 59 0x00, // Buffer address MSB: always 0 60 0x00, // Buffer address LSB: always 0 61 0x06, // Number of bytes to write to command block buffer area 62 0x01, // Direction: 0 = write, 1 = read 63 (uint8_t)length, // Number of bytes to read from register 64 (uint8_t)(addr >> 24), // Register address byte 3 65 (uint8_t)(addr >> 16), // Register address byte 2 66 (uint8_t)(addr >> 8), // Register address byte 1 67 (uint8_t)(addr >> 0), // Register address byte 0 68 }; 69 70 status = i2c_transmit(self->addr << 1, register_read, sizeof(register_read), I2C_TIMEOUT); 71 if (status < 0) { 72 return status; 73 } 74 75 status = usb7206_register_access(self); 76 if (status < 0) { 77 return status; 78 } 79 80 uint16_t read = 0x0006; // Buffer address 6 to skip header 81 uint8_t data_with_buffer_length[length]; 82 status = i2c_read_register16((self->addr << 1), read, data_with_buffer_length, length, I2C_TIMEOUT); 83 84 for (uint16_t i = 0; i < (length - 1) && status >= 0; i++) { 85 data[i] = data_with_buffer_length[i+1]; 86 } 87 88 return (status < 0) ? status : length; 89 } 90 91 // Read 32-bit value from USB7206 register region. 92 // Returns number of bytes read on success or a negative number on error. 93 i2c_status_t usb7206_read_reg_32(struct USB7206* self, uint32_t addr, uint32_t* data) { 94 i2c_status_t status; 95 96 // First byte is available length 97 uint8_t bytes[4] = {0, 0, 0, 0}; 98 99 status = usb7206_read_reg(self, addr, bytes, sizeof(bytes)); 100 if (status < 0) { 101 return status; 102 } 103 104 // Convert from little endian 105 *data = (((uint32_t)bytes[0]) << 0) | (((uint32_t)bytes[1]) << 8) | (((uint32_t)bytes[2]) << 16) | (((uint32_t)bytes[3]) << 24); 106 107 return status; 108 } 109 110 // Write data to USB7206 register region. 111 // Returns number of bytes written on success or a negative number on error. 112 i2c_status_t usb7206_write_reg(struct USB7206* self, uint32_t addr, uint8_t* data, int length) { 113 i2c_status_t status; 114 115 uint8_t register_write[9] = { 116 0x00, // Buffer address MSB: always 0 117 0x00, // Buffer address LSB: always 0 118 ((uint8_t)length) + 6, // Number of bytes to write to command block buffer area 119 0x00, // Direction: 0 = write, 1 = read 120 (uint8_t)length, // Number of bytes to write to register 121 (uint8_t)(addr >> 24), // Register address byte 3 122 (uint8_t)(addr >> 16), // Register address byte 2 123 (uint8_t)(addr >> 8), // Register address byte 1 124 (uint8_t)(addr >> 0), // Register address byte 0 125 }; 126 uint8_t send_buffer_length = sizeof(register_write) + length; 127 uint8_t send_buffer[send_buffer_length]; 128 uint8_t j = 0; 129 130 for (uint16_t i = 0; i < sizeof(register_write); i++) { 131 send_buffer[j++] = register_write[i]; 132 } 133 134 for (uint16_t i = 0; i < length; i++) { 135 send_buffer[j++] = data[i]; 136 } 137 138 status = i2c_transmit((self->addr << 1), send_buffer, send_buffer_length, I2C_TIMEOUT); 139 140 status = usb7206_register_access(self); 141 142 return (status < 0) ? status : length; 143 } 144 145 // Write 8-bit value to USB7206 register region. 146 // Returns number of bytes written on success or a negative number on error. 147 i2c_status_t usb7206_write_reg_8(struct USB7206* self, uint32_t addr, uint8_t data) { return usb7206_write_reg(self, addr, &data, sizeof(data)); } 148 149 // Write 32-bit value to USB7206 register region. 150 // Returns number of bytes written on success or a negative number on error. 151 i2c_status_t usb7206_write_reg_32(struct USB7206* self, uint32_t addr, uint32_t data) { 152 // Convert to little endian 153 uint8_t bytes[4] = { 154 (uint8_t)(data >> 0), 155 (uint8_t)(data >> 8), 156 (uint8_t)(data >> 16), 157 (uint8_t)(data >> 24), 158 }; 159 160 return usb7206_write_reg(self, addr, bytes, sizeof(bytes)); 161 } 162 163 // Initialize USB7206. 164 // Returns zero on success or a negative number on error. 165 int usb7206_init(struct USB7206* self) { 166 i2c_status_t status; 167 uint32_t data; 168 169 // DM and DP are swapped on ports 2 and 3 170 status = usb7206_write_reg_8(self, REG_PRT_SWAP, 0x0C); 171 if (status < 0) { 172 return status; 173 } 174 175 // Disable audio 176 status = usb7206_write_reg_8(self, REG_I2S_FEAT_SEL, 0); 177 if (status < 0) { 178 return status; 179 } 180 181 // Set HFC_DISABLE 182 data = 0; 183 status = usb7206_read_reg_32(self, REG_RUNTIME_FLAGS2, &data); 184 if (status < 0) { 185 return status; 186 } 187 data |= 1; 188 status = usb7206_write_reg_32(self, REG_RUNTIME_FLAGS2, data); 189 if (status < 0) { 190 return status; 191 } 192 193 // Set Vendor ID and Product ID of USB 2 hub 194 status = usb7206_write_reg_32(self, REG_VID, 0x00033384); 195 if (status < 0) { 196 return status; 197 } 198 199 // Set Vendor ID and Product ID of USB 3 hub 200 status = usb7206_write_reg_32(self, REG_USB3_HUB_VID, 0x00043384); 201 if (status < 0) { 202 return status; 203 } 204 205 return 0; 206 } 207 208 // Attach USB7206. 209 // Returns bytes written on success or a negative number on error. 210 i2c_status_t usb7206_attach(struct USB7206* self) { 211 uint8_t data[3] = { 212 0xAA, 213 0x56, 214 0x00, 215 }; 216 217 return i2c_transmit(self->addr << 1, data, sizeof(data), I2C_TIMEOUT); 218 } 219 220 struct USB7206_GPIO { 221 struct USB7206* usb7206; 222 uint32_t pf; 223 }; 224 225 struct USB7206_GPIO usb_gpio_sink = {.usb7206 = &usb_hub, .pf = 29}; // UP_SEL = PF29 = GPIO93 226 struct USB7206_GPIO usb_gpio_source_left = {.usb7206 = &usb_hub, .pf = 10}; // CL_SEL = PF10 = GPIO74 227 struct USB7206_GPIO usb_gpio_source_right = {.usb7206 = &usb_hub, .pf = 25}; // CR_SEL = PF25 = GPIO88 228 229 // Set USB7206 GPIO to specified value. 230 // Returns zero on success or negative number on error. 231 i2c_status_t usb7206_gpio_set(struct USB7206_GPIO* self, bool value) { 232 i2c_status_t status; 233 uint32_t data; 234 235 data = 0; 236 status = usb7206_read_reg_32(self->usb7206, REG_PIO64_OUT, &data); 237 if (status < 0) { 238 return status; 239 } 240 241 if (value) { 242 data |= (((uint32_t)1) << self->pf); 243 } else { 244 data &= ~(((uint32_t)1) << self->pf); 245 } 246 status = usb7206_write_reg_32(self->usb7206, REG_PIO64_OUT, data); 247 if (status < 0) { 248 return status; 249 } 250 251 return 0; 252 } 253 254 // Initialize USB7206 GPIO. 255 // Returns zero on success or a negative number on error. 256 i2c_status_t usb7206_gpio_init(struct USB7206_GPIO* self) { 257 i2c_status_t status; 258 uint32_t data; 259 260 // Set programmable function to GPIO 261 status = usb7206_write_reg_8(self->usb7206, REG_PF1_CTL + (self->pf - 1), 0); 262 if (status < 0) { 263 return status; 264 } 265 266 // Set GPIO to false by default 267 usb7206_gpio_set(self, false); 268 269 // Set GPIO to output 270 data = 0; 271 status = usb7206_read_reg_32(self->usb7206, REG_PIO64_OEN, &data); 272 if (status < 0) { 273 return status; 274 } 275 276 data |= (((uint32_t)1) << self->pf); 277 status = usb7206_write_reg_32(self->usb7206, REG_PIO64_OEN, data); 278 if (status < 0) { 279 return status; 280 } 281 282 return 0; 283 } 284 285 struct PTN5110 { 286 uint8_t addr; 287 uint8_t cc; 288 struct USB7206_GPIO* gpio; 289 }; 290 291 struct PTN5110 usb_sink = {.addr = 0x51, .gpio = &usb_gpio_sink}; 292 struct PTN5110 usb_source_left = {.addr = 0x52, .gpio = &usb_gpio_source_left}; 293 struct PTN5110 usb_source_right = {.addr = 0x50, .gpio = &usb_gpio_source_right}; 294 295 // Initialize PTN5110. 296 // Returns zero on success or a negative number on error. 297 i2c_status_t ptn5110_init(struct PTN5110* self) { 298 // Set last cc to invalid value, to force update 299 self->cc = 0xFF; 300 // Initialize GPIO 301 return usb7206_gpio_init(self->gpio); 302 } 303 304 // Read PTN5110 CC_STATUS. 305 // Returns zero on success or a negative number on error. 306 i2c_status_t ptn5110_get_cc_status(struct PTN5110* self, uint8_t* cc) { return i2c_read_register(self->addr << 1, 0x1D, cc, 1, I2C_TIMEOUT); } 307 308 // Set PTN5110 SSMUX orientation. 309 // Returns zero on success or a negative number on error. 310 i2c_status_t ptn5110_set_ssmux(struct PTN5110* self, bool orientation) { return usb7206_gpio_set(self->gpio, orientation); } 311 312 // Write PTN5110 COMMAND. 313 // Returns zero on success or negative number on error. 314 i2c_status_t ptn5110_command(struct PTN5110* self, uint8_t command) { return i2c_write_register(self->addr << 1, 0x23, &command, 1, I2C_TIMEOUT); } 315 316 // Set orientation of PTN5110 operating as a sink, call this once. 317 // Returns zero on success or a negative number on error. 318 i2c_status_t ptn5110_sink_set_orientation(struct PTN5110* self) { 319 i2c_status_t status; 320 uint8_t cc; 321 322 status = ptn5110_get_cc_status(self, &cc); 323 if (status < 0) { 324 return status; 325 } 326 327 if ((cc & 0x03) == 0) { 328 status = ptn5110_set_ssmux(self, false); 329 if (status < 0) { 330 return status; 331 } 332 } else { 333 status = ptn5110_set_ssmux(self, true); 334 if (status < 0) { 335 return status; 336 } 337 } 338 339 return 0; 340 } 341 342 // Update PTN5110 operating as a source, call this repeatedly. 343 // Returns zero on success or a negative number on error. 344 i2c_status_t ptn5110_source_update(struct PTN5110* self) { 345 i2c_status_t status; 346 uint8_t cc; 347 348 status = ptn5110_get_cc_status(self, &cc); 349 if (status < 0) { 350 return status; 351 } 352 353 if (cc != self->cc) { 354 // WARNING: Setting this here will disable retries 355 self->cc = cc; 356 357 bool connected = false; 358 bool orientation = false; 359 if ((cc & 0x03) == 2) { 360 connected = true; 361 orientation = true; 362 } else if (((cc >> 2) & 0x03) == 2) { 363 connected = true; 364 orientation = false; 365 } 366 367 if (connected) { 368 // Set SS mux orientation 369 status = ptn5110_set_ssmux(self, orientation); 370 if (status < 0) { 371 return status; 372 } 373 374 // Enable source Vbus command 375 status = ptn5110_command(self, 0b01110111); 376 if (status < 0) { 377 return status; 378 } 379 } else { 380 // Disable source Vbus command 381 status = ptn5110_command(self, 0b01100110); 382 if (status < 0) { 383 return status; 384 } 385 } 386 } 387 388 return 0; 389 } 390 391 void usb_mux_event(void) { 392 // Run this on every 1000th matrix scan 393 static int cycle = 0; 394 if (cycle >= 1000) { 395 cycle = 0; 396 ptn5110_source_update(&usb_source_left); 397 ptn5110_source_update(&usb_source_right); 398 } else { 399 cycle += 1; 400 } 401 } 402 403 void usb_mux_init(void) { 404 // Run I2C bus at 100 kHz 405 i2c_init(); 406 407 // Set up hub 408 usb7206_init(&usb_hub); 409 410 // Set up sink 411 ptn5110_init(&usb_sink); 412 ptn5110_sink_set_orientation(&usb_sink); 413 414 // Set up sources 415 ptn5110_init(&usb_source_left); 416 ptn5110_init(&usb_source_right); 417 418 // Attach hub 419 usb7206_attach(&usb_hub); 420 421 // Ensure orientation is correct after attaching hub 422 // TODO: Find reason why GPIO for sink orientation is reset 423 for (int i = 0; i < 100; i++) { 424 ptn5110_sink_set_orientation(&usb_sink); 425 wait_ms(10); 426 } 427 }