cirque_pinnacle.c (18117B)
1 // Copyright (c) 2018 Cirque Corp. Restrictions apply. See: www.cirque.com/sw-license 2 // based on https://github.com/cirque-corp/Cirque_Pinnacle_1CA027/tree/master/Circular_Trackpad 3 // with modifications and changes for QMK 4 // refer to documentation: Gen2 and Gen3 (Pinnacle ASIC) at https://www.cirque.com/documentation 5 6 #include "cirque_pinnacle.h" 7 #include "cirque_pinnacle_gestures.h" 8 #include "wait.h" 9 #include "timer.h" 10 11 #include <stdlib.h> 12 13 #ifndef CIRQUE_PINNACLE_ATTENUATION 14 # ifdef CIRQUE_PINNACLE_CURVED_OVERLAY 15 # define CIRQUE_PINNACLE_ATTENUATION EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_2X 16 # else 17 # define CIRQUE_PINNACLE_ATTENUATION EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_4X 18 # endif 19 #endif 20 21 uint16_t scale_data = CIRQUE_PINNACLE_DEFAULT_SCALE; 22 23 void cirque_pinnacle_clear_flags(void); 24 void cirque_pinnacle_enable_feed(bool feedEnable); 25 void RAP_ReadBytes(uint8_t address, uint8_t* data, uint8_t count); 26 void RAP_Write(uint8_t address, uint8_t data); 27 28 #if CIRQUE_PINNACLE_POSITION_MODE 29 /* Logical Scaling Functions */ 30 // Clips raw coordinates to "reachable" window of sensor 31 // NOTE: values outside this window can only appear as a result of noise 32 void ClipCoordinates(pinnacle_data_t* coordinates) { 33 if (coordinates->xValue < CIRQUE_PINNACLE_X_LOWER) { 34 coordinates->xValue = CIRQUE_PINNACLE_X_LOWER; 35 } else if (coordinates->xValue > CIRQUE_PINNACLE_X_UPPER) { 36 coordinates->xValue = CIRQUE_PINNACLE_X_UPPER; 37 } 38 if (coordinates->yValue < CIRQUE_PINNACLE_Y_LOWER) { 39 coordinates->yValue = CIRQUE_PINNACLE_Y_LOWER; 40 } else if (coordinates->yValue > CIRQUE_PINNACLE_Y_UPPER) { 41 coordinates->yValue = CIRQUE_PINNACLE_Y_UPPER; 42 } 43 } 44 #endif 45 46 uint16_t cirque_pinnacle_get_scale(void) { 47 return scale_data; 48 } 49 void cirque_pinnacle_set_scale(uint16_t scale) { 50 scale_data = scale; 51 } 52 53 // Scales data to desired X & Y resolution 54 void cirque_pinnacle_scale_data(pinnacle_data_t* coordinates, uint16_t xResolution, uint16_t yResolution) { 55 #if CIRQUE_PINNACLE_POSITION_MODE 56 uint32_t xTemp = 0; 57 uint32_t yTemp = 0; 58 59 ClipCoordinates(coordinates); 60 61 xTemp = coordinates->xValue; 62 yTemp = coordinates->yValue; 63 64 // translate coordinates to (0, 0) reference by subtracting edge-offset 65 xTemp -= CIRQUE_PINNACLE_X_LOWER; 66 yTemp -= CIRQUE_PINNACLE_Y_LOWER; 67 68 // scale coordinates to (xResolution, yResolution) range 69 coordinates->xValue = (uint16_t)(xTemp * xResolution / CIRQUE_PINNACLE_X_RANGE); 70 coordinates->yValue = (uint16_t)(yTemp * yResolution / CIRQUE_PINNACLE_Y_RANGE); 71 #else 72 int32_t xTemp = 0, yTemp = 0; 73 ldiv_t temp; 74 static int32_t xRemainder, yRemainder; 75 76 temp = ldiv(((int32_t)coordinates->xDelta) * (int32_t)xResolution + xRemainder, (int32_t)CIRQUE_PINNACLE_X_RANGE); 77 xTemp = temp.quot; 78 xRemainder = temp.rem; 79 80 temp = ldiv(((int32_t)coordinates->yDelta) * (int32_t)yResolution + yRemainder, (int32_t)CIRQUE_PINNACLE_Y_RANGE); 81 yTemp = temp.quot; 82 yRemainder = temp.rem; 83 84 coordinates->xDelta = (int16_t)xTemp; 85 coordinates->yDelta = (int16_t)yTemp; 86 #endif 87 } 88 89 // Clears Status1 register flags (SW_CC and SW_DR) 90 void cirque_pinnacle_clear_flags(void) { 91 RAP_Write(HOSTREG__STATUS1, HOSTREG__STATUS1_DEFVAL & ~(HOSTREG__STATUS1__COMMAND_COMPLETE | HOSTREG__STATUS1__DATA_READY)); 92 wait_us(50); 93 } 94 95 // Enables/Disables the feed 96 void cirque_pinnacle_enable_feed(bool feedEnable) { 97 uint8_t feedconfig1; 98 RAP_ReadBytes(HOSTREG__FEEDCONFIG1, &feedconfig1, 1); 99 100 if (feedEnable) { 101 feedconfig1 |= HOSTREG__FEEDCONFIG1__FEED_ENABLE; 102 } else { 103 feedconfig1 &= ~HOSTREG__FEEDCONFIG1__FEED_ENABLE; 104 } 105 RAP_Write(HOSTREG__FEEDCONFIG1, feedconfig1); 106 } 107 108 /* ERA (Extended Register Access) Functions */ 109 // Reads <count> bytes from an extended register at <address> (16-bit address), 110 // stores values in <*data> 111 void ERA_ReadBytes(uint16_t address, uint8_t* data, uint16_t count) { 112 uint8_t ERAControlValue = 0xFF; 113 uint16_t timeout_timer; 114 115 cirque_pinnacle_enable_feed(false); // Disable feed 116 117 RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_HIGH, (uint8_t)(address >> 8)); // Send upper byte of ERA address 118 RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_LOW, (uint8_t)(address & 0x00FF)); // Send lower byte of ERA address 119 120 for (uint16_t i = 0; i < count; i++) { 121 RAP_Write(HOSTREG__EXT_REG_AXS_CTRL, HOSTREG__EREG_AXS__INC_ADDR_READ | HOSTREG__EREG_AXS__READ); // Signal ERA-read (auto-increment) to Pinnacle 122 123 // Wait for status register 0x1E to clear 124 timeout_timer = timer_read(); 125 do { 126 RAP_ReadBytes(HOSTREG__EXT_REG_AXS_CTRL, &ERAControlValue, 1); 127 } while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT)); 128 129 RAP_ReadBytes(HOSTREG__EXT_REG_AXS_VALUE, data + i, 1); 130 131 cirque_pinnacle_clear_flags(); 132 } 133 } 134 135 // Writes a byte, <data>, to an extended register at <address> (16-bit address) 136 void ERA_WriteByte(uint16_t address, uint8_t data) { 137 uint8_t ERAControlValue = 0xFF; 138 uint16_t timeout_timer; 139 140 cirque_pinnacle_enable_feed(false); // Disable feed 141 142 RAP_Write(HOSTREG__EXT_REG_AXS_VALUE, data); // Send data byte to be written 143 144 RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_HIGH, (uint8_t)(address >> 8)); // Upper byte of ERA address 145 RAP_Write(HOSTREG__EXT_REG_AXS_ADDR_LOW, (uint8_t)(address & 0x00FF)); // Lower byte of ERA address 146 147 RAP_Write(HOSTREG__EXT_REG_AXS_CTRL, HOSTREG__EREG_AXS__WRITE); // Signal an ERA-write to Pinnacle 148 149 // Wait for status register 0x1E to clear 150 timeout_timer = timer_read(); 151 do { 152 RAP_ReadBytes(HOSTREG__EXT_REG_AXS_CTRL, &ERAControlValue, 1); 153 } while ((ERAControlValue != 0x00) && (timer_elapsed(timeout_timer) <= CIRQUE_PINNACLE_TIMEOUT)); 154 155 cirque_pinnacle_clear_flags(); 156 } 157 158 bool cirque_pinnacle_set_adc_attenuation(uint8_t adcGain) { 159 uint8_t adcconfig = 0x00; 160 161 ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1); 162 adcGain &= EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK; 163 if (adcGain == (adcconfig & EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK)) { 164 return false; 165 } 166 adcconfig &= ~EXTREG__TRACK_ADCCONFIG__ADC_ATTENUATE_MASK; 167 adcconfig |= adcGain; 168 ERA_WriteByte(EXTREG__TRACK_ADCCONFIG, adcconfig); 169 ERA_ReadBytes(EXTREG__TRACK_ADCCONFIG, &adcconfig, 1); 170 171 return true; 172 } 173 174 // Changes thresholds to improve detection of fingers 175 // Not needed for flat overlay? 176 void cirque_pinnacle_tune_edge_sensitivity(void) { 177 uint8_t widezmin = 0x00; 178 179 ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &widezmin, 1); 180 ERA_WriteByte(EXTREG__XAXIS_WIDEZMIN, 0x04); // magic number from Cirque sample code 181 ERA_ReadBytes(EXTREG__XAXIS_WIDEZMIN, &widezmin, 1); 182 183 ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &widezmin, 1); 184 ERA_WriteByte(EXTREG__YAXIS_WIDEZMIN, 0x03); // magic number from Cirque sample code 185 ERA_ReadBytes(EXTREG__YAXIS_WIDEZMIN, &widezmin, 1); 186 } 187 188 // Perform calibration 189 void cirque_pinnacle_calibrate(void) { 190 uint8_t calconfig; 191 uint16_t timeout_timer; 192 193 RAP_ReadBytes(HOSTREG__CALCONFIG1, &calconfig, 1); 194 calconfig |= HOSTREG__CALCONFIG1__CALIBRATE; 195 RAP_Write(HOSTREG__CALCONFIG1, calconfig); 196 197 // Calibration takes ~100ms according to GT-AN-090624, doubling the timeout just to be safe 198 timeout_timer = timer_read(); 199 do { 200 RAP_ReadBytes(HOSTREG__CALCONFIG1, &calconfig, 1); 201 } while ((calconfig & HOSTREG__CALCONFIG1__CALIBRATE) && (timer_elapsed(timeout_timer) <= 200)); 202 203 cirque_pinnacle_clear_flags(); 204 } 205 206 // Enable/disable cursor smoothing, smoothing is enabled by default 207 void cirque_pinnacle_cursor_smoothing(bool enable) { 208 uint8_t feedconfig3; 209 210 RAP_ReadBytes(HOSTREG__FEEDCONFIG3, &feedconfig3, 1); 211 if (enable) { 212 feedconfig3 &= ~HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING; 213 } else { 214 feedconfig3 |= HOSTREG__FEEDCONFIG3__DISABLE_CROSS_RATE_SMOOTHING; 215 } 216 RAP_Write(HOSTREG__FEEDCONFIG3, feedconfig3); 217 } 218 219 // Check sensor is connected 220 bool cirque_pinnacle_connected(void) { 221 uint8_t current_zidle = 0; 222 uint8_t temp_zidle = 0; 223 RAP_ReadBytes(HOSTREG__ZIDLE, ¤t_zidle, 1); 224 RAP_Write(HOSTREG__ZIDLE, HOSTREG__ZIDLE_DEFVAL); 225 RAP_ReadBytes(HOSTREG__ZIDLE, &temp_zidle, 1); 226 if (temp_zidle == HOSTREG__ZIDLE_DEFVAL) { 227 RAP_Write(HOSTREG__ZIDLE, current_zidle); 228 return true; 229 } 230 return false; 231 } 232 233 /* Pinnacle-based TM040040/TM035035/TM023023 Functions */ 234 bool cirque_pinnacle_init(void) { 235 #if defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi) 236 spi_init(); 237 #elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) 238 i2c_init(); 239 #endif 240 241 bool touchpad_init = true; 242 243 // send a RESET command now, in case QMK had a soft-reset without a power cycle 244 RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1__RESET); 245 wait_ms(30); // Pinnacle needs 10-15ms to boot, so wait long enough before configuring 246 RAP_Write(HOSTREG__SYSCONFIG1, HOSTREG__SYSCONFIG1_DEFVAL); 247 wait_us(50); 248 249 // Host clears SW_CC flag 250 cirque_pinnacle_clear_flags(); 251 252 #if CIRQUE_PINNACLE_POSITION_MODE 253 RAP_Write(HOSTREG__FEEDCONFIG2, HOSTREG__FEEDCONFIG2_DEFVAL); 254 #else 255 // FeedConfig2 (Feature flags for Relative Mode Only) 256 uint8_t feedconfig2 = HOSTREG__FEEDCONFIG2__GLIDE_EXTEND_DISABLE | HOSTREG__FEEDCONFIG2__INTELLIMOUSE_MODE; 257 # if !defined(CIRQUE_PINNACLE_TAP_ENABLE) 258 feedconfig2 |= HOSTREG__FEEDCONFIG2__ALL_TAP_DISABLE; 259 # endif 260 # if !defined(CIRQUE_PINNACLE_SECONDARY_TAP_ENABLE) 261 feedconfig2 |= HOSTREG__FEEDCONFIG2__SECONDARY_TAP_DISABLE; 262 # elif !defined(CIRQUE_PINNACLE_TAP_ENABLE) 263 # error CIRQUE_PINNACLE_TAP_ENABLE must be defined for CIRQUE_PINNACLE_SECONDARY_TAP_ENABLE to work 264 # endif 265 # if !defined(CIRQUE_PINNACLE_SIDE_SCROLL_ENABLE) 266 feedconfig2 |= HOSTREG__FEEDCONFIG2__SCROLL_DISABLE; 267 # endif 268 RAP_Write(HOSTREG__FEEDCONFIG2, feedconfig2); 269 #endif 270 271 // FeedConfig1 (Data Output Flags) 272 RAP_Write(HOSTREG__FEEDCONFIG1, CIRQUE_PINNACLE_POSITION_MODE ? HOSTREG__FEEDCONFIG1__DATA_TYPE__REL0_ABS1 : HOSTREG__FEEDCONFIG1_DEFVAL); 273 274 #if CIRQUE_PINNACLE_POSITION_MODE 275 // Host sets z-idle packet count to 5 (default is 0x1E/30) 276 RAP_Write(HOSTREG__ZIDLE, 5); 277 #endif 278 279 bool calibrate = cirque_pinnacle_set_adc_attenuation(CIRQUE_PINNACLE_ATTENUATION); 280 281 #ifdef CIRQUE_PINNACLE_CURVED_OVERLAY 282 cirque_pinnacle_tune_edge_sensitivity(); 283 calibrate = true; 284 #endif 285 if (calibrate) { 286 // Force a calibration after setting ADC attenuation 287 cirque_pinnacle_calibrate(); 288 } 289 290 cirque_pinnacle_enable_feed(true); 291 292 #ifndef CIRQUE_PINNACLE_SKIP_SENSOR_CHECK 293 touchpad_init = cirque_pinnacle_connected(); 294 #endif 295 296 return touchpad_init; 297 } 298 299 pinnacle_data_t cirque_pinnacle_read_data(void) { 300 uint8_t data_ready = 0; 301 uint8_t data[6] = {0}; 302 pinnacle_data_t result = {0}; 303 304 // Check if there is valid data available 305 RAP_ReadBytes(HOSTREG__STATUS1, &data_ready, 1); 306 if ((data_ready & HOSTREG__STATUS1__DATA_READY) == 0) { 307 // no data available yet 308 result.valid = false; // be explicit 309 return result; 310 } 311 312 // Read all data bytes 313 RAP_ReadBytes(HOSTREG__PACKETBYTE_0, data, 6); 314 315 // Get ready for the next data sample 316 cirque_pinnacle_clear_flags(); 317 318 #if CIRQUE_PINNACLE_POSITION_MODE 319 // Decode data for absolute mode 320 // Register 0x13 is unused in this mode (palm detection area) 321 result.buttonFlags = data[0] & 0x3F; // bit0 to bit5 are switch 0-5, only hardware button presses (from input pin on the Pinnacle chip) 322 result.xValue = data[2] | ((data[4] & 0x0F) << 8); // merge high and low bits for X 323 result.yValue = data[3] | ((data[4] & 0xF0) << 4); // merge high and low bits for Y 324 result.zValue = data[5] & 0x3F; // Z is only lower 6 bits, upper 2 bits are reserved/unused 325 result.touchDown = (result.xValue != 0 || result.yValue != 0); // (0,0) is a "magic coordinate" to indicate "finger touched down" 326 #else 327 // Decode data for relative mode 328 // Registers 0x16 and 0x17 are unused in this mode 329 result.buttons = data[0] & 0x07; // Only three buttons are supported 330 if ((data[0] & 0x10) && data[1] != 0) { 331 result.xDelta = -((int16_t)256 - (int16_t)(data[1])); 332 } else { 333 result.xDelta = data[1]; 334 } 335 if ((data[0] & 0x20) && data[2] != 0) { 336 result.yDelta = ((int16_t)256 - (int16_t)(data[2])); 337 } else { 338 result.yDelta = -((int16_t)data[2]); 339 } 340 result.wheelCount = ((int8_t*)data)[3]; 341 #endif 342 343 #ifdef CIRQUE_PINNACLE_REACHABLE_CALIBRATION 344 static uint16_t xMin = UINT16_MAX, yMin = UINT16_MAX, yMax = 0, xMax = 0; 345 if (result.xValue < xMin) xMin = result.xValue; 346 if (result.xValue > xMax) xMax = result.xValue; 347 if (result.yValue < yMin) yMin = result.yValue; 348 if (result.yValue > yMax) yMax = result.yValue; 349 pd_dprintf("%s: xLo=%3d xHi=%3d yLo=%3d yHi=%3d\n", __FUNCTION__, xMin, xMax, yMin, yMax); 350 #endif 351 352 result.valid = true; 353 return result; 354 } 355 356 #ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE 357 static bool cursor_glide_enable = true; 358 359 static cursor_glide_context_t glide = {.config = { 360 .coef = 102, /* Good default friction coef */ 361 .interval = 10, /* 100sps */ 362 .trigger_px = 10, /* Default threshold in case of hover, set to 0 if you'd like */ 363 }}; 364 365 void cirque_pinnacle_enable_cursor_glide(bool enable) { 366 cursor_glide_enable = enable; 367 } 368 369 void cirque_pinnacle_configure_cursor_glide(float trigger_px) { 370 glide.config.trigger_px = trigger_px; 371 } 372 #endif 373 374 #if CIRQUE_PINNACLE_POSITION_MODE 375 376 # ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE 377 static bool is_touch_down; 378 379 bool auto_mouse_activation(report_mouse_t mouse_report) { 380 return is_touch_down || mouse_report.x != 0 || mouse_report.y != 0 || mouse_report.h != 0 || mouse_report.v != 0 || mouse_report.buttons; 381 } 382 # endif 383 384 report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) { 385 uint16_t scale = cirque_pinnacle_get_scale(); 386 pinnacle_data_t touchData = cirque_pinnacle_read_data(); 387 mouse_xy_report_t report_x = 0, report_y = 0; 388 static uint16_t x = 0, y = 0, last_scale = 0; 389 390 # if defined(CIRQUE_PINNACLE_TAP_ENABLE) 391 mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1); 392 # endif 393 # ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE 394 cursor_glide_t glide_report = {0}; 395 396 if (cursor_glide_enable) { 397 glide_report = cursor_glide_check(&glide); 398 } 399 # endif 400 401 if (!touchData.valid) { 402 # ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE 403 if (cursor_glide_enable && glide_report.valid) { 404 report_x = glide_report.dx; 405 report_y = glide_report.dy; 406 goto mouse_report_update; 407 } 408 # endif 409 return mouse_report; 410 } 411 412 if (touchData.touchDown) { 413 pd_dprintf("cirque_pinnacle touchData x=%4d y=%4d z=%2d\n", touchData.xValue, touchData.yValue, touchData.zValue); 414 } 415 416 # ifdef POINTING_DEVICE_AUTO_MOUSE_ENABLE 417 is_touch_down = touchData.touchDown; 418 # endif 419 420 // Scale coordinates to arbitrary X, Y resolution 421 cirque_pinnacle_scale_data(&touchData, scale, scale); 422 423 if (!cirque_pinnacle_gestures(&mouse_report, touchData)) { 424 if (last_scale && scale == last_scale && x && y && touchData.xValue && touchData.yValue) { 425 report_x = CONSTRAIN_HID_XY((int16_t)(touchData.xValue - x)); 426 report_y = CONSTRAIN_HID_XY((int16_t)(touchData.yValue - y)); 427 } 428 x = touchData.xValue; 429 y = touchData.yValue; 430 last_scale = scale; 431 432 # ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE 433 if (cursor_glide_enable) { 434 if (touchData.touchDown) { 435 cursor_glide_update(&glide, report_x, report_y, touchData.zValue); 436 } else if (!glide_report.valid) { 437 glide_report = cursor_glide_start(&glide); 438 if (glide_report.valid) { 439 report_x = glide_report.dx; 440 report_y = glide_report.dy; 441 } 442 } 443 } 444 # endif 445 } 446 447 # ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE 448 mouse_report_update: 449 # endif 450 mouse_report.x = report_x; 451 mouse_report.y = report_y; 452 453 return mouse_report; 454 } 455 456 uint16_t cirque_pinnacle_get_cpi(void) { 457 return CIRQUE_PINNACLE_PX_TO_INCH(cirque_pinnacle_get_scale()); 458 } 459 void cirque_pinnacle_set_cpi(uint16_t cpi) { 460 cirque_pinnacle_set_scale(CIRQUE_PINNACLE_INCH_TO_PX(cpi)); 461 } 462 463 // clang-format off 464 const pointing_device_driver_t cirque_pinnacle_pointing_device_driver = { 465 .init = cirque_pinnacle_init, 466 .get_report = cirque_pinnacle_get_report, 467 .set_cpi = cirque_pinnacle_set_cpi, 468 .get_cpi = cirque_pinnacle_get_cpi 469 }; 470 // clang-format on 471 #else 472 report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) { 473 pinnacle_data_t touchData = cirque_pinnacle_read_data(); 474 475 // Scale coordinates to arbitrary X, Y resolution 476 cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale()); 477 478 if (touchData.valid) { 479 mouse_report.buttons = touchData.buttons; 480 mouse_report.x = CONSTRAIN_HID_XY(touchData.xDelta); 481 mouse_report.y = CONSTRAIN_HID_XY(touchData.yDelta); 482 mouse_report.v = touchData.wheelCount; 483 } 484 return mouse_report; 485 } 486 487 // clang-format off 488 const pointing_device_driver_t cirque_pinnacle_pointing_device_driver = { 489 .init = cirque_pinnacle_init, 490 .get_report = cirque_pinnacle_get_report, 491 .set_cpi = cirque_pinnacle_set_scale, 492 .get_cpi = cirque_pinnacle_get_scale 493 }; 494 // clang-format on 495 #endif