From: Sukwon Ryoo Date: Thu, 17 May 2018 04:05:37 +0000 (+0900) Subject: [COMMON] nanohub base from CHRE p-version X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=ad1ce1593d5fa94fda9dba818fe82db493c757bf;p=GitHub%2FLineageOS%2Fandroid_kernel_motorola_exynos9610.git [COMMON] nanohub base from CHRE p-version b4cbbe1 nanohub: fix out of bounds write in nanohub_spi_read fdba416 Merge branch 'android-msm-marlin-3.18-nyc-mr2' into android-msm-marlin-3.18 c9f88a4 nanohub: detect wakeup signal stuck high and reset e8faaee ANDROID: fix uninitilized variable e9199f7 nanohub: error reset time calculation should use monotonic time Change-Id: Ie2cb50046dddc3c607b5977c31813a6e8ee4fab9 Signed-off-by: Sukwon Ryoo --- diff --git a/drivers/staging/nanohub/Kconfig b/drivers/staging/nanohub/Kconfig new file mode 100644 index 000000000000..b798ebbc3ec3 --- /dev/null +++ b/drivers/staging/nanohub/Kconfig @@ -0,0 +1,33 @@ +config NANOHUB + tristate "Nanohub" + default N + help + Enable support for the nanohub sensorhub driver. + + This driver supports the android nanohub sensorhub. + + If in doubt, say N here. + +if NANOHUB + +config NANOHUB_SPI + bool "Nanohub SPI" + default Y + help + Enable nanohub SPI support. + + Either this or NANOHUB_I2C should be selected. + + If in doubt, say Y here. + +config NANOHUB_I2C + bool "Nanohub I2C" + default N + help + Enable nanohub I2C support. + + Either this or NANOHUB_SPI should be selected. + + If in doubt, say N here. + +endif # NANOHUB diff --git a/drivers/staging/nanohub/Makefile b/drivers/staging/nanohub/Makefile new file mode 100644 index 000000000000..d7e0da7f1ae0 --- /dev/null +++ b/drivers/staging/nanohub/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for nanohub +# + +obj-$(CONFIG_NANOHUB) += nanohub.o +nanohub-y := main.o bl.o comms.o +nanohub-$(CONFIG_NANOHUB_SPI) += spi.o +nanohub-$(CONFIG_NANOHUB_I2C) += i2c.o diff --git a/drivers/staging/nanohub/bl.c b/drivers/staging/nanohub/bl.c new file mode 100644 index 000000000000..9a657f420cbf --- /dev/null +++ b/drivers/staging/nanohub/bl.c @@ -0,0 +1,500 @@ +/* + * 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 +#include + +#include "main.h" +#include "bl.h" + +#define MAX_BUFFER_SIZE 1024 +#define MAX_FLASH_BANKS 16 +#define READ_ACK_TIMEOUT 100000 + +static uint8_t write_len(struct nanohub_data *data, int len) +{ + uint8_t buffer[sizeof(uint8_t) + 1]; + + buffer[0] = len - 1; + + return data->bl.write_data(data, buffer, sizeof(uint8_t)); +} + +static uint8_t write_cnt(struct nanohub_data *data, uint16_t cnt) +{ + uint8_t buffer[sizeof(uint16_t) + 1]; + + buffer[0] = (cnt >> 8) & 0xFF; + buffer[1] = (cnt >> 0) & 0xFF; + + return data->bl.write_data(data, buffer, sizeof(uint16_t)); +} + +static uint8_t write_addr(struct nanohub_data *data, uint32_t addr) +{ + uint8_t buffer[sizeof(uint32_t) + 1]; + + buffer[0] = (addr >> 24) & 0xFF; + buffer[1] = (addr >> 16) & 0xFF; + buffer[2] = (addr >> 8) & 0xFF; + buffer[3] = addr & 0xFF; + + return data->bl.write_data(data, buffer, sizeof(uint32_t)); +} + +/* write length followed by the data */ +static uint8_t write_len_data(struct nanohub_data *data, int len, + const uint8_t *buf) +{ + uint8_t buffer[sizeof(uint8_t) + 256 + sizeof(uint8_t)]; + + buffer[0] = len - 1; + + memcpy(&buffer[1], buf, len); + + return data->bl.write_data(data, buffer, sizeof(uint8_t) + len); +} + +/* keep checking for ack until we receive a ack or nack */ +static uint8_t read_ack_loop(struct nanohub_data *data) +{ + uint8_t ret; + int32_t timeout = READ_ACK_TIMEOUT; + + do { + ret = data->bl.read_ack(data); + if (ret != CMD_ACK && ret != CMD_NACK) + schedule(); + } while (ret != CMD_ACK && ret != CMD_NACK && timeout-- > 0); + + return ret; +} + +uint8_t nanohub_bl_sync(struct nanohub_data *data) +{ + return data->bl.sync(data); +} + +int nanohub_bl_open(struct nanohub_data *data) +{ + int ret = -1; + + data->bl.tx_buffer = kmalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); + if (!data->bl.tx_buffer) + goto out; + + data->bl.rx_buffer = kmalloc(MAX_BUFFER_SIZE, GFP_KERNEL | GFP_DMA); + if (!data->bl.rx_buffer) + goto free_tx; + + ret = data->bl.open(data); + if (!ret) + goto out; + + kfree(data->bl.rx_buffer); +free_tx: + kfree(data->bl.tx_buffer); +out: + return ret; +} + +void nanohub_bl_close(struct nanohub_data *data) +{ + data->bl.close(data); + kfree(data->bl.tx_buffer); + kfree(data->bl.rx_buffer); +} + +static uint8_t write_bank(struct nanohub_data *data, int bank, uint32_t addr, + const uint8_t *buf, size_t length) +{ + const struct nanohub_platform_data *pdata = data->pdata; + uint8_t status = CMD_ACK; + uint32_t offset; + + if (addr <= pdata->flash_banks[bank].address) { + offset = pdata->flash_banks[bank].address - addr; + if (addr + length > + pdata->flash_banks[bank].address + + pdata->flash_banks[bank].length) + status = + nanohub_bl_write_memory(data, + pdata->flash_banks[bank]. + address, + pdata->flash_banks[bank]. + length, buf + offset); + else + status = + nanohub_bl_write_memory(data, + pdata->flash_banks[bank]. + address, length - offset, + buf + offset); + } else { + if (addr + length > + pdata->flash_banks[bank].address + + pdata->flash_banks[bank].length) + status = + nanohub_bl_write_memory(data, addr, + pdata->flash_banks[bank]. + address + + pdata->flash_banks[bank]. + length - addr, buf); + else + status = + nanohub_bl_write_memory(data, addr, length, buf); + } + + return status; +} + +uint8_t nanohub_bl_download(struct nanohub_data *data, uint32_t addr, + const uint8_t *image, size_t length) +{ + const struct nanohub_platform_data *pdata = data->pdata; + uint8_t *ptr; + int i, j; + uint8_t status; + uint32_t offset; + uint8_t erase_mask[MAX_FLASH_BANKS] = { 0 }; + uint8_t erase_write_mask[MAX_FLASH_BANKS] = { 0 }; + uint8_t write_mask[MAX_FLASH_BANKS] = { 0 }; + + if (pdata->num_flash_banks > MAX_FLASH_BANKS) { + status = CMD_NACK; + goto out; + } + + status = nanohub_bl_sync(data); + + if (status != CMD_ACK) { + pr_err("nanohub_bl_download: sync=%02x\n", status); + goto out; + } + + ptr = vmalloc(length); + if (!ptr) { + status = CMD_NACK; + goto out; + } + + status = nanohub_bl_read_memory(data, addr, length, ptr); + pr_info( + "nanohub: nanohub_bl_read_memory: status=%02x, addr=%08x, length=%zd\n", + status, addr, length); + + for (i = 0; i < pdata->num_flash_banks; i++) { + if (addr >= pdata->flash_banks[i].address && + addr < + pdata->flash_banks[i].address + + pdata->flash_banks[i].length) { + break; + } + } + + offset = (uint32_t) (addr - pdata->flash_banks[i].address); + j = 0; + while (j < length && i < pdata->num_flash_banks) { + if (image[j] != 0xFF) + erase_write_mask[i] = true; + + if ((ptr[j] & image[j]) != image[j]) { + erase_mask[i] = true; + if (erase_write_mask[i]) { + j += pdata->flash_banks[i].length - offset; + offset = pdata->flash_banks[i].length; + } else { + j++; + offset++; + } + } else { + if (ptr[j] != image[j]) + write_mask[i] = true; + j++; + offset++; + } + + if (offset == pdata->flash_banks[i].length) { + i++; + offset = 0; + if (i < pdata->num_flash_banks) + j += (pdata->flash_banks[i].address - + pdata->flash_banks[i - 1].address - + pdata->flash_banks[i - 1].length); + else + j = length; + } + } + + for (i = 0; status == CMD_ACK && i < pdata->num_flash_banks; i++) { + pr_info("nanohub: i=%d, erase=%d, erase_write=%d, write=%d\n", + i, erase_mask[i], erase_write_mask[i], write_mask[i]); + if (erase_mask[i]) { + status = + nanohub_bl_erase_sector(data, + pdata->flash_banks[i].bank); + if (status == CMD_ACK && erase_write_mask[i]) + status = + write_bank(data, i, addr, image, length); + } else if (write_mask[i]) { + status = write_bank(data, i, addr, image, length); + } + } + + vfree(ptr); +out: + return status; +} + +uint8_t nanohub_bl_erase_shared(struct nanohub_data *data) +{ + const struct nanohub_platform_data *pdata = data->pdata; + int i; + uint8_t status; + + if (pdata->num_shared_flash_banks > MAX_FLASH_BANKS) { + status = CMD_NACK; + goto out; + } + + status = nanohub_bl_sync(data); + + if (status != CMD_ACK) { + pr_err("nanohub_bl_erase_shared: sync=%02x\n", status); + goto out; + } + + for (i = 0; + status == CMD_ACK && i < pdata->num_shared_flash_banks; + i++) { + status = nanohub_bl_erase_sector(data, + pdata->shared_flash_banks[i].bank); + } +out: + return status; +} + +uint8_t nanohub_bl_erase_shared_bl(struct nanohub_data *data) +{ + uint8_t status; + + status = nanohub_bl_sync(data); + + if (status != CMD_ACK) { + pr_err("nanohub_bl_erase_shared_bl: sync=%02x\n", status); + goto out; + } + + status = nanohub_bl_erase_special(data, 0xFFF0); +out: + return status; +} + +/* erase a single sector */ +uint8_t nanohub_bl_erase_sector(struct nanohub_data *data, uint16_t sector) +{ + uint8_t ret; + + data->bl.write_cmd(data, data->bl.cmd_erase); + ret = data->bl.read_ack(data); + if (ret == CMD_ACK) + ret = write_cnt(data, 0x0000); + if (ret != CMD_NACK) + ret = read_ack_loop(data); + if (ret == CMD_ACK) + ret = write_cnt(data, sector); + if (ret != CMD_NACK) + ret = read_ack_loop(data); + + return ret; +} + +/* erase special */ +uint8_t nanohub_bl_erase_special(struct nanohub_data *data, uint16_t special) +{ + uint8_t ret; + + data->bl.write_cmd(data, data->bl.cmd_erase); + ret = data->bl.read_ack(data); + if (ret == CMD_ACK) + ret = write_cnt(data, special); + if (ret != CMD_NACK) + ret = read_ack_loop(data); + + return ret; +} + +/* read memory - this will chop the request into 256 byte reads */ +uint8_t nanohub_bl_read_memory(struct nanohub_data *data, uint32_t addr, + uint32_t length, uint8_t *buffer) +{ + uint8_t ret = CMD_ACK; + uint32_t offset = 0; + + while (ret == CMD_ACK && length > offset) { + data->bl.write_cmd(data, data->bl.cmd_read_memory); + ret = data->bl.read_ack(data); + if (ret == CMD_ACK) { + write_addr(data, addr + offset); + ret = read_ack_loop(data); + if (ret == CMD_ACK) { + if (length - offset >= 256) { + write_len(data, 256); + ret = read_ack_loop(data); + if (ret == CMD_ACK) { + data->bl.read_data(data, + &buffer + [offset], + 256); + offset += 256; + } + } else { + write_len(data, length - offset); + ret = read_ack_loop(data); + if (ret == CMD_ACK) { + data->bl.read_data(data, + &buffer + [offset], + length - + offset); + offset = length; + } + } + } + } + } + + return ret; +} + +/* write memory - this will chop the request into 256 byte writes */ +uint8_t nanohub_bl_write_memory(struct nanohub_data *data, uint32_t addr, + uint32_t length, const uint8_t *buffer) +{ + uint8_t ret = CMD_ACK; + uint32_t offset = 0; + + while (ret == CMD_ACK && length > offset) { + data->bl.write_cmd(data, data->bl.cmd_write_memory); + ret = data->bl.read_ack(data); + if (ret == CMD_ACK) { + write_addr(data, addr + offset); + ret = read_ack_loop(data); + if (ret == CMD_ACK) { + if (length - offset >= 256) { + write_len_data(data, 256, + &buffer[offset]); + offset += 256; + } else { + write_len_data(data, length - offset, + &buffer[offset]); + offset = length; + } + ret = read_ack_loop(data); + } + } + } + + return ret; +} + +uint8_t nanohub_bl_get_version(struct nanohub_data *data, uint8_t *version) +{ + uint8_t status; + + status = nanohub_bl_sync(data); + if (status != CMD_ACK) { + pr_err("nanohub_bl_get_version: sync=%02x\n", status); + goto out; + } + + data->bl.write_cmd(data, data->bl.cmd_get_version); + status = data->bl.read_ack(data); + if (status == CMD_ACK) + data->bl.read_data(data, version, 1); + status = data->bl.read_ack(data); +out: + return status; +} + +uint8_t nanohub_bl_get_id(struct nanohub_data *data, uint16_t *id) +{ + uint8_t status; + uint8_t len; + uint8_t buffer[256]; + + status = nanohub_bl_sync(data); + if (status != CMD_ACK) { + pr_err("nanohub_bl_get_id: sync=%02x\n", status); + goto out; + } + + data->bl.write_cmd(data, data->bl.cmd_get_id); + status = data->bl.read_ack(data); + if (status == CMD_ACK) { + data->bl.read_data(data, &len, 1); + data->bl.read_data(data, buffer, len+1); + *id = (buffer[0] << 8) | buffer[1]; + } + status = data->bl.read_ack(data); +out: + return status; +} + +uint8_t nanohub_bl_lock(struct nanohub_data *data) +{ + uint8_t status; + + status = nanohub_bl_sync(data); + + if (status != CMD_ACK) { + pr_err("nanohub_bl_lock: sync=%02x\n", status); + goto out; + } + + data->bl.write_cmd(data, data->bl.cmd_readout_protect); + status = data->bl.read_ack(data); + if (status == CMD_ACK) + status = read_ack_loop(data); +out: + return status; +} + +uint8_t nanohub_bl_unlock(struct nanohub_data *data) +{ + uint8_t status; + + status = nanohub_bl_sync(data); + + if (status != CMD_ACK) { + pr_err("nanohub_bl_lock: sync=%02x\n", status); + goto out; + } + + data->bl.write_cmd(data, data->bl.cmd_readout_unprotect); + status = data->bl.read_ack(data); + if (status == CMD_ACK) + status = read_ack_loop(data); +out: + return status; +} + +uint8_t nanohub_bl_update_finished(struct nanohub_data *data) +{ + uint8_t ret; + + data->bl.write_cmd(data, data->bl.cmd_update_finished); + ret = read_ack_loop(data); + + return ret; +} diff --git a/drivers/staging/nanohub/bl.h b/drivers/staging/nanohub/bl.h new file mode 100644 index 000000000000..30270585d4e5 --- /dev/null +++ b/drivers/staging/nanohub/bl.h @@ -0,0 +1,96 @@ +/* + * 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. + * + */ + +#ifndef _NANOHUB_BL_H +#define _NANOHUB_BL_H + +#include +#include +#include + +struct nanohub_data; + +struct nanohub_bl { + uint8_t cmd_erase; + uint8_t cmd_read_memory; + uint8_t cmd_write_memory; + uint8_t cmd_get_version; + uint8_t cmd_get_id; + uint8_t cmd_readout_protect; + uint8_t cmd_readout_unprotect; + uint8_t cmd_update_finished; + + int (*open)(const void *); + void (*close)(const void *); + uint8_t (*sync)(const void *); + uint8_t (*write_data)(const void *, uint8_t *, int); + uint8_t (*write_cmd)(const void *, uint8_t); + uint8_t (*read_data)(const void *, uint8_t *, int); + uint8_t (*read_ack)(const void *); + + uint8_t *tx_buffer; + uint8_t *rx_buffer; +}; + +int nanohub_bl_open(struct nanohub_data *); +uint8_t nanohub_bl_sync(struct nanohub_data *); +void nanohub_bl_close(struct nanohub_data *); +uint8_t nanohub_bl_download(struct nanohub_data *, uint32_t addr, + const uint8_t *data, size_t length); +uint8_t nanohub_bl_erase_shared(struct nanohub_data *); +uint8_t nanohub_bl_erase_shared_bl(struct nanohub_data *); +uint8_t nanohub_bl_erase_sector(struct nanohub_data *, uint16_t); +uint8_t nanohub_bl_erase_special(struct nanohub_data *, uint16_t); +uint8_t nanohub_bl_read_memory(struct nanohub_data *, uint32_t, uint32_t, + uint8_t *); +uint8_t nanohub_bl_write_memory(struct nanohub_data *, uint32_t, uint32_t, + const uint8_t *); +uint8_t nanohub_bl_get_version(struct nanohub_data *, uint8_t *); +uint8_t nanohub_bl_get_id(struct nanohub_data *, uint16_t *); +uint8_t nanohub_bl_lock(struct nanohub_data *); +uint8_t nanohub_bl_unlock(struct nanohub_data *); +uint8_t nanohub_bl_update_finished(struct nanohub_data *); + +/* + * Bootloader commands + * _NS versions are no-stretch. (Only valid on I2C) + * will return CMD_BUSY instead of stretching the clock + */ + +#define CMD_GET 0x00 +#define CMD_GET_VERSION 0x01 +#define CMD_GET_ID 0x02 +#define CMD_READ_MEMORY 0x11 +#define CMD_NACK 0x1F +#define CMD_GO 0x21 +#define CMD_WRITE_MEMORY 0x31 +#define CMD_WRITE_MEMORY_NS 0x32 +#define CMD_ERASE 0x44 +#define CMD_ERASE_NS 0x45 +#define CMD_SOF 0x5A +#define CMD_WRITE_PROTECT 0x63 +#define CMD_WRITE_PROTECT_NS 0x64 +#define CMD_WRITE_UNPROTECT 0x73 +#define CMD_WRITE_UNPROTECT_NS 0x74 +#define CMD_BUSY 0x76 +#define CMD_ACK 0x79 +#define CMD_READOUT_PROTECT 0x82 +#define CMD_READOUT_PROTECT_NS 0x83 +#define CMD_READOUT_UNPROTECT 0x92 +#define CMD_READOUT_UNPROTECT_NS 0x93 +#define CMD_SOF_ACK 0xA5 +#define CMD_GET_SIZES 0xEE +#define CMD_UPDATE_FINISHED 0xEF + +#endif diff --git a/drivers/staging/nanohub/comms.c b/drivers/staging/nanohub/comms.c new file mode 100644 index 000000000000..82590d5b7390 --- /dev/null +++ b/drivers/staging/nanohub/comms.c @@ -0,0 +1,582 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "comms.h" + +#define READ_ACK_TIMEOUT_MS 10 +#define READ_MSG_TIMEOUT_MS 70 + +#define RESEND_SHORT_DELAY_US 500 /* 500us - 1ms */ +#define RESEND_LONG_DELAY_US 100000 /* 100ms - 200ms */ + +static const uint32_t crc_table[] = { + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, + 0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005, + 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, + 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD +}; + +static uint32_t crc32_word(uint32_t crc, uint32_t data, int cnt) +{ + int i; + crc = crc ^ data; + + for (i = 0; i < cnt; i++) + crc = (crc << 4) ^ crc_table[crc >> 28]; + + return crc; +} + +uint32_t crc32(const uint8_t *buffer, int length, uint32_t crc) +{ + uint32_t *data = (uint32_t *)buffer; + uint32_t word; + int i; + + /* word by word crc32 */ + for (i = 0; i < (length >> 2); i++) + crc = crc32_word(crc, data[i], 8); + + /* zero pad last word if required */ + if (length & 0x3) { + for (i *= 4, word = 0; i < length; i++) + word |= buffer[i] << ((i & 0x3) * 8); + crc = crc32_word(crc, word, 8); + } + + return crc; +} + +static inline size_t pad(size_t length) +{ + return (length + 3) & ~3; +} + +static inline size_t tot_len(size_t length) +{ + /* [TYPE:1] [LENGTH:3] [DATA] [PAD:0-3] [CRC:4] */ + return sizeof(uint32_t) + pad(length) + sizeof(uint32_t); +} + +static struct nanohub_packet_pad *packet_alloc(int flags) +{ + int len = + sizeof(struct nanohub_packet_pad) + MAX_UINT8 + + sizeof(struct nanohub_packet_crc); + uint8_t *packet = kmalloc(len, flags); + memset(packet, 0xFF, len); + return (struct nanohub_packet_pad *)packet; +} + +static int packet_create(struct nanohub_packet *packet, uint32_t seq, + uint32_t reason, uint8_t len, const uint8_t *data, + bool user) +{ + struct nanohub_packet_crc crc; + int ret = sizeof(struct nanohub_packet) + len + + sizeof(struct nanohub_packet_crc); + + if (packet) { + packet->sync = COMMS_SYNC; + packet->seq = seq; + packet->reason = reason; + packet->len = len; + if (len > 0) { + if (user) { + if (copy_from_user(packet->data, data, len) != + 0) + ret = ERROR_NACK; + } else { + memcpy(packet->data, data, len); + } + } + crc.crc = + crc32((uint8_t *) packet, + sizeof(struct nanohub_packet) + len, ~0); + memcpy(&packet->data[len], &crc.crc, + sizeof(struct nanohub_packet_crc)); + } else { + ret = ERROR_NACK; + } + + return ret; +} + +static int packet_verify(struct nanohub_packet *packet) +{ + struct nanohub_packet_crc crc; + int cmp; + + crc.crc = + crc32((uint8_t *) packet, + sizeof(struct nanohub_packet) + packet->len, ~0); + + cmp = + memcmp(&crc.crc, &packet->data[packet->len], + sizeof(struct nanohub_packet_crc)); + + if (cmp != 0) { + uint8_t *ptr = (uint8_t *)packet; + pr_debug("nanohub: gen crc: %08x, got crc: %08x\n", crc.crc, + *(uint32_t *)&packet->data[packet->len]); + pr_debug( + "nanohub: %02x [%02x %02x %02x %02x] [%02x %02x %02x %02x] [%02x] [%02x %02x %02x %02x\n", + ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], + ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], + ptr[13]); + } + + return cmp; +} + +static void packet_free(struct nanohub_packet_pad *packet) +{ + kfree(packet); +} + +static int read_ack(struct nanohub_data *data, struct nanohub_packet *response, + int timeout) +{ + int ret, i; + const int max_size = sizeof(struct nanohub_packet) + MAX_UINT8 + + sizeof(struct nanohub_packet_crc); + unsigned long end = jiffies + msecs_to_jiffies(READ_ACK_TIMEOUT_MS); + + for (i = 0; time_before_eq(jiffies, end); i++) { + ret = + data->comms.read(data, (uint8_t *) response, max_size, + timeout); + + if (ret == 0) { + pr_debug("nanohub: read_ack: %d: empty packet\n", i); + ret = ERROR_NACK; + continue; + } else if (ret < sizeof(struct nanohub_packet)) { + pr_debug("nanohub: read_ack: %d: too small\n", i); + ret = ERROR_NACK; + continue; + } else if (ret < + sizeof(struct nanohub_packet) + response->len + + sizeof(struct nanohub_packet_crc)) { + pr_debug("nanohub: read_ack: %d: too small length\n", + i); + ret = ERROR_NACK; + continue; + } else if (ret != + sizeof(struct nanohub_packet) + response->len + + sizeof(struct nanohub_packet_crc)) { + pr_debug("nanohub: read_ack: %d: wrong length\n", i); + ret = ERROR_NACK; + break; + } else if (packet_verify(response) != 0) { + pr_debug("nanohub: read_ack: %d: invalid crc\n", i); + ret = ERROR_NACK; + break; + } else { + break; + } + } + + return ret; +} + +static int read_msg(struct nanohub_data *data, struct nanohub_packet *response, + int timeout) +{ + int ret, i; + const int max_size = sizeof(struct nanohub_packet) + MAX_UINT8 + + sizeof(struct nanohub_packet_crc); + unsigned long end = jiffies + msecs_to_jiffies(READ_MSG_TIMEOUT_MS); + + for (i = 0; time_before_eq(jiffies, end); i++) { + ret = + data->comms.read(data, (uint8_t *) response, max_size, + timeout); + + if (ret == 0) { + pr_debug("nanohub: read_msg: %d: empty packet\n", i); + ret = ERROR_NACK; + continue; + } else if (ret < sizeof(struct nanohub_packet)) { + pr_debug("nanohub: read_msg: %d: too small\n", i); + ret = ERROR_NACK; + continue; + } else if (ret < + sizeof(struct nanohub_packet) + response->len + + sizeof(struct nanohub_packet_crc)) { + pr_debug("nanohub: read_msg: %d: too small length\n", + i); + ret = ERROR_NACK; + continue; + } else if (ret != + sizeof(struct nanohub_packet) + response->len + + sizeof(struct nanohub_packet_crc)) { + pr_debug("nanohub: read_msg: %d: wrong length\n", i); + ret = ERROR_NACK; + break; + } else if (packet_verify(response) != 0) { + pr_debug("nanohub: read_msg: %d: invalid crc\n", i); + ret = ERROR_NACK; + break; + } else { + break; + } + } + + return ret; +} + +static int get_reply(struct nanohub_data *data, struct nanohub_packet *response, + uint32_t seq) +{ + int ret; + + ret = read_ack(data, response, data->comms.timeout_ack); + + if (ret >= 0 && response->seq == seq) { + if (response->reason == CMD_COMMS_ACK) { + if (response->len == sizeof(data->interrupts)) + memcpy(data->interrupts, response->data, + response->len); + ret = + read_msg(data, response, data->comms.timeout_reply); + if (ret < 0) + ret = ERROR_NACK; + } else { + int i; + uint8_t *b = (uint8_t *) response; + for (i = 0; i < ret; i += 25) + pr_debug( + "nanohub: %d: %d: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, i, b[i], b[i + 1], b[i + 2], b[i + 3], + b[i + 4], b[i + 5], b[i + 6], b[i + 7], + b[i + 8], b[i + 9], b[i + 10], b[i + 11], + b[i + 12], b[i + 13], b[i + 14], b[i + 15], + b[i + 16], b[i + 17], b[i + 18], b[i + 19], + b[i + 20], b[i + 21], b[i + 22], b[i + 23], + b[i + 24]); + if (response->reason == CMD_COMMS_NACK) + ret = ERROR_NACK; + else if (response->reason == CMD_COMMS_BUSY) + ret = ERROR_BUSY; + } + + if (response->seq != seq) + ret = ERROR_NACK; + } else { + if (ret >= 0) { + int i; + uint8_t *b = (uint8_t *) response; + for (i = 0; i < ret; i += 25) + pr_debug( + "nanohub: %d: %d: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + ret, i, b[i], b[i + 1], b[i + 2], b[i + 3], + b[i + 4], b[i + 5], b[i + 6], b[i + 7], + b[i + 8], b[i + 9], b[i + 10], b[i + 11], + b[i + 12], b[i + 13], b[i + 14], b[i + 15], + b[i + 16], b[i + 17], b[i + 18], b[i + 19], + b[i + 20], b[i + 21], b[i + 22], b[i + 23], + b[i + 24]); + } + ret = ERROR_NACK; + } + + return ret; +} + +static int nanohub_comms_tx_rx(struct nanohub_data *data, + struct nanohub_packet_pad *pad, int packet_size, + uint32_t seq, uint8_t *rx, size_t rx_len) +{ + int ret; + + ret = data->comms.write(data, (uint8_t *)&pad->packet, packet_size, + data->comms.timeout_write); + + if (ret == packet_size) { + ret = get_reply(data, &pad->packet, seq); + + if (ret >= 0) { + if (pad->packet.len > 0) { + if (pad->packet.len > rx_len) { + memcpy(rx, pad->packet.data, rx_len); + ret = rx_len; + } else { + memcpy(rx, pad->packet.data, + pad->packet.len); + ret = pad->packet.len; + } + } else { + ret = 0; + } + } + } else { + ret = ERROR_NACK; + } + + return ret; +} + +int nanohub_comms_rx_retrans_boottime(struct nanohub_data *data, uint32_t cmd, + uint8_t *rx, size_t rx_len, + int retrans_cnt, int retrans_delay) +{ + int packet_size = 0; + struct nanohub_packet_pad *pad = packet_alloc(GFP_KERNEL); + int delay = 0; + int ret; + uint32_t seq; + struct timespec ts; + s64 boottime; + + if (pad == NULL) + return ERROR_NACK; + + seq = data->comms.seq++; + + do { + data->comms.open(data); + get_monotonic_boottime(&ts); + boottime = timespec_to_ns(&ts); + packet_size = + packet_create(&pad->packet, seq, cmd, sizeof(boottime), + (uint8_t *)&boottime, false); + + ret = + nanohub_comms_tx_rx(data, pad, packet_size, seq, rx, + rx_len); + + if (nanohub_wakeup_eom(data, + (ret == ERROR_BUSY) || + (ret == ERROR_NACK && retrans_cnt >= 0))) + ret = -EFAULT; + + data->comms.close(data); + + if (ret == ERROR_NACK) { + retrans_cnt--; + delay += retrans_delay; + if (retrans_cnt >= 0) + udelay(retrans_delay); + } else if (ret == ERROR_BUSY) { + usleep_range(RESEND_LONG_DELAY_US, + RESEND_LONG_DELAY_US * 2); + } + } while ((ret == ERROR_BUSY) + || (ret == ERROR_NACK && retrans_cnt >= 0)); + + packet_free(pad); + + return ret; +} + +int nanohub_comms_tx_rx_retrans(struct nanohub_data *data, uint32_t cmd, + const uint8_t *tx, uint8_t tx_len, + uint8_t *rx, size_t rx_len, bool user, + int retrans_cnt, int retrans_delay) +{ + int packet_size = 0; + struct nanohub_packet_pad *pad = packet_alloc(GFP_KERNEL); + int delay = 0; + int ret; + uint32_t seq; + + if (pad == NULL) + return ERROR_NACK; + + seq = data->comms.seq++; + + do { + packet_size = + packet_create(&pad->packet, seq, cmd, tx_len, tx, user); + + data->comms.open(data); + ret = + nanohub_comms_tx_rx(data, pad, packet_size, seq, rx, + rx_len); + + if (nanohub_wakeup_eom(data, + (ret == ERROR_BUSY) || + (ret == ERROR_NACK && retrans_cnt >= 0))) + ret = -EFAULT; + + data->comms.close(data); + + if (ret == ERROR_NACK) { + retrans_cnt--; + delay += retrans_delay; + if (retrans_cnt >= 0) + udelay(retrans_delay); + } else if (ret == ERROR_BUSY) { + usleep_range(RESEND_LONG_DELAY_US, + RESEND_LONG_DELAY_US * 2); + } + } while ((ret == ERROR_BUSY) + || (ret == ERROR_NACK && retrans_cnt >= 0)); + + packet_free(pad); + + return ret; +} + +struct firmware_header { + uint32_t size; + uint32_t crc; + uint8_t type; +} __packed; + +struct firmware_chunk { + uint32_t offset; + uint8_t data[128]; +} +__packed; + +static int nanohub_comms_download(struct nanohub_data *data, + const uint8_t *image, size_t length, + uint8_t type) +{ + uint8_t accepted; + struct firmware_header header; + struct firmware_chunk chunk; + int max_chunk_size = sizeof(chunk.data); + int chunk_size; + uint32_t offset = 0; + int ret; + uint8_t chunk_reply, upload_reply = 0, last_reply = 0; + uint32_t clear_interrupts[8] = { 0x00000008 }; + uint32_t delay; + + header.type = type; + header.size = cpu_to_le32(length); + header.crc = cpu_to_le32(~crc32(image, length, ~0)); + + if (request_wakeup(data)) + return -ERESTARTSYS; + ret = nanohub_comms_tx_rx_retrans(data, CMD_COMMS_START_KERNEL_UPLOAD, + (const uint8_t *)&header, + sizeof(header), &accepted, + sizeof(accepted), false, 10, 10); + release_wakeup(data); + + if (ret == 1 && accepted == 1) { + do { + if (request_wakeup(data)) + continue; + + delay = 0; + chunk.offset = cpu_to_le32(offset); + if (offset + max_chunk_size > length) + chunk_size = length - offset; + else + chunk_size = max_chunk_size; + memcpy(chunk.data, image + offset, chunk_size); + + ret = + nanohub_comms_tx_rx_retrans(data, + CMD_COMMS_KERNEL_CHUNK, + (const uint8_t *)&chunk, + sizeof(uint32_t) + + chunk_size, + &chunk_reply, + sizeof(chunk_reply), + false, 10, 10); + + pr_debug("nanohub: ret=%d, chunk_reply=%d, offset=%d\n", + ret, chunk_reply, offset); + if (ret == sizeof(chunk_reply)) { + if (chunk_reply == CHUNK_REPLY_ACCEPTED) { + offset += chunk_size; + } else if (chunk_reply == CHUNK_REPLY_WAIT) { + ret = nanohub_wait_for_interrupt(data); + if (ret < 0) { + release_wakeup(data); + continue; + } + nanohub_comms_tx_rx_retrans(data, + CMD_COMMS_CLR_GET_INTR, + (uint8_t *)clear_interrupts, + sizeof(clear_interrupts), + (uint8_t *)data->interrupts, + sizeof(data->interrupts), + false, 10, 0); + } else if (chunk_reply == CHUNK_REPLY_RESEND) { + if (last_reply == CHUNK_REPLY_RESEND) + delay = RESEND_LONG_DELAY_US; + else + delay = RESEND_SHORT_DELAY_US; + } else if (chunk_reply == CHUNK_REPLY_RESTART) + offset = 0; + else if (chunk_reply == CHUNK_REPLY_CANCEL || + chunk_reply == + CHUNK_REPLY_CANCEL_NO_RETRY) { + release_wakeup(data); + break; + } + last_reply = chunk_reply; + } else if (ret <= 0) { + release_wakeup(data); + break; + } + release_wakeup(data); + if (delay > 0) + usleep_range(delay, delay * 2); + } while (offset < length); + } + + do { + if (upload_reply == UPLOAD_REPLY_PROCESSING) + usleep_range(RESEND_LONG_DELAY_US, + RESEND_LONG_DELAY_US * 2); + + if (request_wakeup(data)) { + ret = sizeof(upload_reply); + upload_reply = UPLOAD_REPLY_PROCESSING; + continue; + } + ret = nanohub_comms_tx_rx_retrans(data, + CMD_COMMS_FINISH_KERNEL_UPLOAD, + NULL, 0, + &upload_reply, sizeof(upload_reply), + false, 10, 10); + release_wakeup(data); + } while (ret == sizeof(upload_reply) && + upload_reply == UPLOAD_REPLY_PROCESSING); + + pr_info("nanohub: nanohub_comms_download: ret=%d, upload_reply=%d\n", + ret, upload_reply); + + return 0; +} + +int nanohub_comms_kernel_download(struct nanohub_data *data, + const uint8_t *image, size_t length) +{ + return nanohub_comms_download(data, image, length, + COMMS_FLASH_KERNEL_ID); +} + +int nanohub_comms_app_download(struct nanohub_data *data, const uint8_t *image, + size_t length) +{ + return nanohub_comms_download(data, image, length, COMMS_FLASH_APP_ID); +} diff --git a/drivers/staging/nanohub/comms.h b/drivers/staging/nanohub/comms.h new file mode 100644 index 000000000000..9479a9f4cbd2 --- /dev/null +++ b/drivers/staging/nanohub/comms.h @@ -0,0 +1,135 @@ +/* + * 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. + * + */ + +#ifndef _NANOHUB_COMMS_H +#define _NANOHUB_COMMS_H + +struct __attribute__ ((__packed__)) nanohub_packet { + uint8_t sync; + uint32_t seq; + uint32_t reason; + uint8_t len; + uint8_t data[]; +}; + +struct __attribute__ ((__packed__)) nanohub_packet_pad { + uint8_t pad[3]; + struct nanohub_packet + packet; +}; + +struct __attribute__ ((__packed__)) nanohub_packet_crc { + uint32_t crc; +}; + +struct nanohub_data; + +struct nanohub_comms { + struct semaphore sem; + uint32_t seq; + int timeout_write; + int timeout_ack; + int timeout_reply; + int (*open)(void *); + void (*close)(void *); + int (*write)(void *, uint8_t *, int, int); + int (*read)(void *, uint8_t *, int, int); + + union { + struct i2c_client *i2c_client; + struct spi_device *spi_device; + }; + + uint8_t *tx_buffer; + uint8_t *rx_buffer; +}; + +int nanohub_comms_kernel_download(struct nanohub_data *, const uint8_t *, + size_t); +int nanohub_comms_app_download(struct nanohub_data *, const uint8_t *, size_t); +int nanohub_comms_rx_retrans_boottime(struct nanohub_data *, uint32_t, + uint8_t *, size_t, int, int); +int nanohub_comms_tx_rx_retrans(struct nanohub_data *, uint32_t, + const uint8_t *, uint8_t, uint8_t *, size_t, + bool, int, int); + +#define ERROR_NACK -1 +#define ERROR_BUSY -2 + +#define MAX_UINT8 ((1 << (8*sizeof(uint8_t))) - 1) + +#define COMMS_SYNC 0x31 +#define COMMS_FLASH_KERNEL_ID 0x1 +#define COMMS_FLASH_EEDATA_ID 0x2 +#define COMMS_FLASH_APP_ID 0x4 + +#define CMD_COMMS_ACK 0x00000000 +#define CMD_COMMS_NACK 0x00000001 +#define CMD_COMMS_BUSY 0x00000002 + +#define CMD_COMMS_GET_OS_HW_VERSIONS 0x00001000 +#define CMD_COMMS_GET_APP_VERSIONS 0x00001001 +#define CMD_COMMS_QUERY_APP_INFO 0x00001002 + +#define CMD_COMMS_START_KERNEL_UPLOAD 0x00001040 +#define CMD_COMMS_KERNEL_CHUNK 0x00001041 +#define CMD_COMMS_FINISH_KERNEL_UPLOAD 0x00001042 + +#define CMD_COMMS_START_APP_UPLOAD 0x00001050 +#define CMD_COMMS_APP_CHUNK 0x00001051 + +#define CMD_COMMS_CLR_GET_INTR 0x00001080 +#define CMD_COMMS_MASK_INTR 0x00001081 +#define CMD_COMMS_UNMASK_INTR 0x00001082 +#define CMD_COMMS_READ 0x00001090 +#define CMD_COMMS_WRITE 0x00001091 + +#define CHUNK_REPLY_ACCEPTED 0 +#define CHUNK_REPLY_WAIT 1 +#define CHUNK_REPLY_RESEND 2 +#define CHUNK_REPLY_RESTART 3 +#define CHUNK_REPLY_CANCEL 4 +#define CHUNK_REPLY_CANCEL_NO_RETRY 5 + +#define UPLOAD_REPLY_SUCCESS 0 +#define UPLOAD_REPLY_PROCESSING 1 +#define UPLOAD_REPLY_WAITING_FOR_DATA 2 +#define UPLOAD_REPLY_APP_SEC_KEY_NOT_FOUND 3 +#define UPLOAD_REPLY_APP_SEC_HEADER_ERROR 4 +#define UPLOAD_REPLY_APP_SEC_TOO_MUCH_DATA 5 +#define UPLOAD_REPLY_APP_SEC_TOO_LITTLE_DATA 6 +#define UPLOAD_REPLY_APP_SEC_SIG_VERIFY_FAIL 7 +#define UPLOAD_REPLY_APP_SEC_SIG_DECODE_FAIL 8 +#define UPLOAD_REPLY_APP_SEC_SIG_ROOT_UNKNOWN 9 +#define UPLOAD_REPLY_APP_SEC_MEMORY_ERROR 10 +#define UPLOAD_REPLY_APP_SEC_INVALID_DATA 11 +#define UPLOAD_REPLY_APP_SEC_BAD 12 + +static inline int nanohub_comms_write(struct nanohub_data *data, + const uint8_t *buffer, size_t buffer_len) +{ + uint8_t ret; + if (nanohub_comms_tx_rx_retrans + (data, CMD_COMMS_WRITE, buffer, buffer_len, &ret, sizeof(ret), true, + 10, 10) == sizeof(ret)) { + if (ret) + return buffer_len; + else + return 0; + } else { + return ERROR_NACK; + } +} + +#endif diff --git a/drivers/staging/nanohub/i2c.c b/drivers/staging/nanohub/i2c.c new file mode 100644 index 000000000000..285f4ba9a975 --- /dev/null +++ b/drivers/staging/nanohub/i2c.c @@ -0,0 +1,266 @@ +/* + * 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 +#include + +#include "main.h" +#include "bl.h" +#include "comms.h" + +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 i2c_bl_write_data(const struct nanohub_bl *bl, uint8_t *buffer, + int length) +{ + buffer[length] = bl_checksum(buffer, length); + + if (i2c_master_send(bl->i2c_client, buffer, length + 1) == (length + 1)) + return CMD_ACK; + else + return CMD_NACK; +} + +static uint8_t i2c_bl_write_cmd(const struct nanohub_bl *bl, uint8_t cmd) +{ + uint8_t buffer[sizeof(uint8_t) + 1] = { + cmd + }; + + return bl->write_data(bl, buffer, sizeof(uint8_t)); +} + +static uint8_t i2c_bl_read_data(const struct nanohub_bl *bl, uint8_t *data, + int length) +{ + if (i2c_master_recv(bl->i2c_client, data, length) == length) + return CMD_ACK; + else + return CMD_NACK; +} + +static uint8_t i2c_bl_read_ack(const struct nanohub_bl *bl) +{ + uint8_t buffer; + + if (bl->read_data(bl, &buffer, sizeof(uint8_t)) == CMD_ACK) + return buffer; + else + return CMD_NACK; +} + +static void i2c_bl_open(const struct nanohub_bl *bl) +{ +} + +static void i2c_bl_close(const struct nanohub_bl *bl) +{ +} + +static uint8_t i2c_bl_sync(const struct nanohub_bl *bl) +{ + return CMD_ACK; +} + +void nanohub_i2c_bl_init(struct nanohub_bl *bl) +{ + bl->open = i2c_bl_open; + bl->sync = i2c_bl_sync; + bl->write_data = i2c_bl_write_data; + bl->write_cmd = i2c_bl_write_cmd; + bl->read_data = i2c_bl_read_data; + bl->read_ack = i2c_bl_read_ack; + bl->close = i2c_bl_close; +} + +int nanohub_i2c_write(const struct nanohub_comms *comms, uint8_t *data, + int length) +{ + return i2c_master_send(comms->i2c_client, data, length); +} + +int nanohub_i2c_read(struct nanohub_comms *comms, uint8_t *data, + int max_length, int timeout) +{ + const int min_size = sizeof(struct nanohub_packet) + + sizeof(struct nanohub_packet_crc); + int i, ret, read_cnt = min_size, read_len = 0, read_total = 0; + struct nanohub_packet *packet; + + if (max_length < min_size) + return ERROR_NACK; + + do { + pr_debug + ("master_recv: read_len=%d, read_cnt=%d, read_total=%d\n", + read_len, read_cnt, read_total); + ret = + i2c_master_recv(comms->i2c_client, &data[read_len], + read_cnt); + pr_debug("master_recv: ret=%d\n", ret); + pr_debug( + "data: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + data[0], data[1], data[2], data[3], data[4], data[5], + data[6], data[7], data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15], data[16], data[17], + data[18], data[19], data[20], data[21], data[22]); + if (ret == read_cnt) { + if (read_len == 0) { + for (i = 0; i < ret; i++) { + if (data[i] != 0xFF) { + if (i > 0) { + pr_debug( + "i=%d, read_cnt - i = %d\n", + i, read_cnt - i); + memmove(data, &data[i], + read_cnt - i); + } + read_len = read_cnt - i; + break; + } else { + timeout--; + } + } + } else { + read_len += read_cnt; + } + + packet = (struct nanohub_packet *)data; + if (read_len >= offsetof(struct nanohub_packet, len) + + sizeof(packet->len)) { + read_total = packet->len + min_size; + read_cnt = read_total - read_len; + } else { + read_cnt = min_size - read_len; + } + } else { + break; + } + + if (read_cnt + read_len > max_length) { + ret = ERROR_NACK; + break; + } + } while (timeout > 0 && (read_total == 0 || read_total != read_len)); + + if (ret < 0) + return ret; + else if (timeout <= 0) + return 0; + else + return read_len; +} + +static void nanohub_i2c_open(const struct nanohub_comms *comms) +{ +} + +static void nanohub_i2c_close(const struct nanohub_comms *comms) +{ +} + +void nanohub_i2c_comms_init(struct nanohub_comms *comms) +{ + comms->timeout_ack = 128; + comms->timeout_reply = 512; + comms->open = nanohub_i2c_open; + comms->close = nanohub_i2c_close; + comms->write = nanohub_i2c_write; + comms->read = nanohub_i2c_read; +} + +static int nanohub_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct nanohub_data *data; + struct iio_dev *iio_dev; + + pr_info("nanohub: i2c_probe:\n"); + + data = nanohub_probe(&client->dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + iio_dev = iio_priv_to_dev(data); + i2c_set_clientdata(client, iio_dev); + + data->comms.i2c_client = client; + nanohub_i2c_comms_init(&data->comms); + + data->bl.cmd_erase = CMD_ERASE_NS; + data->bl.cmd_read_memory = CMD_READ_MEMORY; + data->bl.cmd_write_memory = CMD_WRITE_MEMORY_NS; + data->bl.i2c_client = i2c_new_dummy(client->adapter, 0x39); + i2c_set_clientdata(data->bl.i2c_client, iio_dev); + nanohub_i2c_bl_init(&data->bl); + + return 0; +} + +static int nanohub_i2c_remove(struct i2c_client *client) +{ + struct nanohub_data *data; + + data = iio_priv(i2c_get_clientdata(client)); + + i2c_unregister_device(data->bl.i2c_client); + + return nanohub_remove(data); +} + +static struct i2c_device_id nanohub_i2c_id[] = { + {"nanohub", 0}, + {}, +}; + +static struct i2c_driver nanohub_i2c_driver = { + .driver = { + .name = "nanohub", + }, + .probe = nanohub_i2c_probe, + .remove = nanohub_i2c_remove, + .id_table = nanohub_i2c_id, +}; + +static const struct iio_info nanohub_iio_info = { + .driver_module = THIS_MODULE, +}; + +int __init nanohub_i2c_init(void) +{ + return i2c_add_driver(&nanohub_i2c_driver); +} + +void nanohub_i2c_cleanup(void) +{ + i2c_del_driver(&nanohub_i2c_driver); +} + +MODULE_DEVICE_TABLE(i2c, nanohub_i2c_id); diff --git a/drivers/staging/nanohub/i2c.h b/drivers/staging/nanohub/i2c.h new file mode 100644 index 000000000000..23fa27f0baeb --- /dev/null +++ b/drivers/staging/nanohub/i2c.h @@ -0,0 +1,21 @@ +/* + * 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. + * + */ + +#ifndef _NANOHUB_I2C_H +#define _NANOHUB_I2C_H + +int __init nanohub_i2c_init(void); +void nanohub_i2c_cleanup(void); + +#endif diff --git a/drivers/staging/nanohub/main.c b/drivers/staging/nanohub/main.c new file mode 100644 index 000000000000..2d3daacb5ce9 --- /dev/null +++ b/drivers/staging/nanohub/main.c @@ -0,0 +1,1801 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "main.h" +#include "comms.h" +#include "bl.h" +#include "spi.h" + +#define READ_QUEUE_DEPTH 10 +#define APP_FROM_HOST_EVENTID 0x000000F8 +#define FIRST_SENSOR_EVENTID 0x00000200 +#define LAST_SENSOR_EVENTID 0x000002FF +#define APP_TO_HOST_EVENTID 0x00000401 +#define OS_LOG_EVENTID 0x3B474F4C +#define WAKEUP_INTERRUPT 1 +#define WAKEUP_TIMEOUT_MS 1000 +#define SUSPEND_TIMEOUT_MS 100 +#define KTHREAD_ERR_TIME_NS (60LL * NSEC_PER_SEC) +#define KTHREAD_ERR_CNT 70 +#define KTHREAD_WARN_CNT 10 +#define WAKEUP_ERR_TIME_NS (60LL * NSEC_PER_SEC) +#define WAKEUP_ERR_CNT 4 + +/** + * struct gpio_config - this is a binding between platform data and driver data + * @label: for diagnostics + * @flags: to pass to gpio_request_one() + * @options: one or more of GPIO_OPT_* flags, below + * @pdata_off: offset of u32 field in platform data with gpio # + * @data_off: offset of int field in driver data with irq # (optional) + */ +struct gpio_config { + const char *label; + u16 flags; + u16 options; + u16 pdata_off; + u16 data_off; +}; + +#define GPIO_OPT_HAS_IRQ 0x0001 +#define GPIO_OPT_OPTIONAL 0x8000 + +#define PLAT_GPIO_DEF(name, _flags) \ + .pdata_off = offsetof(struct nanohub_platform_data, name ## _gpio), \ + .label = "nanohub_" #name, \ + .flags = _flags \ + +#define PLAT_GPIO_DEF_IRQ(name, _flags, _opts) \ + PLAT_GPIO_DEF(name, _flags), \ + .data_off = offsetof(struct nanohub_data, name), \ + .options = GPIO_OPT_HAS_IRQ | (_opts) \ + +static int nanohub_open(struct inode *, struct file *); +static ssize_t nanohub_read(struct file *, char *, size_t, loff_t *); +static ssize_t nanohub_write(struct file *, const char *, size_t, loff_t *); +static unsigned int nanohub_poll(struct file *, poll_table *); +static int nanohub_release(struct inode *, struct file *); +static int nanohub_hw_reset(struct nanohub_data *data); + +static struct class *sensor_class; +static int major; + +static const struct gpio_config gconf[] = { + { PLAT_GPIO_DEF(nreset, GPIOF_OUT_INIT_HIGH) }, + { PLAT_GPIO_DEF(wakeup, GPIOF_OUT_INIT_HIGH) }, + { PLAT_GPIO_DEF(boot0, GPIOF_OUT_INIT_LOW) }, + { PLAT_GPIO_DEF_IRQ(irq1, GPIOF_DIR_IN, 0) }, + { PLAT_GPIO_DEF_IRQ(irq2, GPIOF_DIR_IN, GPIO_OPT_OPTIONAL) }, +}; + +static const struct iio_info nanohub_iio_info = { + .driver_module = THIS_MODULE, +}; + +static const struct file_operations nanohub_fileops = { + .owner = THIS_MODULE, + .open = nanohub_open, + .read = nanohub_read, + .write = nanohub_write, + .poll = nanohub_poll, + .release = nanohub_release, +}; + +enum { + ST_IDLE, + ST_ERROR, + ST_RUNNING +}; + +static inline bool gpio_is_optional(const struct gpio_config *_cfg) +{ + return _cfg->options & GPIO_OPT_OPTIONAL; +} + +static inline bool gpio_has_irq(const struct gpio_config *_cfg) +{ + return _cfg->options & GPIO_OPT_HAS_IRQ; +} + +static inline bool nanohub_has_priority_lock_locked(struct nanohub_data *data) +{ + return atomic_read(&data->wakeup_lock_cnt) > + atomic_read(&data->wakeup_cnt); +} + +static inline void nanohub_notify_thread(struct nanohub_data *data) +{ + atomic_set(&data->kthread_run, 1); + /* wake_up implementation works as memory barrier */ + wake_up_interruptible_sync(&data->kthread_wait); +} + +static inline void nanohub_io_init(struct nanohub_io *io, + struct nanohub_data *data, + struct device *dev) +{ + init_waitqueue_head(&io->buf_wait); + INIT_LIST_HEAD(&io->buf_list); + io->data = data; + io->dev = dev; +} + +static inline bool nanohub_io_has_buf(struct nanohub_io *io) +{ + return !list_empty(&io->buf_list); +} + +static struct nanohub_buf *nanohub_io_get_buf(struct nanohub_io *io, + bool wait) +{ + struct nanohub_buf *buf = NULL; + int ret; + + spin_lock(&io->buf_wait.lock); + if (wait) { + ret = wait_event_interruptible_locked(io->buf_wait, + nanohub_io_has_buf(io)); + if (ret < 0) { + spin_unlock(&io->buf_wait.lock); + return ERR_PTR(ret); + } + } + + if (nanohub_io_has_buf(io)) { + buf = list_first_entry(&io->buf_list, struct nanohub_buf, list); + list_del(&buf->list); + } + spin_unlock(&io->buf_wait.lock); + + return buf; +} + +static void nanohub_io_put_buf(struct nanohub_io *io, + struct nanohub_buf *buf) +{ + bool was_empty; + + spin_lock(&io->buf_wait.lock); + was_empty = !nanohub_io_has_buf(io); + list_add_tail(&buf->list, &io->buf_list); + spin_unlock(&io->buf_wait.lock); + + if (was_empty) { + if (&io->data->free_pool == io) + nanohub_notify_thread(io->data); + else + wake_up_interruptible(&io->buf_wait); + } +} + +static inline int plat_gpio_get(struct nanohub_data *data, + const struct gpio_config *_cfg) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + return *(u32 *)(((char *)pdata) + (_cfg)->pdata_off); +} + +static inline void nanohub_set_irq_data(struct nanohub_data *data, + const struct gpio_config *_cfg, int val) +{ + int *data_addr = ((int *)(((char *)data) + _cfg->data_off)); + + if ((void *)data_addr > (void *)data && + (void *)data_addr < (void *)(data + 1)) + *data_addr = val; + else + WARN(1, "No data binding defined for %s", _cfg->label); +} + +static inline void mcu_wakeup_gpio_set_value(struct nanohub_data *data, + int val) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + gpio_set_value(pdata->wakeup_gpio, val); +} + +static inline void mcu_wakeup_gpio_get_locked(struct nanohub_data *data, + int priority_lock) +{ + atomic_inc(&data->wakeup_lock_cnt); + if (!priority_lock && atomic_inc_return(&data->wakeup_cnt) == 1 && + !nanohub_has_priority_lock_locked(data)) + mcu_wakeup_gpio_set_value(data, 0); +} + +static inline bool mcu_wakeup_gpio_put_locked(struct nanohub_data *data, + int priority_lock) +{ + bool gpio_done = priority_lock ? + atomic_read(&data->wakeup_cnt) == 0 : + atomic_dec_and_test(&data->wakeup_cnt); + bool done = atomic_dec_and_test(&data->wakeup_lock_cnt); + + if (!nanohub_has_priority_lock_locked(data)) + mcu_wakeup_gpio_set_value(data, gpio_done ? 1 : 0); + + return done; +} + +static inline bool mcu_wakeup_gpio_is_locked(struct nanohub_data *data) +{ + return atomic_read(&data->wakeup_lock_cnt) != 0; +} + +static inline void nanohub_handle_irq1(struct nanohub_data *data) +{ + bool locked; + + spin_lock(&data->wakeup_wait.lock); + locked = mcu_wakeup_gpio_is_locked(data); + spin_unlock(&data->wakeup_wait.lock); + if (!locked) + nanohub_notify_thread(data); + else + wake_up_interruptible_sync(&data->wakeup_wait); +} + +static inline void nanohub_handle_irq2(struct nanohub_data *data) +{ + nanohub_notify_thread(data); +} + +static inline bool mcu_wakeup_try_lock(struct nanohub_data *data, int key) +{ + /* implementation contains memory barrier */ + return atomic_cmpxchg(&data->wakeup_acquired, 0, key) == 0; +} + +static inline void mcu_wakeup_unlock(struct nanohub_data *data, int key) +{ + WARN(atomic_cmpxchg(&data->wakeup_acquired, key, 0) != key, + "%s: failed to unlock with key %d; current state: %d", + __func__, key, atomic_read(&data->wakeup_acquired)); +} + +static inline void nanohub_set_state(struct nanohub_data *data, int state) +{ + atomic_set(&data->thread_state, state); + smp_mb__after_atomic(); /* updated thread state is now visible */ +} + +static inline int nanohub_get_state(struct nanohub_data *data) +{ + smp_mb__before_atomic(); /* wait for all updates to finish */ + return atomic_read(&data->thread_state); +} + +static inline void nanohub_clear_err_cnt(struct nanohub_data *data) +{ + data->kthread_err_cnt = data->wakeup_err_cnt = 0; +} + +/* the following fragment is based on wait_event_* code from wait.h */ +#define wait_event_interruptible_timeout_locked(q, cond, tmo) \ +({ \ + long __ret = (tmo); \ + DEFINE_WAIT(__wait); \ + if (!(cond)) { \ + for (;;) { \ + __wait.flags &= ~WQ_FLAG_EXCLUSIVE; \ + if (list_empty(&__wait.task_list)) \ + __add_wait_queue_tail(&(q), &__wait); \ + set_current_state(TASK_INTERRUPTIBLE); \ + if ((cond)) \ + break; \ + if (signal_pending(current)) { \ + __ret = -ERESTARTSYS; \ + break; \ + } \ + spin_unlock(&(q).lock); \ + __ret = schedule_timeout(__ret); \ + spin_lock(&(q).lock); \ + if (!__ret) { \ + if ((cond)) \ + __ret = 1; \ + break; \ + } \ + } \ + __set_current_state(TASK_RUNNING); \ + if (!list_empty(&__wait.task_list)) \ + list_del_init(&__wait.task_list); \ + else if (__ret == -ERESTARTSYS && \ + /*reimplementation of wait_abort_exclusive() */\ + waitqueue_active(&(q))) \ + __wake_up_locked_key(&(q), TASK_INTERRUPTIBLE, \ + NULL); \ + } else { \ + __ret = 1; \ + } \ + __ret; \ +}) \ + +int request_wakeup_ex(struct nanohub_data *data, long timeout_ms, + int key, int lock_mode) +{ + long timeout; + bool priority_lock = lock_mode > LOCK_MODE_NORMAL; + struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; + int ret; + ktime_t ktime_delta; + ktime_t wakeup_ktime; + + spin_lock(&data->wakeup_wait.lock); + mcu_wakeup_gpio_get_locked(data, priority_lock); + timeout = (timeout_ms != MAX_SCHEDULE_TIMEOUT) ? + msecs_to_jiffies(timeout_ms) : + MAX_SCHEDULE_TIMEOUT; + + if (!priority_lock && !data->wakeup_err_cnt) + wakeup_ktime = ktime_get_boottime(); + timeout = wait_event_interruptible_timeout_locked( + data->wakeup_wait, + ((priority_lock || nanohub_irq1_fired(data)) && + mcu_wakeup_try_lock(data, key)), + timeout + ); + + if (timeout <= 0) { + if (!timeout && !priority_lock) { + if (!data->wakeup_err_cnt) + data->wakeup_err_ktime = wakeup_ktime; + ktime_delta = ktime_sub(ktime_get_boottime(), + data->wakeup_err_ktime); + data->wakeup_err_cnt++; + if (ktime_to_ns(ktime_delta) > WAKEUP_ERR_TIME_NS + && data->wakeup_err_cnt > WAKEUP_ERR_CNT) { + mcu_wakeup_gpio_put_locked(data, priority_lock); + spin_unlock(&data->wakeup_wait.lock); + dev_info(sensor_dev, + "wakeup: hard reset due to consistent error\n"); + ret = nanohub_hw_reset(data); + if (ret) { + dev_info(sensor_dev, + "%s: failed to reset nanohub: ret=%d\n", + __func__, ret); + } + return -ETIME; + } + } + mcu_wakeup_gpio_put_locked(data, priority_lock); + + if (timeout == 0) + timeout = -ETIME; + } else { + data->wakeup_err_cnt = 0; + timeout = 0; + } + spin_unlock(&data->wakeup_wait.lock); + + return timeout; +} + +void release_wakeup_ex(struct nanohub_data *data, int key, int lock_mode) +{ + bool done; + bool priority_lock = lock_mode > LOCK_MODE_NORMAL; + + spin_lock(&data->wakeup_wait.lock); + done = mcu_wakeup_gpio_put_locked(data, priority_lock); + mcu_wakeup_unlock(data, key); + spin_unlock(&data->wakeup_wait.lock); + + if (!done) + wake_up_interruptible_sync(&data->wakeup_wait); + else if (nanohub_irq1_fired(data) || nanohub_irq2_fired(data)) + nanohub_notify_thread(data); +} + +int nanohub_wait_for_interrupt(struct nanohub_data *data) +{ + int ret = -EFAULT; + + /* release the wakeup line, and wait for nanohub to send + * us an interrupt indicating the transaction completed. + */ + spin_lock(&data->wakeup_wait.lock); + if (mcu_wakeup_gpio_is_locked(data)) { + mcu_wakeup_gpio_set_value(data, 1); + ret = wait_event_interruptible_locked(data->wakeup_wait, + nanohub_irq1_fired(data)); + mcu_wakeup_gpio_set_value(data, 0); + } + spin_unlock(&data->wakeup_wait.lock); + + return ret; +} + +int nanohub_wakeup_eom(struct nanohub_data *data, bool repeat) +{ + int ret = -EFAULT; + + spin_lock(&data->wakeup_wait.lock); + if (mcu_wakeup_gpio_is_locked(data)) { + mcu_wakeup_gpio_set_value(data, 1); + if (repeat) + mcu_wakeup_gpio_set_value(data, 0); + ret = 0; + } + spin_unlock(&data->wakeup_wait.lock); + + return ret; +} + +static void __nanohub_interrupt_cfg(struct nanohub_data *data, + u8 interrupt, bool mask) +{ + int ret; + uint8_t mask_ret; + int cnt = 10; + struct device *dev = data->io[ID_NANOHUB_SENSOR].dev; + int cmd = mask ? CMD_COMMS_MASK_INTR : CMD_COMMS_UNMASK_INTR; + + do { + ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); + if (ret) { + dev_err(dev, + "%s: interrupt %d %smask failed: ret=%d\n", + __func__, interrupt, mask ? "" : "un", ret); + return; + } + + ret = + nanohub_comms_tx_rx_retrans(data, cmd, + &interrupt, sizeof(interrupt), + &mask_ret, sizeof(mask_ret), + false, 10, 0); + release_wakeup(data); + dev_dbg(dev, + "%smasking interrupt %d, ret=%d, mask_ret=%d\n", + mask ? "" : "un", + interrupt, ret, mask_ret); + } while ((ret != 1 || mask_ret != 1) && --cnt > 0); +} + +static inline void nanohub_mask_interrupt(struct nanohub_data *data, + u8 interrupt) +{ + __nanohub_interrupt_cfg(data, interrupt, true); +} + +static inline void nanohub_unmask_interrupt(struct nanohub_data *data, + u8 interrupt) +{ + __nanohub_interrupt_cfg(data, interrupt, false); +} + +static ssize_t nanohub_wakeup_query(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + const struct nanohub_platform_data *pdata = data->pdata; + + nanohub_clear_err_cnt(data); + if (nanohub_irq1_fired(data) || nanohub_irq2_fired(data)) + wake_up_interruptible(&data->wakeup_wait); + + return scnprintf(buf, PAGE_SIZE, "WAKEUP: %d INT1: %d INT2: %d\n", + gpio_get_value(pdata->wakeup_gpio), + gpio_get_value(pdata->irq1_gpio), + data->irq2 ? gpio_get_value(pdata->irq2_gpio) : -1); +} + +static ssize_t nanohub_app_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + struct { + uint64_t appId; + uint32_t appVer; + uint32_t appSize; + } __packed buffer; + uint32_t i = 0; + int ret; + ssize_t len = 0; + + do { + if (request_wakeup(data)) + return -ERESTARTSYS; + + if (nanohub_comms_tx_rx_retrans + (data, CMD_COMMS_QUERY_APP_INFO, (uint8_t *)&i, + sizeof(i), (u8 *)&buffer, sizeof(buffer), + false, 10, 10) == sizeof(buffer)) { + ret = + scnprintf(buf + len, PAGE_SIZE - len, + "app: %d id: %016llx ver: %08x size: %08x\n", + i, buffer.appId, buffer.appVer, + buffer.appSize); + if (ret > 0) { + len += ret; + i++; + } + } else { + ret = -1; + } + + release_wakeup(data); + } while (ret > 0); + + return len; +} + +static ssize_t nanohub_firmware_query(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + uint16_t buffer[6]; + + if (request_wakeup(data)) + return -ERESTARTSYS; + + if (nanohub_comms_tx_rx_retrans + (data, CMD_COMMS_GET_OS_HW_VERSIONS, NULL, 0, (uint8_t *)&buffer, + sizeof(buffer), false, 10, 10) == sizeof(buffer)) { + release_wakeup(data); + return scnprintf(buf, PAGE_SIZE, + "hw type: %04x hw ver: %04x bl ver: %04x os ver: %04x variant ver: %08x\n", + buffer[0], buffer[1], buffer[2], buffer[3], + buffer[5] << 16 | buffer[4]); + } else { + release_wakeup(data); + return 0; + } +} + +static inline int nanohub_wakeup_lock(struct nanohub_data *data, int mode) +{ + int ret; + + if (data->irq2) + disable_irq(data->irq2); + else + nanohub_mask_interrupt(data, 2); + + ret = request_wakeup_ex(data, + mode == LOCK_MODE_SUSPEND_RESUME ? + SUSPEND_TIMEOUT_MS : WAKEUP_TIMEOUT_MS, + KEY_WAKEUP_LOCK, mode); + if (ret < 0) { + if (data->irq2) + enable_irq(data->irq2); + else + nanohub_unmask_interrupt(data, 2); + return ret; + } + + if (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) + ret = nanohub_bl_open(data); + if (ret < 0) { + release_wakeup_ex(data, KEY_WAKEUP_LOCK, mode); + return ret; + } + if (mode != LOCK_MODE_SUSPEND_RESUME) + disable_irq(data->irq1); + + atomic_set(&data->lock_mode, mode); + mcu_wakeup_gpio_set_value(data, mode != LOCK_MODE_IO_BL); + + return 0; +} + +/* returns lock mode used to perform this lock */ +static inline int nanohub_wakeup_unlock(struct nanohub_data *data) +{ + int mode = atomic_read(&data->lock_mode); + + atomic_set(&data->lock_mode, LOCK_MODE_NONE); + if (mode != LOCK_MODE_SUSPEND_RESUME) + enable_irq(data->irq1); + if (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) + nanohub_bl_close(data); + if (data->irq2) + enable_irq(data->irq2); + release_wakeup_ex(data, KEY_WAKEUP_LOCK, mode); + if (!data->irq2) + nanohub_unmask_interrupt(data, 2); + nanohub_notify_thread(data); + + return mode; +} + +static void __nanohub_hw_reset(struct nanohub_data *data, int boot0) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + gpio_set_value(pdata->nreset_gpio, 0); + gpio_set_value(pdata->boot0_gpio, boot0 > 0); + usleep_range(30, 40); + gpio_set_value(pdata->nreset_gpio, 1); + if (boot0 > 0) + usleep_range(70000, 75000); + else if (!boot0) + usleep_range(750000, 800000); + nanohub_clear_err_cnt(data); +} + +static int nanohub_hw_reset(struct nanohub_data *data) +{ + int ret; + ret = nanohub_wakeup_lock(data, LOCK_MODE_RESET); + + if (!ret) { + __nanohub_hw_reset(data, 0); + nanohub_wakeup_unlock(data); + } + + return ret; +} + +static ssize_t nanohub_try_hw_reset(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + int ret; + + ret = nanohub_hw_reset(data); + + return ret < 0 ? ret : count; +} + +static ssize_t nanohub_erase_shared(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + uint8_t status = CMD_ACK; + int ret; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); + if (ret < 0) + return ret; + + __nanohub_hw_reset(data, 1); + + status = nanohub_bl_erase_shared(data); + dev_info(dev, "nanohub_bl_erase_shared: status=%02x\n", + status); + + __nanohub_hw_reset(data, 0); + nanohub_wakeup_unlock(data); + + return ret < 0 ? ret : count; +} + +static ssize_t nanohub_erase_shared_bl(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + uint8_t status = CMD_ACK; + int ret; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO_BL); + if (ret < 0) + return ret; + + __nanohub_hw_reset(data, -1); + + status = nanohub_bl_erase_shared_bl(data); + dev_info(dev, "%s: status=%02x\n", __func__, status); + + __nanohub_hw_reset(data, 0); + nanohub_wakeup_unlock(data); + + return ret < 0 ? ret : count; +} + +static ssize_t nanohub_download_bl(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + const struct nanohub_platform_data *pdata = data->pdata; + const struct firmware *fw_entry; + int ret; + uint8_t status = CMD_ACK; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); + if (ret < 0) + return ret; + + __nanohub_hw_reset(data, 1); + + ret = request_firmware(&fw_entry, "nanohub.full.bin", dev); + if (ret) { + dev_err(dev, "%s: err=%d\n", __func__, ret); + } else { + status = nanohub_bl_download(data, pdata->bl_addr, + fw_entry->data, fw_entry->size); + dev_info(dev, "%s: status=%02x\n", __func__, status); + release_firmware(fw_entry); + } + + __nanohub_hw_reset(data, 0); + nanohub_wakeup_unlock(data); + + return ret < 0 ? ret : count; +} + +static ssize_t nanohub_download_kernel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + const struct firmware *fw_entry; + int ret; + + ret = request_firmware(&fw_entry, "nanohub.update.bin", dev); + if (ret) { + dev_err(dev, "nanohub_download_kernel: err=%d\n", ret); + return -EIO; + } else { + ret = + nanohub_comms_kernel_download(data, fw_entry->data, + fw_entry->size); + + release_firmware(fw_entry); + + return count; + } + +} + +static ssize_t nanohub_download_kernel_bl(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + const struct firmware *fw_entry; + int ret; + uint8_t status = CMD_ACK; + + ret = request_firmware(&fw_entry, "nanohub.kernel.signed", dev); + if (ret) { + dev_err(dev, "%s: err=%d\n", __func__, ret); + } else { + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO_BL); + if (ret < 0) + return ret; + + __nanohub_hw_reset(data, -1); + + status = nanohub_bl_erase_shared_bl(data); + dev_info(dev, "%s: (erase) status=%02x\n", __func__, status); + if (status == CMD_ACK) { + status = nanohub_bl_write_memory(data, 0x50000000, + fw_entry->size, + fw_entry->data); + mcu_wakeup_gpio_set_value(data, 1); + dev_info(dev, "%s: (write) status=%02x\n", __func__, status); + if (status == CMD_ACK) { + status = nanohub_bl_update_finished(data); + dev_info(dev, "%s: (finish) status=%02x\n", __func__, status); + } + } else { + mcu_wakeup_gpio_set_value(data, 1); + } + + __nanohub_hw_reset(data, 0); + nanohub_wakeup_unlock(data); + + release_firmware(fw_entry); + } + + return ret < 0 ? ret : count; +} + +static ssize_t nanohub_download_app(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + const struct firmware *fw_entry; + char buffer[70]; + int i, ret, ret1, ret2, file_len = 0, appid_len = 0, ver_len = 0; + const char *appid = NULL, *ver = NULL; + unsigned long version; + uint64_t id; + uint32_t cur_version; + bool update = true; + + for (i = 0; i < count; i++) { + if (buf[i] == ' ') { + if (i + 1 == count) { + break; + } else { + if (appid == NULL) + appid = buf + i + 1; + else if (ver == NULL) + ver = buf + i + 1; + else + break; + } + } else if (buf[i] == '\n' || buf[i] == '\r') { + break; + } else { + if (ver) + ver_len++; + else if (appid) + appid_len++; + else + file_len++; + } + } + + if (file_len > 64 || appid_len > 16 || ver_len > 8 || file_len < 1) + return -EIO; + + memcpy(buffer, buf, file_len); + memcpy(buffer + file_len, ".napp", 5); + buffer[file_len + 5] = '\0'; + + ret = request_firmware(&fw_entry, buffer, dev); + if (ret) { + dev_err(dev, "nanohub_download_app(%s): err=%d\n", + buffer, ret); + return -EIO; + } + if (appid_len > 0 && ver_len > 0) { + memcpy(buffer, appid, appid_len); + buffer[appid_len] = '\0'; + + ret1 = kstrtoull(buffer, 16, &id); + + memcpy(buffer, ver, ver_len); + buffer[ver_len] = '\0'; + + ret2 = kstrtoul(buffer, 16, &version); + + if (ret1 == 0 && ret2 == 0) { + if (request_wakeup(data)) + return -ERESTARTSYS; + if (nanohub_comms_tx_rx_retrans + (data, CMD_COMMS_GET_APP_VERSIONS, + (uint8_t *)&id, sizeof(id), + (uint8_t *)&cur_version, + sizeof(cur_version), false, 10, + 10) == sizeof(cur_version)) { + if (cur_version == version) + update = false; + } + release_wakeup(data); + } + } + + if (update) + ret = + nanohub_comms_app_download(data, fw_entry->data, + fw_entry->size); + + release_firmware(fw_entry); + + return count; +} + +static ssize_t nanohub_lock_bl(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + int ret; + uint8_t status = CMD_ACK; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); + if (ret < 0) + return ret; + + __nanohub_hw_reset(data, 1); + + gpio_set_value(data->pdata->boot0_gpio, 0); + /* this command reboots itself */ + status = nanohub_bl_lock(data); + dev_info(dev, "%s: status=%02x\n", __func__, status); + msleep(350); + + nanohub_wakeup_unlock(data); + + return ret < 0 ? ret : count; +} + +static ssize_t nanohub_unlock_bl(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nanohub_data *data = dev_get_nanohub_data(dev); + int ret; + uint8_t status = CMD_ACK; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); + if (ret < 0) + return ret; + + __nanohub_hw_reset(data, 1); + + gpio_set_value(data->pdata->boot0_gpio, 0); + /* this command reboots itself (erasing the flash) */ + status = nanohub_bl_unlock(data); + dev_info(dev, "%s: status=%02x\n", __func__, status); + msleep(20); + + nanohub_wakeup_unlock(data); + + return ret < 0 ? ret : count; +} + +static struct device_attribute attributes[] = { + __ATTR(wakeup, 0440, nanohub_wakeup_query, NULL), + __ATTR(app_info, 0440, nanohub_app_info, NULL), + __ATTR(firmware_version, 0440, nanohub_firmware_query, NULL), + __ATTR(download_bl, 0220, NULL, nanohub_download_bl), + __ATTR(download_kernel, 0220, NULL, nanohub_download_kernel), + __ATTR(download_kernel_bl, 0220, NULL, nanohub_download_kernel_bl), + __ATTR(download_app, 0220, NULL, nanohub_download_app), + __ATTR(erase_shared, 0220, NULL, nanohub_erase_shared), + __ATTR(erase_shared_bl, 0220, NULL, nanohub_erase_shared_bl), + __ATTR(reset, 0220, NULL, nanohub_try_hw_reset), + __ATTR(lock, 0220, NULL, nanohub_lock_bl), + __ATTR(unlock, 0220, NULL, nanohub_unlock_bl), +}; + +static inline int nanohub_create_sensor(struct nanohub_data *data) +{ + int i, ret; + struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; + + for (i = 0, ret = 0; i < ARRAY_SIZE(attributes); i++) { + ret = device_create_file(sensor_dev, &attributes[i]); + if (ret) { + dev_err(sensor_dev, + "create sysfs attr %d [%s] failed; err=%d\n", + i, attributes[i].attr.name, ret); + goto fail_attr; + } + } + + ret = sysfs_create_link(&sensor_dev->kobj, + &data->iio_dev->dev.kobj, "iio"); + if (ret) { + dev_err(sensor_dev, + "sysfs_create_link failed; err=%d\n", ret); + goto fail_attr; + } + goto done; + +fail_attr: + for (i--; i >= 0; i--) + device_remove_file(sensor_dev, &attributes[i]); +done: + return ret; +} + +static int nanohub_create_devices(struct nanohub_data *data) +{ + int i, ret; + static const char *names[ID_NANOHUB_MAX] = { + "nanohub", "nanohub_comms" + }; + + for (i = 0; i < ID_NANOHUB_MAX; ++i) { + struct nanohub_io *io = &data->io[i]; + + nanohub_io_init(io, data, device_create(sensor_class, NULL, + MKDEV(major, i), + io, names[i])); + if (IS_ERR(io->dev)) { + ret = PTR_ERR(io->dev); + pr_err("nanohub: device_create failed for %s; err=%d\n", + names[i], ret); + goto fail_dev; + } + } + + ret = nanohub_create_sensor(data); + if (!ret) + goto done; + +fail_dev: + for (--i; i >= 0; --i) + device_destroy(sensor_class, MKDEV(major, i)); +done: + return ret; +} + +static int nanohub_match_devt(struct device *dev, const void *data) +{ + const dev_t *devt = data; + + return dev->devt == *devt; +} + +static int nanohub_open(struct inode *inode, struct file *file) +{ + dev_t devt = inode->i_rdev; + struct device *dev; + + dev = class_find_device(sensor_class, NULL, &devt, nanohub_match_devt); + if (dev) { + file->private_data = dev_get_drvdata(dev); + nonseekable_open(inode, file); + return 0; + } + + return -ENODEV; +} + +static ssize_t nanohub_read(struct file *file, char *buffer, size_t length, + loff_t *offset) +{ + struct nanohub_io *io = file->private_data; + struct nanohub_data *data = io->data; + struct nanohub_buf *buf; + int ret; + + if (!nanohub_io_has_buf(io) && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + buf = nanohub_io_get_buf(io, true); + if (IS_ERR_OR_NULL(buf)) + return PTR_ERR(buf); + + ret = copy_to_user(buffer, buf->buffer, buf->length); + if (ret != 0) + ret = -EFAULT; + else + ret = buf->length; + + nanohub_io_put_buf(&data->free_pool, buf); + + return ret; +} + +static ssize_t nanohub_write(struct file *file, const char *buffer, + size_t length, loff_t *offset) +{ + struct nanohub_io *io = file->private_data; + struct nanohub_data *data = io->data; + int ret; + + ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); + if (ret) + return ret; + + ret = nanohub_comms_write(data, buffer, length); + + release_wakeup(data); + + return ret; +} + +static unsigned int nanohub_poll(struct file *file, poll_table *wait) +{ + struct nanohub_io *io = file->private_data; + unsigned int mask = POLLOUT | POLLWRNORM; + + poll_wait(file, &io->buf_wait, wait); + + if (nanohub_io_has_buf(io)) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +static int nanohub_release(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + + return 0; +} + +static void nanohub_destroy_devices(struct nanohub_data *data) +{ + int i; + struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; + + sysfs_remove_link(&sensor_dev->kobj, "iio"); + for (i = 0; i < ARRAY_SIZE(attributes); i++) + device_remove_file(sensor_dev, &attributes[i]); + for (i = 0; i < ID_NANOHUB_MAX; ++i) + device_destroy(sensor_class, MKDEV(major, i)); +} + +static irqreturn_t nanohub_irq1(int irq, void *dev_id) +{ + struct nanohub_data *data = (struct nanohub_data *)dev_id; + + nanohub_handle_irq1(data); + + return IRQ_HANDLED; +} + +static irqreturn_t nanohub_irq2(int irq, void *dev_id) +{ + struct nanohub_data *data = (struct nanohub_data *)dev_id; + + nanohub_handle_irq2(data); + + return IRQ_HANDLED; +} + +static bool nanohub_os_log(char *buffer, int len) +{ + if (le32_to_cpu((((uint32_t *)buffer)[0]) & 0x7FFFFFFF) == + OS_LOG_EVENTID) { + char *mtype, *mdata = &buffer[5]; + + buffer[len] = 0x00; + + switch (buffer[4]) { + case 'E': + mtype = KERN_ERR; + break; + case 'W': + mtype = KERN_WARNING; + break; + case 'I': + mtype = KERN_INFO; + break; + case 'D': + mtype = KERN_DEBUG; + break; + default: + mtype = KERN_DEFAULT; + mdata--; + break; + } + printk("%snanohub: %s", mtype, mdata); + return true; + } else { + return false; + } +} + +static void nanohub_process_buffer(struct nanohub_data *data, + struct nanohub_buf **buf, + int ret) +{ + uint32_t event_id; + uint8_t interrupt; + bool wakeup = false; + struct nanohub_io *io = &data->io[ID_NANOHUB_SENSOR]; + + data->kthread_err_cnt = 0; + if (ret < 4 || nanohub_os_log((*buf)->buffer, ret)) { + release_wakeup(data); + return; + } + + (*buf)->length = ret; + + event_id = le32_to_cpu((((uint32_t *)(*buf)->buffer)[0]) & 0x7FFFFFFF); + if (ret >= sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint32_t) && + event_id > FIRST_SENSOR_EVENTID && + event_id <= LAST_SENSOR_EVENTID) { + interrupt = (*buf)->buffer[sizeof(uint32_t) + + sizeof(uint64_t) + 3]; + if (interrupt == WAKEUP_INTERRUPT) + wakeup = true; + } + if (event_id == APP_TO_HOST_EVENTID) { + wakeup = true; + io = &data->io[ID_NANOHUB_COMMS]; + } + + nanohub_io_put_buf(io, *buf); + + *buf = NULL; + /* (for wakeup interrupts): hold a wake lock for 250ms so the sensor hal + * has time to grab its own wake lock */ + if (wakeup) + wake_lock_timeout(&data->wakelock_read, msecs_to_jiffies(250)); + release_wakeup(data); +} + +static int nanohub_kthread(void *arg) +{ + struct nanohub_data *data = (struct nanohub_data *)arg; + struct nanohub_buf *buf = NULL; + int ret; + ktime_t ktime_delta; + uint32_t clear_interrupts[8] = { 0x00000006 }; + struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; + static const struct sched_param param = { + .sched_priority = (MAX_USER_RT_PRIO/2)-1, + }; + + data->kthread_err_cnt = 0; + sched_setscheduler(current, SCHED_FIFO, ¶m); + nanohub_set_state(data, ST_IDLE); + + while (!kthread_should_stop()) { + switch (nanohub_get_state(data)) { + case ST_IDLE: + wait_event_interruptible(data->kthread_wait, + atomic_read(&data->kthread_run) + ); + nanohub_set_state(data, ST_RUNNING); + break; + case ST_ERROR: + ktime_delta = ktime_sub(ktime_get_boottime(), + data->kthread_err_ktime); + if (ktime_to_ns(ktime_delta) > KTHREAD_ERR_TIME_NS + && data->kthread_err_cnt > KTHREAD_ERR_CNT) { + dev_info(sensor_dev, + "kthread: hard reset due to consistent error\n"); + ret = nanohub_hw_reset(data); + if (ret) { + dev_info(sensor_dev, + "%s: failed to reset nanohub: ret=%d\n", + __func__, ret); + } + } + msleep_interruptible(WAKEUP_TIMEOUT_MS); + nanohub_set_state(data, ST_RUNNING); + break; + case ST_RUNNING: + break; + } + atomic_set(&data->kthread_run, 0); + if (!buf) + buf = nanohub_io_get_buf(&data->free_pool, + false); + if (buf) { + ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); + if (ret) { + dev_info(sensor_dev, + "%s: request_wakeup_timeout: ret=%d\n", + __func__, ret); + continue; + } + + ret = nanohub_comms_rx_retrans_boottime( + data, CMD_COMMS_READ, buf->buffer, + sizeof(buf->buffer), 10, 0); + + if (ret > 0) { + nanohub_process_buffer(data, &buf, ret); + if (!nanohub_irq1_fired(data) && + !nanohub_irq2_fired(data)) { + nanohub_set_state(data, ST_IDLE); + continue; + } + } else if (ret == 0) { + /* queue empty, go to sleep */ + data->kthread_err_cnt = 0; + data->interrupts[0] &= ~0x00000006; + release_wakeup(data); + nanohub_set_state(data, ST_IDLE); + continue; + } else { + release_wakeup(data); + if (data->kthread_err_cnt == 0) + data->kthread_err_ktime = + ktime_get_boottime(); + + data->kthread_err_cnt++; + if (data->kthread_err_cnt >= KTHREAD_WARN_CNT) { + dev_err(sensor_dev, + "%s: kthread_err_cnt=%d\n", + __func__, + data->kthread_err_cnt); + nanohub_set_state(data, ST_ERROR); + continue; + } + } + } else { + if (!nanohub_irq1_fired(data) && + !nanohub_irq2_fired(data)) { + nanohub_set_state(data, ST_IDLE); + continue; + } + /* pending interrupt, but no room to read data - + * clear interrupts */ + if (request_wakeup(data)) + continue; + nanohub_comms_tx_rx_retrans(data, + CMD_COMMS_CLR_GET_INTR, + (uint8_t *) + clear_interrupts, + sizeof(clear_interrupts), + (uint8_t *) data-> + interrupts, + sizeof(data->interrupts), + false, 10, 0); + release_wakeup(data); + nanohub_set_state(data, ST_IDLE); + } + } + + return 0; +} + +#ifdef CONFIG_OF +static struct nanohub_platform_data *nanohub_parse_dt(struct device *dev) +{ + struct nanohub_platform_data *pdata; + struct device_node *dt = dev->of_node; + const uint32_t *tmp; + struct property *prop; + uint32_t u, i; + int ret; + + if (!dt) + return ERR_PTR(-ENODEV); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = pdata->irq1_gpio = + of_get_named_gpio(dt, "sensorhub,irq1-gpio", 0); + if (ret < 0) { + pr_err("nanohub: missing sensorhub,irq1-gpio in device tree\n"); + goto free_pdata; + } + + /* optional (strongly recommended) */ + pdata->irq2_gpio = of_get_named_gpio(dt, "sensorhub,irq2-gpio", 0); + + ret = pdata->wakeup_gpio = + of_get_named_gpio(dt, "sensorhub,wakeup-gpio", 0); + if (ret < 0) { + pr_err + ("nanohub: missing sensorhub,wakeup-gpio in device tree\n"); + goto free_pdata; + } + + ret = pdata->nreset_gpio = + of_get_named_gpio(dt, "sensorhub,nreset-gpio", 0); + if (ret < 0) { + pr_err + ("nanohub: missing sensorhub,nreset-gpio in device tree\n"); + goto free_pdata; + } + + /* optional (stm32f bootloader) */ + pdata->boot0_gpio = of_get_named_gpio(dt, "sensorhub,boot0-gpio", 0); + + /* optional (spi) */ + pdata->spi_cs_gpio = of_get_named_gpio(dt, "sensorhub,spi-cs-gpio", 0); + + /* optional (stm32f bootloader) */ + of_property_read_u32(dt, "sensorhub,bl-addr", &pdata->bl_addr); + + /* optional (stm32f bootloader) */ + tmp = of_get_property(dt, "sensorhub,num-flash-banks", NULL); + if (tmp) { + pdata->num_flash_banks = be32_to_cpup(tmp); + pdata->flash_banks = + devm_kzalloc(dev, + sizeof(struct nanohub_flash_bank) * + pdata->num_flash_banks, GFP_KERNEL); + if (!pdata->flash_banks) + goto no_mem; + + /* TODO: investigate replacing with of_property_read_u32_array + */ + i = 0; + of_property_for_each_u32(dt, "sensorhub,flash-banks", prop, tmp, + u) { + if (i / 3 >= pdata->num_flash_banks) + break; + switch (i % 3) { + case 0: + pdata->flash_banks[i / 3].bank = u; + break; + case 1: + pdata->flash_banks[i / 3].address = u; + break; + case 2: + pdata->flash_banks[i / 3].length = u; + break; + } + i++; + } + } + + /* optional (stm32f bootloader) */ + tmp = of_get_property(dt, "sensorhub,num-shared-flash-banks", NULL); + if (tmp) { + pdata->num_shared_flash_banks = be32_to_cpup(tmp); + pdata->shared_flash_banks = + devm_kzalloc(dev, + sizeof(struct nanohub_flash_bank) * + pdata->num_shared_flash_banks, GFP_KERNEL); + if (!pdata->shared_flash_banks) + goto no_mem_shared; + + /* TODO: investigate replacing with of_property_read_u32_array + */ + i = 0; + of_property_for_each_u32(dt, "sensorhub,shared-flash-banks", + prop, tmp, u) { + if (i / 3 >= pdata->num_shared_flash_banks) + break; + switch (i % 3) { + case 0: + pdata->shared_flash_banks[i / 3].bank = u; + break; + case 1: + pdata->shared_flash_banks[i / 3].address = u; + break; + case 2: + pdata->shared_flash_banks[i / 3].length = u; + break; + } + i++; + } + } + + return pdata; + +no_mem_shared: + devm_kfree(dev, pdata->flash_banks); +no_mem: + ret = -ENOMEM; +free_pdata: + devm_kfree(dev, pdata); + return ERR_PTR(ret); +} +#else +static struct nanohub_platform_data *nanohub_parse_dt(struct device *dev) +{ + struct nanohub_platform_data *pdata; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + return pdata; +} +#endif + +static int nanohub_request_irqs(struct nanohub_data *data) +{ + int ret; + + ret = request_threaded_irq(data->irq1, NULL, nanohub_irq1, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "nanohub-irq1", data); + if (ret < 0) + data->irq1 = 0; + else + disable_irq(data->irq1); + if (data->irq2 <= 0 || ret < 0) { + data->irq2 = 0; + return ret; + } + + ret = request_threaded_irq(data->irq2, NULL, nanohub_irq2, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "nanohub-irq2", data); + if (ret < 0) { + data->irq2 = 0; + WARN(1, "failed to request optional IRQ %d; err=%d", + data->irq2, ret); + } else { + disable_irq(data->irq2); + } + + /* if 2d request fails, hide this; it is optional IRQ, + * and failure should not interrupt driver init sequence. + */ + return 0; +} + +static int nanohub_request_gpios(struct nanohub_data *data) +{ + int i, ret = 0; + + for (i = 0; i < ARRAY_SIZE(gconf); ++i) { + const struct gpio_config *cfg = &gconf[i]; + unsigned int gpio = plat_gpio_get(data, cfg); + const char *label; + bool optional = gpio_is_optional(cfg); + + ret = 0; /* clear errors on optional pins, if any */ + + if (!gpio_is_valid(gpio) && optional) + continue; + + label = cfg->label; + ret = gpio_request_one(gpio, cfg->flags, label); + if (ret && !optional) { + pr_err("nanohub: gpio %d[%s] request failed;err=%d\n", + gpio, label, ret); + break; + } + if (gpio_has_irq(cfg)) { + int irq = gpio_to_irq(gpio); + if (irq > 0) { + nanohub_set_irq_data(data, cfg, irq); + } else if (!optional) { + ret = -EINVAL; + pr_err("nanohub: no irq; gpio %d[%s];err=%d\n", + gpio, label, irq); + break; + } + } + } + if (i < ARRAY_SIZE(gconf)) { + for (--i; i >= 0; --i) + gpio_free(plat_gpio_get(data, &gconf[i])); + } + + return ret; +} + +static void nanohub_release_gpios_irqs(struct nanohub_data *data) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + if (data->irq2) + free_irq(data->irq2, data); + if (data->irq1) + free_irq(data->irq1, data); + if (gpio_is_valid(pdata->irq2_gpio)) + gpio_free(pdata->irq2_gpio); + gpio_free(pdata->irq1_gpio); + gpio_set_value(pdata->nreset_gpio, 0); + gpio_free(pdata->nreset_gpio); + mcu_wakeup_gpio_set_value(data, 1); + gpio_free(pdata->wakeup_gpio); + gpio_set_value(pdata->boot0_gpio, 0); + gpio_free(pdata->boot0_gpio); +} + +struct iio_dev *nanohub_probe(struct device *dev, struct iio_dev *iio_dev) +{ + int ret, i; + const struct nanohub_platform_data *pdata; + struct nanohub_data *data; + struct nanohub_buf *buf; + bool own_iio_dev = !iio_dev; + + pdata = dev_get_platdata(dev); + if (!pdata) { + pdata = nanohub_parse_dt(dev); + if (IS_ERR(pdata)) + return ERR_PTR(PTR_ERR(pdata)); + } + + if (own_iio_dev) { + iio_dev = iio_device_alloc(sizeof(struct nanohub_data)); + if (!iio_dev) + return ERR_PTR(-ENOMEM); + } + + iio_dev->name = "nanohub"; + iio_dev->dev.parent = dev; + iio_dev->info = &nanohub_iio_info; + iio_dev->channels = NULL; + iio_dev->num_channels = 0; + + data = iio_priv(iio_dev); + data->iio_dev = iio_dev; + data->pdata = pdata; + + init_waitqueue_head(&data->kthread_wait); + + nanohub_io_init(&data->free_pool, data, dev); + + buf = vmalloc(sizeof(*buf) * READ_QUEUE_DEPTH); + data->vbuf = buf; + if (!buf) { + ret = -ENOMEM; + goto fail_vma; + } + + for (i = 0; i < READ_QUEUE_DEPTH; i++) + nanohub_io_put_buf(&data->free_pool, &buf[i]); + atomic_set(&data->kthread_run, 0); + wake_lock_init(&data->wakelock_read, WAKE_LOCK_SUSPEND, + "nanohub_wakelock_read"); + + atomic_set(&data->lock_mode, LOCK_MODE_NONE); + atomic_set(&data->wakeup_cnt, 0); + atomic_set(&data->wakeup_lock_cnt, 0); + atomic_set(&data->wakeup_acquired, 0); + init_waitqueue_head(&data->wakeup_wait); + + ret = nanohub_request_gpios(data); + if (ret) + goto fail_gpio; + + ret = nanohub_request_irqs(data); + if (ret) + goto fail_irq; + + ret = iio_device_register(iio_dev); + if (ret) { + pr_err("nanohub: iio_device_register failed\n"); + goto fail_irq; + } + + ret = nanohub_create_devices(data); + if (ret) + goto fail_dev; + + data->thread = kthread_run(nanohub_kthread, data, "nanohub"); + + udelay(30); + + return iio_dev; + +fail_dev: + iio_device_unregister(iio_dev); +fail_irq: + nanohub_release_gpios_irqs(data); +fail_gpio: + wake_lock_destroy(&data->wakelock_read); + vfree(buf); +fail_vma: + if (own_iio_dev) + iio_device_free(iio_dev); + + return ERR_PTR(ret); +} + +int nanohub_reset(struct nanohub_data *data) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + gpio_set_value(pdata->nreset_gpio, 1); + usleep_range(650000, 700000); + enable_irq(data->irq1); + if (data->irq2) + enable_irq(data->irq2); + else + nanohub_unmask_interrupt(data, 2); + + return 0; +} + +int nanohub_remove(struct iio_dev *iio_dev) +{ + struct nanohub_data *data = iio_priv(iio_dev); + + nanohub_notify_thread(data); + kthread_stop(data->thread); + + nanohub_destroy_devices(data); + iio_device_unregister(iio_dev); + nanohub_release_gpios_irqs(data); + wake_lock_destroy(&data->wakelock_read); + vfree(data->vbuf); + iio_device_free(iio_dev); + + return 0; +} + +int nanohub_suspend(struct iio_dev *iio_dev) +{ + struct nanohub_data *data = iio_priv(iio_dev); + int ret; + + ret = nanohub_wakeup_lock(data, LOCK_MODE_SUSPEND_RESUME); + if (!ret) { + int cnt; + const int max_cnt = 10; + + for (cnt = 0; cnt < max_cnt; ++cnt) { + if (!nanohub_irq1_fired(data)) + break; + usleep_range(10, 15); + } + if (cnt < max_cnt) { + dev_dbg(&iio_dev->dev, "%s: cnt=%d\n", __func__, cnt); + enable_irq_wake(data->irq1); + return 0; + } + ret = -EBUSY; + dev_info(&iio_dev->dev, + "%s: failed to suspend: IRQ1=%d, state=%d\n", + __func__, nanohub_irq1_fired(data), + nanohub_get_state(data)); + nanohub_wakeup_unlock(data); + } else { + dev_info(&iio_dev->dev, "%s: could not take wakeup lock\n", + __func__); + } + + return ret; +} + +int nanohub_resume(struct iio_dev *iio_dev) +{ + struct nanohub_data *data = iio_priv(iio_dev); + + disable_irq_wake(data->irq1); + nanohub_wakeup_unlock(data); + + return 0; +} + +static int __init nanohub_init(void) +{ + int ret = 0; + + sensor_class = class_create(THIS_MODULE, "nanohub"); + if (IS_ERR(sensor_class)) { + ret = PTR_ERR(sensor_class); + pr_err("nanohub: class_create failed; err=%d\n", ret); + } + if (!ret) + major = __register_chrdev(0, 0, ID_NANOHUB_MAX, "nanohub", + &nanohub_fileops); + + if (major < 0) { + ret = major; + major = 0; + pr_err("nanohub: can't register; err=%d\n", ret); + } + +#ifdef CONFIG_NANOHUB_I2C + if (ret == 0) + ret = nanohub_i2c_init(); +#endif +#ifdef CONFIG_NANOHUB_SPI + if (ret == 0) + ret = nanohub_spi_init(); +#endif + pr_info("nanohub: loaded; ret=%d\n", ret); + return ret; +} + +static void __exit nanohub_cleanup(void) +{ +#ifdef CONFIG_NANOHUB_I2C + nanohub_i2c_cleanup(); +#endif +#ifdef CONFIG_NANOHUB_SPI + nanohub_spi_cleanup(); +#endif + __unregister_chrdev(major, 0, ID_NANOHUB_MAX, "nanohub"); + class_destroy(sensor_class); + major = 0; + sensor_class = 0; +} + +module_init(nanohub_init); +module_exit(nanohub_cleanup); + +MODULE_AUTHOR("Ben Fennema"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/nanohub/main.h b/drivers/staging/nanohub/main.h new file mode 100644 index 000000000000..9fd28ab7070a --- /dev/null +++ b/drivers/staging/nanohub/main.h @@ -0,0 +1,151 @@ +/* + * 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. + * + */ + +#ifndef _NANOHUB_MAIN_H +#define _NANOHUB_MAIN_H + +#include +#include +#include +#include +#include +#include +#include + +#include "comms.h" +#include "bl.h" + +#define NANOHUB_NAME "nanohub" + +struct nanohub_buf { + struct list_head list; + uint8_t buffer[255]; + uint8_t length; +}; + +struct nanohub_data; + +struct nanohub_io { + struct device *dev; + struct nanohub_data *data; + wait_queue_head_t buf_wait; + struct list_head buf_list; +}; + +static inline struct nanohub_data *dev_get_nanohub_data(struct device *dev) +{ + struct nanohub_io *io = dev_get_drvdata(dev); + + return io->data; +} + +struct nanohub_data { + /* indices for io[] array */ + #define ID_NANOHUB_SENSOR 0 + #define ID_NANOHUB_COMMS 1 + #define ID_NANOHUB_MAX 2 + + struct iio_dev *iio_dev; + struct nanohub_io io[ID_NANOHUB_MAX]; + + struct nanohub_comms comms; + struct nanohub_bl bl; + const struct nanohub_platform_data *pdata; + int irq1; + int irq2; + + atomic_t kthread_run; + atomic_t thread_state; + wait_queue_head_t kthread_wait; + + struct wake_lock wakelock_read; + + struct nanohub_io free_pool; + + atomic_t lock_mode; + /* these 3 vars should be accessed only with wakeup_wait.lock held */ + atomic_t wakeup_cnt; + atomic_t wakeup_lock_cnt; + atomic_t wakeup_acquired; + wait_queue_head_t wakeup_wait; + + uint32_t interrupts[8]; + + ktime_t wakeup_err_ktime; + int wakeup_err_cnt; + + ktime_t kthread_err_ktime; + int kthread_err_cnt; + + void *vbuf; + struct task_struct *thread; +}; + +enum { + KEY_WAKEUP_NONE, + KEY_WAKEUP, + KEY_WAKEUP_LOCK, +}; + +enum { + LOCK_MODE_NONE, + LOCK_MODE_NORMAL, + LOCK_MODE_IO, + LOCK_MODE_IO_BL, + LOCK_MODE_RESET, + LOCK_MODE_SUSPEND_RESUME, +}; + +int request_wakeup_ex(struct nanohub_data *data, long timeout, + int key, int lock_mode); +void release_wakeup_ex(struct nanohub_data *data, int key, int lock_mode); +int nanohub_wait_for_interrupt(struct nanohub_data *data); +int nanohub_wakeup_eom(struct nanohub_data *data, bool repeat); +struct iio_dev *nanohub_probe(struct device *dev, struct iio_dev *iio_dev); +int nanohub_reset(struct nanohub_data *data); +int nanohub_remove(struct iio_dev *iio_dev); +int nanohub_suspend(struct iio_dev *iio_dev); +int nanohub_resume(struct iio_dev *iio_dev); + +static inline int nanohub_irq1_fired(struct nanohub_data *data) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + return !gpio_get_value(pdata->irq1_gpio); +} + +static inline int nanohub_irq2_fired(struct nanohub_data *data) +{ + const struct nanohub_platform_data *pdata = data->pdata; + + return data->irq2 && !gpio_get_value(pdata->irq2_gpio); +} + +static inline int request_wakeup_timeout(struct nanohub_data *data, int timeout) +{ + return request_wakeup_ex(data, timeout, KEY_WAKEUP, LOCK_MODE_NORMAL); +} + +static inline int request_wakeup(struct nanohub_data *data) +{ + return request_wakeup_ex(data, MAX_SCHEDULE_TIMEOUT, KEY_WAKEUP, + LOCK_MODE_NORMAL); +} + +static inline void release_wakeup(struct nanohub_data *data) +{ + release_wakeup_ex(data, KEY_WAKEUP, LOCK_MODE_NORMAL); +} + +#endif diff --git a/drivers/staging/nanohub/spi.c b/drivers/staging/nanohub/spi.c new file mode 100644 index 000000000000..a315327d8b74 --- /dev/null +++ b/drivers/staging/nanohub/spi.c @@ -0,0 +1,549 @@ +/* + * 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 +#include +#include +#include +#include + +#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_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; + 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; + 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); + + 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); diff --git a/drivers/staging/nanohub/spi.h b/drivers/staging/nanohub/spi.h new file mode 100644 index 000000000000..0155c812ac30 --- /dev/null +++ b/drivers/staging/nanohub/spi.h @@ -0,0 +1,21 @@ +/* + * 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. + * + */ + +#ifndef _NANOHUB_SPI_H +#define _NANOHUB_SPI_H + +int __init nanohub_spi_init(void); +void nanohub_spi_cleanup(void); + +#endif diff --git a/include/linux/platform_data/nanohub.h b/include/linux/platform_data/nanohub.h new file mode 100644 index 000000000000..f3050bdfb3b1 --- /dev/null +++ b/include/linux/platform_data/nanohub.h @@ -0,0 +1,26 @@ +#ifndef __LINUX_PLATFORM_DATA_NANOHUB_H +#define __LINUX_PLATFORM_DATA_NANOHUB_H + +#include + +struct nanohub_flash_bank { + int bank; + u32 address; + size_t length; +}; + +struct nanohub_platform_data { + u32 wakeup_gpio; + u32 nreset_gpio; + u32 boot0_gpio; + u32 irq1_gpio; + u32 irq2_gpio; + u32 spi_cs_gpio; + u32 bl_addr; + u32 num_flash_banks; + struct nanohub_flash_bank *flash_banks; + u32 num_shared_flash_banks; + struct nanohub_flash_bank *shared_flash_banks; +}; + +#endif /* __LINUX_PLATFORM_DATA_NANOHUB_H */