blob: fc111bd625980cc71095d747b5827e6b830355b1 [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 "comms.h"
#ifdef CONFIG_NANOHUB_BL_ST
#include "bl_st.h"
#endif
#ifdef CONFIG_NANOHUB_BL_NXP
#include "bl_nxp.h"
#endif
#define SPI_TIMEOUT 65535
#define SPI_MIN_DMA 48
#define SPI_MAX_SPEED_HZ 10000000
#define SPI_BITS_PER_WORD 8
#define DMA_BURST_WIDTH 4
#define DMA_PAD 3
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;
};
// Set cs if it is under nanohub control.
static void cs_set_value(const struct nanohub_spi_data *spi_data, int value) {
if (gpio_is_valid(spi_data->cs)) {
gpio_set_value(spi_data->cs, value);
}
}
#ifdef CONFIG_NANOHUB_BL_ST
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 = spi_data->data.pdata->bl_max_speed_hz;
spi_data->device->mode = SPI_MODE_0;
spi_data->device->bits_per_word = 8;
ret = spi_setup(spi_data->device);
if (!ret)
cs_set_value(spi_data, 0);
return ret;
}
static void spi_bl_close(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
cs_set_value(spi_data, 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;
}
#endif
#ifdef CONFIG_NANOHUB_BL_NXP
#define CRC_POLY 0x1021
static uint16_t crc_update(uint16_t crc_in, int incr)
{
uint16_t xor = crc_in >> 15;
uint16_t out = crc_in << 1;
if (incr)
out++;
if (xor)
out ^= CRC_POLY;
return out;
}
// Calculate a packet CRC, ignoring the CRC bytes at offset 4 & 5.
static uint16_t bl_crc16(const uint8_t *data, uint16_t size)
{
uint16_t crc, i, j;
for (crc = 0, j = 0; j < size; j++, data++) {
if (j == 4 || j == 5)
continue; // skip the CRC word
for (i = 0x80; i; i >>= 1)
crc = crc_update(crc, *data & i);
}
for (i = 0; i < 16; i++)
crc = crc_update(crc, 0);
return crc;
}
enum bl_readiness {
BL_READY_READ = 0,
BL_READY_WRITE = 1,
BL_READY_NONE = 2,
};
static int spi_bl_wait(const void *data, enum bl_readiness bl_ready_state)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_platform_data *pdata = spi_data->data.pdata;
int retry, delay;
if (bl_ready_state == BL_READY_NONE)
return 0;
for (retry = 0, delay = 0; retry < 20; retry++) {
if (gpio_get_value(pdata->irq1_gpio) == bl_ready_state)
return 0;
delay += 10;
udelay(delay);
}
pr_warn("nanohub: %s timed out waiting for bootloader (%s)\n", __func__,
bl_ready_state ? "write" : "read");
return -BL_ERR_TIMEOUT;
}
static int spi_bl_tx_rx(const void *data, struct spi_transfer *xfer,
enum bl_readiness bl_ready_state)
{
const struct nanohub_spi_data *spi_data = data;
struct spi_message msg;
int ret;
spi_message_init_with_transfers(&msg, xfer, 1);
ret = spi_bl_wait(data, bl_ready_state);
if (ret < 0)
return ret;
cs_set_value(spi_data, 0);
ret = spi_sync_locked(spi_data->device, &msg);
cs_set_value(spi_data, 1);
return ret;
}
static int spi_bl_write_ack(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_transfer xfer = {
.len = 2,
.tx_buf = bl->tx_buffer,
.rx_buf = NULL,
.cs_change = 1,
};
bl->tx_buffer[0] = BL_FRAME_SYNC;
bl->tx_buffer[1] = BL_FRAME_ACK;
return spi_bl_tx_rx(data, &xfer, BL_READY_WRITE);
}
// Read a packet beginning with a sync byte, eg, ACK and Frame packets.
// Works around some bootloader issues that can cause random errors.
static int spi_bl_read_sync_packet(const void *data, int length)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_transfer xfer = {
.len = length,
.tx_buf = NULL,
.rx_buf = bl->rx_buffer,
.cs_change = 1,
};
int ret;
ret = spi_bl_tx_rx(data, &xfer, BL_READY_READ);
if (ret < 0)
return ret;
if (bl->rx_buffer[0] != BL_FRAME_SYNC)
return -BL_ERR_SYNC;
if (bl->rx_buffer[1] == BL_FRAME_SYNC) {
// The NXP bootloader can sometimes return an extra sync byte.
// Discard the extra sync and refetch the last content byte.
// This also removes the need to wait 50us b/w sync and ack.
// See ref manual 18.7.2.6.7
memmove(bl->rx_buffer, bl->rx_buffer + 1, length - 1);
xfer.len = 1;
xfer.rx_buf = bl->rx_buffer + length - 1;
ret = spi_bl_tx_rx(data, &xfer, BL_READY_NONE);
}
return ret;
}
static int spi_bl_read_ack(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
uint8_t ack;
int ret = spi_bl_read_sync_packet(data, 2);
if (ret < 0)
return ret;
ack = bl->rx_buffer[1];
return ack == BL_FRAME_ACK ? 0 : -ack;
}
static int spi_bl_read_frame(const void *data)
{
return spi_bl_read_sync_packet(data, 6);
}
// NXP bootloader command and response packet format
// Frame
// 0 sync 5A
// 1 type A4
// 2 length 2 bytes, length of cmd packet, little endian
// 4 crc 2 bytes, crc of entire packet, not including crc, little endian
// Command or Response
// 6 cmd/resp 1 byte
// 7 flags 1 byte
// 8 reserved 00
// 9 n_args 1 byte
// 10 arg1 4 bytes, little endian
// 14 ...
static int spi_bl_write_cmd(const void *data, uint8_t cmd, uint8_t flags,
int n_args, ...)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_transfer xfer = {
.len = 10 + 4 * n_args,
.tx_buf = bl->tx_buffer,
.rx_buf = NULL,
.cs_change = 1,
};
int i, ret;
uint16_t crc = 0;
va_list args;
bl->tx_buffer[0] = BL_FRAME_SYNC;
bl->tx_buffer[1] = BL_FRAME_COMMAND;
put_le16(bl->tx_buffer + 2, 4 + 4 * n_args);
bl->tx_buffer[6] = cmd;
bl->tx_buffer[7] = flags;
bl->tx_buffer[8] = 0;
bl->tx_buffer[9] = n_args;
va_start(args, n_args);
for (i = 0; i < n_args; i++) {
put_le32(bl->tx_buffer + 10 + i * 4, va_arg(args, uint32_t));
}
va_end(args);
crc = bl_crc16(bl->tx_buffer, 10 + 4 * n_args);
put_le16(bl->tx_buffer + 4, crc);
ret = spi_bl_tx_rx(data, &xfer, BL_READY_WRITE);
if (ret < 0)
return ret;
return spi_bl_read_ack(data);
}
static int spi_bl_write_data(const void *data, const uint8_t *buf, int length)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_transfer xfer = {
.len = 6 + length,
.tx_buf = bl->tx_buffer,
.rx_buf = NULL,
.cs_change = 1,
};
uint16_t crc = 0;
int ret;
bl->tx_buffer[0] = BL_FRAME_SYNC;
bl->tx_buffer[1] = BL_FRAME_DATA;
put_le16(bl->tx_buffer + 2, length);
memcpy(bl->tx_buffer + 6, buf, length);
crc = bl_crc16(bl->tx_buffer, 6 + length);
put_le16(bl->tx_buffer + 4, crc);
ret = spi_bl_tx_rx(data, &xfer, BL_READY_WRITE);
if (ret < 0)
return ret;
return spi_bl_read_ack(data);
}
static int spi_bl_read_response(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
const struct nanohub_bl *bl = &spi_data->data.bl;
struct spi_transfer xfer = {
.tx_buf = NULL,
.cs_change = 1,
};
int ret;
uint16_t length, expected_crc, actual_crc;
// Read the frame
// This delay allows the MCU to begin a new data ready notification.
// It could be removed if we changed to an edge-driven interrupt.
udelay(60);
ret = spi_bl_read_frame(data);
if (ret < 0)
return ret;
length = get_le16(bl->rx_buffer + 2);
expected_crc = get_le16(bl->rx_buffer + 4);
if (length > 32)
return -BL_ERR_BAD_RESPONSE;
// Read the response
xfer.len = length;
xfer.rx_buf = bl->rx_buffer + 6;
ret = spi_bl_tx_rx(data, &xfer, BL_READY_NONE);
if (ret < 0)
return ret;
actual_crc = bl_crc16(bl->rx_buffer, 6 + length);
if (actual_crc != expected_crc)
return -BL_ERR_CRC;
return spi_bl_write_ack(data);
}
static int spi_bl_open(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
spi_bus_lock(spi_data->device->master);
return 0;
}
static void spi_bl_close(const void *data)
{
const struct nanohub_spi_data *spi_data = data;
spi_bus_unlock(spi_data->device->master);
}
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->write_ack = spi_bl_write_ack;
bl->write_cmd = spi_bl_write_cmd;
bl->write_data = spi_bl_write_data;
bl->read_ack = spi_bl_read_ack;
bl->read_response = spi_bl_read_response;
bl->close = spi_bl_close;
}
#endif
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,
};
xfer.len = (xfer.len + DMA_PAD) & ~DMA_PAD;
spi_data->rx_offset = max_len;
spi_data->rx_length = xfer.len;
memcpy(comms->tx_buffer, tx, length);
memset(comms->tx_buffer + length, 0xFF, xfer.len - 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;
}
}
}
}
}
xfer.len = (xfer.len + DMA_PAD) & ~DMA_PAD;
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_length = xfer.len;
spi_data->rx_offset = min_size + packet->len - offset;
} 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;
down(&spi_data->spi_sem);
spi_bus_lock(spi_data->device->master);
cs_set_value(spi_data, 0);
return 0;
}
static void nanohub_spi_close(void *data)
{
struct nanohub_spi_data *spi_data = data;
cs_set_value(spi_data, 1);
spi_bus_unlock(spi_data->device->master);
up(&spi_data->spi_sem);
}
void nanohub_spi_comms_init(struct nanohub_spi_data *spi_data)
{
struct nanohub_comms *comms = &spi_data->data.comms;
int max_write, max_read;
int max_len = sizeof(struct nanohub_packet) + MAX_UINT8 +
sizeof(struct nanohub_packet_crc);
comms->seq = 1;
comms->timeout_write = 470;
comms->timeout_read = 740;
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;
// nanohub_spi_write transactions
max_write = max_len + comms->timeout_write;
max_read = max_len + comms->timeout_read;
max_len = max(max_read, max_write);
// nanohub_spi_read transactions
max_len = max(max_len, comms->timeout_ack);
max_len = max(max_len, comms->timeout_reply);
max_len = (max_len + DMA_PAD) & ~DMA_PAD;
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;
struct spi_message msg;
struct spi_transfer xfer = {
.len = 0,
.tx_buf = NULL,
.rx_buf = NULL
};
int error;
iio_dev = iio_device_alloc(&spi->dev, 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);
spi_data->cs = spi_data->data.pdata->spi_cs_gpio;
if (gpio_is_valid(spi_data->cs)) {
error = gpio_request(spi_data->cs, "nanohub_spi_cs");
if (error) {
spi_data->cs = -1;
pr_err("nanohub: spi_cs_gpio request failed\n");
} else {
gpio_direction_output(spi_data->cs, 1);
}
}
spi_data->device = spi;
spi_data->data.max_speed_hz = spi->max_speed_hz ? : SPI_MAX_SPEED_HZ;
nanohub_spi_comms_init(spi_data);
#ifdef CONFIG_NANOHUB_BL_ST
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;
spi_data->data.bl.cmd_get_version = CMD_GET_VERSION;
spi_data->data.bl.cmd_get_id = CMD_GET_ID;
spi_data->data.bl.cmd_readout_protect = CMD_READOUT_PROTECT;
spi_data->data.bl.cmd_readout_unprotect = CMD_READOUT_UNPROTECT;
spi_data->data.bl.cmd_update_finished = CMD_UPDATE_FINISHED;
nanohub_spi_bl_init(spi_data);
#endif
#ifdef CONFIG_NANOHUB_BL_NXP
nanohub_spi_bl_init(spi_data);
#endif
// Set up the SPI bus and run an empty message to set the idle state.
// This setup can overridden in nanohub_spi_open and spi_bl_open.
spi_data->device->max_speed_hz = spi_data->data.max_speed_hz;
spi_data->device->mode = SPI_MODE_3;
spi_data->device->bits_per_word = SPI_BITS_PER_WORD;
spi_setup(spi_data->device);
spi_message_init_with_transfers(&msg, &xfer, 1);
spi_sync_locked(spi_data->device, &msg);
nanohub_start(&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_suspend_noirq(struct device *dev)
{
struct iio_dev *iio_dev = spi_get_drvdata(to_spi_device(dev));
return nanohub_suspend_noirq(iio_dev);
}
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,
.suspend_noirq = nanohub_spi_suspend_noirq,
.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);