/* * Copyright (c) 2017 Linaro Limited * Copyright (c) 2016 Nordic Semiconductor ASA * Copyright (c) 2015-2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_MODULE_NAME hci_spi LOG_MODULE_REGISTER(LOG_MODULE_NAME); #define HCI_CMD 0x01 #define HCI_ACL 0x02 #define HCI_SCO 0x03 #define HCI_EVT 0x04 /* Special Values */ #define SPI_WRITE 0x0A #define SPI_READ 0x0B #define READY_NOW 0x02 #define SANITY_CHECK 0x02 /* Offsets */ #define STATUS_HEADER_READY 0 #define STATUS_HEADER_TOREAD 3 #define PACKET_TYPE 0 #define EVT_BLUE_INITIALIZED 0x01 /* Needs to be aligned with the SPI master buffer size */ #define SPI_MAX_MSG_LEN 255 static uint8_t rxmsg[SPI_MAX_MSG_LEN]; static struct spi_buf rx; const static struct spi_buf_set rx_bufs = { .buffers = &rx, .count = 1, }; static uint8_t txmsg[SPI_MAX_MSG_LEN]; static struct spi_buf tx; const static struct spi_buf_set tx_bufs = { .buffers = &tx, .count = 1, }; /* HCI buffer pools */ #define CMD_BUF_SIZE BT_BUF_RX_SIZE /* * This finds an arbitrary node with compatible * "zephyr,bt-hci-spi-slave". There should just be one in the * devicetree. * * If for some reason you have more than one of these in your * devicetree, replace this macro definition to pick one, e.g. using * DT_NODELABEL(). */ #define HCI_SPI_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_bt_hci_spi_slave) /* * This is the SPI bus controller device used to exchange data with * the SPI-based BT controller. */ static const struct device *const spi_hci_dev = DEVICE_DT_GET(DT_BUS(HCI_SPI_NODE)); static struct spi_config spi_cfg = { .operation = SPI_WORD_SET(8) | SPI_OP_MODE_SLAVE, }; /* * The GPIO used to send interrupts to the host, * configured in the 'irq-gpios' property in HCI_SPI_NODE. */ static const struct gpio_dt_spec irq = GPIO_DT_SPEC_GET(HCI_SPI_NODE, irq_gpios); static K_THREAD_STACK_DEFINE(bt_tx_thread_stack, CONFIG_BT_HCI_TX_STACK_SIZE); static struct k_thread bt_tx_thread_data; static K_SEM_DEFINE(sem_spi_rx, 0, 1); static K_SEM_DEFINE(sem_spi_tx, 0, 1); static inline int spi_send(struct net_buf *buf) { uint8_t header_master[5] = { 0 }; uint8_t header_slave[5] = { READY_NOW, SANITY_CHECK, 0x00, 0x00, 0x00 }; int ret; LOG_DBG("buf %p type %u len %u", buf, bt_buf_get_type(buf), buf->len); switch (bt_buf_get_type(buf)) { case BT_BUF_ACL_IN: net_buf_push_u8(buf, HCI_ACL); break; case BT_BUF_EVT: net_buf_push_u8(buf, HCI_EVT); break; default: LOG_ERR("Unknown type %u", bt_buf_get_type(buf)); net_buf_unref(buf); return -EINVAL; } if (buf->len > SPI_MAX_MSG_LEN) { LOG_ERR("TX message too long"); net_buf_unref(buf); return -EINVAL; } header_slave[STATUS_HEADER_TOREAD] = buf->len; gpio_pin_set(irq.port, irq.pin, 1); /* Coordinate transfer lock with the spi rx thread */ k_sem_take(&sem_spi_tx, K_FOREVER); tx.buf = header_slave; tx.len = 5; rx.buf = header_master; rx.len = 5; do { ret = spi_transceive(spi_hci_dev, &spi_cfg, &tx_bufs, &rx_bufs); if (ret < 0) { LOG_ERR("SPI transceive error: %d", ret); } } while (header_master[STATUS_HEADER_READY] != SPI_READ); tx.buf = buf->data; tx.len = buf->len; ret = spi_write(spi_hci_dev, &spi_cfg, &tx_bufs); if (ret < 0) { LOG_ERR("SPI transceive error: %d", ret); } net_buf_unref(buf); gpio_pin_set(irq.port, irq.pin, 0); k_sem_give(&sem_spi_rx); return 0; } static void bt_tx_thread(void *p1, void *p2, void *p3) { uint8_t header_master[5]; uint8_t header_slave[5] = { READY_NOW, SANITY_CHECK, 0x00, 0x00, 0x00 }; struct net_buf *buf = NULL; union { struct bt_hci_cmd_hdr *cmd_hdr; struct bt_hci_acl_hdr *acl_hdr; } hci_hdr; hci_hdr.cmd_hdr = (struct bt_hci_cmd_hdr *)&rxmsg[1]; int ret; ARG_UNUSED(p1); ARG_UNUSED(p2); ARG_UNUSED(p3); (void)memset(txmsg, 0xFF, SPI_MAX_MSG_LEN); while (1) { tx.buf = header_slave; tx.len = 5; rx.buf = header_master; rx.len = 5; do { ret = spi_transceive(spi_hci_dev, &spi_cfg, &tx_bufs, &rx_bufs); if (ret < 0) { LOG_ERR("SPI transceive error: %d", ret); } } while ((header_master[STATUS_HEADER_READY] != SPI_READ) && (header_master[STATUS_HEADER_READY] != SPI_WRITE)); if (header_master[STATUS_HEADER_READY] == SPI_READ) { /* Unblock the spi tx thread and wait for it */ k_sem_give(&sem_spi_tx); k_sem_take(&sem_spi_rx, K_FOREVER); continue; } tx.buf = txmsg; tx.len = SPI_MAX_MSG_LEN; rx.buf = rxmsg; rx.len = SPI_MAX_MSG_LEN; /* Receiving data from the SPI Host */ ret = spi_transceive(spi_hci_dev, &spi_cfg, &tx_bufs, &rx_bufs); if (ret < 0) { LOG_ERR("SPI transceive error: %d", ret); continue; } switch (rxmsg[PACKET_TYPE]) { case HCI_CMD: buf = bt_buf_get_tx(BT_BUF_CMD, K_NO_WAIT, hci_hdr.cmd_hdr, sizeof(*hci_hdr.cmd_hdr)); if (buf) { net_buf_add_mem(buf, &rxmsg[4], hci_hdr.cmd_hdr->param_len); } else { LOG_ERR("No available command buffers!"); continue; } break; case HCI_ACL: buf = bt_buf_get_tx(BT_BUF_ACL_OUT, K_NO_WAIT, hci_hdr.acl_hdr, sizeof(*hci_hdr.acl_hdr)); if (buf) { net_buf_add_mem(buf, &rxmsg[5], sys_le16_to_cpu(hci_hdr.acl_hdr->len)); } else { LOG_ERR("No available ACL buffers!"); continue; } break; default: LOG_ERR("Unknown BT HCI buf type"); continue; } LOG_DBG("buf %p type %u len %u", buf, bt_buf_get_type(buf), buf->len); ret = bt_send(buf); if (ret) { LOG_ERR("Unable to send (ret %d)", ret); net_buf_unref(buf); } /* Make sure other threads get a chance to run */ k_yield(); } } static int hci_spi_init(void) { LOG_DBG(""); if (!device_is_ready(spi_hci_dev)) { LOG_ERR("SPI bus %s is not ready", spi_hci_dev->name); return -EINVAL; } if (!gpio_is_ready_dt(&irq)) { LOG_ERR("IRQ GPIO port %s is not ready", irq.port->name); return -EINVAL; } gpio_pin_configure_dt(&irq, GPIO_OUTPUT_INACTIVE); return 0; } SYS_INIT(hci_spi_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); int main(void) { static K_FIFO_DEFINE(rx_queue); struct bt_hci_evt_hdr *evt_hdr; struct net_buf *buf; k_tid_t tx_id; int err; LOG_DBG("Start"); err = bt_enable_raw(&rx_queue); if (err) { LOG_ERR("bt_enable_raw: %d; aborting", err); return 0; } /* Spawn the TX thread, which feeds cmds and data to the controller */ tx_id = k_thread_create(&bt_tx_thread_data, bt_tx_thread_stack, K_THREAD_STACK_SIZEOF(bt_tx_thread_stack), bt_tx_thread, NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); k_thread_name_set(&bt_tx_thread_data, "bt_tx_thread"); /* Send a vendor event to announce that the slave is initialized */ buf = bt_buf_get_rx(BT_BUF_EVT, K_FOREVER); evt_hdr = net_buf_add(buf, sizeof(*evt_hdr)); evt_hdr->evt = BT_HCI_EVT_VENDOR; evt_hdr->len = 2U; net_buf_add_le16(buf, EVT_BLUE_INITIALIZED); err = spi_send(buf); if (err) { LOG_ERR("can't send initialization event; aborting"); k_thread_abort(tx_id); return 0; } while (1) { buf = k_fifo_get(&rx_queue, K_FOREVER); err = spi_send(buf); if (err) { LOG_ERR("Failed to send"); } /* Ensure that the IRQ line is de-asserted for some minimum * duration between buffers, so that the HCI controller has * time to observe the edge. */ k_sleep(K_TICKS(1)); } return 0; }