blob: 42ada3b9ede85eee2953104e7bbdfb50f28a3be5 [file] [log] [blame]
/*
* Copyright (C) 2018 Samsung Electronics Co., Ltd.
*
* Authors: Surendran K <surendran.k@samsung.com>
* Shaik Ameer Basha <shaik.ameer@samsung.com>
*
* Airbrush SPI Slave driver.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*/
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spi/spi.h>
#include <linux/io.h>
#include <linux/debugfs.h>
#include <linux/of.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include "airbrush-spi.h"
#define AB_SPI_MAX_BURST_SIZE 0x2000
#define AB_SPI_RESPONSE_TIMEOUT 1000
//#define DEBUG
#define WAIT_FOR_RESPONSE() send_dummy_byte()
struct spi_device *abc_spi_dev;
struct packet_header {
uint32_t command;
uint8_t granularity;
uint64_t base_address;
uint16_t length;
};
/* Internal data structure mainly for buffer allocation */
struct airbrush_spi_client {
struct spi_device *spi_device;
uint8_t *tx_buff_onetime;
uint8_t *rx_buff_onetime;
uint8_t *tx_buff_for_cmd;
uint8_t *rx_buff_for_cmd;
size_t buff_len_onetime;
size_t buff_len_for_cmd;
};
int airbrush_spi_command_code[] = {
[AB_SPI_CMD_FSM_READ_SINGLE] = 0x02,
[AB_SPI_CMD_FSM_WRITE_SINGLE] = 0x03,
[AB_SPI_CMD_FSM_BURST_WRITE] = 0x04,
[AB_SPI_CMD_READ_SINGLE] = 0x03,
[AB_SPI_CMD_WRITE_SINGLE] = 0x02,
[AB_SPI_CMD_READ_RANGE] = 0x0B,
[AB_SPI_CMD_WRITE_RANGE] = 0x0A,
[AB_SPI_CMD_MEMSET_RANGE] = 0x1A,
[AB_SPI_CMD_BIT_SET] = 0xC1,
[AB_SPI_CMD_BIT_CLEAR] = 0xC0,
[AB_SPI_CMD_BIT_CLEAR_SET] = 0xC2,
[AB_SPI_CMD_READ_VER] = 0x9E,
[AB_SPI_CMD_READ_ID] = 0x9F,
[AB_SPI_CMD_READ_STATUS] = 0x01,
[AB_SPI_CMD_GO_BOOT] = 0xA0,
};
/* Table of command codes and acceptable granularity flags */
static int granularity_table[][2] = {
{ AB_SPI_CMD_FSM_READ_SINGLE, FOUR_BYTE},
{ AB_SPI_CMD_FSM_WRITE_SINGLE, FOUR_BYTE},
{ AB_SPI_CMD_FSM_BURST_WRITE, FOUR_BYTE},
{ AB_SPI_CMD_READ_SINGLE, FOUR_BYTE},
{ AB_SPI_CMD_WRITE_SINGLE, FOUR_BYTE},
{ AB_SPI_CMD_READ_RANGE, FOUR_BYTE},
{ AB_SPI_CMD_WRITE_RANGE, FOUR_BYTE},
{ AB_SPI_CMD_MEMSET_RANGE, FOUR_BYTE},
{ AB_SPI_CMD_BIT_SET, FOUR_BYTE},
{ AB_SPI_CMD_BIT_CLEAR, FOUR_BYTE | EIGHT_BYTE },
{ AB_SPI_CMD_BIT_CLEAR_SET, FOUR_BYTE | EIGHT_BYTE },
{ AB_SPI_CMD_READ_VER, DONT_CARE},
{ AB_SPI_CMD_READ_ID, DONT_CARE},
{ AB_SPI_CMD_READ_STATUS, DONT_CARE},
{ AB_SPI_CMD_GO_BOOT, DONT_CARE},
};
static void airbrush_spi_send(void *tx_data, void *rx_data, uint16_t _len)
{
#ifdef DEBUG
int i;
#endif
struct spi_transfer t = {
.tx_buf = tx_data,
.rx_buf = rx_data,
.len = _len,
};
struct spi_message message;
spi_message_init(&message);
spi_message_add_tail(&t, &message);
#ifdef DEBUG
for (i = 0 ; i < _len ; i++)
printk("In %s : %x\n", __func__, *((uint8_t *)tx_data + i));
#endif
spi_sync(abc_spi_dev, &message);
}
static int is_valid_packet(struct airbrush_spi_packet *pkt)
{
if (pkt->command < AB_SPI_CMD_MAX)
if (pkt->granularity & granularity_table[pkt->command][1])
return 1;
return 0;
}
static bool send_to_fsm(struct airbrush_spi_packet *pkt, char *tx_buff,
char *rx_buff)
{
uint16_t _itr;
uint32_t command = airbrush_spi_command_code[pkt->command];
uint32_t base_address = pkt->base_address;
/* for Burst write first byte must be length */
if (pkt->command == AB_SPI_CMD_FSM_BURST_WRITE) {
/* In FSM for n-1 length n writes happen */
if (pkt->data_length == 0)
return false;
pkt->data_length--;
tx_buff[0] = *((uint8_t *)&pkt->data_length + 1);
tx_buff[1] = *((uint8_t *)&pkt->data_length);
pkt->data_length++; /* restore */
} else {
/* first 2 bytes has to be 0 for read and write single */
tx_buff[0] = 0x0;
tx_buff[1] = 0x0;
}
tx_buff[2] = *((uint8_t *)&command + 1);
tx_buff[3] = *((uint8_t *)&command);
/* Address phase */
tx_buff[4] = *((uint8_t *)&base_address + 3);
tx_buff[5] = *((uint8_t *)&base_address + 2);
tx_buff[6] = *((uint8_t *)&base_address + 1);
tx_buff[7] = *((uint8_t *)&base_address);
if (pkt->command == AB_SPI_CMD_FSM_READ_SINGLE) {
airbrush_spi_send(tx_buff, rx_buff, 8);
return true;
}
/* Data phase */
tx_buff[8] = *((uint8_t *)pkt->data + 3);
tx_buff[9] = *((uint8_t *)pkt->data + 2);
tx_buff[10] = *((uint8_t *)pkt->data + 1);
tx_buff[11] = *((uint8_t *)pkt->data);
airbrush_spi_send(tx_buff, rx_buff, 12);
if (pkt->command == AB_SPI_CMD_FSM_WRITE_SINGLE)/* write is done */
return true;
/* send the remaining n-1 data */
for (_itr = 1; _itr < pkt->data_length; _itr++) {
tx_buff[0] = *((uint8_t *)pkt->data + 4 * _itr + 3);
tx_buff[1] = *((uint8_t *)pkt->data + 4 * _itr + 2);
tx_buff[2] = *((uint8_t *)pkt->data + 4 * _itr + 1);
tx_buff[3] = *((uint8_t *)pkt->data + 4 * _itr);
#ifdef DEBUG
printk("0x%x 0x%x 0x%x 0x%x\n",
tx_buff[0], tx_buff[1], tx_buff[2], tx_buff[3]);
#endif
airbrush_spi_send(tx_buff, rx_buff, 4);
}
return true;
}
/*
* Send a byte with 0x00 and read one byte.
* Returns read value.
*/
static uint8_t send_dummy_byte(void)
{
struct airbrush_spi_client *client;
uint8_t *tx_buff;
uint8_t *rx_buff;
client = spi_get_drvdata(abc_spi_dev);
tx_buff = client->tx_buff_onetime;
rx_buff = client->rx_buff_onetime;
*tx_buff = 0x00;
airbrush_spi_send(tx_buff, rx_buff, 1);
return *rx_buff;
}
int airbrush_spi_run_cmd(struct airbrush_spi_packet *pkt)
{
struct airbrush_spi_client *client;
uint8_t *tx_buff;
uint8_t *rx_buff;
uint32_t *result = pkt->data;
uint32_t timeout = AB_SPI_RESPONSE_TIMEOUT;
client = spi_get_drvdata(abc_spi_dev);
tx_buff = client->tx_buff_for_cmd;
rx_buff = client->rx_buff_for_cmd;
/* validate packet */
if (!is_valid_packet(pkt))
return -EINVAL;
/* max size can be only of 32kb */
if (pkt->command == AB_SPI_CMD_FSM_BURST_WRITE &&
pkt->data_length > AB_SPI_MAX_BURST_SIZE)
return -EINVAL;
/*check if it FSM or non FSM */
if (!((pkt->command == AB_SPI_CMD_FSM_READ_SINGLE) ||
(pkt->command == AB_SPI_CMD_FSM_WRITE_SINGLE) ||
(pkt->command == AB_SPI_CMD_FSM_BURST_WRITE)))
return 0;
if (send_to_fsm(pkt, tx_buff, rx_buff) == false)
return -EIO;
if (pkt->command == AB_SPI_CMD_FSM_READ_SINGLE) {
while ((WAIT_FOR_RESPONSE() != 0x1) && (--timeout > 0))
;
if (timeout == 0) {
pr_err("%s: Timeout waiting for SPI response\n",
__func__);
return -EINVAL;
}
/* read response data */
rx_buff[0] = send_dummy_byte();
rx_buff[1] = send_dummy_byte();
rx_buff[2] = send_dummy_byte();
rx_buff[3] = send_dummy_byte();
if (pkt->data) {
*((uint8_t *)result) = rx_buff[3];
*((uint8_t *)result + 1) = rx_buff[2];
*((uint8_t *)result + 2) = rx_buff[1];
*((uint8_t *)result + 3) = rx_buff[0];
}
}
return 0;
}
EXPORT_SYMBOL(airbrush_spi_run_cmd);
static const struct of_device_id airbrush_spi_of_match[] = {
{ .compatible = "abc,airbrush-spi" },
{},
};
MODULE_DEVICE_TABLE(of, airbrush_spi_of_match);
/*
* Allocate Resource-managed memory as SPI buffer.
*
* Returns 0 on success, -ENOMEM on failure.
*/
static int airbrush_spi_client_alloc_buf(struct airbrush_spi_client *client)
{
client->buff_len_onetime = 1; /* single read/write */
client->buff_len_for_cmd = 16; /* sufficient for a cmd */
/* Allocate for both tx and rx */
client->tx_buff_onetime = devm_kmalloc(
&client->spi_device->dev,
2 * client->buff_len_onetime,
GFP_KERNEL | GFP_DMA);
if (client->tx_buff_onetime == NULL)
return -ENOMEM;
client->rx_buff_onetime = client->tx_buff_onetime +
client->buff_len_onetime;
/* Allocate for both tx and rx */
client->tx_buff_for_cmd = devm_kmalloc(
&client->spi_device->dev,
2 * client->buff_len_for_cmd,
GFP_KERNEL | GFP_DMA);
if (client->tx_buff_for_cmd == NULL)
return -ENOMEM;
client->rx_buff_for_cmd = client->tx_buff_onetime +
client->buff_len_for_cmd;
return 0;
}
static int airbrush_spi_probe(struct spi_device *spi)
{
struct airbrush_spi_client *client;
int ret;
abc_spi_dev = spi;
client = devm_kzalloc(&abc_spi_dev->dev,
sizeof(struct airbrush_spi_client),
GFP_KERNEL);
if (!client)
return -ENOMEM;
client->spi_device = spi;
ret = airbrush_spi_client_alloc_buf(client);
if (ret)
return ret;
spi_set_drvdata(spi, client);
return 0;
}
static int airbrush_spi_remove(struct spi_device *spi)
{
return 0;
}
static struct spi_driver airbrush_spi_driver = {
.driver = {
.name = "abc,airbrush-spi",
.of_match_table = airbrush_spi_of_match,
},
.probe = airbrush_spi_probe,
.remove = airbrush_spi_remove,
};
module_spi_driver(airbrush_spi_driver);
MODULE_DESCRIPTION("Airbrush SPI driver");
MODULE_LICENSE("GPL");