Touch firmware flasher (#21)

* wip: touch firmware flasher

* almost working

* working now
This commit is contained in:
robbederks
2021-02-17 15:07:19 +01:00
committed by Willem Melching
parent 964407ab97
commit 007cde2c3d
5 changed files with 429 additions and 10 deletions

1
.gitignore vendored
View File

@@ -122,3 +122,4 @@ out
.*.swp
*.deb
*.changes
.vscode*

View File

@@ -73,6 +73,7 @@
interrupt-gpios = <&tlmm 125 0x2002>;
reset-gpios = <&tlmm 104 0>;
ta-gpios = <&tlmm 128 0>;
vdd-supply = <&tp3v3>;
};
};
@@ -151,6 +152,7 @@
gpio = <&pm8998_gpios 10 0>;
regulator-boot-on;
regulator-always-on;
};
ssd3v3: ssd3v3 {

View File

@@ -1232,7 +1232,8 @@ CONFIG_STANDALONE=y
CONFIG_PREVENT_FIRMWARE_BUILD=y
CONFIG_FW_LOADER=y
CONFIG_FIRMWARE_IN_KERNEL=y
CONFIG_EXTRA_FIRMWARE=""
CONFIG_EXTRA_FIRMWARE="samsung_touch.img"
# CONFIG_EXTRA_FIRMWARE_DIR is not set
CONFIG_FW_LOADER_USER_HELPER=y
CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y
# CONFIG_FW_CACHE is not set
@@ -1317,7 +1318,6 @@ CONFIG_BLK_DEV_RAM_SIZE=8192
# CONFIG_ATA_OVER_ETH is not set
# CONFIG_BLK_DEV_RBD is not set
# CONFIG_BLK_DEV_RSXX is not set
# CONFIG_BLK_DEV_NVME is not set
CONFIG_NVME_CORE=y
CONFIG_BLK_DEV_NVME=y
# CONFIG_NVME_TARGET is not set

View File

@@ -24,6 +24,7 @@
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/firmware.h>
#include <asm/unaligned.h>
#define SS_I2C_NAME "samsung_i2c_touchpanel"
@@ -40,6 +41,15 @@
/* Touchscreen registers */
#define SS_ONE_EVENT_CMD 0x60
#define SS_ONE_EVENT_SIZE 15
#define SS_BOOT_STATUS_CMD 0x55
#define SS_ENTER_FW_MODE_CMD 0x57
#define SS_CHIP_ID_CMD 0x52
#define SS_FLASH_ERASE_CMD 0xD8
#define SS_FLASH_WRITE_CMD 0xD9
#define SS_PROTOCOL_ID_CMD 0xDD
#define SS_SW_RESET_APP_CMD 0x12
#define SS_SW_RESET_BOOT_CMD 0x42
#define SS_ENABLE_SENSING_CMD 0x10
/* Touchscreen events */
#define SS_EVENT_COORDINATE 0
@@ -53,20 +63,60 @@
#define SS_EVENT_COORDINATE_ACTION_MOVE 2
#define SS_EVENT_COORDINATE_ACTION_UP 3
/* Touchscreen ACKs */
#define SS_ACK_BOOT_COMPLETE 0x00
/* Constants */
#define SS_STOP 0
#define SS_CONTINUE 1
#define SS_BOOT_STATUS_BOOT 0x10
#define SS_BOOT_STATUS_APP 0x20
#define SS_FIRMWARE "samsung_touch.img"
#define SS_HEADER_SIGNATURE 0x53494654
#define SS_CHUNK_SIGNATURE 0x53434654
#define SS_PAGE_SIZE 256
#define SS_PROTOCOL_APP 0x1
uint8_t expected_chip_id[3] = {0xBA, 0xB6, 0x61};
struct ss_ts_data {
struct i2c_client *client;
struct input_dev *input;
struct regulator *vdd;
struct gpio_desc *interrupt_gpio;
struct gpio_desc *reset_gpio;
struct gpio_desc *ta_gpio;
uint8_t chip_id[3];
};
/* Firmware header structures */
typedef struct {
uint32_t signature;
uint32_t version;
uint32_t totalsize;
uint32_t checksum;
uint32_t img_ver;
uint32_t img_date;
uint32_t img_description;
uint32_t fw_ver;
uint32_t fw_date;
uint32_t fw_description;
uint32_t para_ver;
uint32_t para_date;
uint32_t para_description;
uint32_t num_chunk;
uint32_t reserved1;
uint32_t reserved2;
} ss_ts_fw_header;
typedef struct {
uint32_t signature;
uint32_t addr;
uint32_t size;
uint32_t reserved;
} ss_ts_fw_chunk;
/* Event structures */
struct sec_ts_event_coordinate {
uint8_t eid:2;
@@ -102,6 +152,33 @@ static int ss_read(struct ss_ts_data *ts, uint8_t command, uint8_t *data, uint8_
return 0;
}
static int ss_write(struct ss_ts_data *ts, uint8_t command, uint8_t *data, uint8_t len)
{
int ret;
ret = i2c_smbus_write_i2c_block_data(ts->client, command, len, data);
if(ret != 0){
dev_err(&ts->client->dev, "%s: i2c_smbus_write_i2c_block_data: %d\n", __func__, ret);
return ret;
}
return 0;
}
// This doesn't have the 32 byte limit
static int ss_write_bulk(struct ss_ts_data *ts, uint8_t command, uint8_t *data, uint16_t len)
{
int ret;
uint8_t buffer[len + 1];
buffer[0] = command;
memcpy(&buffer[1], data, len);
ret = i2c_master_send(ts->client, buffer, len + 1);
if(ret != (len + 1)){
dev_err(&ts->client->dev, "%s: i2c_master_send: %d\n", __func__, ret);
return ret;
}
return 0;
}
static int ss_report_touch(struct ss_ts_data *ts, int touch_id, int action, int x, int y, int z, int type, int major, int minor, int ewx, int ewy)
{
input_mt_slot(ts->input, touch_id);
@@ -117,7 +194,7 @@ static int ss_report_touch(struct ss_ts_data *ts, int touch_id, int action, int
break;
case SS_EVENT_COORDINATE_ACTION_UP:
input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, false);
break;
break;
}
input_mt_report_pointer_emulation(ts->input, true);
input_sync(ts->input);
@@ -133,7 +210,7 @@ static int ss_handle_coordinate_event(struct ss_ts_data *ts, uint8_t *event)
touch_id = (event_coord->tid - 1);
if(touch_id < SS_MAX_FINGERS + SS_MAX_HOVER && touch_id >= 0){
dev_dbg(&ts->client->dev, "%s: coordinate event: ID: %d ACT: %d X: %d, Y: %d Z: %d Type: %d Major: %d Minor: %d WX: %d WY: %d EWX: %d EWY: %d",
dev_dbg(&ts->client->dev, "%s: coordinate event: ID: %d ACT: %d X: %d, Y: %d Z: %d Type: %d Major: %d Minor: %d WX: %d WY: %d EWX: %d EWY: %d\n",
__func__,
touch_id,
event_coord->tchsta,
@@ -149,7 +226,7 @@ static int ss_handle_coordinate_event(struct ss_ts_data *ts, uint8_t *event)
ret = ss_report_touch(
ts,
touch_id,
touch_id,
event_coord->tchsta,
(event_coord->x_11_4 << 4) | (event_coord->x_3_0),
(event_coord->y_11_4 << 4) | (event_coord->y_3_0),
@@ -161,7 +238,7 @@ static int ss_handle_coordinate_event(struct ss_ts_data *ts, uint8_t *event)
event_coord->ewy
);
if(ret < 0){
dev_err(&ts->client->dev, "%s: report touch failed: %d", __func__, ret);
dev_err(&ts->client->dev, "%s: report touch failed: %d\n", __func__, ret);
}
// Maybe there's more???
@@ -239,12 +316,294 @@ static void ss_ts_reset(struct ss_ts_data *ts)
}
}
static int ss_get_boot_status(struct ss_ts_data *ts)
{
int ret = 0;
uint8_t result[1];
ret = ss_read(ts, SS_BOOT_STATUS_CMD, result, 1);
if(ret < 0){
dev_err(&ts->client->dev, "%s: reading boot status: %d\n", __func__, ret);
return ret;
}
return result[0];
}
static int ss_get_protocol_id(struct ss_ts_data *ts)
{
int ret = 0;
uint8_t result[1];
ret = ss_read(ts, SS_PROTOCOL_ID_CMD, result, 1);
if(ret < 0){
dev_err(&ts->client->dev, "%s: reading protocol id: %d\n", __func__, ret);
return ret;
}
return result[0];
}
static int ss_enter_boot_mode(struct ss_ts_data *ts)
{
int ret = 0;
int boot_status = 0;
uint8_t firmware_password[2] = {0x55, 0xAC};
boot_status = ss_get_boot_status(ts);
if(boot_status < 0) return boot_status;
if(boot_status != SS_BOOT_STATUS_BOOT){
// Enter firmware mode
ret = ss_write(ts, SS_ENTER_FW_MODE_CMD, firmware_password, sizeof(firmware_password));
if(ret < 0){
dev_err(&ts->client->dev, "%s: entering fw mode: %d\n", __func__, ret);
return ret;
}
msleep(200);
// Check that it worked
boot_status = ss_get_boot_status(ts);
if(boot_status != SS_BOOT_STATUS_BOOT) {
dev_err(&ts->client->dev, "%s: didn't enter fw mode. Boot status: %d\n", __func__, boot_status);
return -1;
}
}
return 0;
}
static int ss_wait_for_ack(struct ss_ts_data *ts, uint8_t ack)
{
int ret = 0;
uint8_t event[8];
uint32_t i;
dev_info(&ts->client->dev, "%s: waiting for ack %d\n", __func__, ack);
for(i = 0; i < 100; i++){
// Read event
ret = ss_read(ts, SS_ONE_EVENT_CMD, event, 8);
if(ret < 0){
dev_err(&ts->client->dev, "%s: reading event failed: %d\n", __func__, ret);
return ret;
}
// Check if it's right
if(((event[0] >> 2) & 0xF) == ack){
dev_info(&ts->client->dev, "%s: ack received\n", __func__);
return 0;
}
msleep(20);
}
dev_err(&ts->client->dev, "%s: timed out\n", __func__);
return -ETIMEDOUT;
}
static int ss_exit_boot_mode(struct ss_ts_data *ts)
{
int ret = 0;
int boot_status = 0, protocol_id;
uint8_t firmware_password[2] = {0x55, 0xAC};
boot_status = ss_get_boot_status(ts);
if(boot_status < 0) return boot_status;
if(boot_status != SS_BOOT_STATUS_APP){
// Software reset
protocol_id = ss_get_protocol_id(ts);
if(IS_ERR(protocol_id)){
dev_err(&ts->client->dev, "%s: couldn't read protocol ID: %d\n", __func__, protocol_id);
return protocol_id;
}
ret = ss_write(ts, (protocol_id == SS_PROTOCOL_APP) ? SS_SW_RESET_APP_CMD : SS_SW_RESET_BOOT_CMD, NULL, 0);
if(ret < 0){
dev_err(&ts->client->dev, "%s: couldn't perform SW reset: %d\n", __func__, ret);
return ret;
}
msleep(100);
// Wait for boot acknowledge
ret = ss_wait_for_ack(ts, SS_ACK_BOOT_COMPLETE);
if(ret != 0){
dev_err(&ts->client->dev, "%s: SW reset failed: %d\n", __func__, ret);
return ret;
}
// Check that it worked
boot_status = ss_get_boot_status(ts);
if(boot_status != SS_BOOT_STATUS_APP) {
dev_err(&ts->client->dev, "%s: didn't exit fw mode. Boot status: %d\n", __func__, boot_status);
return -1;
}
}
return 0;
}
static int ss_read_chip_id(struct ss_ts_data *ts)
{
int ret = 0;
ret = ss_read(ts, SS_CHIP_ID_CMD, ts->chip_id, 3);
if(ret < 0){
dev_err(&ts->client->dev, "%s: reading chip id: %d\n", __func__, ret);
return ret;
}
return 0;
}
static int ss_power_init(struct ss_ts_data *ts)
{
int ret = 0;
ts->vdd = devm_regulator_get(&ts->client->dev, "vdd");
if(IS_ERR(ts->vdd)){
ret = PTR_ERR(ts->vdd);
if(ret == -EPROBE_DEFER){
dev_info(&ts->client->dev, "%s: regulator vdd not ready. Deferring probe...\n", __func__);
} else {
dev_err(&ts->client->dev, "%s: getting regulator vdd: %d\n", __func__, ret);
}
goto err;
}
// Enabling crashes the kernel.
// Not sure why, but it's a fixed regulator which is always enabled anyway
//ret = regulator_enable(ts->vdd);
if(ret){
dev_err(&ts->client->dev, "%s: enabling regulator vdd: %d\n", __func__, ret);
}
err:
return ret;
}
static int ss_erase_pages(struct ss_ts_data *ts, uint32_t start_page, uint32_t len)
{
uint8_t data[4];
data[0] = (uint8_t)((start_page >> 8) & 0xFF);
data[1] = (uint8_t)(start_page & 0xFF);
data[2] = (uint8_t)((len >> 8) & 0xFF);
data[3] = (uint8_t)(len & 0xFF);
dev_info(&ts->client->dev, "%s: erasing pages: 0x%x - 0x%x\n", __func__, start_page, start_page + len);
return ss_write(ts, SS_FLASH_ERASE_CMD, data, sizeof(data));
}
static uint8_t ss_calculate_checksum(uint8_t *data, uint32_t len){
uint8_t result = 0;
uint32_t i;
for(i = 0; i<len; i++){
result += data[i];
}
return result;
}
static int ss_flash_memory(struct ss_ts_data *ts, uint32_t addr, uint8_t *data, uint32_t len)
{
int ret = 0;
uint32_t page_index, data_len, i;
uint8_t buf[2 + SS_PAGE_SIZE + 1];
uint32_t num_pages = (len / SS_PAGE_SIZE) + ((len % SS_PAGE_SIZE) != 0);
uint32_t start_page = (addr / SS_PAGE_SIZE);
if ((start_page * SS_PAGE_SIZE) != addr) {
dev_err(&ts->client->dev, "%s: non-aligned flashing is not supported!\n", __func__);
return -EINVAL;
}
ret = ss_erase_pages(ts, start_page, num_pages);
if(IS_ERR(ret)){
dev_err(&ts->client->dev, "%s: failed to erase pages: %d\n", __func__, ret);
goto err;
}
msleep(100);
dev_info(&ts->client->dev, "%s: flashing pages: 0x%x - 0x%x\n", __func__, start_page, start_page + num_pages);
for(i = 0; i<num_pages; i++){
page_index = start_page + i;
// Set page index
buf[0] = (uint8_t)((page_index >> 8) & 0xFF);
buf[1] = (uint8_t)(page_index & 0xFF);
// Append data (zero padded)
memset(&buf[2], 0, SS_PAGE_SIZE);
data_len = min(SS_PAGE_SIZE, (len - (i * SS_PAGE_SIZE)));
memcpy(&buf[2], (data + (i * SS_PAGE_SIZE)), data_len);
// Append checksum
buf[SS_PAGE_SIZE + 2] = ss_calculate_checksum(buf, (SS_PAGE_SIZE + 2));
// Write the page
ret = ss_write_bulk(ts, SS_FLASH_WRITE_CMD, buf, sizeof(buf));
if(IS_ERR(ret)){
dev_err(&ts->client->dev, "%s: failed to write page %d: %d\n", __func__, page_index, ret);
goto err;
}
msleep(5);
}
dev_info(&ts->client->dev, "%s: flashed pages: 0x%x - 0x%x\n", __func__, start_page, start_page + num_pages);
err:
return ret;
}
static int ss_flash_firmware(struct ss_ts_data *ts, struct firmware *fw)
{
int ret = 0;
uint32_t i;
uint8_t *data_ptr = (uint8_t *) fw->data;
ss_ts_fw_header *header;
ss_ts_fw_chunk *chunk_header;
// Parse header
header = (ss_ts_fw_header *) data_ptr;
data_ptr += sizeof(ss_ts_fw_header);
if(header->signature != SS_HEADER_SIGNATURE){
dev_err(&ts->client->dev, "%s: header signature mismatch\n", __func__);
return -ENODATA;
}
for (i = 0; i<header->num_chunk; i++){
// Parse chunk
chunk_header = (ss_ts_fw_chunk *) data_ptr;
data_ptr += sizeof(ss_ts_fw_chunk);
if(chunk_header->signature != SS_CHUNK_SIGNATURE){
dev_err(&ts->client->dev, "%s: chunk %d signature mismatch\n", __func__, i);
return -ENODATA;
}
// Flash chunk
ret = ss_flash_memory(ts, chunk_header->addr, data_ptr, chunk_header->size);
if(IS_ERR(ret)){
return ret;
}
data_ptr += chunk_header->size;
// TODO: check that flashed pages match
}
}
static int ss_enable_sensing(struct ss_ts_data *ts)
{
int ret = 0;
ret = ss_write(ts, SS_ENABLE_SENSING_CMD, NULL, 0);
if(ret < 0){
dev_err(&ts->client->dev, "%s: enabling sensing failed: %d\n", __func__, ret);
return ret;
}
dev_info(&ts->client->dev, "%s: enabled sensing\n", __func__);
return 0;
}
static int ss_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct ss_ts_data *ts;
int error;
struct firmware *fw;
int error, boot_status;
dev_info(&client->dev, "SAMSUNG PANEL probe");
dev_info(&client->dev, "SAMSUNG PANEL probe\n");
ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL);
if (!ts)
@@ -253,6 +612,12 @@ static int ss_ts_probe(struct i2c_client *client, const struct i2c_device_id *id
ts->client = client;
i2c_set_clientdata(client, ts);
// Init regulator
error = ss_power_init(ts);
if(IS_ERR(error)) {
return error;
}
ts->interrupt_gpio = devm_gpiod_get(&client->dev, "interrupt", GPIOD_IN);
if (IS_ERR(ts->interrupt_gpio)) {
error = PTR_ERR(ts->interrupt_gpio);
@@ -277,8 +642,56 @@ static int ss_ts_probe(struct i2c_client *client, const struct i2c_device_id *id
return error;
}
// Reset panel
ss_ts_reset(ts);
// Check that the panel is running in app mode, otherwise flash
boot_status = ss_get_boot_status(ts);
if (boot_status != SS_BOOT_STATUS_APP) {
dev_info(&client->dev, "Device not in app mode (current status: 0x%x). Attempting reflash\n", boot_status);
// Enter boot mode
error = ss_enter_boot_mode(ts);
if(error < 0) {
return error;
}
// Check that the chip ID matches
error = ss_read_chip_id(ts);
if(error < 0) {
return error;
} else if(memcmp(ts->chip_id, expected_chip_id, sizeof(expected_chip_id))){
dev_err(&client->dev, "Chip ID does not match expected: 0x%x%x%x\n", ts->chip_id[0], ts->chip_id[1], ts->chip_id[2]);
return -1;
}
// Load firmware
error = request_firmware(&fw, SS_FIRMWARE, &client->dev);
if(error < 0){
dev_err(&client->dev, "firmware request failed: %d\n", error);
return error;
}
// Flash firmware
error = ss_flash_firmware(ts, fw);
if(error < 0){
dev_err(&client->dev, "firmware flash failed: %d\n", error);
return error;
}
// Exit boot mode
error = ss_exit_boot_mode(ts);
if(error < 0) {
return error;
}
}
// Enable sensing
error = ss_enable_sensing(ts);
if(error < 0) {
return error;
}
ts->input = devm_input_allocate_device(&client->dev);
if (!ts->input) {
dev_err(&client->dev, "failed to allocate input device\n");
@@ -316,12 +729,15 @@ static int ss_ts_probe(struct i2c_client *client, const struct i2c_device_id *id
return error;
}
// Handle all events currently stored
ss_ts_irq_handler(client->irq, ts);
return 0;
}
static int ss_ts_remove(struct i2c_client *client)
{
dev_info(&client->dev, "SAMSUNG PANEL remove");
dev_info(&client->dev, "SAMSUNG PANEL remove\n");
return 0;
}

BIN
firmware/samsung_touch.img Normal file

Binary file not shown.