kernel_samsung_a53x/drivers/gpu/drm/samsung/panel/panel_spi.c
2024-06-15 16:02:09 -03:00

1298 lines
34 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) Samsung Electronics Co., Ltd.
* Kimyung Lee <kernel.lee@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/of_platform.h>
#include "panel_drv.h"
#include "panel_spi.h"
#include "panel_debug.h"
#define PANEL_SPI_MAX_CMD_SIZE 16
#define PANEL_SPI_RX_BUF_SIZE 2048
const char * const panel_spi_cmd_str[MAX_PANEL_SPI_CMD] = {
[PANEL_SPI_CMD_NONE] = "panel_spi_cmd_none",
[PANEL_SPI_CMD_FLASH_INIT1] = "panel_spi_cmd_flash_init1",
[PANEL_SPI_CMD_FLASH_INIT1_DONE] = "panel_spi_cmd_flash_init1_done",
[PANEL_SPI_CMD_FLASH_INIT2] = "panel_spi_cmd_flash_init2",
[PANEL_SPI_CMD_FLASH_INIT2_DONE] = "panel_spi_cmd_flash_init2_done",
[PANEL_SPI_CMD_FLASH_EXIT1] = "panel_spi_cmd_flash_exit1",
[PANEL_SPI_CMD_FLASH_EXIT1_DONE] = "panel_spi_cmd_flash_exit1_done",
[PANEL_SPI_CMD_FLASH_EXIT2] = "panel_spi_cmd_flash_exit2",
[PANEL_SPI_CMD_FLASH_EXIT2_DONE] = "panel_spi_cmd_flash_exit2_done",
[PANEL_SPI_CMD_FLASH_BUSY_CLEAR] = "panel_spi_cmd_flash_busy_clear",
[PANEL_SPI_CMD_FLASH_READ] = "panel_spi_cmd_flash_read",
[PANEL_SPI_CMD_FLASH_WRITE_ENABLE] = "panel_spi_cmd_flash_write_enable",
[PANEL_SPI_CMD_FLASH_WRITE_DISABLE] = "panel_spi_cmd_flash_write_disable",
[PANEL_SPI_CMD_FLASH_WRITE] = "panel_spi_cmd_flash_write",
[PANEL_SPI_CMD_FLASH_WRITE_DONE] = "panel_spi_cmd_flash_write_done",
[PANEL_SPI_CMD_FLASH_ERASE_4K] = "panel_spi_cmd_flash_erase_4k",
[PANEL_SPI_CMD_FLASH_ERASE_4K_DONE] = "panel_spi_cmd_flash_erase_4k_done",
[PANEL_SPI_CMD_FLASH_ERASE_32K] = "panel_spi_cmd_flash_erase_32k",
[PANEL_SPI_CMD_FLASH_ERASE_32K_DONE] = "panel_spi_cmd_flash_erase_32k_done",
[PANEL_SPI_CMD_FLASH_ERASE_64K] = "panel_spi_cmd_flash_erase_64k",
[PANEL_SPI_CMD_FLASH_ERASE_64K_DONE] = "panel_spi_cmd_flash_erase_64k_done",
[PANEL_SPI_CMD_FLASH_ERASE_256K] = "panel_spi_cmd_flash_erase_256k",
[PANEL_SPI_CMD_FLASH_ERASE_256K_DONE] = "panel_spi_cmd_flash_erase_256k_done",
[PANEL_SPI_CMD_FLASH_ERASE_CHIP] = "panel_spi_cmd_flash_erase_chip",
[PANEL_SPI_CMD_FLASH_ERASE_CHIP_DONE] = "panel_spi_cmd_flash_erase_chip_done",
};
#define PANEL_SPI_DEV_NAME "panel_spi"
static bool panel_spi_conv_addressing_bit(u8 *dst, u32 addr, int addr_size)
{
if (addr_size > MAX_ADDRESSING_BYTE) {
panel_err("address size is invalid in ic's header file %d, %d. skipped\n",
addr_size, MAX_ADDRESSING_BYTE);
return false;
}
if (addr_size == 4) {
dst[3] = addr & 0xFF;
dst[2] = (addr >> 8) & 0xFF;
dst[1] = (addr >> 16) & 0xFF;
dst[0] = (addr >> 24) & 0xFF;
} else if (addr_size == 3) {
dst[2] = addr & 0xFF;
dst[1] = (addr >> 8) & 0xFF;
dst[0] = (addr >> 16) & 0xFF;
} else {
panel_err("address size is invalid in ic's header file %d. skipped\n", addr_size);
return false;
}
return true;
}
static bool panel_spi_cmd_exists(struct panel_spi_dev *spi_dev, int idx)
{
struct spi_dev_info *spi_info = &spi_dev->spi_info;
if (!(idx < MAX_PANEL_SPI_CMD)) {
panel_err("cmd %d limit exceeded\n", idx);
return false;
}
if (!spi_info->cmd_list) {
panel_dbg("cmd_list is null. check matched panel id\n");
return false;
}
if (!spi_info->cmd_list[idx]) {
panel_dbg("cmd %d(%s) is null\n", idx, panel_spi_cmd_str[idx]);
return false;
}
return true;
}
static int panel_spi_pdrv_read_param(struct panel_spi_dev *spi_dev, const u8 *wbuf, int wsize)
{
if (wsize < 1 || !wbuf)
return -EINVAL;
memcpy(spi_dev->setparam_buffer, wbuf, wsize);
spi_dev->setparam_buffer_size = wsize;
return wsize;
}
static int panel_spi_sync(struct panel_spi_dev *spi_dev, const u8 *wbuf, int wsize, u8 *rbuf, int rsize)
{
int ret = 0;
u32 speed_hz;
struct spi_device *spi;
struct spi_dev_info *spi_info;
struct spi_message msg;
struct spi_transfer x_write = { .bits_per_word = 8, };
struct spi_transfer x_read = { .bits_per_word = 8, };
spi = spi_dev->spi;
spi_info = &spi_dev->spi_info;
if (IS_ERR_OR_NULL(spi)) {
panel_err("spi device is not initialized\n");
return -ENODEV;
}
if (spi_info->speed_hz > 0)
spi->max_speed_hz = spi_info->speed_hz;
spi_message_init(&msg);
if (wsize < 1 || !wbuf)
return -EINVAL;
speed_hz = spi_info->speed_hz;
if (speed_hz <= 0)
speed_hz = 0;
else if (speed_hz > spi->max_speed_hz)
speed_hz = spi->max_speed_hz;
x_write.len = wsize;
x_write.tx_buf = wbuf;
x_write.speed_hz = speed_hz;
spi_message_add_tail(&x_write, &msg);
if (rsize) {
memset(spi_dev->read_buf_data, 0, PANEL_SPI_RX_BUF_SIZE);
x_read.len = rsize;
x_read.rx_buf = spi_dev->read_buf_data;
x_read.speed_hz = speed_hz;
spi_message_add_tail(&x_read, &msg);
spi_dev->read_buf_cmd = wbuf[0];
spi_dev->read_buf_size = rsize;
}
ret = spi_sync(spi, &msg);
if (ret < 0) {
panel_err("failed to spi_sync %d\n", ret);
return ret;
}
if (rbuf != NULL)
memcpy(rbuf, spi_dev->read_buf_data, rsize);
#if 0
if (panel_cmd_log_enabled(PANEL_CMD_LOG_SPI_TX))
print_hex_dump(KERN_ERR, "spi_cmd_print write ", DUMP_PREFIX_ADDRESS, 16, 1, wbuf, wsize, false);
if (panel_cmd_log_enabled(PANEL_CMD_LOG_SPI_RX))
print_hex_dump(KERN_ERR, "spi_cmd_print read ", DUMP_PREFIX_ADDRESS, 16, 1, rbuf, rsize, false);
#endif
return rsize;
}
static int panel_spi_pdrv_read(struct panel_spi_dev *spi_dev, const u8 rcmd, u8 *rbuf, int rsize)
{
int ret = 0;
const u8 *wbuf;
int wsize;
//check setparam cmd for read operation
if (!spi_dev->setparam_buffer)
return -EINVAL;
wbuf = &rcmd;
wsize = 1;
if (spi_dev->setparam_buffer_size > 0 && spi_dev->setparam_buffer[0] == rcmd) {
//setparam cmd
wbuf = spi_dev->setparam_buffer;
wsize = spi_dev->setparam_buffer_size;
}
ret = panel_spi_sync(spi_dev, wbuf, wsize, rbuf, rsize);
if (ret < 0)
return ret;
if (!ret)
return -EINVAL;
//setparam_buffer_size set to 0 if setparam once
spi_dev->setparam_buffer_size = 0;
return rsize;
}
static int panel_spi_read_id(struct panel_spi_dev *spi_dev, u32 *id)
{
int ret = 0;
const u8 wbuf[] = { 0x9F };
u8 rbuf[3] = { 0x00, };
int wsize = 1;
int rsize = 3;
ret = panel_spi_sync(spi_dev, wbuf, wsize, rbuf, rsize);
if (ret < 0) {
panel_err("spi sync failed: %d\n", ret);
return ret;
}
if (!ret)
return -EIO;
*id = (rbuf[0] << 16) | (rbuf[1] << 8) | rbuf[2];
panel_info("0x%06X\n", *id);
return 0;
}
static int panel_spi_execute_cmd_addr_data(struct panel_spi_dev *spi_dev, struct spi_cmd *spi_cmd,
u32 addr, u8 *write_buf, int write_len, u8 *read_buf, const int read_len)
{
int i, ret;
int verify_error_cnt;
u8 send_buf[MAX_PANEL_SPI_TX_BUF] = { 0x00, };
u8 receive_buf[MAX_PANEL_SPI_RX_BUF] = { 0x00, };
int send_len = 0, receive_len = 0, read_data_len = 0;
int addr_byte_size = 0;
if (!spi_cmd) {
//err notfound
panel_err("got null cmd\n");
return -EINVAL;
}
if (spi_cmd->reg) {
send_buf[send_len++] = spi_cmd->reg;
} else {
if ((spi_cmd->opt & PANEL_SPI_CMD_OPTION_ONLY_DELAY) == 0) {
//err notfound
return -EINVAL;
}
if (spi_cmd->delay_before_usecs > 0)
usleep_range(spi_cmd->delay_before_usecs, spi_cmd->delay_before_usecs + 10);
if (spi_cmd->delay_after_usecs > 0)
usleep_range(spi_cmd->delay_after_usecs, spi_cmd->delay_after_usecs + 10);
return 0;
}
if ((spi_cmd->opt & PANEL_SPI_CMD_OPTION_ADDR_4BYTE) > 0)
addr_byte_size = 4;
else if ((spi_cmd->opt & PANEL_SPI_CMD_OPTION_ADDR_3BYTE) > 0)
addr_byte_size = 3;
if (addr_byte_size > 0) {
if (!panel_spi_conv_addressing_bit(send_buf + send_len, addr, addr_byte_size))
return -EINVAL;
send_len += addr_byte_size;
}
if (spi_cmd->wlen > 0) {
for (i = 0; i < spi_cmd->wlen; i++)
send_buf[send_len++] = spi_cmd->wval[i];
}
if (write_len > 0) {
for (i = 0; i < write_len && send_len < MAX_PANEL_SPI_TX_BUF; i++)
send_buf[send_len++] = write_buf[i];
}
if (spi_cmd->rlen > 0)
receive_len += spi_cmd->rlen;
if (read_len > 0) {
read_data_len = read_len;
if (!(read_data_len < MAX_PANEL_SPI_RX_DATA))
read_data_len = MAX_PANEL_SPI_RX_DATA;
receive_len += read_data_len;
}
if (!(receive_len < MAX_PANEL_SPI_RX_BUF))
receive_len = MAX_PANEL_SPI_RX_BUF;
if (spi_cmd->delay_before_usecs > 0)
usleep_range(spi_cmd->delay_before_usecs, spi_cmd->delay_before_usecs + 10);
if (send_len > 0) {
ret = panel_spi_sync(spi_dev, send_buf, send_len, receive_buf, receive_len);
if (ret < 0)
goto fail_delay;
}
if (read_data_len > 0)
memcpy(read_buf, receive_buf + spi_cmd->rlen, read_data_len);
if ((spi_cmd->opt & PANEL_SPI_CMD_OPTION_READ_COMPARE) > 0) {
verify_error_cnt = 0;
for (i = 0; i < spi_cmd->rlen; i++) {
if ((receive_buf[i] & spi_cmd->rmask[i]) != spi_cmd->rval[i]) {
//error
verify_error_cnt++;
panel_dbg("verify mismatch, idx %d(%s) buf %02x mask %02x val %02x\n",
i, panel_spi_cmd_str[i], receive_buf[i], spi_cmd->rmask[i], spi_cmd->rval[i]);
} else {
panel_dbg("verify completed, idx %d(%s) buf %02x mask %02x val %02x\n",
i, panel_spi_cmd_str[i], receive_buf[i], spi_cmd->rmask[i], spi_cmd->rval[i]);
}
}
if (verify_error_cnt > 0) {
ret = verify_error_cnt;
goto fail_delay;
}
}
if (spi_cmd->delay_after_usecs > 0)
usleep_range(spi_cmd->delay_after_usecs, spi_cmd->delay_after_usecs + 10);
return 0;
fail_delay:
if (spi_cmd->delay_retry_usecs > 0)
usleep_range(spi_cmd->delay_retry_usecs, spi_cmd->delay_retry_usecs + 10);
return ret;
}
static inline int panel_spi_execute_cmd_addr(struct panel_spi_dev *spi_dev, struct spi_cmd *spi_cmd, u32 addr)
{
return panel_spi_execute_cmd_addr_data(spi_dev, spi_cmd, addr, NULL, 0, NULL, 0);
}
static inline int panel_spi_execute_cmd(struct panel_spi_dev *spi_dev, struct spi_cmd *spi_cmd)
{
return panel_spi_execute_cmd_addr(spi_dev, spi_cmd, INT_MIN);
}
static int panel_spi_execute_cmd_retry(struct panel_spi_dev *spi_dev, struct spi_cmd *spi_cmd, const int retry)
{
int ret, i;
for (i = 0; i < retry; i++) {
ret = panel_spi_execute_cmd(spi_dev, spi_cmd);
if (!ret)
return 0;
}
panel_err("fail cnt exceeded %d\n", retry);
return ret;
}
static int panel_spi_flash_init(struct panel_spi_dev *spi_dev)
{
struct spi_dev_info *spi_info = &spi_dev->spi_info;
size_t i;
int ret = 0;
const int q[] = {
PANEL_SPI_CMD_FLASH_WRITE_ENABLE,
PANEL_SPI_CMD_FLASH_INIT1,
PANEL_SPI_CMD_FLASH_INIT1_DONE,
PANEL_SPI_CMD_FLASH_WRITE_ENABLE,
PANEL_SPI_CMD_FLASH_INIT2,
PANEL_SPI_CMD_FLASH_INIT2_DONE,
PANEL_SPI_CMD_FLASH_WRITE_DISABLE,
};
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
for (i = 0; i < ARRAY_SIZE(q); i++) {
if (!panel_spi_cmd_exists(spi_dev, q[i])) {
panel_info("skip cmd %d %s\n", q[i], panel_spi_cmd_str[q[i]]);
continue;
}
ret = panel_spi_execute_cmd_retry(spi_dev, spi_info->cmd_list[q[i]], MAX_STATUS_RETRY);
if (ret) {
panel_err("error occurred when send cmd %d %s ret %d\n", q[i], panel_spi_cmd_str[q[i]], ret);
return ret;
}
}
return ret;
}
static int panel_spi_flash_exit(struct panel_spi_dev *spi_dev)
{
struct spi_dev_info *spi_info = &spi_dev->spi_info;
size_t i;
int ret = 0;
const int q[] = {
PANEL_SPI_CMD_FLASH_WRITE_ENABLE,
PANEL_SPI_CMD_FLASH_EXIT1,
PANEL_SPI_CMD_FLASH_EXIT1_DONE,
PANEL_SPI_CMD_FLASH_WRITE_ENABLE,
PANEL_SPI_CMD_FLASH_EXIT2,
PANEL_SPI_CMD_FLASH_EXIT2_DONE,
PANEL_SPI_CMD_FLASH_WRITE_DISABLE,
};
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
for (i = 0; i < ARRAY_SIZE(q); i++) {
if (!panel_spi_cmd_exists(spi_dev, q[i])) {
panel_info("skip cmd %d %s\n", q[i], panel_spi_cmd_str[q[i]]);
continue;
}
ret = panel_spi_execute_cmd_retry(spi_dev, spi_info->cmd_list[q[i]], MAX_STATUS_RETRY);
if (ret) {
panel_err("error occurred when send cmd %d %s ret %d\n", q[i], panel_spi_cmd_str[q[i]], ret);
return ret;
}
}
return ret;
}
static int panel_spi_flash_erase(struct panel_spi_dev *spi_dev, struct spi_data_packet *data_packet)
{
struct spi_dev_info *spi_info;
int ret = 0;
int erase_idx, erase_wait_idx;
int erase_size = 0, target_addr = 0, remain_size = 0;
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
if (!data_packet)
return -EINVAL;
panel_info("+++, 0x%06x %d\n", data_packet->addr, data_packet->size);
spi_info = &spi_dev->spi_info;
remain_size = data_packet->size;
target_addr = data_packet->addr;
if ((target_addr % SZ_4K) != 0) {
panel_err("address must be multiple of 4k(%d)\n", target_addr);
ret = -EINVAL;
goto erase_out;
}
while (remain_size > 0) {
erase_idx = erase_wait_idx = erase_size = -1;
if (spi_info->erase_type == PANEL_SPI_ERASE_TYPE_BLOCK) {
if (remain_size >= SZ_256K
&& (target_addr % SZ_256K) == 0
&& panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_ERASE_256K)) {
erase_idx = PANEL_SPI_CMD_FLASH_ERASE_256K;
erase_wait_idx = PANEL_SPI_CMD_FLASH_ERASE_256K_DONE;
erase_size = SZ_256K;
} else if (remain_size >= SZ_64K
&& (target_addr % SZ_64K) == 0
&& panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_ERASE_64K)) {
erase_idx = PANEL_SPI_CMD_FLASH_ERASE_64K;
erase_wait_idx = PANEL_SPI_CMD_FLASH_ERASE_64K_DONE;
erase_size = SZ_64K;
} else if (remain_size >= SZ_32K
&& (target_addr % SZ_32K) == 0
&& panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_ERASE_32K)) {
erase_idx = PANEL_SPI_CMD_FLASH_ERASE_32K;
erase_wait_idx = PANEL_SPI_CMD_FLASH_ERASE_32K_DONE;
erase_size = SZ_32K;
} else if (panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_ERASE_4K)) {
erase_idx = PANEL_SPI_CMD_FLASH_ERASE_4K;
erase_wait_idx = PANEL_SPI_CMD_FLASH_ERASE_4K_DONE;
erase_size = SZ_4K;
}
} else if (spi_info->erase_type == PANEL_SPI_ERASE_TYPE_CHIP) {
if (panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_ERASE_CHIP)) {
erase_idx = PANEL_SPI_CMD_FLASH_ERASE_CHIP;
erase_wait_idx = PANEL_SPI_CMD_FLASH_ERASE_CHIP_DONE;
erase_size = SZ_1G;
}
}
if (erase_idx < 0) {
panel_err("operation is not supported in this ic%d\n", remain_size);
ret = -ENODATA; // 74
goto erase_out;
}
// write en
if (panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_WRITE_ENABLE)) {
ret = panel_spi_execute_cmd_retry(spi_dev, spi_info->cmd_list[PANEL_SPI_CMD_FLASH_WRITE_ENABLE], MAX_BUSY_RETRY);
if (ret) {
panel_err("error occurred when send cmd %d(%s) retry %d ret %d\n",
PANEL_SPI_CMD_FLASH_BUSY_CLEAR,
panel_spi_cmd_str[PANEL_SPI_CMD_FLASH_BUSY_CLEAR], MAX_BUSY_RETRY, ret);
goto erase_out;
}
}
//send erase cmd
ret = panel_spi_execute_cmd_addr(spi_dev, spi_info->cmd_list[erase_idx], target_addr);
if (ret) {
panel_err("error occurred when send cmd %d(%s) ret %d\n", erase_idx,
panel_spi_cmd_str[erase_idx], ret);
goto erase_out;
}
// exit erase function on 1st loop if nonblock flag has been raised
if (data_packet->type & PANEL_SPI_PACKET_TYPE_ERASE_NONBLOCK) {
target_addr += erase_size;
remain_size -= erase_size;
panel_info("nonblock erase has been called\n");
goto erase_out;
}
// busy wait
if (panel_spi_cmd_exists(spi_dev, erase_wait_idx)) {
ret = panel_spi_execute_cmd_retry(spi_dev, spi_info->cmd_list[erase_wait_idx], MAX_BUSY_RETRY);
if (ret) {
panel_err("error occurred when send cmd %d(%s) retry %d ret %d\n", erase_wait_idx,
panel_spi_cmd_str[erase_wait_idx], MAX_BUSY_RETRY, ret);
goto erase_out;
}
}
target_addr += erase_size;
remain_size -= erase_size;
panel_info("erased addr 0x%06X size %d(0x%X) remain %d\n",
target_addr - erase_size, erase_size, erase_size, (remain_size > 0) ? remain_size : 0);
}
erase_out:
panel_info("---, erased from 0x%06x to 0x%06x(%d) remain %d\n", data_packet->addr, target_addr - 1,
target_addr - data_packet->addr, remain_size);
return ret;
}
static int panel_spi_flash_read(struct panel_spi_dev *spi_dev, struct spi_data_packet *data_buf)
{
struct spi_dev_info *spi_info;
int ret = 0;
int read_size, data_buf_pos;
int remain_size = 0, read_addr = 0;
u8 *buf;
if (!SPI_IS_READY(spi_dev)) {
ret = -ENODEV;
goto read_out;
}
if (!data_buf) {
ret = -EINVAL;
goto read_out;
}
panel_dbg("+++, 0x%06x %d\n", data_buf->addr, data_buf->size);
spi_info = &spi_dev->spi_info;
remain_size = data_buf->size;
read_addr = data_buf->addr;
data_buf_pos = 0;
if (!panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_READ)) {
ret = -ENOENT;
goto read_out;
}
if (data_buf->size < 1) {
panel_err("data_buf is invalid %d\n", data_buf->size);
ret = -EINVAL;
goto read_out;
}
buf = kzalloc(sizeof(u8) * MAX_PANEL_SPI_RX_DATA, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto read_out;
}
while (remain_size > 0) {
if (remain_size > MAX_PANEL_SPI_RX_DATA)
read_size = MAX_PANEL_SPI_RX_DATA;
else
read_size = remain_size;
//send read cmd
ret = panel_spi_execute_cmd_addr_data(spi_dev, spi_info->cmd_list[PANEL_SPI_CMD_FLASH_READ],
read_addr, NULL, 0, buf, read_size);
if (ret) {
panel_err("error occurred when send cmd %d(%s) ret %d\n", PANEL_SPI_CMD_FLASH_READ,
panel_spi_cmd_str[PANEL_SPI_CMD_FLASH_READ], ret);
goto buf_free;
}
//copy readed data
memcpy(data_buf->buf + data_buf_pos, buf, read_size);
data_buf_pos += read_size;
read_addr += read_size;
remain_size -= read_size;
}
buf_free:
kfree(buf);
read_out:
panel_dbg("---, 0x%06x %d\n", read_addr, remain_size);
return ret;
}
static int panel_spi_flash_write(struct panel_spi_dev *spi_dev, struct spi_data_packet *data_buf)
{
struct spi_dev_info *spi_info;
int ret = 0;
int write_size, data_buf_pos;
int remain_size = 0, write_addr = 0;
u8 *buf;
if (!SPI_IS_READY(spi_dev)) {
ret = -ENODEV;
goto write_out;
}
if (!data_buf) {
ret = -EINVAL;
goto write_out;
}
panel_dbg("+++, 0x%06x %d\n", data_buf->addr, data_buf->size);
spi_info = &spi_dev->spi_info;
remain_size = data_buf->size;
write_addr = data_buf->addr;
data_buf_pos = 0;
if (!panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_WRITE)) {
ret = -ENOENT;
goto write_out;
}
if (data_buf->size < 1) {
panel_err("data_buf is invalid %d\n", data_buf->size);
ret = -EINVAL;
goto write_out;
}
buf = kzalloc(sizeof(u8) * MAX_PANEL_SPI_TX_DATA, GFP_KERNEL);
if (!buf) {
ret = -ENOMEM;
goto write_out;
}
while (remain_size > 0) {
if (remain_size > MAX_PANEL_SPI_TX_DATA)
write_size = MAX_PANEL_SPI_TX_DATA;
else
write_size = remain_size;
//copy write data
memcpy(buf, data_buf->buf + data_buf_pos, write_size);
// busy wait
if (panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_WRITE_ENABLE)) {
ret = panel_spi_execute_cmd_retry(spi_dev, spi_info->cmd_list[PANEL_SPI_CMD_FLASH_WRITE_ENABLE], MAX_CMD_RETRY);
if (ret) {
panel_err("error occurred when send cmd %d(%s) retry %d ret %d\n",
PANEL_SPI_CMD_FLASH_WRITE_ENABLE,
panel_spi_cmd_str[PANEL_SPI_CMD_FLASH_WRITE_ENABLE],
MAX_CMD_RETRY, ret);
goto buf_free;
}
}
//send read cmd
if (panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_WRITE_DONE)) {
ret = panel_spi_execute_cmd_addr_data(spi_dev, spi_info->cmd_list[PANEL_SPI_CMD_FLASH_WRITE],
write_addr, buf, write_size, NULL, 0);
if (ret) {
panel_err("error occurred when send cmd %d(%s) ret %d\n",
PANEL_SPI_CMD_FLASH_WRITE,
panel_spi_cmd_str[PANEL_SPI_CMD_FLASH_WRITE], ret);
goto buf_free;
}
}
// busy wait
if (panel_spi_cmd_exists(spi_dev, PANEL_SPI_CMD_FLASH_WRITE_DONE)) {
ret = panel_spi_execute_cmd_retry(spi_dev, spi_info->cmd_list[PANEL_SPI_CMD_FLASH_WRITE_DONE], MAX_BUSY_RETRY);
if (ret) {
panel_err("error occurred when send cmd %d(%s) retry %d ret %d\n",
PANEL_SPI_CMD_FLASH_WRITE_DONE, panel_spi_cmd_str[PANEL_SPI_CMD_FLASH_WRITE_DONE], MAX_BUSY_RETRY, ret);
goto buf_free;
}
}
data_buf_pos += write_size;
write_addr += write_size;
remain_size -= write_size;
}
buf_free:
kfree(buf);
write_out:
panel_dbg("---, 0x%06x %d\n", write_addr, remain_size);
return ret;
}
static int panel_spi_flash_get_buf_size(struct panel_spi_dev *spi_dev, int type)
{
struct spi_dev_info *spi_info = &spi_dev->spi_info;
if (!SPI_IS_READY(spi_dev)) {
return -ENODEV;
}
if (type == PANEL_SPI_GET_READ_SIZE) {
if (spi_info->byte_per_read > 0)
return spi_info->byte_per_read;
else
return MAX_PANEL_SPI_RX_DATA;
} else if (type == PANEL_SPI_GET_WRITE_SIZE) {
if (spi_info->byte_per_write > 0)
return spi_info->byte_per_write;
else
return MAX_PANEL_SPI_TX_DATA;
}
return -EINVAL;
}
static int panel_spi_ctrl(struct panel_spi_dev *spi_dev, int ctrl_msg, void *data)
{
int ret = 0;
u32 id = 0;
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
switch (ctrl_msg) {
case PANEL_SPI_CTRL_INIT:
ret = panel_spi_flash_init(spi_dev);
break;
case PANEL_SPI_CTRL_EXIT:
ret = panel_spi_flash_exit(spi_dev);
break;
case PANEL_SPI_CTRL_BUSY_CHECK:
ret = panel_spi_execute_cmd(spi_dev, spi_dev->spi_info.cmd_list[PANEL_SPI_CMD_FLASH_BUSY_CLEAR]);
break;
case PANEL_SPI_CTRL_ID_READ:
ret = panel_spi_read_id(spi_dev, &id);
break;
case PANEL_SPI_CTRL_GET_READ_SIZE:
ret = panel_spi_flash_get_buf_size(spi_dev, PANEL_SPI_GET_READ_SIZE);
if (ret >= 0 && data != NULL)
*((int *)data) = ret;
break;
case PANEL_SPI_CTRL_GET_WRITE_SIZE:
ret = panel_spi_flash_get_buf_size(spi_dev, PANEL_SPI_GET_WRITE_SIZE);
if (ret >= 0 && data != NULL)
*((int *)data) = ret;
break;
default:
break;
}
if (ret < 0)
panel_err("ret %d\n", ret);
else
panel_dbg("ret %d\n", ret);
return ret;
}
static int panel_spi_probe_dt(struct spi_device *spi)
{
struct device_node *nc = spi->dev.of_node;
int rc;
u32 value;
rc = of_property_read_u32(nc, "bits-per-word", &value);
if (rc) {
dev_err(&spi->dev, "%s has no valid 'bits-per-word' property (%d)\n",
nc->full_name, rc);
return rc;
}
spi->bits_per_word = value;
rc = of_property_read_u32(nc, "spi-max-frequency", &value);
if (rc) {
dev_err(&spi->dev, "%s has no valid 'spi-max-frequency' property (%d)\n",
nc->full_name, rc);
return rc;
}
spi->max_speed_hz = value;
return 0;
}
static int panel_spi_probe(struct spi_device *spi)
{
int ret;
dev_info(&spi->dev, "++\n");
if (unlikely(!spi)) {
panel_err("invalid spi\n");
return -EINVAL;
}
ret = panel_spi_probe_dt(spi);
if (ret < 0) {
dev_err(&spi->dev, "failed to parse device tree, ret %d\n", ret);
return ret;
}
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "failed to setup spi, ret %d\n", ret);
return ret;
}
dev_info(&spi->dev, "--\n");
return 0;
}
static int panel_spi_remove(struct spi_device *spi)
{
return 0;
}
static int panel_spi_fops_open(struct inode *inode, struct file *file)
{
int ret = 0;
struct miscdevice *dev = file->private_data;
struct panel_spi_dev *spi_dev = container_of(dev, struct panel_spi_dev, dev);
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
mutex_lock(&spi_dev->f_lock);
panel_info("was called\n");
file->private_data = spi_dev;
/*
* if (spi_dev->fopened) {
* panel_err("Already in use");
* mutex_unlock(&spi_dev->f_lock);
* return -EBUSY;
* }
*/
ret = panel_spi_flash_init(spi_dev);
if (ret) {
panel_err("error occurred. init at read-only mode.");
// mutex_unlock(&spi_dev->f_lock);
// return -EBUSY;
}
spi_dev->fopened = true;
mutex_unlock(&spi_dev->f_lock);
return ret;
}
static int panel_spi_fops_release(struct inode *inode, struct file *file)
{
struct panel_spi_dev *spi_dev = file->private_data;
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
panel_info("was called\n");
mutex_lock(&spi_dev->f_lock);
panel_spi_flash_exit(spi_dev);
spi_dev->fopened = false;
mutex_unlock(&spi_dev->f_lock);
return 0;
}
static ssize_t panel_spi_fops_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
{
struct panel_spi_dev *spi_dev = file->private_data;
struct panel_device *panel = container_of(spi_dev, struct panel_device, panel_spi_dev);
struct spi_data_packet spi_data_buf;
int ret;
u8 *read_buf;
u32 read_addr, read_done = 0, read_size = 0, count = (u32)len;
loff_t empty_pos = 0;
ssize_t res;
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
panel_dbg("count %lu ppos %llu fpos: %llu\n", count, *ppos, file->f_pos);
if (!spi_dev->fopened) {
panel_err("panel_spi device is not opened\n");
return -EIO;
}
if (!buf) {
panel_err("invalid read buffer\n");
return -EINVAL;
}
mutex_lock(&spi_dev->f_lock);
read_buf = (u8 *)devm_kzalloc(panel->dev, count * sizeof(u8), GFP_KERNEL);
if (!read_buf) {
panel_err("invalid alloc mem\n");
mutex_unlock(&spi_dev->f_lock);
return -EINVAL;
}
while (read_done < count) {
read_addr = *ppos + read_done;
read_size = count - read_done;
if (read_size > PANEL_SPI_RX_BUF_SIZE)
read_size = PANEL_SPI_RX_BUF_SIZE;
spi_data_buf.addr = read_addr;
spi_data_buf.size = read_size;
spi_data_buf.buf = read_buf + read_done;
panel_dbg("addr 0x%06X, size %d\n", spi_data_buf.addr, spi_data_buf.size);
ret = panel_spi_flash_read(spi_dev, &spi_data_buf);
if (ret < 0) {
panel_err("failed to read 0x%06x %lu\n", read_addr, read_size);
mutex_unlock(&spi_dev->f_lock);
return -EIO;
}
read_done += read_size;
}
if (panel_log_level > 6)
print_hex_dump(KERN_ERR, "panel_spi_read ", DUMP_PREFIX_ADDRESS, 16, 1, read_buf, read_size, false);
res = simple_read_from_buffer(buf, count, &empty_pos, read_buf, count);
if (res < 0) {
panel_err("copy failed\n");
mutex_unlock(&spi_dev->f_lock);
return res;
}
*ppos = *ppos + count;
mutex_unlock(&spi_dev->f_lock);
return count;
}
static ssize_t panel_spi_fops_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos)
{
struct panel_spi_dev *spi_dev = file->private_data;
struct panel_device *panel = container_of(spi_dev, struct panel_device, panel_spi_dev);
struct spi_data_packet spi_data_buf;
u8 *write_buf;
u32 write_addr, write_done = 0, write_size, count = (u32)len;
loff_t empty_pos = 0;
ssize_t res;
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
panel_dbg("count %lu ppos %llu fpos: %llu\n", count, *ppos, file->f_pos);
if (!spi_dev->fopened) {
panel_err("panel_spi device is not opened\n");
return -EIO;
}
if (!buf) {
panel_err("invalid write buffer\n");
return -EINVAL;
}
mutex_lock(&spi_dev->f_lock);
write_buf = (u8 *)devm_kzalloc(panel->dev, count * sizeof(u8), GFP_KERNEL);
if (!write_buf) {
panel_err("invalid alloc mem\n");
mutex_unlock(&spi_dev->f_lock);
return -EINVAL;
}
res = simple_write_to_buffer(write_buf, count, &empty_pos, buf, count);
if (res < 0) {
panel_err("copy failed\n");
mutex_unlock(&spi_dev->f_lock);
return res;
}
while (write_done < count) {
write_addr = *ppos + write_done;
if (write_addr % SZ_256 != 0) {
panel_err("byte align error 0x%06X\n", write_addr);
mutex_unlock(&spi_dev->f_lock);
return -EINVAL;
}
if (spi_dev->auto_erase && write_addr % SZ_4K == 0) {
panel_info("auto erase 0x%06X\n", write_addr);
spi_data_buf.addr = write_addr;
spi_data_buf.size = SZ_4K;
panel_spi_flash_erase(spi_dev, &spi_data_buf);
}
write_size = count - write_done;
if (write_size > SZ_256)
write_size = SZ_256;
spi_data_buf.addr = write_addr;
spi_data_buf.size = write_size;
spi_data_buf.buf = write_buf + write_done;
panel_spi_flash_write(spi_dev, &spi_data_buf);
write_done += write_size;
}
*ppos += write_done;
mutex_unlock(&spi_dev->f_lock);
return count;
}
static int __ioctl_check_state(struct panel_spi_dev *spi_dev, unsigned long arg)
{
int ret;
ret = panel_spi_execute_cmd(spi_dev, spi_dev->spi_info.cmd_list[PANEL_SPI_CMD_FLASH_BUSY_CLEAR]);
panel_info("ret %d\n", ret);
if (copy_to_user((int *)arg, &ret, sizeof(int))) {
ret = -EFAULT;
}
return ret;
}
static int __ioctl_erase(struct panel_spi_dev *spi_dev, unsigned long arg)
{
struct ioc_erase_info erase_info;
struct spi_data_packet spi_data_buf;
int ret;
if (copy_from_user(&erase_info, (struct ioc_erase_info __user *)arg,
sizeof(struct ioc_erase_info))) {
panel_err("invalid data\n");
return -EINVAL;
}
if (erase_info.offset % SZ_4K != 0) {
panel_err("offset must be 4K aligned value\n");
return -EINVAL;
}
if (erase_info.length % SZ_4K != 0) {
panel_err("length must be 4K aligned value\n");
return -EINVAL;
}
spi_data_buf.addr = erase_info.offset;
spi_data_buf.size = erase_info.length;
spi_data_buf.type = PANEL_SPI_PACKET_TYPE_ERASE_NONBLOCK;
ret = panel_spi_flash_erase(spi_dev, &spi_data_buf);
if (ret < 0) {
panel_err("failed to erase %d", ret);
}
return ret;
}
static long panel_spi_fops_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct panel_spi_dev *spi_dev = file->private_data;
int ret = 0;
bool old_val;
panel_info("was called\n");
if (!SPI_IS_READY(spi_dev))
return -ENODEV;
mutex_lock(&spi_dev->f_lock);
switch (cmd) {
case IOCTL_AUTO_ERASE_ENABLE:
old_val = spi_dev->auto_erase;
spi_dev->auto_erase = true;
panel_info("IOCTL_AUTO_ERASE_ENABLE %d -> %d\n",
old_val, spi_dev->auto_erase);
break;
case IOCTL_AUTO_ERASE_DISABLE:
old_val = spi_dev->auto_erase;
spi_dev->auto_erase = false;
panel_info("IOCTL_AUTO_ERASE_DISABLE %d -> %d\n",
old_val, spi_dev->auto_erase);
break;
case IOCTL_CHECK_STATE:
panel_info("IOCTL_CHECK_STATE\n");
ret = __ioctl_check_state(spi_dev, arg);
if (ret < 0) {
panel_info("IOCTL_ERASE failed %d\n", ret);
}
break;
case IOCTL_ERASE:
panel_info("IOCTL_ERASE\n");
ret = __ioctl_erase(spi_dev, arg);
if (ret < 0) {
panel_info("IOCTL_ERASE failed %d\n", ret);
}
break;
default:
panel_info("Unknown cmd %d\n", cmd);
break;
}
mutex_unlock(&spi_dev->f_lock);
return ret;
}
static const struct file_operations panel_spi_drv_fops = {
.owner = THIS_MODULE,
.open = panel_spi_fops_open,
.release = panel_spi_fops_release,
.read = panel_spi_fops_read,
.write = panel_spi_fops_write,
.unlocked_ioctl = panel_spi_fops_ioctl,
.llseek = generic_file_llseek,
};
static const struct of_device_id panel_spi_match_table[] = {
{ .compatible = "poc_spi",},
{}
};
static struct spi_drv_ops panel_spi_drv_ops = {
.ctl = panel_spi_ctrl,
.cmd = panel_spi_sync,
.erase = panel_spi_flash_erase,
.read = panel_spi_flash_read,
.write = panel_spi_flash_write,
.init = panel_spi_flash_init,
.exit = panel_spi_flash_exit,
.get_buf_size = panel_spi_flash_get_buf_size,
};
static struct spi_drv_pdrv_ops panel_spi_drv_pdrv_ops = {
.pdrv_read = panel_spi_pdrv_read,
.pdrv_cmd = panel_spi_sync,
.pdrv_read_param = panel_spi_pdrv_read_param,
};
static struct spi_driver panel_spi_driver = {
.driver = {
.name = PANEL_SPI_DRIVER_NAME,
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.of_match_table = panel_spi_match_table,
},
.probe = panel_spi_probe,
.remove = panel_spi_remove,
};
struct spi_data *panel_spi_find_matched_data(struct spi_data **spi_data_tbl, int nr_spi_data_tbl, u32 read_id)
{
int i;
struct spi_data *data = NULL;
for (i = 0; i < nr_spi_data_tbl; i++) {
data = spi_data_tbl[i];
if ((read_id & data->compat_mask) == data->compat_id) {
panel_info("found spi_ic %s(%s)\n", data->model, data->vendor);
return data;
}
}
panel_err("cannot found spi_ic 0x%06X\n", read_id);
return NULL;
}
int panel_spi_drv_probe(struct panel_device *panel, struct spi_data **spi_data_tbl, int nr_spi_data_tbl)
{
int ret = 0, i;
struct panel_spi_dev *spi_dev;
struct spi_device *sdev;
struct device_node *np;
struct spi_data *spi_data;
struct spi_dev_info *spi_info;
static bool initialized;
if (!panel || nr_spi_data_tbl < 1) {
panel_err("spi_data_tbl(%d) not exist\n", nr_spi_data_tbl);
ret = -ENODEV;
goto rest_init;
}
spi_dev = &panel->panel_spi_dev;
spi_info = &spi_dev->spi_info;
if (!initialized) {
initialized = true;
spi_dev->ops = &panel_spi_drv_ops;
spi_dev->pdrv_ops = &panel_spi_drv_pdrv_ops;
spi_dev->setparam_buffer = (u8 *)devm_kzalloc(panel->dev, PANEL_SPI_MAX_CMD_SIZE * sizeof(u8), GFP_KERNEL);
spi_dev->read_buf_data = (u8 *)devm_kzalloc(panel->dev, PANEL_SPI_RX_BUF_SIZE * sizeof(u8), GFP_KERNEL);
spi_dev->dev.minor = MISC_DYNAMIC_MINOR;
spi_dev->dev.fops = &panel_spi_drv_fops;
spi_dev->dev.name = DRIVER_NAME;
spi_dev->auto_erase = false;
mutex_init(&spi_dev->f_lock);
ret = misc_register(&spi_dev->dev);
if (ret) {
panel_err("failed to register for spi_dev\n");
goto rest_init;
}
ret = spi_register_driver(&panel_spi_driver);
if (ret) {
panel_err("failed to register for spi device\n");
goto rest_init;
}
np = of_find_compatible_node(NULL, NULL, "poc_spi");
if (!np) {
panel_err("compatible(\"poc_spi\") node not found\n");
return -ENOENT;
}
sdev = of_find_spi_device_by_node(np);
of_node_put(np);
if (!sdev) {
panel_err("panel_spi device not found\n");
return -ENODEV;
}
spi_dev = (struct panel_spi_dev *)spi_get_drvdata(sdev);
if (!spi_dev) {
panel_err("failed to get pane_spi_device\n");
return -ENODEV;
}
spi_dev->spi = sdev;
}
spi_info->ready = false;
ret = panel_spi_read_id(spi_dev, &spi_info->id);
if (ret < 0) {
panel_err("failed to read id. check cable connection\n");
return -EIO;
}
panel_info("read id: 0x%06X\n", spi_info->id);
spi_data = panel_spi_find_matched_data(spi_data_tbl, nr_spi_data_tbl, spi_info->id);
if (spi_data == NULL) {
panel_err("failed to find spi data. unknown ic 0x%06X\n", spi_info->id);
return -ENODATA;
}
spi_info->model = spi_data->model;
spi_info->vendor = spi_data->vendor;
spi_info->cmd_list = spi_data->cmd_list;
spi_info->speed_hz = spi_data->speed_hz;
spi_info->erase_type = spi_data->erase_type;
spi_info->byte_per_write = spi_data->byte_per_write;
spi_info->byte_per_read = spi_data->byte_per_read;
if (spi_info->cmd_list) {
for (i = 0; i < MAX_PANEL_SPI_CMD; i++) {
if (panel_spi_cmd_exists(spi_dev, i)) {
panel_dbg("cmd found %d(%s) reg:0x%02X opt:%d\n",
i, panel_spi_cmd_str[i], spi_info->cmd_list[i]->reg,
spi_info->cmd_list[i]->opt);
} else {
panel_dbg("cmd empty %d(%s)\n", i, panel_spi_cmd_str[i]);
}
}
spi_info->ready = true;
} else {
panel_err("cmd list not found. check ic's header file\n");
}
panel_info("done\n");
rest_init:
return ret;
}
int panel_spi_drv_remove(struct panel_device *panel)
{
return 0;
}