921 lines
21 KiB
C
921 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2024-2025, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/firmware.h>
|
|
#include <sound/soc.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/extcon-provider.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/usb/usbpd.h>
|
|
#include <linux/bitfield.h>
|
|
|
|
#define I2C_TRAN_SIZE 32
|
|
#define DATA_PAGE_SIZE 4096
|
|
#define DATA_BUF_MAX_SIZE (DATA_PAGE_SIZE+12)
|
|
|
|
#define WRITE_BUF_MAX_SIZE DATA_BUF_MAX_SIZE
|
|
#define READ_BUF_MAX_SIZE DATA_BUF_MAX_SIZE
|
|
#define BLOCK_ERASE_DELAY_TIME 500
|
|
#define WRITE_DATA_DELAY_TIME 1
|
|
|
|
#define DEBUG_FLG KERN_INFO
|
|
|
|
#define KTM5030_GPIO_HIGH 0
|
|
#define KTM5030_GPIO_LOW 1
|
|
|
|
struct ktm5030_reg_cfg {
|
|
u8 reg;
|
|
u8 val;
|
|
};
|
|
|
|
enum ktm5030_fw_upgrade_status {
|
|
UPDATE_DONE = 0,
|
|
UPDATE_START,
|
|
UPDATE_IROM, /* enter force-IROM mode */
|
|
UPDATE_DRIVERWRITE, /* enter isp driver write state */
|
|
UPDATE_DRIVERIMG, /* finish writing isp driver */
|
|
UPDATE_RUNDRIVER, /* run isp driver */
|
|
UPDATE_ERASEBANK, /* erase inactive bank */
|
|
UPDATE_COREIMG, /* finish writing core image */
|
|
};
|
|
|
|
|
|
#define KTM5030_FW_REG 0x10
|
|
#define KTM5030_DRIVER_IMG "driver.bin"
|
|
#define KTM5030_CORE_IMG "core.bin"
|
|
|
|
/* isp write related */
|
|
const char isp_w_enter_irom[] = {0x51, 0x89, 0xc2, 0x00, 0x00, 0x07,
|
|
0x07, 0x50, 0x00, 0x00, 0x10, 0xbc};
|
|
const char isp_w_enter_isp[] = {0x51, 0x89, 0xc2, 0x00, 0x00, 0x07,
|
|
0x07, 0x50, 0x00, 0x00, 0x11, 0xbd};
|
|
const char isp_w_run_isp[] = {0x51, 0x89, 0xc2, 0x00, 0x00, 0x07,
|
|
0x07, 0x50, 0x00, 0x00, 0x12, 0xbe};
|
|
const char isp_w_erase_bank[] = {0x51, 0x89, 0xc2, 0x00, 0x00, 0x07,
|
|
0x07, 0x50, 0x00, 0x00, 0x20, 0x8c};
|
|
const char isp_w_hard_reset[] = {0x51, 0x89, 0xc2, 0x00, 0x00, 0x07,
|
|
0x07, 0x50, 0x00, 0x01, 0x10, 0xbd};
|
|
const char isp_w_write_primer[] = {0x51, 0x85, 0xc2, 0x00,
|
|
0x00, 0x03, 0x10, 0xe3};
|
|
|
|
/* isp read related */
|
|
const char isp_r_ack[] = {0xe6, 0x85, 0xc2, 0x00, 0x00, 0x03, 0x0c, 0xfe};
|
|
const char isp_r_nack[] = {0xe6, 0x85, 0xc2, 0x00, 0x00, 0x03, 0x0b, 0xfe};
|
|
const char isp_r_busy[] = {0xe6, 0x80, 0x36};
|
|
|
|
struct ktm5030 {
|
|
struct extcon_dev *edev;
|
|
struct device *dev;
|
|
|
|
struct i2c_client *i2c_client;
|
|
|
|
u8 i2c_wbuf[WRITE_BUF_MAX_SIZE];
|
|
u8 i2c_rbuf[READ_BUF_MAX_SIZE];
|
|
int n_i2c_msg;
|
|
struct i2c_msg *i2c_msg_buf;
|
|
|
|
enum ktm5030_fw_upgrade_status fw_status;
|
|
|
|
struct mutex mutex;
|
|
|
|
unsigned int chip_fw_version;
|
|
unsigned int image_fw_version;
|
|
u32 reset_gpio;
|
|
|
|
};
|
|
|
|
/*
|
|
* Calculate CRC16
|
|
*/
|
|
static unsigned short calculate_crc_16(const void *buf, unsigned int buf_size)
|
|
{
|
|
const unsigned char *byte_buf;
|
|
unsigned short crc;
|
|
unsigned char data;
|
|
unsigned char i;
|
|
unsigned char flag;
|
|
|
|
byte_buf = (const unsigned char *)buf;
|
|
|
|
crc = 0x1021;
|
|
for ( ; buf_size > 0; --buf_size) {
|
|
data = *byte_buf++;
|
|
for (i = 8; i > 0; --i) {
|
|
flag = data ^ (crc >> 8);
|
|
crc <<= 1;
|
|
if (flag & 0x80)
|
|
crc ^= 0x1021;
|
|
data <<= 1;
|
|
}
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/*
|
|
* Write raw data
|
|
*/
|
|
static bool ktm5030_write_raw(struct ktm5030 *pdata, const u8 *buf, int size)
|
|
{
|
|
bool ret = true;
|
|
int i2c_pages = 0, i2c_res = 0;
|
|
struct i2c_client *client = pdata->i2c_client;
|
|
const u8 *pbuf;
|
|
int i = 0;
|
|
|
|
if (size > (WRITE_BUF_MAX_SIZE)) {
|
|
dev_err(pdata->dev, "invalid write buffer size %d\n", size);
|
|
return false;
|
|
}
|
|
i2c_pages = size / I2C_TRAN_SIZE;
|
|
i2c_res = size % I2C_TRAN_SIZE;
|
|
pbuf = buf;
|
|
if (i2c_pages > pdata->n_i2c_msg) {
|
|
dev_err(pdata->dev, "invalid i2c_pages %d\n", i2c_pages);
|
|
return false;
|
|
}
|
|
for (i = 0; i < i2c_pages; i++) {
|
|
pdata->i2c_msg_buf[i].addr = client->addr;
|
|
pdata->i2c_msg_buf[i].flags = 0;
|
|
pdata->i2c_msg_buf[i].len = I2C_TRAN_SIZE;
|
|
memcpy(pdata->i2c_msg_buf[i].buf, pbuf, I2C_TRAN_SIZE);
|
|
pbuf += I2C_TRAN_SIZE;
|
|
}
|
|
|
|
if (i2c_res) {
|
|
pdata->i2c_msg_buf[i].addr = client->addr;
|
|
pdata->i2c_msg_buf[i].flags = 0;
|
|
pdata->i2c_msg_buf[i].len = i2c_res;
|
|
memcpy(pdata->i2c_msg_buf[i].buf, pbuf, i2c_res);
|
|
i2c_pages++;
|
|
}
|
|
|
|
if (i2c_transfer(client->adapter,
|
|
pdata->i2c_msg_buf, i2c_pages) < i2c_pages) {
|
|
dev_err(pdata->dev, "i2c_transfer failed\n");
|
|
return false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Read reg : values
|
|
*/
|
|
static bool ktm5030_read(struct ktm5030 *pdata, u16 addr,
|
|
u8 reg, char *buf, u32 size)
|
|
{
|
|
struct i2c_client *client = pdata->i2c_client;
|
|
struct i2c_msg msg[2] = {
|
|
{
|
|
.addr = addr,
|
|
.flags = 0,
|
|
.len = 1,
|
|
.buf = pdata->i2c_wbuf,
|
|
},
|
|
{
|
|
.addr = addr,
|
|
.flags = I2C_M_RD,
|
|
.len = size,
|
|
.buf = pdata->i2c_rbuf,
|
|
}
|
|
};
|
|
|
|
if (size > READ_BUF_MAX_SIZE) {
|
|
dev_err(pdata->dev, "invalid read buff size %d\n", size);
|
|
return false;
|
|
}
|
|
|
|
memset(pdata->i2c_wbuf, 0x0, WRITE_BUF_MAX_SIZE);
|
|
memset(pdata->i2c_rbuf, 0x0, READ_BUF_MAX_SIZE);
|
|
pdata->i2c_wbuf[0] = reg;
|
|
|
|
if (i2c_transfer(client->adapter, msg, 2) != 2) {
|
|
dev_err(pdata->dev, "i2c read failed\n");
|
|
return false;
|
|
}
|
|
|
|
memcpy(buf, pdata->i2c_rbuf, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Read raw data
|
|
*/
|
|
static bool ktm5030_read_raw(struct ktm5030 *pdata, char *buf, u32 size)
|
|
{
|
|
struct i2c_client *client = pdata->i2c_client;
|
|
struct i2c_msg msg[1] = {
|
|
{
|
|
.addr = client->addr,
|
|
.flags = I2C_M_RD,
|
|
.len = size,
|
|
.buf = pdata->i2c_rbuf,
|
|
}
|
|
};
|
|
|
|
if (size > READ_BUF_MAX_SIZE) {
|
|
dev_err(pdata->dev, "invalid read buff size %d\n", size);
|
|
return false;
|
|
}
|
|
|
|
memset(pdata->i2c_rbuf, 0x0, READ_BUF_MAX_SIZE);
|
|
|
|
if (i2c_transfer(client->adapter, msg, 1) != 1) {
|
|
dev_err(pdata->dev, "i2c read failed\n");
|
|
return false;
|
|
}
|
|
|
|
memcpy(buf, pdata->i2c_rbuf, size);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ktm5030_wait_ack(struct ktm5030 *pdata, bool forever, int cn)
|
|
{
|
|
bool ret = false;
|
|
bool running = true;
|
|
u8 ack[10];
|
|
|
|
if (!forever && cn <= 0)
|
|
return false;
|
|
|
|
while (running) {
|
|
msleep(40);
|
|
memset(ack, 0x00, 10);
|
|
|
|
if (ktm5030_read_raw(pdata, ack, sizeof(isp_r_ack))) {
|
|
if (memcmp(ack, isp_r_ack, sizeof(isp_r_ack)) == 0) {
|
|
pr_debug("get ack\n");
|
|
ret = true;
|
|
break;
|
|
}
|
|
/* skip checksum */
|
|
if (memcmp(ack, isp_r_nack, sizeof(isp_r_nack)-1) == 0)
|
|
pr_debug("get NACK\n");
|
|
pr_debug("Not get ack\n");
|
|
}
|
|
|
|
if (!forever) {
|
|
cn--;
|
|
if (cn == 0)
|
|
running = false;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool ktm5030_enter_irom(struct ktm5030 *pdata)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (pdata->fw_status != UPDATE_START)
|
|
return false;
|
|
|
|
/* enter force-IROM mode */
|
|
if (!ktm5030_write_raw(pdata, isp_w_enter_irom,
|
|
sizeof(isp_w_enter_irom))) {
|
|
dev_err(pdata->dev, "fail\n");
|
|
return false;
|
|
}
|
|
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
if (ret)
|
|
pdata->fw_status = UPDATE_IROM;
|
|
|
|
pr_debug("pdata->fw_status: %d\n", pdata->fw_status);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* enter isp driver write state */
|
|
static bool ktm5030_enter_driverwrite(struct ktm5030 *pdata)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (pdata->fw_status != UPDATE_IROM)
|
|
return false;
|
|
|
|
/* enter isp driver write state */
|
|
if (!ktm5030_write_raw(pdata, isp_w_enter_isp,
|
|
sizeof(isp_w_enter_isp))) {
|
|
dev_err(pdata->dev, "fail\n");
|
|
return false;
|
|
}
|
|
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
if (ret)
|
|
pdata->fw_status = UPDATE_DRIVERWRITE;
|
|
pr_debug("end pdata->fw_status: %d\n", pdata->fw_status);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* run isp */
|
|
static bool ktm5030_run_driver(struct ktm5030 *pdata)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (pdata->fw_status != UPDATE_DRIVERIMG)
|
|
return false;
|
|
|
|
/* enter isp driver write state */
|
|
if (!ktm5030_write_raw(pdata, isp_w_run_isp, sizeof(isp_w_run_isp))) {
|
|
dev_err(pdata->dev, "fail\n");
|
|
return false;
|
|
}
|
|
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
if (ret)
|
|
pdata->fw_status = UPDATE_RUNDRIVER;
|
|
pr_debug("end pdata->fw_status: %d\n", pdata->fw_status);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* erase bank */
|
|
static bool ktm5030_erase_bank(struct ktm5030 *pdata)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (pdata->fw_status != UPDATE_RUNDRIVER)
|
|
return false;
|
|
|
|
/* enter isp driver write state */
|
|
if (!ktm5030_write_raw(pdata, isp_w_erase_bank,
|
|
sizeof(isp_w_erase_bank))) {
|
|
dev_err(pdata->dev, "fail\n");
|
|
return false;
|
|
}
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
if (ret)
|
|
pdata->fw_status = UPDATE_ERASEBANK;
|
|
pr_debug("end pdata->fw_status: %d\n", pdata->fw_status);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* hard reset */
|
|
static bool ktm5030_hard_reset(struct ktm5030 *pdata)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (pdata->fw_status != UPDATE_COREIMG)
|
|
return false;
|
|
|
|
/* enter isp driver write state */
|
|
if (!ktm5030_write_raw(pdata, isp_w_hard_reset,
|
|
sizeof(isp_w_hard_reset))) {
|
|
dev_err(pdata->dev, "fail\n");
|
|
return false;
|
|
}
|
|
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
if (ret)
|
|
pdata->fw_status = UPDATE_DONE;
|
|
pr_debug("end pdata->fw_status: %d\n", pdata->fw_status);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* write image */
|
|
static bool ktm5030_write_image(const struct firmware *fw,
|
|
struct ktm5030 *pdata, bool show)
|
|
{
|
|
bool ret = true;
|
|
u8 databuf[DATA_BUF_MAX_SIZE];
|
|
const u8 *fdata = fw->data;
|
|
int dlen = (int)fw->size;
|
|
int start_addr = 0, wlen = 0, *ptr, crc16;
|
|
int total_page = 0, rest_data = 0, i = 0;
|
|
|
|
if ((pdata->fw_status != UPDATE_DRIVERWRITE) &&
|
|
(pdata->fw_status != UPDATE_ERASEBANK))
|
|
return false;
|
|
|
|
total_page = dlen / DATA_PAGE_SIZE;
|
|
rest_data = dlen % DATA_PAGE_SIZE;
|
|
pr_debug("total_page: %d\n", total_page);
|
|
pr_debug("rest_data: %d\n", rest_data);
|
|
|
|
for (i = 0; i < total_page; i++) {
|
|
pr_debug("write %d-page\n", i);
|
|
|
|
/* flash write command */
|
|
if (!ktm5030_write_raw(pdata, isp_w_write_primer,
|
|
sizeof(isp_w_write_primer)))
|
|
dev_err(pdata->dev, "write primer fail\n", i);
|
|
msleep(20);
|
|
|
|
wlen = DATA_PAGE_SIZE;
|
|
ptr = (int *)&databuf[0];
|
|
*ptr++ = ((start_addr>>24)&0xff) | ((start_addr<<8)&0xff0000) |
|
|
((start_addr>>8)&0xff00) | ((start_addr<<24)&0xff000000);
|
|
*ptr++ = ((wlen>>24)&0xff) | ((wlen<<8)&0xff0000) |
|
|
((wlen>>8)&0xff00) | ((wlen<<24)&0xff000000);
|
|
|
|
memcpy(databuf+8, fdata, wlen);
|
|
crc16 = calculate_crc_16(fdata, wlen);
|
|
ptr = (int *)&databuf[8+wlen];
|
|
*ptr = ((crc16>>24)&0xff) | ((crc16<<8)&0xff0000) |
|
|
((crc16>>8)&0xff00) | ((crc16<<24)&0xff000000);
|
|
|
|
if (show) {
|
|
int ii = 0;
|
|
|
|
pr_debug("crc: 0x%x\n", crc16);
|
|
for (ii = 0; ii < (wlen+12); ii++)
|
|
pr_debug("0x%x\n", databuf[ii]);
|
|
}
|
|
|
|
if (ktm5030_write_raw(pdata, databuf, wlen+12))
|
|
pr_debug("ktm5030_write_raw ok\n");
|
|
else
|
|
pr_debug("ktm5030_write_raw fail\n");
|
|
|
|
start_addr += DATA_PAGE_SIZE;
|
|
fdata += DATA_PAGE_SIZE;
|
|
msleep(20);
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
}
|
|
|
|
if (rest_data > 0) {
|
|
pr_debug("write rest_data\n");
|
|
|
|
/* flash write command */
|
|
ktm5030_write_raw(pdata, isp_w_write_primer,
|
|
sizeof(isp_w_write_primer));
|
|
msleep(20);
|
|
|
|
wlen = rest_data;
|
|
ptr = (int *)&databuf[0];
|
|
*ptr++ = ((start_addr>>24)&0xff) | ((start_addr<<8)&0xff0000) |
|
|
((start_addr>>8)&0xff00) | ((start_addr<<24)&0xff000000);
|
|
*ptr++ = ((wlen>>24)&0xff) | ((wlen<<8)&0xff0000) |
|
|
((wlen>>8)&0xff00) | ((wlen<<24)&0xff000000);
|
|
|
|
memcpy(databuf+8, fdata, wlen);
|
|
crc16 = calculate_crc_16(fdata, wlen);
|
|
ptr = (int *)&databuf[8+wlen];
|
|
*ptr = ((crc16>>24)&0xff) | ((crc16<<8)&0xff0000) |
|
|
((crc16>>8)&0xff00) | ((crc16<<24)&0xff000000);
|
|
|
|
if (ktm5030_write_raw(pdata, databuf, wlen+12))
|
|
pr_debug("ktm5030_write_raw ok\n");
|
|
else
|
|
pr_debug("ktm5030_write_raw fail\n");
|
|
|
|
msleep(20);
|
|
/* check ok or not */
|
|
ret = ktm5030_wait_ack(pdata, true, 0);
|
|
}
|
|
|
|
if (ret) {
|
|
if (pdata->fw_status == UPDATE_DRIVERWRITE)
|
|
pdata->fw_status = UPDATE_DRIVERIMG;
|
|
|
|
if (pdata->fw_status == UPDATE_ERASEBANK)
|
|
pdata->fw_status = UPDATE_COREIMG;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ktm5030_parse_dt(struct ktm5030 *pdata)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (of_property_read_u32(pdata->dev->of_node, "img-fw-rev",
|
|
&pdata->image_fw_version) < 0) {
|
|
dev_err(pdata->dev, "failed reading image firmware version\n");
|
|
pdata->image_fw_version = 0;
|
|
}
|
|
|
|
pdata->reset_gpio = of_get_named_gpio(pdata->dev->of_node,
|
|
"reset-gpio", 0);
|
|
if (!gpio_is_valid(pdata->reset_gpio)) {
|
|
dev_err(pdata->dev, "reset gpio not specified\n");
|
|
ret = -EINVAL;
|
|
} else
|
|
pr_debug("reset_gpio=%d\n", pdata->reset_gpio);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ktm5030_gpio_configure(struct ktm5030 *pdata, bool on)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (on) {
|
|
ret = gpio_request(pdata->reset_gpio, "ktm5030-reset-gpio");
|
|
if (ret) {
|
|
dev_err(pdata->dev, "ktm5030 reset gpio request failed\n");
|
|
goto err;
|
|
}
|
|
|
|
ret = gpio_direction_output(pdata->reset_gpio,
|
|
KTM5030_GPIO_HIGH);
|
|
if (ret) {
|
|
dev_err(pdata->dev, "ktm5030 reset gpio direction failed\n");
|
|
goto reset_err;
|
|
}
|
|
} else {
|
|
if (gpio_is_valid(pdata->reset_gpio))
|
|
gpio_free(pdata->reset_gpio);
|
|
}
|
|
return ret;
|
|
reset_err:
|
|
gpio_free(pdata->reset_gpio);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static void ktm5030_reset(struct ktm5030 *pdata, bool on_off)
|
|
{
|
|
pr_debug("reset: %d\n", on_off);
|
|
|
|
if (on_off) {
|
|
gpio_set_value(pdata->reset_gpio, KTM5030_GPIO_HIGH);
|
|
pr_debug("ktm5030 reset GPIO_HIGH\n");
|
|
msleep(100);
|
|
gpio_set_value(pdata->reset_gpio, KTM5030_GPIO_LOW);
|
|
pr_debug("ktm5030 reset GPIO_LOW\n");
|
|
msleep(20);
|
|
gpio_set_value(pdata->reset_gpio, KTM5030_GPIO_HIGH);
|
|
pr_debug("ktm5030 reset GPIO_HIGH\n");
|
|
msleep(800);
|
|
} else {
|
|
gpio_set_value(pdata->reset_gpio, KTM5030_GPIO_HIGH);
|
|
}
|
|
}
|
|
|
|
|
|
static int ktm5030_read_firmware_version(struct ktm5030 *pdata)
|
|
{
|
|
u8 rev[6];
|
|
int ret = -1;
|
|
|
|
memset(rev, 0x0, 6);
|
|
|
|
if (ktm5030_read(pdata, 0x74, KTM5030_FW_REG, rev, 6)) {
|
|
|
|
pdata->chip_fw_version = ((rev[0] << 24) | (rev[3] << 16) |
|
|
(rev[5] << 8) | rev[4]);
|
|
pr_debug("Firmware version: 0x%x\n", pdata->chip_fw_version);
|
|
ret = 0;
|
|
} else
|
|
dev_err(pdata->dev, "read Firmware version fail\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ktm5030_firmware_cb_driver(const struct firmware *cfg, void *data)
|
|
{
|
|
struct ktm5030 *pdata = (struct ktm5030 *)data;
|
|
|
|
pdata->fw_status = UPDATE_START;
|
|
if (!cfg) {
|
|
dev_err(pdata->dev, "get firmware failed\n");
|
|
return;
|
|
}
|
|
|
|
ktm5030_enter_irom(pdata);
|
|
if (pdata->fw_status == UPDATE_IROM)
|
|
ktm5030_enter_driverwrite(pdata);
|
|
|
|
if (pdata->fw_status == UPDATE_DRIVERWRITE)
|
|
ktm5030_write_image(cfg, pdata, false);
|
|
|
|
if (pdata->fw_status == UPDATE_DRIVERIMG)
|
|
ktm5030_run_driver(pdata);
|
|
release_firmware(cfg);
|
|
}
|
|
|
|
static void ktm5030_firmware_cb_core(const struct firmware *cfg, void *data)
|
|
{
|
|
struct ktm5030 *pdata = (struct ktm5030 *)data;
|
|
|
|
if (!cfg) {
|
|
dev_err(pdata->dev, "get firmware failed\n");
|
|
return;
|
|
}
|
|
if (pdata->fw_status == UPDATE_RUNDRIVER) {
|
|
ktm5030_erase_bank(pdata);
|
|
pr_debug("sleep 1s\n");
|
|
msleep(1000);
|
|
}
|
|
|
|
if (pdata->fw_status == UPDATE_ERASEBANK)
|
|
ktm5030_write_image(cfg, pdata, false);
|
|
|
|
if (pdata->fw_status == UPDATE_COREIMG) {
|
|
pr_debug("sleep 3s\n");
|
|
msleep(3000);
|
|
ktm5030_hard_reset(pdata);
|
|
}
|
|
release_firmware(cfg);
|
|
dev_info(pdata->dev, "ktm5030 Firmware upgrade success.\n");
|
|
}
|
|
|
|
static void ktm5030_firmware_main(const struct firmware *cfg, void *data)
|
|
{
|
|
struct ktm5030 *pdata = (struct ktm5030 *)data;
|
|
int ret = 0;
|
|
|
|
pdata->fw_status = UPDATE_START;
|
|
if (!cfg) {
|
|
dev_err(pdata->dev, "get firmware failed\n");
|
|
return;
|
|
}
|
|
|
|
ktm5030_enter_irom(pdata);
|
|
if (pdata->fw_status == UPDATE_IROM)
|
|
ktm5030_enter_driverwrite(pdata);
|
|
|
|
if (pdata->fw_status == UPDATE_DRIVERWRITE)
|
|
ktm5030_write_image(cfg, pdata, false);
|
|
|
|
if (pdata->fw_status == UPDATE_DRIVERIMG)
|
|
ktm5030_run_driver(pdata);
|
|
release_firmware(cfg);
|
|
ret = request_firmware_nowait(THIS_MODULE, true,
|
|
KTM5030_CORE_IMG, &pdata->i2c_client->dev,
|
|
GFP_KERNEL, pdata, ktm5030_firmware_cb_core);
|
|
if (ret)
|
|
dev_err(pdata->dev, "Failed to invoke firmware loader: %d\n", ret);
|
|
else
|
|
dev_info(pdata->dev, "driver F/W starts upgrading, waiting for 70s\n");
|
|
}
|
|
|
|
static ssize_t firmware_upgrade_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct ktm5030 *pdata = dev_get_drvdata(dev);
|
|
int get = 0;
|
|
int ret = 0;
|
|
|
|
if (!pdata) {
|
|
dev_err(dev, "pdata is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sscanf(buf, "%d", &get);
|
|
|
|
switch (get) {
|
|
case 0:
|
|
pr_debug("ktm5030 test load isp driver image\n");
|
|
/* Upgrade driver image */
|
|
ret = request_firmware_nowait(THIS_MODULE, true,
|
|
KTM5030_DRIVER_IMG,
|
|
&pdata->i2c_client->dev, GFP_KERNEL,
|
|
pdata, ktm5030_firmware_cb_driver);
|
|
if (ret)
|
|
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
|
|
else
|
|
dev_info(dev, "driver F/W starts upgrading, wait for 70s\n");
|
|
break;
|
|
case 1:
|
|
pr_debug("ktm5030 test load core driver image\n");
|
|
/* Upgrade driver image */
|
|
ret = request_firmware_nowait(THIS_MODULE, true,
|
|
KTM5030_CORE_IMG,
|
|
&pdata->i2c_client->dev, GFP_KERNEL,
|
|
pdata, ktm5030_firmware_cb_core);
|
|
if (ret)
|
|
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
|
|
else
|
|
dev_info(dev, "driver F/W starts upgrading, wait for 70s\n");
|
|
break;
|
|
case 2:
|
|
pr_debug("ktm5030 test load all image\n");
|
|
/* Upgrade driver image */
|
|
ret = request_firmware_nowait(THIS_MODULE, true,
|
|
KTM5030_DRIVER_IMG,
|
|
&pdata->i2c_client->dev, GFP_KERNEL,
|
|
pdata, ktm5030_firmware_main);
|
|
if (ret)
|
|
dev_err(dev, "Failed to invoke firmware loader: %d\n", ret);
|
|
else
|
|
dev_info(dev, "driver F/W starts upgrading, wait for 70s\n");
|
|
break;
|
|
case 3:
|
|
pr_debug("ktm5030 test reset\n");
|
|
ktm5030_reset(pdata, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t firmware_upgrade_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ktm5030 *pdata = dev_get_drvdata(dev);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%d\n", pdata->fw_status);
|
|
}
|
|
|
|
static ssize_t get_fw_version_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct ktm5030 *pdata = dev_get_drvdata(dev);
|
|
|
|
if (pdata->fw_status != UPDATE_DONE) {
|
|
dev_err(dev, "can't check firmware while upgrading bridge\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ktm5030_read_firmware_version(pdata);
|
|
return scnprintf(buf, PAGE_SIZE, "%#x\n", pdata->chip_fw_version);
|
|
}
|
|
|
|
/*macro make firmware_upgrade_store, firmware_upgrade_show */
|
|
static DEVICE_ATTR_RW(firmware_upgrade);
|
|
static DEVICE_ATTR_RO(get_fw_version);
|
|
|
|
static struct attribute *ktm5030_sysfs_attrs[] = {
|
|
&dev_attr_firmware_upgrade.attr,
|
|
&dev_attr_get_fw_version.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group ktm5030_attr_group = {
|
|
.attrs = ktm5030_sysfs_attrs,
|
|
};
|
|
|
|
static int ktm5030_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct ktm5030 *pdata;
|
|
int ret = 0, i = 0;
|
|
|
|
pdata = devm_kzalloc(&client->dev,
|
|
sizeof(struct ktm5030), GFP_KERNEL);
|
|
|
|
if (!pdata) {
|
|
pr_err("Allocate failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
pdata->dev = &client->dev;
|
|
pdata->i2c_client = client;
|
|
|
|
i2c_set_clientdata(client, pdata);
|
|
|
|
ret = ktm5030_parse_dt(pdata);
|
|
if (ret) {
|
|
dev_err(pdata->dev, "failed to parse device tree\n");
|
|
goto err_dt_parse;
|
|
}
|
|
|
|
ret = ktm5030_gpio_configure(pdata, true);
|
|
if (ret) {
|
|
dev_err(pdata->dev, "failed to configure GPIOs\n");
|
|
goto error;
|
|
}
|
|
|
|
mutex_init(&pdata->mutex);
|
|
|
|
ktm5030_reset(pdata, true);
|
|
|
|
pdata->fw_status = UPDATE_DONE;
|
|
|
|
ret = ktm5030_read_firmware_version(pdata);
|
|
if (ret) {
|
|
dev_err(pdata->dev, "failed to read fw version\n");
|
|
} else {
|
|
dev_info(pdata->dev, "chip firmware version: %#06x\n",
|
|
pdata->chip_fw_version);
|
|
dev_info(pdata->dev, "image firmware version: %#06x\n",
|
|
pdata->image_fw_version);
|
|
|
|
/* determine if firmware upgrade is needed,
|
|
* only accept exact version
|
|
*/
|
|
if (pdata->image_fw_version > pdata->chip_fw_version) {
|
|
dev_info(pdata->dev, "Upgrading fw %#06x => %#06x\n",
|
|
pdata->chip_fw_version,
|
|
pdata->image_fw_version);
|
|
ret = request_firmware_nowait(THIS_MODULE, true,
|
|
KTM5030_DRIVER_IMG,
|
|
&client->dev, GFP_KERNEL,
|
|
pdata, ktm5030_firmware_main);
|
|
if (ret)
|
|
dev_err(pdata->dev, "firmware load: ret %d\n",
|
|
ret);
|
|
} else
|
|
dev_info(pdata->dev, "firmware up-to-date\n");
|
|
}
|
|
|
|
ret = sysfs_create_group(&client->dev.kobj, &ktm5030_attr_group);
|
|
if (ret < 0)
|
|
dev_info(pdata->dev, "attr group create failed\n");
|
|
else
|
|
dev_info(pdata->dev, "attr group create Successfully\n");
|
|
|
|
pdata->n_i2c_msg = DATA_BUF_MAX_SIZE/I2C_TRAN_SIZE + 1;
|
|
pdata->i2c_msg_buf = kmalloc_array(pdata->n_i2c_msg,
|
|
sizeof(struct i2c_msg), GFP_KERNEL);
|
|
if (!pdata->i2c_msg_buf) {
|
|
ret = -ENOMEM;
|
|
goto err_dt_parse;
|
|
}
|
|
|
|
for (i = 0; i < pdata->n_i2c_msg; i++) {
|
|
pdata->i2c_msg_buf[i].buf = kmalloc(I2C_TRAN_SIZE, GFP_KERNEL);
|
|
if (!pdata->i2c_msg_buf[i].buf) {
|
|
ret = -ENOMEM;
|
|
goto err_dt_parse;
|
|
}
|
|
}
|
|
|
|
pr_debug("ktm5030 probe successfully\n");
|
|
|
|
return 0;
|
|
error:
|
|
ret = ktm5030_gpio_configure(pdata, false);
|
|
err_dt_parse:
|
|
return ret;
|
|
}
|
|
|
|
static int ktm5030_remove(struct i2c_client *client)
|
|
{
|
|
struct ktm5030 *pdata = i2c_get_clientdata(client);
|
|
|
|
sysfs_remove_group(&client->dev.kobj, &ktm5030_attr_group);
|
|
ktm5030_gpio_configure(pdata, false);
|
|
return 0;
|
|
}
|
|
|
|
static int ktm5030_pm_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ktm5030_pm_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = container_of(dev,
|
|
struct i2c_client, dev);
|
|
struct ktm5030 *pdata = NULL;
|
|
|
|
if (!client)
|
|
return 0;
|
|
pdata = i2c_get_clientdata(client);
|
|
if (!pdata)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops ktm5030_pm_ops = {
|
|
.suspend = ktm5030_pm_suspend,
|
|
.resume = ktm5030_pm_resume,
|
|
};
|
|
|
|
static struct i2c_device_id ktm5030_id[] = {
|
|
{ "kinet,ktm5030", 0},
|
|
{}
|
|
};
|
|
|
|
static const struct of_device_id ktm5030_match_table[] = {
|
|
{.compatible = "kinet,ktm5030"},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ktm5030_match_table);
|
|
|
|
static struct i2c_driver ktm5030_driver = {
|
|
.driver = {
|
|
.name = "ktm5030",
|
|
.of_match_table = ktm5030_match_table,
|
|
.pm = &ktm5030_pm_ops,
|
|
},
|
|
.probe = ktm5030_probe,
|
|
.remove = ktm5030_remove,
|
|
.id_table = ktm5030_id,
|
|
};
|
|
|
|
module_i2c_driver(ktm5030_driver);
|
|
|
|
MODULE_LICENSE("GPL");
|