summaryrefslogtreecommitdiffstats
path: root/bt-vendor/gta04_bt_vendor.c
diff options
context:
space:
mode:
Diffstat (limited to 'bt-vendor/gta04_bt_vendor.c')
-rw-r--r--bt-vendor/gta04_bt_vendor.c429
1 files changed, 429 insertions, 0 deletions
diff --git a/bt-vendor/gta04_bt_vendor.c b/bt-vendor/gta04_bt_vendor.c
new file mode 100644
index 0000000..8b49bb5
--- /dev/null
+++ b/bt-vendor/gta04_bt_vendor.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2014 Paul Kocialkowski <contact@paulk.fr>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <termios.h>
+
+#define LOG_TAG "gta04_bt_vendor"
+#include <cutils/log.h>
+
+#include <bt_vendor_lib.h>
+#include <bt_hci_bdroid.h>
+
+#include "gta04_bt_vendor.h"
+
+/*
+ * Globals
+ */
+
+const char serial_path[] = "/dev/ttyO0";
+const speed_t serial_init_speed = B3000000;
+const speed_t serial_work_speed = B3000000;
+
+struct gta04_bt_vendor *gta04_bt_vendor = NULL;
+
+/*
+ * Serial
+ */
+
+int gta04_bt_vendor_serial_open(void)
+{
+ struct termios termios;
+ int serial_fd = -1;
+ int rc;
+
+ if (gta04_bt_vendor == NULL)
+ return -1;
+
+ serial_fd = open(serial_path, O_RDWR | O_NONBLOCK);
+ if (serial_fd < 0) {
+ ALOGE("%s: Opening serial failed", __func__);
+ goto error;
+ }
+
+ tcflush(serial_fd, TCIOFLUSH);
+
+ rc = tcgetattr(serial_fd, &termios);
+ if (rc < 0) {
+ ALOGE("%s: Getting serial attributes failed", __func__);
+ goto error;
+ }
+
+ cfmakeraw(&termios);
+
+ cfsetispeed(&termios, serial_init_speed);
+ cfsetospeed(&termios, serial_init_speed);
+
+ rc = tcsetattr(serial_fd, TCSANOW, &termios);
+ if (rc < 0) {
+ ALOGE("%s: Setting serial attributes failed", __func__);
+ goto error;
+ }
+
+ tcflush(serial_fd, TCIOFLUSH);
+
+ gta04_bt_vendor->serial_fd = serial_fd;
+
+ rc = 1;
+ goto complete;
+
+error:
+ if (serial_fd >= 0)
+ close(serial_fd);
+
+ gta04_bt_vendor->serial_fd = -1;
+
+ rc = -1;
+
+complete:
+ return rc;
+}
+
+int gta04_bt_vendor_serial_close(void)
+{
+ if (gta04_bt_vendor == NULL)
+ return -1;
+
+ if (gta04_bt_vendor->serial_fd < 0)
+ return 0;
+
+ close(gta04_bt_vendor->serial_fd);
+ gta04_bt_vendor->serial_fd = -1;
+
+ return 0;
+}
+
+int gta04_bt_vendor_serial_read(void *buffer, size_t length)
+{
+ if (buffer == NULL || length == 0)
+ return -EINVAL;
+
+ if (gta04_bt_vendor == NULL || gta04_bt_vendor->serial_fd < 0)
+ return -1;
+
+ return read(gta04_bt_vendor->serial_fd, buffer, length);
+}
+
+int gta04_bt_vendor_serial_write(void *buffer, size_t length)
+{
+ unsigned int count;
+ unsigned char *p;
+ int rc;
+
+ if (buffer == NULL || length == 0)
+ return -EINVAL;
+
+ if (gta04_bt_vendor == NULL || gta04_bt_vendor->serial_fd < 0)
+ return -1;
+
+ p = (unsigned char *) buffer;
+ count = 0;
+
+ while (count < length) {
+ rc = write(gta04_bt_vendor->serial_fd, p, length - count);
+ if (rc <= 0)
+ return -1;
+
+ p += rc;
+ count += rc;
+ }
+
+ return count;
+}
+
+int gta04_bt_vendor_serial_work_speed(void)
+{
+ struct termios termios;
+ int rc;
+
+ if (gta04_bt_vendor == NULL || gta04_bt_vendor->serial_fd < 0)
+ return -1;
+
+ tcflush(gta04_bt_vendor->serial_fd, TCIOFLUSH);
+
+ rc = tcgetattr(gta04_bt_vendor->serial_fd, &termios);
+ if (rc < 0) {
+ ALOGE("%s: Getting serial attributes failed", __func__);
+ return -1;
+ }
+
+ cfsetispeed(&termios, serial_work_speed);
+ cfsetospeed(&termios, serial_work_speed);
+
+ rc = tcsetattr(gta04_bt_vendor->serial_fd, TCSANOW, &termios);
+ if (rc < 0) {
+ ALOGE("%s: Setting serial attributes failed", __func__);
+ return -1;
+ }
+
+ tcflush(gta04_bt_vendor->serial_fd, TCIOFLUSH);
+
+ return 0;
+}
+
+/*
+ * Bluetooth Interface
+ */
+
+int gta04_bt_vendor_init(const bt_vendor_callbacks_t *callbacks,
+ unsigned char *local_bdaddr)
+{
+ int rc;
+
+ ALOGD("%s(%p, %p)", __func__, callbacks, local_bdaddr);
+
+ if (callbacks == NULL || local_bdaddr == NULL)
+ return -EINVAL;
+
+ if (gta04_bt_vendor != NULL)
+ free(gta04_bt_vendor);
+
+ gta04_bt_vendor = (struct gta04_bt_vendor *) calloc(1, sizeof(struct gta04_bt_vendor));
+ gta04_bt_vendor->callbacks = (bt_vendor_callbacks_t *) callbacks;
+
+ rc = 0;
+ goto complete;
+
+error:
+ if (gta04_bt_vendor != NULL) {
+ free(gta04_bt_vendor);
+ gta04_bt_vendor = NULL;
+ }
+
+ rc = -1;
+
+complete:
+ return rc;
+}
+
+void gta04_bt_vendor_cleanup(void)
+{
+ if (gta04_bt_vendor == NULL)
+ return;
+
+ free(gta04_bt_vendor);
+ gta04_bt_vendor = NULL;
+}
+
+void gta04_bt_vendor_op_fw_cfg_callback(void *response)
+{
+ void *buffer, *data;
+ size_t length, size;
+ int rc;
+
+ ALOGD("%s(%p)", __func__, response);
+
+ if (response == NULL)
+ return;
+
+ if (gta04_bt_vendor == NULL || gta04_bt_vendor->callbacks == NULL || gta04_bt_vendor->callbacks->fwcfg_cb == NULL)
+ return;
+
+ // Perform warm reset so that the work UART speed can take effect
+
+ length = BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE + 1 + HCI_BCCMD_WORDS_COUNT * sizeof(unsigned short);
+ buffer = calloc(1, length);
+
+ gta04_bt_vendor_hci_cmd_prepare(buffer, length);
+
+ data = (void *) ((unsigned char *) buffer + BT_HC_HDR_SIZE);
+ size = length - BT_HC_HDR_SIZE;
+
+ gta04_bt_vendor_hci_preamble(data, size, HCI_CMD_VENDOR);
+
+ data = (void *) ((unsigned char *) buffer + BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE);
+ size = length - BT_HC_HDR_SIZE - HCI_CMD_PREAMBLE_SIZE;
+
+ gta04_bt_vendor_hci_bccmd(data, size, HCI_BCCMD_PDU_SETREQ, HCI_BCCMD_VARID_WARM_RESET);
+
+ rc = gta04_bt_vendor_hci_h4_write(buffer, length);
+ if (rc < 0) {
+ ALOGE("%s: Writing HCI failed", __func__);
+ return;
+ }
+
+ free(buffer);
+
+ sleep(1);
+
+ rc = gta04_bt_vendor_serial_work_speed();
+ if (rc < 0) {
+ ALOGD("%s: Switching to serial work speed failed", __func__);
+ return;
+ }
+
+ ALOGD("%s: Switched to serial work speed", __func__);
+
+ gta04_bt_vendor->callbacks->fwcfg_cb(BT_VND_OP_RESULT_SUCCESS);
+}
+
+int gta04_bt_vendor_op_fw_cfg(void *param)
+{
+ void *buffer, *data;
+ size_t length, size;
+ unsigned short speed;
+ unsigned char *p;
+
+ ALOGD("%s(%p)", __func__, param);
+
+ if (gta04_bt_vendor == NULL || gta04_bt_vendor->callbacks == NULL || gta04_bt_vendor->callbacks->fwcfg_cb == NULL || gta04_bt_vendor->callbacks->alloc == NULL || gta04_bt_vendor->callbacks->xmit_cb == NULL)
+ return -1;
+
+ if (serial_init_speed == serial_work_speed) {
+ gta04_bt_vendor->callbacks->fwcfg_cb(BT_VND_OP_RESULT_SUCCESS);
+ return 0;
+ }
+
+ // Set work UART speed to PSRAM
+
+ speed = gta04_bt_vendor_hci_bccmd_speed(serial_work_speed);
+
+ length = BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE + 1 + HCI_BCCMD_WORDS_COUNT * sizeof(unsigned short);
+ buffer = gta04_bt_vendor->callbacks->alloc(length);
+
+ memset(buffer, 0, length);
+
+ gta04_bt_vendor_hci_cmd_prepare(buffer, length);
+
+ data = (void *) ((unsigned char *) buffer + BT_HC_HDR_SIZE);
+ size = length - BT_HC_HDR_SIZE;
+
+ gta04_bt_vendor_hci_preamble(data, size, HCI_CMD_VENDOR);
+
+ data = (void *) ((unsigned char *) buffer + BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE);
+ size = length - BT_HC_HDR_SIZE - HCI_CMD_PREAMBLE_SIZE;
+
+ gta04_bt_vendor_hci_bccmd(data, size, HCI_BCCMD_PDU_SETREQ, HCI_BCCMD_VARID_PS);
+
+ p = (unsigned char *) data + 1 + 5 * sizeof(unsigned short);
+
+ p[0] = HCI_BCCMD_PSKEY_UART_BAUDRATE & 0xff;
+ p[1] = HCI_BCCMD_PSKEY_UART_BAUDRATE >> 8;
+ p[2] = 0x01;
+ p[3] = 0x00;
+ p[4] = HCI_BCCMD_STORES_PSRAM & 0xff;
+ p[5] = HCI_BCCMD_STORES_PSRAM >> 8;
+ p[6] = speed & 0xff;
+ p[7] = speed >> 8;
+
+ gta04_bt_vendor->callbacks->xmit_cb(HCI_CMD_VENDOR, buffer, gta04_bt_vendor_op_fw_cfg_callback);
+
+ return 0;
+}
+
+int gta04_bt_vendor_op_userial_open(void *param)
+{
+ void *buffer, *data;
+ size_t length, size;
+ unsigned int i;
+ int *fds;
+ int rc;
+
+ ALOGD("%s(%p)", __func__, param);
+
+ if (param == NULL)
+ return -EINVAL;
+
+ if (gta04_bt_vendor == NULL)
+ return -1;
+
+ fds = (int *) param;
+
+ rc = gta04_bt_vendor_serial_open();
+ if (rc < 0 || gta04_bt_vendor->serial_fd < 0) {
+ ALOGD("%s: Opening serial failed", __func__);
+ return -1;
+ }
+
+ length = BT_HC_HDR_SIZE + HCI_CMD_PREAMBLE_SIZE;
+ buffer = calloc(1, length);
+
+ gta04_bt_vendor_hci_cmd_prepare(buffer, length);
+
+ data = (void *) ((unsigned char *) buffer + BT_HC_HDR_SIZE);
+ size = length - BT_HC_HDR_SIZE;
+
+ gta04_bt_vendor_hci_preamble(data, size, HCI_CMD_RESET);
+
+ rc = gta04_bt_vendor_hci_h4_write(buffer, length);
+ if (rc < 0) {
+ ALOGE("%s: Writing HCI failed", __func__);
+ return -1;
+ }
+
+ free(buffer);
+
+ sleep(1);
+
+ rc = gta04_bt_vendor_hci_h4_read_event(NULL, 0, HCI_EVENT_VENDOR);
+ if (rc < 0) {
+ ALOGE("%s: Reading HCI failed", __func__);
+ return -1;
+ }
+
+ for (i = 0; i < CH_MAX; i++)
+ fds[i] = gta04_bt_vendor->serial_fd;
+
+ return 1;
+}
+
+int gta04_bt_vendor_op_userial_close(void *param)
+{
+ ALOGD("%s(%p)", __func__, param);
+
+ return gta04_bt_vendor_serial_close();
+}
+
+int gta04_bt_vendor_op(bt_vendor_opcode_t opcode, void *param)
+{
+ ALOGD("%s(%d, %p)", __func__, opcode, param);
+
+ switch (opcode) {
+ case BT_VND_OP_POWER_CTRL:
+ return 0;
+ case BT_VND_OP_FW_CFG:
+ return gta04_bt_vendor_op_fw_cfg(param);
+ case BT_VND_OP_SCO_CFG:
+ gta04_bt_vendor->callbacks->scocfg_cb(BT_VND_OP_RESULT_SUCCESS);
+ return 0;
+ case BT_VND_OP_USERIAL_OPEN:
+ return gta04_bt_vendor_op_userial_open(param);
+ case BT_VND_OP_USERIAL_CLOSE:
+ return gta04_bt_vendor_op_userial_close(param);
+ case BT_VND_OP_GET_LPM_IDLE_TIMEOUT:
+ *((int *) param) = 0;
+ return 0;
+ case BT_VND_OP_LPM_SET_MODE:
+ gta04_bt_vendor->callbacks->lpm_cb(BT_VND_OP_RESULT_SUCCESS);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Interface
+ */
+
+const bt_vendor_interface_t BLUETOOTH_VENDOR_LIB_INTERFACE = {
+ .size = sizeof(bt_vendor_interface_t),
+ .init = gta04_bt_vendor_init,
+ .cleanup = gta04_bt_vendor_cleanup,
+ .op = gta04_bt_vendor_op,
+};