blob: 30c0bf8357fc85a52410304a6cb4b5cc057d4861 [file] [log] [blame]
/*
* Copyright (C) 2016 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 the
* GNU General Public License for more details.
*
*/
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/iio/iio.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include "main.h"
#include "bl.h"
#include "comms.h"
#define SPI_TIMEOUT 65535
#define SPI_MIN_DMA 48
struct nanohub_spi_data {
struct nanohub_data data;
struct spi_device *device;
struct semaphore spi_sem;
int cs;
uint16_t rx_length;
uint16_t rx_offset;
};
static uint8_t bl_checksum(const uint8_t *bytes, int length)
{
int i;
uint8_t csum;
if (length == 1) {
csum = ~bytes[0];
} else if (length > 1) {
for (csum = 0, i = 0; i < length; i++)
csum ^= bytes[i];
} else {
csum = 0xFF;
}
return csum;
}
static uint8_t spi_bl_write_data(const void *data, uint8_t *tx, int length)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_message msg;
struct spi_transfer xfer = {
.len = length + 1,
.tx_buf = bl->tx_buffer,
.rx_buf = bl->rx_buffer,
.cs_change = 1,
};
tx[length] = bl_checksum(tx, length);
memcpy(bl->tx_buffer, tx, length + 1);
spi_message_init_with_transfers(&msg, &xfer, 1);
if (spi_sync_locked(spi_data->device, &msg) == 0)
return bl->rx_buffer[length];
else
return CMD_NACK;
}
static uint8_t spi_bl_write_cmd(const void *data, uint8_t cmd)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_message msg;
struct spi_transfer xfer = {
.len = 3,
.tx_buf = bl->tx_buffer,
.rx_buf = bl->rx_buffer,
.cs_change = 1,
};
bl->tx_buffer[0] = CMD_SOF;
bl->tx_buffer[1] = cmd;
bl->tx_buffer[2] = ~cmd;
spi_message_init_with_transfers(&msg, &xfer, 1);
if (spi_sync_locked(spi_data->device, &msg) == 0)
return CMD_ACK;
else
return CMD_NACK;
}
static uint8_t spi_bl_read_data(const void *data, uint8_t *rx, int length)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_message msg;
struct spi_transfer xfer = {
.len = length + 1,
.tx_buf = bl->tx_buffer,
.rx_buf = bl->rx_buffer,
.cs_change = 1,
};
memset(&bl->tx_buffer[0], 0x00, length + 1);
spi_message_init_with_transfers(&msg, &xfer, 1);
if (spi_sync_locked(spi_data->device, &msg) == 0) {
memcpy(rx, &bl->rx_buffer[1], length);
return CMD_ACK;
} else {
return CMD_NACK;
}
}
static uint8_t spi_bl_read_ack(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
int32_t timeout = SPI_TIMEOUT;
uint8_t ret;
struct spi_message msg;
struct spi_transfer xfer = {
.len = 1,
.tx_buf = bl->tx_buffer,
.rx_buf = bl->rx_buffer,
.cs_change = 1,
};
bl->tx_buffer[0] = 0x00;
spi_message_init_with_transfers(&msg, &xfer, 1);
if (spi_sync_locked(spi_data->device, &msg) == 0) {
do {
spi_sync_locked(spi_data->device, &msg);
timeout--;
if (bl->rx_buffer[0] != CMD_ACK
&& bl->rx_buffer[0] != CMD_NACK
&& timeout % 256 == 0)
schedule();
} while (bl->rx_buffer[0] != CMD_ACK
&& bl->rx_buffer[0] != CMD_NACK && timeout > 0);
if (bl->rx_buffer[0] != CMD_ACK && bl->rx_buffer[0] != CMD_NACK
&& timeout == 0)
ret = CMD_NACK;
else
ret = bl->rx_buffer[0];
bl->tx_buffer[0] = CMD_ACK;
spi_sync_locked(spi_data->device, &msg);
return ret;
} else {
return CMD_NACK;
}
}
static int spi_bl_open(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
int ret;
spi_bus_lock(spi_data->device->master);
spi_data->device->max_speed_hz = 1000000;
spi_data->device->mode = SPI_MODE_0;
spi_data->device->bits_per_word = 8;
ret = spi_setup(spi_data->device);
if (!ret)
gpio_set_value(spi_data->cs, 0);
return ret;
}
static void spi_bl_close(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
gpio_set_value(spi_data->cs, 1);
spi_bus_unlock(spi_data->device->master);
}
static uint8_t spi_bl_sync(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
int32_t timeout = SPI_TIMEOUT;
struct spi_message msg;
struct spi_transfer xfer = {
.len = 1,
.tx_buf = bl->tx_buffer,
.rx_buf = bl->rx_buffer,
.cs_change = 1,
};
bl->tx_buffer[0] = CMD_SOF;
spi_message_init_with_transfers(&msg, &xfer, 1);
do {
if (spi_sync_locked(spi_data->device, &msg) != 0)
return CMD_NACK;
timeout--;
if (bl->rx_buffer[0] != CMD_SOF_ACK && timeout % 256 == 0)
schedule();
} while (bl->rx_buffer[0] != CMD_SOF_ACK && timeout > 0);
if (bl->rx_buffer[0] == CMD_SOF_ACK)
return bl->read_ack(data);
else
return CMD_NACK;
}
void nanohub_spi_bl_init(struct nanohub_spi_data *spi_data)
{
struct nanohub_bl *bl = &spi_data->data.bl;
bl->open = spi_bl_open;
bl->sync = spi_bl_sync;
bl->write_data = spi_bl_write_data;
bl->write_cmd = spi_bl_write_cmd;
bl->read_data = spi_bl_read_data;
bl->read_ack = spi_bl_read_ack;
bl->close = spi_bl_close;
}
int nanohub_spi_write(void *data, uint8_t *tx, int length, int timeout)
{
struct nanohub_spi_data *spi_data = data;
const struct nanohub_comms *comms = &spi_data->data.comms;
int max_len = sizeof(struct nanohub_packet) + MAX_UINT8 +
sizeof(struct nanohub_packet_crc);
struct spi_message msg;
struct spi_transfer xfer = {
.len = max_len + timeout,
.tx_buf = comms->tx_buffer,
.rx_buf = comms->rx_buffer,
.cs_change = 1,
};
spi_data->rx_offset = max_len;
spi_data->rx_length = max_len + timeout;
memcpy(comms->tx_buffer, tx, length);
memset(comms->tx_buffer + length, 0xFF, max_len + timeout - length);
spi_message_init_with_transfers(&msg, &xfer, 1);
if (spi_sync_locked(spi_data->device, &msg) == 0)
return length;
else
return ERROR_NACK;
}
int nanohub_spi_read(void *data, uint8_t *rx, int max_length, int timeout)
{
struct nanohub_spi_data *spi_data = data;
struct nanohub_comms *comms = &spi_data->data.comms;
const int min_size = sizeof(struct nanohub_packet) +
sizeof(struct nanohub_packet_crc);
int i, ret;
int offset = 0;
struct nanohub_packet *packet = NULL;
struct spi_message msg;
struct spi_transfer xfer = {
.len = timeout,
.tx_buf = comms->tx_buffer,
.rx_buf = comms->rx_buffer,
.cs_change = 1,
};
if (max_length < min_size)
return ERROR_NACK;
/* consume leftover bytes, if any */
if (spi_data->rx_offset < spi_data->rx_length) {
for (i = spi_data->rx_offset; i < spi_data->rx_length; i++) {
if (comms->rx_buffer[i] != 0xFF) {
offset = spi_data->rx_length - i;
if (offset <
offsetof(struct nanohub_packet,
len) + sizeof(packet->len)) {
memcpy(rx, &comms->rx_buffer[i],
offset);
xfer.len =
min_size + MAX_UINT8 - offset;
break;
} else {
packet =
(struct nanohub_packet *)&comms->
rx_buffer[i];
if (offset < min_size + packet->len) {
memcpy(rx, packet, offset);
xfer.len =
min_size + packet->len -
offset;
break;
} else {
memcpy(rx, packet,
min_size + packet->len);
spi_data->rx_offset = i +
min_size + packet->len;
return min_size + packet->len;
}
}
}
}
}
if (xfer.len != 1 && xfer.len < SPI_MIN_DMA)
xfer.len = SPI_MIN_DMA;
memset(comms->tx_buffer, 0xFF, xfer.len);
spi_message_init_with_transfers(&msg, &xfer, 1);
ret = spi_sync_locked(spi_data->device, &msg);
if (ret == 0) {
if (offset > 0) {
packet = (struct nanohub_packet *)rx;
memcpy(&rx[offset], comms->rx_buffer, xfer.len);
spi_data->rx_offset = spi_data->rx_length = 0;
} else {
for (i = 0; i < xfer.len; i++) {
if (comms->rx_buffer[i] != 0xFF) {
spi_data->rx_length = xfer.len;
if (xfer.len - i < min_size) {
spi_data->rx_offset = i;
break;
} else {
packet =
(struct nanohub_packet *)
&comms->rx_buffer[i];
if (xfer.len - i <
min_size + packet->len) {
packet = NULL;
spi_data->rx_offset = i;
} else {
memcpy(rx, packet,
min_size +
packet->len);
spi_data->rx_offset =
i + min_size +
packet->len;
}
}
break;
}
}
}
}
if (ret < 0)
return ret;
else if (!packet)
return 0;
else
return min_size + packet->len;
}
static int nanohub_spi_open(void *data)
{
struct nanohub_spi_data *spi_data = data;
int ret;
down(&spi_data->spi_sem);
spi_bus_lock(spi_data->device->master);
spi_data->device->max_speed_hz = 10000000;
spi_data->device->mode = SPI_MODE_0;
spi_data->device->bits_per_word = 8;
ret = spi_setup(spi_data->device);
if (!ret) {
udelay(40);
gpio_set_value(spi_data->cs, 0);
udelay(30);
}
return ret;
}
static void nanohub_spi_close(void *data)
{
struct nanohub_spi_data *spi_data = data;
gpio_set_value(spi_data->cs, 1);
spi_bus_unlock(spi_data->device->master);
up(&spi_data->spi_sem);
udelay(60);
}
void nanohub_spi_comms_init(struct nanohub_spi_data *spi_data)
{
struct nanohub_comms *comms = &spi_data->data.comms;
int max_len = sizeof(struct nanohub_packet) + MAX_UINT8 +
sizeof(struct nanohub_packet_crc);
comms->seq = 1;
comms->timeout_write = 544;
comms->timeout_ack = 272;
comms->timeout_reply = 512;
comms->open = nanohub_spi_open;
comms->close = nanohub_spi_close;
comms->write = nanohub_spi_write;
comms->read = nanohub_spi_read;
max_len += comms->timeout_write;
max_len = max(max_len, comms->timeout_ack);
max_len = max(max_len, comms->timeout_reply);
comms->tx_buffer = kmalloc(max_len, GFP_KERNEL | GFP_DMA);
comms->rx_buffer = kmalloc(max_len, GFP_KERNEL | GFP_DMA);
spi_data->rx_length = 0;
spi_data->rx_offset = 0;
sema_init(&spi_data->spi_sem, 1);
}
static int nanohub_spi_probe(struct spi_device *spi)
{
struct nanohub_spi_data *spi_data;
struct iio_dev *iio_dev;
int error;
iio_dev = iio_device_alloc(sizeof(struct nanohub_spi_data));
iio_dev = nanohub_probe(&spi->dev, iio_dev);
if (IS_ERR(iio_dev))
return PTR_ERR(iio_dev);
spi_data = iio_priv(iio_dev);
spi_set_drvdata(spi, iio_dev);
if (gpio_is_valid(spi_data->data.pdata->spi_cs_gpio)) {
error =
gpio_request(spi_data->data.pdata->spi_cs_gpio,
"nanohub_spi_cs");
if (error) {
pr_err("nanohub: spi_cs_gpio request failed\n");
} else {
spi_data->cs = spi_data->data.pdata->spi_cs_gpio;
gpio_direction_output(spi_data->cs, 1);
}
} else {
pr_err("nanohub: spi_cs_gpio is not valid\n");
}
spi_data->device = spi;
nanohub_spi_comms_init(spi_data);
spi_data->data.bl.cmd_erase = CMD_ERASE;
spi_data->data.bl.cmd_read_memory = CMD_READ_MEMORY;
spi_data->data.bl.cmd_write_memory = CMD_WRITE_MEMORY;
nanohub_spi_bl_init(spi_data);
nanohub_reset(&spi_data->data);
return 0;
}
static int nanohub_spi_remove(struct spi_device *spi)
{
struct nanohub_spi_data *spi_data;
struct iio_dev *iio_dev;
iio_dev = spi_get_drvdata(spi);
spi_data = iio_priv(iio_dev);
if (gpio_is_valid(spi_data->cs)) {
gpio_direction_output(spi_data->cs, 1);
gpio_free(spi_data->cs);
}
return nanohub_remove(iio_dev);
}
static int nanohub_spi_suspend(struct device *dev)
{
struct iio_dev *iio_dev = spi_get_drvdata(to_spi_device(dev));
struct nanohub_spi_data *spi_data = iio_priv(iio_dev);
int ret;
ret = nanohub_suspend(iio_dev);
if (!ret) {
ret = down_interruptible(&spi_data->spi_sem);
if (ret)
up(&spi_data->spi_sem);
}
return ret;
}
static int nanohub_spi_resume(struct device *dev)
{
struct iio_dev *iio_dev = spi_get_drvdata(to_spi_device(dev));
struct nanohub_spi_data *spi_data = iio_priv(iio_dev);
up(&spi_data->spi_sem);
return nanohub_resume(iio_dev);
}
static struct spi_device_id nanohub_spi_id[] = {
{NANOHUB_NAME, 0},
{},
};
static const struct dev_pm_ops nanohub_spi_pm_ops = {
.suspend = nanohub_spi_suspend,
.resume = nanohub_spi_resume,
};
static struct spi_driver nanohub_spi_driver = {
.driver = {
.name = NANOHUB_NAME,
.owner = THIS_MODULE,
.pm = &nanohub_spi_pm_ops,
},
.probe = nanohub_spi_probe,
.remove = nanohub_spi_remove,
.id_table = nanohub_spi_id,
};
int __init nanohub_spi_init(void)
{
return spi_register_driver(&nanohub_spi_driver);
}
void nanohub_spi_cleanup(void)
{
spi_unregister_driver(&nanohub_spi_driver);
}
MODULE_DEVICE_TABLE(spi, nanohub_spi_id);