kernel-brax3-ubuntu-touch/drivers/misc/mediatek/spi_slave_drv/spi_slave.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

597 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022 MediaTek Inc.
*/
#define SPI_SLAVE_DRV_NAME "spi-slave"
#define pr_fmt(fmt) SPI_SLAVE_DRV_NAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cache.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/kthread.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/pinctrl/consumer.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <linux/platform_data/spi-mt65xx.h>
#include "spi_slave.h"
/*
* SPI command description.
*/
#define CMD_PWOFF 0x02 /* Power Off */
#define CMD_PWON 0x04 /* Power On */
#define CMD_RS 0x06 /* Read Status */
#define CMD_WS 0x08 /* Write Status */
#define CMD_CR 0x0a /* Config Read */
#define CMD_CW 0x0c /* Config Write */
#define CMD_RD 0x81 /* Read Data */
#define CMD_WD 0x0e /* Write Data */
#define CMD_CT 0x10 /* Config Type */
/*
* SPI slave status register (to master).
*/
#define SLV_ON BIT(0)
#define SR_CFG_SUCCESS BIT(1)
#define SR_TXRX_FIFO_RDY BIT(2)
#define SR_RD_ERR BIT(3)
#define SR_WR_ERR BIT(4)
#define SR_RDWR_FINISH BIT(5)
#define SR_TIMEOUT_ERR BIT(6)
#define SR_CMD_ERR BIT(7)
#define CONFIG_READY ((SR_CFG_SUCCESS | SR_TXRX_FIFO_RDY))
/*
* hardware limit for once transfter.
*/
#define MAX_SPI_XFER_SIZE_ONCE (64 * 1024 - 1)
#define MAX_SPI_TRY_CNT (3)
/*
* default never pass more than 32 bytes
*/
#define MTK_SPI_BUFSIZ min(32, SMP_CACHE_BYTES)
#define SPI_READ_STA_ERR_RET (1)
/*
* spi slave config
*/
#define IOCFG_BASE_ADDR 0x00005000
#define DRV_CFG0 (IOCFG_BASE_ADDR + 0x0)
#define SPIS_SLVO_MASK (0x7 << 21)
#define SPISLV_BASE_ADDR 0x00002000
#define SPISLV_CTRL (SPISLV_BASE_ADDR + 0x0)
#define EARLY_TRANS_MASK (0x1 << 16)
/* specific SPI data */
struct mtk_spi_slave_data {
struct spi_device *spi;
u32 tx_speed_hz;
u32 rx_speed_hz;
u8 slave_drive_strength;
u8 high_speed_tick_delay;
u8 low_speed_tick_delay;
u8 high_speed_early_trans;
u8 low_speed_early_trans;
/* mutex for SPI Slave IO */
struct mutex spislv_mutex;
u8 tx_nbits:3;
u8 rx_nbits:3;
};
static struct mtk_spi_slave_data slv_data = {
.spi = NULL,
.tx_speed_hz = SPI_TX_LOW_SPEED_HZ,
.rx_speed_hz = SPI_RX_LOW_SPEED_HZ,
.slave_drive_strength = 0,
.high_speed_tick_delay = 0,
.low_speed_tick_delay = 0,
.high_speed_early_trans = 0,
.low_speed_early_trans = 0,
.tx_nbits = 0,
.rx_nbits = 0,
};
/*
* A piece of default chip info unless the platform
* supplies it.
*/
static struct mtk_chip_config spislv_chip_info = {
//.rx_mlsb = 0,
//.tx_mlsb = 0,
.sample_sel = 0,
.cs_setuptime = 0,
.cs_holdtime = 0,
.cs_idletime = 0,
//.deassert_mode = false,
.tick_delay = 0,
};
static u8 cmd_trans_type_4byte_single[2] = {CMD_CT, 0x04};
static u8 tx_cmd_read_sta[2] = {CMD_RS, 0x00};
static u8 rx_cmd_read_sta[2] = {0x00, 0x00};
static struct spi_transfer CT_TRANSFER = {0};
static struct spi_transfer RS_TRANSFER = {0};
static int spislv_sync_sub(u32 addr, void *val, u32 len, bool is_read)
{
int ret = 0, i = 0;
struct spi_message msg;
struct spi_transfer x[3] = {0}; /* CW/CR, WD/RD, WS */
void *local_buf = NULL;
u8 mtk_spi_buffer[MTK_SPI_BUFSIZ];
u8 cmd_write_sta[2] = {CMD_WS, 0xff};
u8 status = 0;
u32 retry = 0;
u8 cmd_config[9] = {0};
/* CR or CW */
if (is_read)
cmd_config[0] = CMD_CR;
else
cmd_config[0] = CMD_CW;
for (i = 0; i < 4; i++) {
cmd_config[1 + i] = (addr & (0xff << (i * 8))) >> (i * 8);
cmd_config[5 + i] = ((len - 1) & (0xff << (i * 8))) >> (i * 8);
}
x[0].tx_buf = cmd_config;
x[0].len = ARRAY_SIZE(cmd_config);
x[0].tx_nbits = slv_data.tx_nbits;
x[0].rx_nbits = slv_data.rx_nbits;
x[0].speed_hz = slv_data.tx_speed_hz;
x[0].cs_change = 1;
spi_message_init(&msg);
spi_message_add_tail(&x[0], &msg);
/* RS */
rx_cmd_read_sta[1] = 0;
spi_message_add_tail(&RS_TRANSFER, &msg);
ret = spi_sync(slv_data.spi, &msg);
if (ret)
goto fail;
status = rx_cmd_read_sta[1];
/* ignore status for set early transfer bit */
if (addr == SPISLV_CTRL && !is_read)
status = 0x6;
if ((status & CONFIG_READY) != CONFIG_READY) {
pr_notice("SPI config %s but status error: 0x%x, latched by %dHZ, err addr: 0x%x\n",
is_read ? "read" : "write", status, slv_data.rx_speed_hz, addr);
ret = SPI_READ_STA_ERR_RET;
goto fail;
}
/* RD or WD */
if (len > MTK_SPI_BUFSIZ - 1) {
local_buf = kzalloc(len + 1, GFP_KERNEL);
if (!local_buf) {
ret = -ENOMEM;
goto fail;
}
} else {
local_buf = mtk_spi_buffer;
memset(local_buf, 0, MTK_SPI_BUFSIZ);
}
if (is_read) {
*((u8 *)local_buf) = CMD_RD;
x[1].tx_buf = local_buf;
x[1].rx_buf = local_buf;
x[1].speed_hz = slv_data.rx_speed_hz;
} else {
*((u8 *)local_buf) = CMD_WD;
memcpy((u8 *)local_buf + 1, val, len);
x[1].tx_buf = local_buf;
x[1].speed_hz = slv_data.tx_speed_hz;
}
x[1].tx_nbits = slv_data.tx_nbits;
x[1].rx_nbits = slv_data.rx_nbits;
x[1].len = len + 1;
x[1].cs_change = 1;
spi_message_init(&msg);
spi_message_add_tail(&x[1], &msg);
/* RS */
rx_cmd_read_sta[1] = 0;
spi_message_add_tail(&RS_TRANSFER, &msg);
ret = spi_sync(slv_data.spi, &msg);
if (ret)
goto fail;
status = rx_cmd_read_sta[1];
/* ignore status for set early transfer bit */
if (addr == SPISLV_CTRL && !is_read)
status = 0x26;
if (((status & SR_RD_ERR) == SR_RD_ERR) ||
((status & SR_WR_ERR) == SR_WR_ERR) ||
((status & SR_TIMEOUT_ERR) == SR_TIMEOUT_ERR)) {
pr_notice("SPI %s error, status: 0x%x, latched by %dHZ, err addr: 0x%x\n",
is_read ? "read" : "write", status, slv_data.rx_speed_hz, addr);
/* WS */
x[2].tx_buf = cmd_write_sta;
x[2].len = ARRAY_SIZE(cmd_write_sta);
x[2].tx_nbits = slv_data.tx_nbits;
x[2].rx_nbits = slv_data.rx_nbits;
x[2].speed_hz = slv_data.tx_speed_hz;
spi_message_init(&msg);
spi_message_add_tail(&x[2], &msg);
ret = spi_sync(slv_data.spi, &msg);
if (ret)
goto fail;
ret = SPI_READ_STA_ERR_RET;
} else {
while (((status & SR_RDWR_FINISH) != SR_RDWR_FINISH)) {
pr_notice("SPI %s not finish, status: 0x%x, latched by %dHZ, err addr: 0x%x, polling: %d\n",
is_read ? "read" : "write",
status, slv_data.rx_speed_hz, addr, retry);
if (retry++ >= MAX_SPI_TRY_CNT) {
ret = SPI_READ_STA_ERR_RET;
goto fail;
}
mdelay(1);
/* RS */
rx_cmd_read_sta[1] = 0;
spi_message_init(&msg);
spi_message_add_tail(&RS_TRANSFER, &msg);
ret = spi_sync(slv_data.spi, &msg);
if (ret)
goto fail;
status = rx_cmd_read_sta[1];
}
}
fail:
/* Only for successful read */
if (is_read && !ret)
memcpy(val, ((u8 *)x[1].rx_buf + 1), len);
if (local_buf != mtk_spi_buffer)
kfree(local_buf);
return ret;
}
static int spislv_sync(u32 addr, void *val, u32 len, bool is_read)
{
int ret = 0;
u32 addr_local = addr;
void *val_local = val;
u32 len_local = len;
u32 try = 0;
mutex_lock(&slv_data.spislv_mutex);
if (len_local < MAX_SPI_XFER_SIZE_ONCE)
goto transfer_drect;
while (len_local > MAX_SPI_XFER_SIZE_ONCE) {
ret = spislv_sync_sub(addr_local, val_local, MAX_SPI_XFER_SIZE_ONCE, is_read);
while (ret) {
pr_notice("spi slave error, addr: 0x%x, ret(%d), retry: %d\n",
addr_local, ret, try);
if (try++ == MAX_SPI_TRY_CNT)
goto fail;
ret = spislv_sync_sub(addr_local, val_local,
MAX_SPI_XFER_SIZE_ONCE, is_read);
}
addr_local = addr_local + MAX_SPI_XFER_SIZE_ONCE;
val_local = (u8 *)val_local + MAX_SPI_XFER_SIZE_ONCE;
len_local = len_local - MAX_SPI_XFER_SIZE_ONCE;
}
transfer_drect:
try = 0;
ret = spislv_sync_sub(addr_local, val_local, len_local, is_read);
while (ret) {
pr_notice("spi slave error, addr: 0x%x, ret(%d), retry: %d\n",
addr_local, ret, try);
if (try++ == MAX_SPI_TRY_CNT)
goto fail;
ret = spislv_sync_sub(addr_local, val_local, len_local, is_read);
}
fail:
mutex_unlock(&slv_data.spislv_mutex);
return ret;
}
static u8 tick_window_early_0[8];
static u8 tick_window_early_0_len;
static u8 tick_window_early_1[8];
static u8 tick_window_early_1_len;
static u8 spislv_select_tick_delay(u8 *tick_delay_window, u8 win_len)
{
u8 index = 0, win_start = 0, tick_delay = 0;
for (index = 0; index < 8; index++) {
if (tick_delay_window[index] == 1) {
win_start = index;
break;
}
}
if (win_len % 2)
tick_delay = win_start + (win_len-1)/2;
else
tick_delay = win_start + win_len/2;
if (tick_delay_window[tick_delay] == 1)
return tick_delay;
else
return win_start;
}
static u32 spislv_test(u32 tx_speed_hz, u32 rx_speed_hz, u32 tick_delay)
{
int i, ret = 0;
u32 addr = 0x00002000;
u32 len = 4;
u8 cmd_config[] = {
CMD_CR, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};
//debug
u8 cmd_config_status[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};
u8 read_status;
struct spi_transfer x[2];
struct spi_message msg;
memset(x, 0, sizeof(x));
for (i = 0; i < 4; i++) {
cmd_config[1 + i] = (addr & (0xff << (i * 8))) >> (i * 8);
cmd_config[5 + i] = ((len - 1) & (0xff << (i * 8))) >> (i * 8);
}
x[0].tx_buf = cmd_config;
x[0].rx_buf = cmd_config_status;
x[0].len = ARRAY_SIZE(cmd_config);
x[0].tx_nbits = slv_data.tx_nbits;
x[0].rx_nbits = slv_data.rx_nbits;
x[0].speed_hz = tx_speed_hz;
x[0].cs_change = 1;
spislv_chip_info.tick_delay = tick_delay;
spi_message_init(&msg);
spi_message_add_tail(&x[0], &msg);
rx_cmd_read_sta[1] = 0;
RS_TRANSFER.speed_hz = rx_speed_hz;
spi_message_add_tail(&RS_TRANSFER, &msg);
ret = spi_sync(slv_data.spi, &msg);
if (ret)
goto fail;
read_status = rx_cmd_read_sta[1];
pr_info("spi read_cmd: %x, cmd_sta[0]: %x, cmd_sta[1]: %x\n",
tx_cmd_read_sta[0], rx_cmd_read_sta[0], rx_cmd_read_sta[1]);
if ((read_status & CONFIG_READY) != CONFIG_READY)
return 0;
else
return 1;
fail:
if (ret)
pr_notice("error: spi sync err: %d\n", ret);
return 0;
}
static void spislv_autok(u32 tx_speed_hz, u32 rx_speed_hz)
{
u32 index;
u8 early_trans, tick_delay;
tick_window_early_0_len = 0;
tick_window_early_1_len = 0;
pr_info("[spislv] write: %dHz, read: %dHz, autok window:\n",
tx_speed_hz, rx_speed_hz);
/* set early_trans: 0 */
spislv_write_register(SPISLV_CTRL, (0x40 & (~(EARLY_TRANS_MASK)))
| ((0 << 16) & (EARLY_TRANS_MASK)));
/* scan window */
for (index = 0; index < 8; index++) {
if (spislv_test(tx_speed_hz, rx_speed_hz, index)) {
tick_window_early_0[index] = 1;
tick_window_early_0_len++;
} else
tick_window_early_0[index] = 0;
}
for (index = 0; index < 8; index++) {
pr_info("[spislv] autok: early_trans: 0, tick_delay: %d, window: %s\n",
index, tick_window_early_0[index] == 1 ? "O" : "X");
}
/* set early_trans: 1 */
spislv_write_register(SPISLV_CTRL, (0x40 & (~(EARLY_TRANS_MASK)))
| ((1 << 16) & (EARLY_TRANS_MASK)));
/* scan window */
for (index = 0; index < 8; index++) {
if (spislv_test(SPI_TX_LOW_SPEED_HZ, SPI_RX_LOW_SPEED_HZ,
index)) {
tick_window_early_1[index] = 1;
tick_window_early_1_len++;
} else
tick_window_early_1[index] = 0;
}
for (index = 0; index < 8; index++) {
pr_info("[spislv] autok: early_trans: 1, tick_delay: %d, window: %s\n",
index, tick_window_early_1[index] == 1 ? "O" : "X");
}
if (tick_window_early_0_len >= tick_window_early_1_len) {
early_trans = 0;
tick_delay = spislv_select_tick_delay
(tick_window_early_0, tick_window_early_0_len);
} else {
early_trans = 1;
tick_delay = spislv_select_tick_delay
(tick_window_early_1, tick_window_early_1_len);
}
if (rx_speed_hz >= SPI_RX_MAX_SPEED_HZ) {
slv_data.high_speed_early_trans = early_trans;
slv_data.high_speed_tick_delay = tick_delay;
pr_info("[spislv] autok result: high_speed_early_trans: %d, high_speed_tick_delay: %d\n",
slv_data.high_speed_early_trans, slv_data.high_speed_tick_delay);
} else {
slv_data.low_speed_early_trans = early_trans;
slv_data.low_speed_tick_delay = tick_delay;
pr_info("[spislv] autok low_speed_early_trans: %d, low_speed_tick_delay: %d\n",
slv_data.low_speed_early_trans, slv_data.low_speed_tick_delay);
}
}
int spislv_init(void)
{
struct spi_message msg;
int ret = 0;
spislv_chip_info.tick_delay = slv_data.low_speed_tick_delay;
slv_data.tx_speed_hz = SPI_TX_LOW_SPEED_HZ;
slv_data.rx_speed_hz = SPI_RX_LOW_SPEED_HZ;
RS_TRANSFER.speed_hz = slv_data.rx_speed_hz;
spi_message_init(&msg);
spi_message_add_tail(&CT_TRANSFER, &msg);
ret = spi_sync(slv_data.spi, &msg);
if (ret)
return ret;
ret = spislv_write_register(SPISLV_CTRL, (0x40 & (~(EARLY_TRANS_MASK)))
| ((slv_data.low_speed_early_trans << 16) & (EARLY_TRANS_MASK)));
if (ret)
return ret;
ret = spislv_write_register_mask(DRV_CFG0,
(slv_data.slave_drive_strength << 21), SPIS_SLVO_MASK);
return ret;
}
EXPORT_SYMBOL(spislv_init);
int spislv_switch_speed_hz(u32 tx_speed_hz, u32 rx_speed_hz)
{
int ret = 0;
if (rx_speed_hz >= SPI_RX_MAX_SPEED_HZ) {
ret = spislv_write_register(SPISLV_CTRL, (0x40 & (~(EARLY_TRANS_MASK)))
| ((slv_data.high_speed_early_trans << 16) & (EARLY_TRANS_MASK)));
spislv_chip_info.tick_delay = slv_data.high_speed_tick_delay;
} else {
ret = spislv_write_register(SPISLV_CTRL, (0x40 & (~(EARLY_TRANS_MASK)))
| ((slv_data.low_speed_early_trans << 16) & (EARLY_TRANS_MASK)));
spislv_chip_info.tick_delay = slv_data.low_speed_tick_delay;
}
slv_data.tx_speed_hz =
(tx_speed_hz > SPI_TX_MAX_SPEED_HZ ? SPI_TX_MAX_SPEED_HZ : tx_speed_hz);
slv_data.rx_speed_hz =
(rx_speed_hz > SPI_RX_MAX_SPEED_HZ ? SPI_RX_MAX_SPEED_HZ : rx_speed_hz);
RS_TRANSFER.speed_hz = slv_data.rx_speed_hz;
return ret;
}
EXPORT_SYMBOL(spislv_switch_speed_hz);
int spislv_write(u32 addr, void *val, u32 len)
{
return spislv_sync(addr, val, len, 0);
}
EXPORT_SYMBOL(spislv_write);
int spislv_read(u32 addr, void *val, u32 len)
{
return spislv_sync(addr, val, len, 1);
}
EXPORT_SYMBOL(spislv_read);
int spislv_read_register(u32 addr, u32 *val)
{
return spislv_read(addr, (u8 *)val, 4);
}
EXPORT_SYMBOL(spislv_read_register);
int spislv_write_register(u32 addr, u32 val)
{
return spislv_write(addr, (u8 *)&val, 4);
}
EXPORT_SYMBOL(spislv_write_register);
int spislv_write_register_mask(u32 addr, u32 val, u32 msk)
{
u32 ret = 0;
u32 read_val;
ret = spislv_read_register(addr, &read_val);
if (ret)
return ret;
ret = spislv_write_register(addr, ((read_val & (~(msk))) | ((val) & (msk))));
return ret;
}
EXPORT_SYMBOL(spislv_write_register_mask);
static int spi_slave_probe(struct spi_device *spi)
{
int ret = 0;
struct device_node *nc = spi->dev.of_node;
struct pinctrl *spislv_pinctrl;
struct pinctrl_state *pin_spi_mode;
slv_data.spi = spi;
ret = of_property_read_u8(nc, "slave-drive-strength", &(slv_data.slave_drive_strength));
if (ret)
pr_info("slave-drive-strength isn't setting!\n");
else
pr_info("slave-drive-strength = %d\n", slv_data.slave_drive_strength);
if (spi->mode & SPI_TX_DUAL)
slv_data.tx_nbits = SPI_NBITS_DUAL;
else if (spi->mode & SPI_TX_QUAD)
slv_data.tx_nbits = SPI_NBITS_QUAD;
else
slv_data.tx_nbits = SPI_NBITS_SINGLE;
if (spi->mode & SPI_RX_DUAL)
slv_data.rx_nbits = SPI_NBITS_DUAL;
else if (spi->mode & SPI_RX_QUAD)
slv_data.rx_nbits = SPI_NBITS_QUAD;
else
slv_data.rx_nbits = SPI_NBITS_SINGLE;
/* set spi master driving */
spislv_pinctrl = devm_pinctrl_get(slv_data.spi->controller->dev.parent);
if (IS_ERR_OR_NULL(spislv_pinctrl))
pr_notice("Failed to get pinctrl handler!\n");
pin_spi_mode = pinctrl_lookup_state(spislv_pinctrl, "default");
ret = pinctrl_select_state(spislv_pinctrl, pin_spi_mode);
if (ret < 0)
pr_notice("Failed to select pinctrl!\n");
/* init transfers */
if (slv_data.tx_nbits == SPI_NBITS_SINGLE) {
CT_TRANSFER.tx_buf = cmd_trans_type_4byte_single;
CT_TRANSFER.len = ARRAY_SIZE(cmd_trans_type_4byte_single);
} else {
pr_notice("spi slave: don't support other transfer type.\n");
return -EINVAL;
}
CT_TRANSFER.tx_nbits = slv_data.tx_nbits;
CT_TRANSFER.rx_nbits = slv_data.rx_nbits;
CT_TRANSFER.speed_hz = slv_data.tx_speed_hz;
RS_TRANSFER.tx_buf = tx_cmd_read_sta;
RS_TRANSFER.rx_buf = rx_cmd_read_sta;
RS_TRANSFER.len = ARRAY_SIZE(tx_cmd_read_sta);
RS_TRANSFER.tx_nbits = slv_data.tx_nbits;
RS_TRANSFER.rx_nbits = slv_data.rx_nbits;
RS_TRANSFER.speed_hz = slv_data.rx_speed_hz;
/* fix 6382 Screen still be black when lock phone and select power on key */
spi->mode = 0x08;
spi->bits_per_word = 8;
spi->cs_setup.unit = SPI_DELAY_UNIT_NSECS;
spi->cs_setup.value = 33;
spi->controller_data = (void *)&spislv_chip_info;
spislv_chip_info.tick_delay = slv_data.low_speed_tick_delay;
spislv_autok(SPI_TX_LOW_SPEED_HZ, SPI_RX_LOW_SPEED_HZ);
spislv_autok(SPI_TX_MAX_SPEED_HZ, SPI_RX_MAX_SPEED_HZ);
mutex_init(&slv_data.spislv_mutex);
return 0;
}
static int spi_slave_remove(struct spi_device *spi)
{
if (spi && spi->controller_data)
kfree(spi->controller_data);
return 0;
}
static const struct of_device_id spi_slave_of_ids[] = {
{ .compatible = "mediatek,spi_slave" },
{}
};
MODULE_DEVICE_TABLE(of, spi_slave_of_ids);
static struct spi_driver spi_slave_drv = {
.driver = {
.name = SPI_SLAVE_DRV_NAME,
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.of_match_table = spi_slave_of_ids,
},
.probe = spi_slave_probe,
.remove = spi_slave_remove,
};
module_spi_driver(spi_slave_drv);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shi Ma <shi.ma@mediatek.com>");
MODULE_DESCRIPTION("SPI driver for mt6382");