You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

604 lines
14 KiB

/*
* Copyright (C) 2016 MediaTek Inc.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See http://www.gnu.org/licenses/gpl-2.0.html for more details.
*/
#include <linux/spi/spi.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/platform_data/spi-mt65xx.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/clk.h>
#ifdef CONFIG_TRUSTONIC_TEE_SUPPORT
#define SPI_TRUSTONIC_TEE_SUPPORT
#endif
#ifdef SPI_TRUSTONIC_TEE_SUPPORT
#include <mobicore_driver_api.h>
#include <tlspi_Api.h>
#endif
#define SPIDEV_LOG(fmt, args...) pr_err("[SPI-UT]: [%s]:[%d]" fmt, __func__, __LINE__, ##args)
#define SPIDEV_MSG(fmt, args...) pr_err(fmt, ##args)
static struct spi_device *spi_test;
struct mtk_spi {
void __iomem *base;
void __iomem *peri_regs;
u32 state;
int pad_num;
u32 *pad_sel;
struct clk *parent_clk, *sel_clk, *spi_clk;
struct spi_transfer *cur_transfer;
u32 xfer_len;
struct scatterlist *tx_sgl, *rx_sgl;
u32 tx_sgl_len, rx_sgl_len;
const struct mtk_spi_compatible *dev_comp;
u32 dram_8gb_offset;
};
#ifdef SPI_TRUSTONIC_TEE_SUPPORT
#define DEFAULT_HANDLES_NUM (64)
#define MAX_OPEN_SESSIONS (0xffffffff - 1)
/*
* Trustlet UUID.
*/
u8 spi_uuid[10][16] = {{0x09, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0x09, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} };
static struct mc_session_handle secspi_session = { 0 };
static u32 secspi_session_ref;
static u32 secspi_devid = MC_DEVICE_ID_DEFAULT;
static tciSpiMessage_t *secspi_tci;
static DEFINE_MUTEX(secspi_lock);
int secspi_session_open(u32 spinum)
{
enum mc_result mc_ret = MC_DRV_OK;
mutex_lock(&secspi_lock);
pr_warn("secspi_session_open start\n");
do {
/* sessions reach max numbers ? */
if (secspi_session_ref > MAX_OPEN_SESSIONS) {
pr_warn("secspi_session > 0x%x\n", MAX_OPEN_SESSIONS);
break;
}
if (secspi_session_ref > 0) {
secspi_session_ref++;
break;
}
/* open device */
mc_ret = mc_open_device(secspi_devid);
if (mc_ret != MC_DRV_OK) {
pr_warn("mc_open_device failed: %d\n", mc_ret);
break;
}
/* allocating WSM for DCI */
mc_ret =
mc_malloc_wsm(secspi_devid, 0, sizeof(tciSpiMessage_t),
(uint8_t **) &secspi_tci, 0);
if (mc_ret != MC_DRV_OK) {
pr_warn("mc_malloc_wsm failed: %d\n", mc_ret);
mc_close_device(secspi_devid);
break;
}
/* open session */
secspi_session.device_id = secspi_devid;
/* mc_ret =
* mc_open_session(&secspi_session, &secspi_uuid, (uint8_t *) secspi_tci,
* sizeof(tciSpiMessage_t));
*/
mc_ret = mc_open_session(&secspi_session, (struct mc_uuid_t *)&spi_uuid[spinum][0],
(uint8_t *)secspi_tci, sizeof(tciSpiMessage_t));
if (mc_ret != MC_DRV_OK) {
pr_warn("secspi_session_open fail: %d\n", mc_ret);
mc_free_wsm(secspi_devid, (uint8_t *) secspi_tci);
mc_close_device(secspi_devid);
secspi_tci = NULL;
break;
}
secspi_session_ref = 1;
} while (0);
pr_warn("secspi_session_open: ret=%d, ref=%d\n", mc_ret, secspi_session_ref);
mutex_unlock(&secspi_lock);
pr_err("secspi_session_open end\n");
if (mc_ret != MC_DRV_OK)
return -ENXIO;
return 0;
}
static int secspi_session_close(void)
{
enum mc_result mc_ret = MC_DRV_OK;
mutex_lock(&secspi_lock);
do {
/* session is already closed ? */
if (secspi_session_ref == 0) {
SPIDEV_MSG("spi_session already closed\n");
break;
}
if (secspi_session_ref > 1) {
secspi_session_ref--;
break;
}
/* close session */
mc_ret = mc_close_session(&secspi_session);
if (mc_ret != MC_DRV_OK) {
SPIDEV_MSG("SPI mc_close_session failed: %d\n", mc_ret);
break;
}
/* free WSM for DCI */
mc_ret = mc_free_wsm(secspi_devid, (uint8_t *) secspi_tci);
if (mc_ret != MC_DRV_OK) {
SPIDEV_MSG("SPI mc_free_wsm failed: %d\n", mc_ret);
break;
}
secspi_tci = NULL;
secspi_session_ref = 0;
/* close device */
mc_ret = mc_close_device(secspi_devid);
if (mc_ret != MC_DRV_OK)
SPIDEV_MSG("SPI mc_close_device failed: %d\n", mc_ret);
} while (0);
SPIDEV_MSG("secspi_session_close: ret=%d, ref=%d\n", mc_ret, secspi_session_ref);
mutex_unlock(&secspi_lock);
if (mc_ret != MC_DRV_OK)
return -ENXIO;
return 0;
}
void secspi_enable_clk(struct spi_device *spidev)
{
int ret;
struct spi_master *master;
struct mtk_spi *ms;
master = spidev->master;
ms = spi_master_get_devdata(master);
/*
* prepare the clock source
*/
ret = clk_prepare_enable(ms->spi_clk);
}
int secspi_execute(u32 cmd, tciSpiMessage_t *param)
{
enum mc_result mc_ret;
pr_warn("secspi_execute\n");
mutex_lock(&secspi_lock);
if (secspi_tci == NULL) {
mutex_unlock(&secspi_lock);
pr_warn("secspi_tci not exist\n");
return -ENODEV;
}
/*set transfer data para */
if (param == NULL) {
pr_warn("secspi_execute parameter is NULL !!\n");
} else {
secspi_tci->tx_buf = param->tx_buf;
secspi_tci->rx_buf = param->rx_buf;
secspi_tci->len = param->len;
secspi_tci->is_dma_used = param->is_dma_used;
secspi_tci->tx_dma = param->tx_dma;
secspi_tci->rx_dma = param->rx_dma;
secspi_tci->tl_chip_config = param->tl_chip_config;
}
secspi_tci->cmd_spi.header.commandId = (tciCommandId_t) cmd;
secspi_tci->cmd_spi.len = 0;
pr_warn("mc_notify\n");
/* enable_clock(MT_CG_PERI_SPI0, "spi"); */
/* enable_clk(ms); */
mc_ret = mc_notify(&secspi_session);
if (mc_ret != MC_DRV_OK) {
pr_warn("mc_notify failed: %d", mc_ret);
goto exit;
}
pr_warn("SPI mc_wait_notification\n");
mc_ret = mc_wait_notification(&secspi_session, -1);
if (mc_ret != MC_DRV_OK) {
pr_warn("SPI mc_wait_notification failed: %d", mc_ret);
goto exit;
}
exit:
mutex_unlock(&secspi_lock);
if (mc_ret != MC_DRV_OK)
return -ENOSPC;
return 0;
}
/*used for REE to detach IRQ of TEE*/
void spi_detach_irq_tee(u32 spinum)
{
secspi_session_open(spinum);
secspi_execute(2, NULL);
pr_warn("secspi_execute 2 finished!!!\n");
}
#endif
void mt_spi_disable_master_clk(struct spi_device *spidev)
{
struct mtk_spi *ms;
ms = spi_master_get_devdata(spidev->master);
/*
* unprepare the clock source
*/
clk_disable_unprepare(ms->spi_clk);
}
EXPORT_SYMBOL(mt_spi_disable_master_clk);
void mt_spi_enable_master_clk(struct spi_device *spidev)
{
int ret;
struct mtk_spi *ms;
ms = spi_master_get_devdata(spidev->master);
/*
* prepare the clock source
*/
ret = clk_prepare_enable(ms->spi_clk);
}
EXPORT_SYMBOL(mt_spi_enable_master_clk);
static int spi_setup_xfer(struct device *dev, struct spi_transfer *xfer, u32 len, u32 flag)
{
u32 tx_buffer = 0x12345678;
u32 cnt, i;
xfer->len = len;
xfer->tx_buf = kzalloc(len, GFP_KERNEL);
xfer->rx_buf = kzalloc(len, GFP_KERNEL);
xfer->speed_hz = 500000;
xfer->len = len;
/* Instead of using kzalloc, if using below
* dma_alloc_coherent to allocate tx_dma/rx_dma, remember:
* 1. remove kfree in spi_recv_check_all, otherwise KE reboot
* 2. set msg.is_dma_mapped = 1 before calling spi_sync();
*/
#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
SPIDEV_LOG("Transfer addr:Tx:0x%llx, Rx:0x%llx\n", xfer->tx_dma, xfer->rx_dma);
#else
SPIDEV_LOG("Transfer addr:Tx:0x%x, Rx:0x%x\n", xfer->tx_dma, xfer->rx_dma);
#endif
if ((xfer->tx_buf == NULL) || (xfer->rx_buf == NULL))
return -1;
cnt = (len%4)?(len/4 + 1):(len/4);
if (flag == 0) {
for (i = 0; i < cnt; i++)
*((u32 *)xfer->tx_buf + i) = tx_buffer;
} else if (flag == 1) {
for (i = 0; i < cnt; i++)
*((u32 *)xfer->tx_buf + i) = tx_buffer + i;
} else if (flag == 2) {
} else {
return -EINVAL;
}
return 0;
}
static int spi_recv_check(struct spi_message *msg)
{
struct spi_transfer *xfer;
u32 cnt, i, err = 0;
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
if (!xfer) {
SPIDEV_MSG("rv msg is NULL.\n");
return -1;
}
cnt = (xfer->len%4)?(xfer->len/4 + 1):(xfer->len/4);
for (i = 0; i < cnt; i++) {
if (*((u32 *) xfer->rx_buf + i) != *((u32 *) xfer->tx_buf + i)) {
SPIDEV_LOG("tx xfer %d is:%.8x\n", i, *((u32 *) xfer->tx_buf + i));
SPIDEV_LOG("rx xfer %d is:%.8x\n", i, *((u32 *) xfer->rx_buf + i));
SPIDEV_LOG("\n");
err++;
}
}
kfree(xfer->tx_buf);
kfree(xfer->rx_buf);
}
SPIDEV_LOG("Message:0x%p,error %d,actual xfer length is:%d\n", msg, err, msg->actual_length);
return err;
}
static ssize_t spi_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi;
struct mtk_chip_config *chip_config;
#ifdef CONFIG_TRUSTONIC_TEE_SUPPORT
u32 spinum;
#endif
spi = container_of(dev, struct spi_device, dev);
SPIDEV_LOG("SPIDEV name is:%s\n", spi->modalias);
chip_config = (struct mtk_chip_config *) spi->controller_data;
if (!chip_config) {
SPIDEV_LOG("chip_config is NULL.\n");
chip_config = kzalloc(sizeof(struct mtk_chip_config), GFP_KERNEL);
if (!chip_config)
return -ENOMEM;
}
if (!buf) {
SPIDEV_LOG("buf is NULL.\n");
goto out;
}
#ifdef CONFIG_TRUSTONIC_TEE_SUPPORT
if (!strncmp(buf, "send", 4) && (sscanf(buf + 4, "%d", &spinum) == 1)) { /*TRANSFER*/
SPIDEV_MSG("start to access TL SPI driver.\n");
secspi_session_open(spinum);
secspi_enable_clk(spi);
secspi_execute(1, NULL);
secspi_session_close();
SPIDEV_MSG("secspi_execute 1 finished!!!\n");
} else if (!strncmp(buf, "config", 6) && (sscanf(buf + 6, "%d", &spinum) == 1)) { /*HW CONFIG*/
SPIDEV_MSG("start to access TL SPI driver.\n");
secspi_session_open(spinum);
secspi_execute(2, NULL);
secspi_session_close();
SPIDEV_MSG("secspi_execute 2 finished!!!\n");
} else if (!strncmp(buf, "debug", 5) && (sscanf(buf + 5, "%d", &spinum) == 1)) { /*DEBUG*/
SPIDEV_MSG("start to access TL SPI driver.\n");
secspi_session_open(spinum);
secspi_execute(3, NULL);
secspi_session_close();
SPIDEV_MSG("secspi_execute 3 finished!!!\n");
} else if (!strncmp(buf, "test", 4) && (sscanf(buf + 4, "%d", &spinum) == 1)) { /*TEST*/
SPIDEV_MSG("start to access TL SPI driver.\n");
secspi_session_open(spinum);
secspi_execute(4, NULL);
secspi_session_close();
SPIDEV_MSG("secspi_execute 4 finished!!!\n");
#else
if (!strncmp(buf, "-h", 2)) {
SPIDEV_MSG("Please input the parameters for this device.\n");
#endif
} else if (!strncmp(buf, "-w", 2)) {
}
out:
if (!spi->controller_data)
kfree(chip_config);
return count;
}
static ssize_t
spi_msg_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret = 0;
struct spi_device *spi;
struct spi_transfer transfer = {0,};
struct spi_message msg;
/*struct mtk_chip_config *chip_config;*/
u32 len = 4;
u32 tx_buffer = 0x12345678;
u32 rx_buffer = 0xaaaaaaaa;
transfer.tx_buf = &tx_buffer;
transfer.rx_buf = &rx_buffer;
transfer.len = 4;
spi = container_of(dev, struct spi_device, dev);
if (unlikely(!spi)) {
SPIDEV_LOG("spi device is invalid\n");
goto out;
}
if (unlikely(!buf)) {
SPIDEV_LOG("buf is invalid\n");
goto out;
}
spi_message_init(&msg);
if (!strncmp(buf, "-h", 2)) {
SPIDEV_MSG("Please input the message of this device to send and receive.\n");
} else if (!strncmp(buf, "-func", 5)) {
buf += 6;
if (!strncmp(buf, "len=", 4) && 1 == sscanf(buf + 4, "%d", &len)) {
spi_setup_xfer(&spi->dev, &transfer, len, 1);
spi_message_add_tail(&transfer, &msg);
ret = spi_sync(spi, &msg);
if (ret < 0) {
SPIDEV_LOG("Message transfer err:%d\n", ret);
} else {
ret = spi_recv_check(&msg);
if (ret != 0) {
ret = -ret;
SPIDEV_LOG("Message transfer err:%d\n", ret);
goto out;
}
}
}
} else{
SPIDEV_LOG("Wrong parameters.\n");
ret = -1;
goto out;
}
ret = count;
out:
return ret;
}
static DEVICE_ATTR(spi, 0200, NULL, spi_store);
static DEVICE_ATTR(spi_msg, 0200, NULL, spi_msg_store);
static struct device_attribute *spi_attribute[] = {
&dev_attr_spi,
&dev_attr_spi_msg,
};
static int spi_create_attribute(struct device *dev)
{
int num, idx;
int err = 0;
num = (int)ARRAY_SIZE(spi_attribute);
for (idx = 0; idx < num; idx++) {
err = device_create_file(dev, spi_attribute[idx]);
if (err)
break;
}
return err;
}
static void spi_remove_attribute(struct device *dev)
{
int num, idx;
num = (int)ARRAY_SIZE(spi_attribute);
for (idx = 0; idx < num; idx++)
device_remove_file(dev, spi_attribute[idx]);
}
static int spi_test_remove(struct spi_device *spi)
{
SPIDEV_LOG("spi_test_remove.\n");
spi_remove_attribute(&spi->dev);
return 0;
}
static int spi_test_probe(struct spi_device *spi)
{
SPIDEV_LOG("spi test probe enter\n");
spi_test = spi;
spi->mode = SPI_MODE_3;
spi->bits_per_word = 8;
return spi_create_attribute(&spi->dev);
return 0;
}
struct spi_device_id spi_id_table[] = {
{"spi-ut", 0},
{},
};
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "mediatek,spi-mt65xx-test" },
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
static struct spi_driver spi_test_driver = {
.driver = {
.name = "test_spi",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.of_match_table = spidev_dt_ids,
},
.probe = spi_test_probe,
.remove = spi_test_remove,
.id_table = spi_id_table,
};
static int __init spi_dev_init(void)
{
SPIDEV_LOG("SPI_DEV_INIT.\n");
return spi_register_driver(&spi_test_driver);
}
static void __exit spi_test_exit(void)
{
SPIDEV_LOG("SPI_DEV_EXIT.\n");
spi_unregister_driver(&spi_test_driver);
}
module_init(spi_dev_init);
module_exit(spi_test_exit);
MODULE_DESCRIPTION("mt SPI test device driver");
MODULE_AUTHOR("ZH Chen <zh.chen@mediatek.com>");
MODULE_LICENSE("GPL");