blob: a5549d34e314a0c86bf33878d707d756e936e246 [file] [log] [blame]
/*
* Wacom Penabled Driver for I2C
*
* Copyright (c) 2011-2014 Tatsunosuke Tobita, Wacom.
* <tobita.tatsunosuke@wacom.co.jp>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software
* Foundation; either version of 2 of the License,
* or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include "wacom.h"
#include "w9018_flash.h"
bool wacom_i2c_set_feature(struct wacom_i2c *wac_i2c, u8 report_id,
unsigned int buf_size, u8 *data, u16 cmdreg,
u16 datareg)
{
int i, ret = -1;
int total = SFEATURE_SIZE + buf_size;
u8 *sFeature = NULL;
bool bRet = false;
sFeature = kzalloc(sizeof(u8) * total, GFP_KERNEL);
if (!sFeature) {
input_err(true, &wac_i2c->client->dev,
"%s cannot preserve memory\n", __func__);
goto out;
}
memset(sFeature, 0, sizeof(u8) * total);
sFeature[0] = (u8) (cmdreg & 0x00ff);
sFeature[1] = (u8) ((cmdreg & 0xff00) >> 8);
sFeature[2] = (RTYPE_FEATURE << 4) | report_id;
sFeature[3] = CMD_SET_FEATURE;
sFeature[4] = (u8) (datareg & 0x00ff);
sFeature[5] = (u8) ((datareg & 0xff00) >> 8);
if ((buf_size + 2) > 255) {
sFeature[6] = (u8) ((buf_size + 2) & 0x00ff);
sFeature[7] = (u8) (((buf_size + 2) & 0xff00) >> 8);
} else {
sFeature[6] = (u8) (buf_size + 2);
sFeature[7] = (u8) (0x00);
}
for (i = 0; i < buf_size; i++)
sFeature[i + SFEATURE_SIZE] = *(data + i);
ret = wacom_i2c_send(wac_i2c, sFeature, total, WACOM_I2C_MODE_BOOT);
if (ret != total) {
input_err(true, &wac_i2c->client->dev,
"Sending Set_Feature failed sent bytes: %d\n", ret);
goto err;
}
usleep_range(60, 61);
bRet = true;
err:
kfree(sFeature);
sFeature = NULL;
out:
return bRet;
}
bool wacom_i2c_get_feature(struct wacom_i2c *wac_i2c, u8 report_id,
unsigned int buf_size, u8 *data, u16 cmdreg,
u16 datareg, int delay)
{
int ret = -1;
u8 *recv = NULL;
bool bRet = false;
u8 gFeature[] = {
(u8) (cmdreg & 0x00ff),
(u8) ((cmdreg & 0xff00) >> 8),
(RTYPE_FEATURE << 4) | report_id,
CMD_GET_FEATURE,
(u8) (datareg & 0x00ff),
(u8) ((datareg & 0xff00) >> 8)
};
/*"+ 2", adding 2 more spaces for organizeing again later in the passed data, "data" */
recv = kzalloc(sizeof(u8) * (buf_size + 0), GFP_KERNEL);
if (!recv) {
input_err(true, &wac_i2c->client->dev,
"%s cannot preserve memory\n", __func__);
goto err_alloc_memory;
}
/*Append 2 bytes for length low and high of the byte */
memset(recv, 0, sizeof(u8) * (buf_size + 0));
ret =
wacom_i2c_send(wac_i2c, gFeature, GFEATURE_SIZE,
WACOM_I2C_MODE_BOOT);
if (ret != GFEATURE_SIZE) {
input_info(true, &wac_i2c->client->dev,
"%s Sending Get_Feature failed; sent bytes: %d\n",
__func__, ret);
udelay(delay);
goto err_fail_i2c;
}
udelay(delay);
ret = wacom_i2c_recv(wac_i2c, recv, buf_size, WACOM_I2C_MODE_BOOT);
if (ret != buf_size) {
input_err(true, &wac_i2c->client->dev,
"%s Receiving data failed; recieved bytes: %d\n",
__func__, ret);
goto err_fail_i2c;
}
/*Coppy data pointer, subtracting the first two bytes of the length */
memcpy(data, (recv + 0), buf_size);
bRet = true;
err_fail_i2c:
kfree(recv);
recv = NULL;
err_alloc_memory:
return bRet;
}
static int wacom_flash_cmd(struct wacom_i2c *wac_i2c)
{
u8 command[10];
int len = 0;
int ret = -1;
command[len++] = 0x0d;
command[len++] = FLASH_START0;
command[len++] = FLASH_START1;
command[len++] = FLASH_START2;
command[len++] = FLASH_START3;
command[len++] = FLASH_START4;
command[len++] = FLASH_START5;
command[len++] = 0x0d;
ret = wacom_i2c_send(wac_i2c, command, len, WACOM_I2C_MODE_BOOT);
if (ret < 0) {
input_err(true, &wac_i2c->client->dev,
"Sending flash command failed\n");
return -EXIT_FAIL;
}
msleep(300);
return 0;
}
int flash_query_w9018(struct wacom_i2c *wac_i2c)
{
bool bRet = false;
u8 command[CMD_SIZE];
u8 response[RSP_SIZE];
int ECH, len = 0;
command[len++] = BOOT_CMD_REPORT_ID; /* Report:ReportID */
command[len++] = BOOT_QUERY; /* Report:Boot Query command */
command[len++] = ECH = 7; /* Report:echo */
bRet =
wacom_i2c_set_feature(wac_i2c, REPORT_ID_1, len, command, COMM_REG,
DATA_REG);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature\n", __func__);
return -EXIT_FAIL_SEND_QUERY_COMMAND;
}
bRet =
wacom_i2c_get_feature(wac_i2c, REPORT_ID_2, RSP_SIZE, response,
COMM_REG, DATA_REG, (10 * 1000));
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to get feature\n", __func__);
return -EXIT_FAIL_SEND_QUERY_COMMAND;
}
if ((response[3] != QUERY_CMD) || (response[4] != ECH)) {
input_err(true, &wac_i2c->client->dev, "%s res3:%x res4:%x\n",
__func__, response[3], response[4]);
return -EXIT_FAIL_SEND_QUERY_COMMAND;
}
if (response[5] != QUERY_RSP) {
input_err(true, &wac_i2c->client->dev, "%s res5:%x\n",
__func__, response[5]);
return -EXIT_FAIL_SEND_QUERY_COMMAND;
}
input_info(true, &wac_i2c->client->dev, "QUERY SUCCEEDED\n");
return 0;
}
static bool flash_blver_w9018(struct wacom_i2c *wac_i2c, int *blver)
{
bool bRet = false;
u8 command[CMD_SIZE];
u8 response[RSP_SIZE];
int ECH, len = 0;
command[len++] = BOOT_CMD_REPORT_ID; /* Report:ReportID */
command[len++] = BOOT_BLVER; /* Report:Boot Version command */
command[len++] = ECH = 7; /* Report:echo */
bRet =
wacom_i2c_set_feature(wac_i2c, REPORT_ID_1, len, command, COMM_REG,
DATA_REG);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature1\n", __func__);
return false;
}
bRet =
wacom_i2c_get_feature(wac_i2c, REPORT_ID_2, RSP_SIZE, response,
COMM_REG, DATA_REG, (10 * 1000));
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s 2 failed to set feature\n", __func__);
return false;
}
if ((response[3] != BOOT_CMD) || (response[4] != ECH)) {
input_err(true, &wac_i2c->client->dev, "%s res3:%x res4:%x\n",
__func__, response[3], response[4]);
return false;
}
*blver = (int)response[5];
return true;
}
static bool flash_mputype_w9018(struct wacom_i2c *wac_i2c, int *pMpuType)
{
bool bRet = false;
u8 command[CMD_SIZE];
u8 response[RSP_SIZE];
int ECH, len = 0;
command[len++] = BOOT_CMD_REPORT_ID; /* Report:ReportID */
command[len++] = BOOT_MPU; /* Report:Boot Query command */
command[len++] = ECH = 7; /* Report:echo */
bRet =
wacom_i2c_set_feature(wac_i2c, REPORT_ID_1, len, command, COMM_REG,
DATA_REG);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature\n", __func__);
return false;
}
bRet =
wacom_i2c_get_feature(wac_i2c, REPORT_ID_2, RSP_SIZE, response,
COMM_REG, DATA_REG, (10 * 1000));
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to get feature\n", __func__);
return false;
}
if ((response[3] != MPU_CMD) || (response[4] != ECH)) {
input_err(true, &wac_i2c->client->dev, "%s res3:%x res4:%x\n",
__func__, response[3], response[4]);
return false;
}
*pMpuType = (int)response[5];
return true;
}
static bool flash_end_w9018(struct wacom_i2c *wac_i2c)
{
bool bRet = false;
u8 command[CMD_SIZE];
int len = 0;
command[len++] = BOOT_CMD_REPORT_ID;
command[len++] = BOOT_EXIT;
command[len++] = 0;
bRet =
wacom_i2c_set_feature(wac_i2c, REPORT_ID_1, len, command, COMM_REG,
DATA_REG);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature 1\n", __func__);
return false;
}
return true;
}
static int check_progress(u8 *data, size_t size, u8 cmd, u8 ech)
{
if (data[0] != cmd || data[1] != ech) {
pr_err("%s sec_epen: %s failed to erase\n", SECLOG, __func__);
return -EXIT_FAIL;
}
switch (data[2]) {
case PROCESS_CHKSUM1_ERR:
case PROCESS_CHKSUM2_ERR:
case PROCESS_TIMEOUT_ERR:
pr_err("%s sec_epen: %s error: %x\n", SECLOG, __func__,
data[2]);
return -EXIT_FAIL;
}
return data[2];
}
static bool flash_erase_all(struct wacom_i2c *wac_i2c)
{
bool bRet = false;
u8 command[BOOT_CMD_SIZE];
u8 response[BOOT_RSP_SIZE];
int i, len = 0;
int ECH, sum = 0;
int ret = -1;
command[len++] = 7;
command[len++] = ERS_ALL_CMD;
command[len++] = ECH = 2;
command[len++] = ERS_ECH2;
/* Preliminarily stored data that cannnot appear here, but in wacom_set_feature() */
sum += 0x05;
sum += 0x07;
for (i = 0; i < len; i++)
sum += command[i];
command[len++] = ~sum + 1;
bRet =
wacom_i2c_set_feature(wac_i2c, REPORT_ID_1, len, command, COMM_REG,
DATA_REG);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature\n", __func__);
return false;
}
do {
bRet =
wacom_i2c_get_feature(wac_i2c, REPORT_ID_2, BOOT_RSP_SIZE,
response, COMM_REG, DATA_REG, 0);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature\n", __func__);
return false;
}
ret = check_progress(&response[3], (BOOT_RSP_SIZE - 3), ERS_ALL_CMD, ECH);
if (ret < 0)
return false;
} while (ret == PROCESS_INPROGRESS);
return true;
}
static bool flash_write_block_w9018(struct wacom_i2c *wac_i2c, char *flash_data,
unsigned long ulAddress, u8 *pcommand_id,
int *ECH)
{
const int MAX_COM_SIZE = (8 + FLASH_BLOCK_SIZE + 2); /* 8: num of command[0] to command[7] */
/* FLASH_BLOCK_SIZE: unit to erase the block */
/* Num of Last 2 checksums */
bool bRet = false;
u8 command[300];
unsigned char sum = 0;
int i;
command[0] = BOOT_CMD_REPORT_ID; /* Report:ReportID */
command[1] = BOOT_WRITE_FLASH; /* Report:program command */
command[2] = *ECH = ++(*pcommand_id); /* Report:echo */
command[3] = ulAddress & 0x000000ff;
command[4] = (ulAddress & 0x0000ff00) >> 8;
command[5] = (ulAddress & 0x00ff0000) >> 16;
command[6] = (ulAddress & 0xff000000) >> 24; /* Report:address(4bytes) */
command[7] = 0x20;
/*Preliminarily stored data that cannnot appear here, but in wacom_set_feature() */
sum = (0x05 + 0x0c + 0x01);
for (i = 0; i < 8; i++)
sum += command[i];
command[MAX_COM_SIZE - 2] = ~sum + 1; /* Report:command checksum */
sum = 0;
for (i = 8; i < (FLASH_BLOCK_SIZE + 8); i++) {
command[i] = flash_data[ulAddress + (i - 8)];
sum += flash_data[ulAddress + (i - 8)];
}
command[MAX_COM_SIZE - 1] = ~sum + 1; /* Report:data checksum */
/*Subtract 8 for the first 8 bytes */
bRet =
wacom_i2c_set_feature(wac_i2c, REPORT_ID_1, (BOOT_CMD_SIZE + 4 - 8),
command, COMM_REG, DATA_REG);
if (!bRet) {
input_err(true, &wac_i2c->client->dev,
"%s failed to set feature\n", __func__);
return false;
}
udelay(50);
return true;
}
static bool flash_write_w9018(struct wacom_i2c *wac_i2c,
unsigned char *flash_data,
unsigned long start_address,
unsigned long *max_address)
{
bool bRet = false;
u8 command_id = 0;
u8 response[BOOT_RSP_SIZE];
int i, j, ECH = 0, ECH_len = 0;
int ECH_ARRAY[3];
int ret = -1;
unsigned long ulAddress;
j = 0;
for (ulAddress = start_address; ulAddress < *max_address;
ulAddress += FLASH_BLOCK_SIZE) {
for (i = 0; i < FLASH_BLOCK_SIZE; i++) {
if (flash_data[ulAddress + i] != 0xFF)
break;
}
if (i == (FLASH_BLOCK_SIZE))
continue;
bRet =
flash_write_block_w9018(wac_i2c, flash_data, ulAddress,
&command_id, &ECH);
if (!bRet)
return false;
if (ECH_len == 3)
ECH_len = 0;
ECH_ARRAY[ECH_len++] = ECH;
if (ECH_len == 3) {
for (j = 0; j < 3; j++) {
do {
bRet =
wacom_i2c_get_feature(wac_i2c,
REPORT_ID_2,
BOOT_RSP_SIZE,
response,
COMM_REG,
DATA_REG, 50);
if (!bRet) {
input_err(true,
&wac_i2c->client->dev,
"%s failed to set feature\n",
__func__);
return false;
}
ret = check_progress(&response[3], (BOOT_RSP_SIZE - 3), 0x01, ECH_ARRAY[j]);
if (ret < 0) {
input_err(true,
&wac_i2c->client->dev,
"%s mismatched echo array\n",
__func__);
return false;
}
} while (ret == PROCESS_INPROGRESS);
}
}
}
return true;
}
int wacom_i2c_flash_w9018(struct wacom_i2c *wac_i2c, unsigned char *fw_data)
{
struct i2c_client *client = wac_i2c->client;
bool bRet = false;
int iBLVer = 0, iMpuType = 0;
unsigned long max_address = W9018_END_ADDR; /* Max.address of Load data */
unsigned long start_address = W9018_START_ADDR; /* Start.address of Load data */
/*Obtain boot loader version */
if (!flash_blver_w9018(wac_i2c, &iBLVer)) {
input_err(true, &client->dev,
"%s failed to get Boot Loader version\n", __func__);
return -EXIT_FAIL_GET_BOOT_LOADER_VERSION;
}
input_info(true, &client->dev, "BL version: %x\n", iBLVer);
/*Obtain MPUtype: this can be manually done in user space */
if (!flash_mputype_w9018(wac_i2c, &iMpuType)) {
input_err(true, &client->dev,
"%s failed to get MPU type\n", __func__);
return -EXIT_FAIL_GET_MPU_TYPE;
}
if (iMpuType != MPU_W9018) {
input_err(true, &client->dev,
"MPU is not for W9018 : %x\n", iMpuType);
return -EXIT_FAIL_GET_MPU_TYPE;
}
input_info(true, &client->dev, "MPU type: %x\n", iMpuType);
/*-----------------------------------*/
/*Flashing operation starts from here */
/*Erase the current loaded program */
input_info(true, &client->dev,
"%s erasing the current firmware\n", __func__);
bRet = flash_erase_all(wac_i2c);
if (!bRet) {
input_err(true, &client->dev,
"%s failed to erase the user program\n", __func__);
return -EXIT_FAIL_ERASE;
}
/*Write the new program */
input_info(true, &client->dev, "%s writing new firmware\n",
__func__);
bRet =
flash_write_w9018(wac_i2c, wac_i2c->fw_data, start_address,
&max_address);
if (!bRet) {
input_err(true, &client->dev,
"%s failed to write firmware\n", __func__);
return -EXIT_FAIL_WRITE_FIRMWARE;
}
/*Return to the user mode */
input_info(true, &client->dev, "%s closing the boot mode\n",
__func__);
bRet = flash_end_w9018(wac_i2c);
if (!bRet) {
input_err(true, &client->dev,
"%s closing boot mode failed\n", __func__);
return -EXIT_FAIL_WRITING_MARK_NOT_SET;
}
input_info(true, &client->dev,
"%s write and verify completed\n", __func__);
return EXIT_OK;
}
int wacom_i2c_flash(struct wacom_i2c *wac_i2c)
{
int ret;
if (wac_i2c->fw_data == NULL) {
input_err(true, &wac_i2c->client->dev,
"epen:Data is NULL. Exit.\n");
return -EINVAL;
}
wacom_compulsory_flash_mode(wac_i2c, true);
wacom_reset_hw(wac_i2c);
ret = wacom_flash_cmd(wac_i2c);
if (ret < 0) {
input_err(true, &wac_i2c->client->dev,
"epen:%s cannot send flash command\n", __func__);
ret = -EXIT_FAIL;
goto out;
}
ret = flash_query_w9018(wac_i2c);
if (ret < 0) {
input_err(true, &wac_i2c->client->dev,
"epen:%s Error: cannot send query\n", __func__);
ret = -EXIT_FAIL;
goto out;
}
ret = wacom_i2c_flash_w9018(wac_i2c, wac_i2c->fw_data);
if (ret < 0) {
input_err(true, &wac_i2c->client->dev,
"epen:%s Error: flash failed\n", __func__);
ret = -EXIT_FAIL;
goto out;
}
msleep(200);
out:
wac_i2c->function_result &= ~EPEN_EVENT_SURVEY;
wac_i2c->survey_mode = EPEN_SURVEY_MODE_NONE;
wacom_compulsory_flash_mode(wac_i2c, false);
wacom_reset_hw(wac_i2c);
return ret;
}