544 lines
13 KiB
C
544 lines
13 KiB
C
/*
|
|
* 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;
|
|
if (offset + xfer.len > max_length)
|
|
memcpy(&rx[offset], comms->rx_buffer,
|
|
max_length - offset);
|
|
else
|
|
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);
|