Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,374 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "touch_sensor_definitions.h"
#include "drivers/touch/touch_sensor.h"
#include "board/board.h"
#include "drivers/exti.h"
#include "drivers/gpio.h"
#include "drivers/i2c.h"
#include "drivers/rtc.h"
#include "kernel/events.h"
#include "kernel/util/delay.h"
#include "os/tick.h"
#include "services/common/touch/touch.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/attributes.h"
#include "util/net.h"
#include <stdint.h>
// general constants for the controller
#define INIT_TIMEOUT_S (1)
// definition of active region on touch panel
#define MIN_RAW_X (220)
#define MAX_RAW_X (820)
#define MIN_RAW_Y (120)
#define MAX_RAW_Y (820)
// packet ids
#define PACKET_ID_STATUS_RESPONSE (0x52)
#define PACKET_ID_STATUS_READ (0x53)
#define PACKET_ID_STATUS_WRITE (0x54)
#define PACKET_ID_RAM_RESPONSE (0x95)
#define PACKET_ID_RAM_READ (0x96)
#define PACKET_ID_RAM_WRITE (0x97)
#define PACKET_ID_FLASH_RESPONSE (0x97)
#define PACKET_ID_FLASH_READ (0x98)
#define PACKET_ID_HELLO (0x55)
#define PACKET_ID_TOUCH_STATUS (0x5A)
#define PACKET_ID_PALM_DETECTION (0xBA)
// packet footers
#define PACKET_FOOTER (0x01)
#define RAM_FLASH_FOOTER (0xF1)
// message-related defines
#define HELLO_MESSAGE_DATA (0x55555555)
#define MESSAGE_PADDING (0xFFFFFFFF)
// addresses in RAM of interesting settings
#define RAM_ADDR_UNLOCK (0xFFF1)
#define RAM_ADDR_LOCK (0xFFF0)
#define RAM_ADDR_PALM_DETECTION (0x04F1)
#define RAM_ADDR_TOUCHDOWN_RETRIES (0x0474)
#define RAM_ADDR_LIFTOFF_RETRIES (0x04D3)
// magic number for enabling palm detection + reporting
#define RAM_VALUE_ENABLE_PALM_DETECTION (0x02BC)
// If we don't service an interrupt, the controller will retry every 10ms. We never want to lose a
// touch event, so we set the number of retries to a very high value (currently 1 minute worth).
// HACK WARNING: This is a bit of a hack to get around their very low default retry count which
// could cause us to lose events if the system is busy doing something else.
#define NUM_RETRIES (0x10CC)
// data lengths
#define DATA_LEN_FINGER (3)
#define DATA_LEN_STATUS_RESPONSE (3)
typedef union {
uint8_t packet_id;
struct PACKED {
uint32_t data;
uint32_t padding;
} raw;
struct PACKED {
uint8_t packet_id;
uint8_t finger_data[MAX_NUM_TOUCHES][DATA_LEN_FINGER];
uint8_t active_fingers;
} touch;
} EventMessage;
_Static_assert(sizeof(EventMessage) == 8, "eWD1000 event messages should be 8 bytes.");
#if 0
// TODO: used by prv_status_register_read()
typedef enum {
StatusReadType_8bit,
StatusReadType_12bit,
StatusReadType_16bit
} StatusReadType;
typedef struct PACKED {
uint8_t packet_id;
uint8_t address;
uint8_t padding;
uint8_t footer;
} StatusRegisterRequest;
typedef struct PACKED {
uint8_t packet_id;
uint8_t data[DATA_LEN_STATUS_RESPONSE];
uint32_t padding;
} StatusRegisterResponse;
#endif
typedef struct PACKED {
uint8_t packet_id;
uint16_t address;
uint16_t value;
uint8_t footer;
} MemoryPacket;
static bool s_callback_scheduled = false;
// Low-level helper functions
////////////////////////////////////////////////////////////////////////////////
static void prv_write_data(const uint8_t *data, size_t len) {
i2c_use(EWD1000->i2c);
PBL_ASSERTN(i2c_write_block(EWD1000->i2c, len, data));
i2c_release(EWD1000->i2c);
}
static void prv_read_data(uint8_t *data, size_t len) {
i2c_use(EWD1000->i2c);
PBL_ASSERTN(i2c_read_block(EWD1000->i2c, len, data));
i2c_release(EWD1000->i2c);
}
static void prv_wait_for_interrupt(void) {
const RtcTicks timeout = rtc_get_ticks() + RTC_TICKS_HZ * INIT_TIMEOUT_S;
while (gpio_input_read(&EWD1000->int_gpio)) {
if (rtc_get_ticks() > timeout) {
PBL_CROAK("Touch controller didn't respond!");
}
}
}
// Status Register Operations
////////////////////////////////////////////////////////////////////////////////
#if 0
// TODO: this will be used to get FW information from the controller
static void prv_status_register_read(uint8_t address, void *result, StatusReadType type) {
const StatusRegisterRequest request = {
.packet_id = PACKET_ID_STATUS_READ,
.address = address,
.padding = 0,
// 12-bit reads set the footer to 0
.footer = type == StatusReadType_12bit ? 0x00 : PACKET_FOOTER
};
prv_write_data((uint8_t *)&request, sizeof(request));
prv_wait_for_interrupt();
StatusRegisterResponse response = { };
prv_read_data((uint8_t *)&response, sizeof(response));
PBL_ASSERTN(response.packet_id == PACKET_ID_STATUS_RESPONSE);
// TODO: remove this assert and footer ones below once we're sure the controller FW is stable
PBL_ASSERTN(response.padding == MESSAGE_PADDING);
// What follows is some rather hacky code to get the data into a nice and neat integer.
// More info: https://pebbletechnology.atlassian.net/wiki/display/PRODUCT/Elan+Protocol
if (type == StatusReadType_8bit) {
// The 3 data bytes are ST PQ 01 (in hex) with ST being the address and PQ being the value.
PBL_ASSERTN(response.data[0] == address);
PBL_ASSERTN(response.data[2] == PACKET_FOOTER);
*(uint8_t *)result = response.data[1];
} else if (type == StatusReadType_12bit) {
// The 3 data bytes are ST PQ R1 (in hex) with ST being the address and RPQ being the value.
PBL_ASSERTN(response.data[0] == address);
PBL_ASSERTN((response.data[2] & 0x0F) == PACKET_FOOTER);
*(uint16_t *)result = response.data[1] | ((uint16_t)(response.data[2] & 0xF0) << 4);
} else if (type == StatusReadType_16bit) {
// The 3 data bytes are SP QR S1 (in hex) with S being the first 4-bits of the address and PQRS
// being the value.
PBL_ASSERTN((response.data[0] & 0xF0) == (address & 0xF0));
PBL_ASSERTN((response.data[2] & 0x0F) == PACKET_FOOTER);
*(uint16_t *)result = (response.data[0] << 4) | (response.data[1] >> 4);
*(uint16_t *)result <<= 8;
*(uint16_t *)result |= (response.data[1] << 4) | (response.data[2] >> 4);
} else {
// invalid type
WTF;
}
}
#endif
// Memory Operations
////////////////////////////////////////////////////////////////////////////////
#if 0
// TODO: we might want to use these this to verify configuration settings
static uint16_t prv_read_ram(uint16_t address) {
address = htons(address);
MemoryPacket packet = {
.packet_id = PACKET_ID_RAM_READ,
.address = address,
.value = 0x00,
.footer = RAM_FLASH_FOOTER
};
prv_write_data((uint8_t *)&packet, sizeof(packet));
prv_wait_for_interrupt();
prv_read_data((uint8_t *)&packet, sizeof(packet));
PBL_ASSERTN(packet.packet_id == PACKET_ID_RAM_RESPONSE);
PBL_ASSERTN(packet.address == address);
PBL_ASSERTN(packet.footer == RAM_FLASH_FOOTER);
return ntohs(packet.value);
}
#endif
static void prv_write_ram(uint16_t address, uint16_t value) {
address = htons(address);
value = htons(value);
MemoryPacket data = {
.packet_id = PACKET_ID_RAM_WRITE,
.address = address,
.value = value,
.footer = RAM_FLASH_FOOTER
};
prv_write_data((uint8_t *)&data, sizeof(data));
}
#if 0
// TODO: we will use this to verify firmware when upgrading
static uint16_t prv_read_flash(uint16_t address) {
address = htons(address);
MemoryPacket packet = {
.packet_id = PACKET_ID_FLASH_READ,
.address = address,
.value = 0x00,
.footer = RAM_FLASH_FOOTER
};
prv_write_data((uint8_t *)&packet, sizeof(packet));
prv_wait_for_interrupt();
prv_read_data((uint8_t *)&packet, sizeof(packet));
PBL_ASSERTN(packet.packet_id == PACKET_ID_FLASH_RESPONSE);
PBL_ASSERTN(packet.address == address);
PBL_ASSERTN(packet.footer == RAM_FLASH_FOOTER);
return ntohs(packet.value);
}
#endif
// Interrupt / Callback
////////////////////////////////////////////////////////////////////////////////
static void prv_scale_position(GPoint *pos) {
// swap X and Y since the screen is rotated
int32_t x = pos->y;
int32_t y = pos->x;
// clip down to the box we care about on the screen
x = CLIP(x, MIN_RAW_X, MAX_RAW_X-1) - MIN_RAW_X;
y = CLIP(y, MIN_RAW_Y, MAX_RAW_Y-1) - MIN_RAW_Y;
// scale to our screen size
x = (x * DISP_COLS) / (MAX_RAW_X - MIN_RAW_X);
y = (y * DISP_ROWS) / (MAX_RAW_Y - MIN_RAW_Y);
// fix the fact that Y is inverted
y = DISP_ROWS - 1 - y;
// set the result
*pos = GPoint(x, y);
}
static void prv_process_pending_messages(void *context) {
s_callback_scheduled = false;
const uint64_t current_time_ms = ticks_to_milliseconds(rtc_get_ticks());
while (!gpio_input_read(&EWD1000->int_gpio)) {
EventMessage message = { };
prv_read_data((uint8_t *)&message, sizeof(message));
// Packet format: https://pebbletechnology.atlassian.net/wiki/display/PRODUCT/Elan+Protocol
if (message.packet_id == PACKET_ID_TOUCH_STATUS) {
for (uint32_t i = 0; i < ARRAY_LENGTH(message.touch.finger_data); i++) {
const uint8_t msbs = message.touch.finger_data[i][0];
const uint8_t x_lsb = message.touch.finger_data[i][1];
const uint8_t y_lsb = message.touch.finger_data[i][2];
GPoint point = {
.x = ((uint16_t)msbs & 0xF0) << 4 | x_lsb,
.y = ((uint16_t)msbs & 0x0F) << 8 | y_lsb
};
prv_scale_position(&point);
if (message.touch.active_fingers & (1 << i)) {
touch_handle_update(i, TouchState_FingerDown, &point, 0, current_time_ms);
} else {
touch_handle_update(i, TouchState_FingerUp, NULL, 0, current_time_ms);
}
}
} else if (message.packet_id == PACKET_ID_PALM_DETECTION) {
touch_handle_driver_event(TouchDriverEvent_PalmDetect);
} else if (message.packet_id == PACKET_ID_HELLO) {
// TODO: PBL-29944 handle this gracefully by re-initializing - should "never" happen
PBL_CROAK("Touch controller reset!");
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Got unexpected packet (%"PRIx8")", message.packet_id);
}
}
}
static void prv_exti_cb(bool *should_context_switch) {
if (s_callback_scheduled) {
return;
}
PebbleEvent e = {
.type = PEBBLE_CALLBACK_EVENT,
.callback.callback = prv_process_pending_messages
};
*should_context_switch = event_put_isr(&e);
s_callback_scheduled = true;
}
// Initialization
////////////////////////////////////////////////////////////////////////////////
void touch_sensor_init(void) {
// configure INT and RESET pins and INT exti
// TODO: PBL-29944 Is this pull-up needed?
gpio_input_init_pull_up_down(&EWD1000->int_gpio, GPIO_PuPd_UP);
gpio_output_init(&EWD1000->reset_gpio, GPIO_OType_PP, GPIO_Speed_2MHz);
// toggle the reset line and wait for the "Hello" message
gpio_output_set(&EWD1000->reset_gpio, false);
delay_us(1000);
gpio_output_set(&EWD1000->reset_gpio, true);
prv_wait_for_interrupt();
// read the "Hello" message explicitly
EventMessage message = { };
prv_read_data((uint8_t *)&message, sizeof(message));
PBL_ASSERTN(message.raw.data == HELLO_MESSAGE_DATA);
// TODO: remove this assert once we're sure the controller FW is stable
PBL_ASSERTN(message.raw.padding == MESSAGE_PADDING);
// unlock the ram so we can modify it
prv_write_ram(RAM_ADDR_UNLOCK, 0);
// enable palm detection reporting
prv_write_ram(RAM_ADDR_PALM_DETECTION, RAM_VALUE_ENABLE_PALM_DETECTION);
// increase the retries
prv_write_ram(RAM_ADDR_LIFTOFF_RETRIES, NUM_RETRIES);
// lock the ram again
prv_write_ram(RAM_ADDR_LOCK, 0);
// initialize exti
exti_configure_pin(EWD1000->int_exti, ExtiTrigger_Falling, prv_exti_cb);
exti_enable(EWD1000->int_exti);
PBL_LOG(LOG_LEVEL_DEBUG, "Initialized eWD1000 touch controller");
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "board/board.h"
#include <stdbool.h>
#include <stdint.h>
typedef const struct TouchSensor {
I2CSlavePort *i2c;
InputConfig int_gpio;
ExtiConfig int_exti;
OutputConfig reset_gpio;
} TouchSensor;

View file

@ -0,0 +1,19 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
void touch_sensor_init(void);