bqtool: add first version of bqtool.c
authorAneesh V <aneesh@ti.com>
Wed, 12 Feb 2014 18:05:25 +0000 (10:05 -0800)
committerAneesh V <aneesh@ti.com>
Wed, 12 Feb 2014 18:05:25 +0000 (10:05 -0800)
Signed-off-by: Aneesh V <aneesh@ti.com>
bqtool.c [new file with mode: 0644]

diff --git a/bqtool.c b/bqtool.c
new file mode 100644 (file)
index 0000000..84384cd
--- /dev/null
+++ b/bqtool.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2014 Texas Instruments Inc
+ *
+ * Aneesh V <aneesh@ti.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <i2c-dev.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <stdbool.h>
+
+/* #define BQPROG_DEBUG */
+
+#define CMD_MAX_DATA_SIZE      96
+#define MAX_LINE_LEN           ((CMD_MAX_DATA_SIZE + 4) * 3)
+#define RETRY_LIMIT            3
+#define CMD_RETRY_DELAY                100 /* in ms */
+#define DEFAULT_POLL_INTVL     360
+#define I2C_BUS                        "2"
+#define I2C_DEV                        "/dev/i2c-"I2C_BUS
+#define SYSFS_POLL_INTVL "/sys/module/bq27x00_battery/parameters/poll_interval"
+
+#ifdef __GNUC__
+#define __PACKED       __attribute__((packed))
+#else
+#error "Make sure structure cmd_t is packed"
+#endif
+
+typedef enum {
+       CMD_INVALID = 0,
+       CMD_R,  /* Read */
+       CMD_W,  /* Write */
+       CMD_C,  /* Compare */
+       CMD_X,  /* Delay */
+} cmd_type_t;
+
+/*
+ * DO NOT change the order of fields - particularly reg
+ * should be immediately followed by data
+ */
+typedef struct {
+       cmd_type_t cmd_type;
+       uint8_t addr;
+       uint8_t reg;
+       union {
+               uint8_t bytes[CMD_MAX_DATA_SIZE + 1];
+               uint16_t delay;
+       } data;
+       uint8_t data_len;
+       uint32_t line_num;
+} __PACKED cmd_t;
+
+static uint32_t line_num;
+
+/*
+ * Print usage of all commands when cmd == NULL
+ * Otherwise print the usage of the respective command
+ * Currently we have only one commnd
+ */
+static void usage(const char *cmd)
+{
+       fprintf(stderr, "Usage: bqtool --flash <bqfs-file|dffs-file>\n");
+}
+
+static void print_delay(cmd_t *cmd)
+{
+       fprintf(stdout, "delay: %d", cmd->data.delay);
+}
+
+static void print_buf(FILE *fd, uint8_t *buf, uint8_t len)
+{
+       uint8_t i;
+
+       for (i = 0; i < len; i++)
+               fprintf(fd, " %02x", buf[i]);
+}
+
+static void print_data(cmd_t *cmd)
+{
+       int i;
+
+       fprintf(stdout, "data: ");
+       print_buf(stdout, cmd->data.bytes, cmd->data_len);
+}
+static void print_addr_reg(cmd_t *cmd)
+{
+       fprintf(stdout, "addr: %02x ", cmd->addr);
+       fprintf(stdout, "reg: %02x ", cmd->reg);
+}
+
+#ifdef BQPROG_DEBUG
+static void print_cmd(cmd_t *cmd)
+{
+       switch (cmd->cmd_type) {
+       case CMD_R:
+               fprintf(stdout, "R: ");
+               print_addr_reg(cmd);
+               break;
+       case CMD_W:
+               fprintf(stdout, "W: ");
+               print_addr_reg(cmd);
+               print_data(cmd);
+               break;
+       case CMD_C:
+               fprintf(stdout, "C: ");
+               print_addr_reg(cmd);
+               print_data(cmd);
+               break;
+       case CMD_X:
+               fprintf(stdout, "X: ");
+               print_delay(cmd);
+               break;
+       default:
+               fprintf(stdout, "Unknown: ");
+               break;
+       }
+       fprintf(stdout, "\n");
+}
+#else
+static void print_cmd(cmd_t *cmd)
+{
+}
+#endif
+
+static int read_bq_poll_intvl(void)
+{
+       int poll_file = -1;
+       int poll_intvl = -1;
+       char buf[20];
+
+       poll_file = open(SYSFS_POLL_INTVL, O_RDONLY);
+
+       if ((poll_file >= 0) && read(poll_file, buf, 20))
+               sscanf(buf, "%d", &poll_intvl);
+       else
+               fprintf(stderr, "Failed to read %s\n", SYSFS_POLL_INTVL);
+
+       if (poll_file >= 0)
+               close(poll_file);
+
+       return poll_intvl;
+}
+
+static bool write_bq_poll_intvl(int poll_intvl)
+{
+       int poll_file = -1;
+       char buf[20];
+
+       poll_file = open(SYSFS_POLL_INTVL, O_RDWR);
+
+       if (poll_file >= 0) {
+               sprintf(buf, "%d", poll_intvl);
+               write(poll_file, buf, 20);
+               close(poll_file);
+       }
+
+       if (poll_intvl == read_bq_poll_intvl())
+               return true;
+       else
+               return false;
+}
+
+static bool i2c_rw(int i2c_file, cmd_t *cmd, int write)
+{
+       int ret;
+       struct i2c_rdwr_ioctl_data i2c_data;
+       char *op;
+       /* msg[0] for write command and msg[1] for read command */
+       struct i2c_msg msgs[2];
+
+       /* Linux expects 7 bit address */
+       msgs[0].addr = cmd->addr >> 1;
+       /* reg is data too as far as I2C xfr is concerned */
+       msgs[0].buf = (char *)&cmd->reg;
+       msgs[0].flags = 0;
+
+       if (write) {
+               msgs[0].len = cmd->data_len + 1;
+               i2c_data.nmsgs = 1;
+               op = "write";
+       } else {
+               msgs[0].len = 1;
+
+               /* read command */
+               msgs[1].addr = cmd->addr >> 1;
+               msgs[1].buf = (char *)cmd->data.bytes;
+               msgs[1].flags = I2C_M_RD;
+               msgs[1].len = cmd->data_len;
+
+               i2c_data.nmsgs = 2;
+               op = "read";
+       }
+
+       i2c_data.msgs = msgs;
+       ret = ioctl(i2c_file, I2C_RDWR, &i2c_data);
+       if (ret < 0) {
+               fprintf(stderr, "I2C %s failed at line %d error = %d\n",
+                       op, cmd->line_num, ret);
+               return false;
+       }
+
+       return true;
+}
+
+static bool do_exec_cmd(int i2c_file, cmd_t *cmd)
+{
+       uint8_t tmp_buf[CMD_MAX_DATA_SIZE];
+
+       switch (cmd->cmd_type) {
+       case CMD_R:
+               return i2c_rw(i2c_file, cmd, 0);
+
+       case CMD_W:
+               return i2c_rw(i2c_file, cmd, 1);
+
+       case CMD_C:
+               memcpy(tmp_buf, cmd->data.bytes, cmd->data_len);
+               if (!i2c_rw(i2c_file, cmd, 0))
+                       return false;
+               if (memcmp(tmp_buf, cmd->data.bytes, cmd->data_len)) {
+                       fprintf(stderr, "\nCommand C failed at line %d:\n",
+                               cmd->line_num);
+                       fprintf(stderr, "Expected data:");
+                       print_buf(stderr, tmp_buf, cmd->data_len);
+                       fprintf(stderr, "\nReceived data:");
+                       print_buf(stderr, cmd->data.bytes, cmd->data_len);
+                       fprintf(stderr, "\n");
+                       return false;
+               }
+               return true;
+
+       case CMD_X:
+               usleep(cmd->data.delay * 1000);
+               return true;
+
+       default:
+               fprintf(stderr, "Unsupported command at line %d\n",
+                       cmd->line_num);
+               return false;
+       }
+}
+
+static bool execute_cmd(int i2c_file, cmd_t *cmd)
+{
+       int i = 1;
+       bool ret;
+
+       ret = do_exec_cmd(i2c_file, cmd);
+
+       while (!ret && i < RETRY_LIMIT) {
+               usleep(CMD_RETRY_DELAY * 1000);
+               ret = do_exec_cmd(i2c_file, cmd);
+               i++;
+       }
+
+       if (!ret) {
+               fprintf(stderr, "Command execution failed at line %d"
+                       " addr - 0x%02x reg - 0x%02x, tried %d times\n",
+                       cmd->line_num, cmd->addr, cmd->reg, RETRY_LIMIT);
+       }
+
+       return ret;
+}
+
+static bool get_delay(uint16_t *delay)
+{
+       char *tok;
+       uint32_t temp;
+
+       tok = strtok(NULL, " ");
+       if (!tok)
+               return false; /*end of line or file */
+
+       if (1 != sscanf(tok, "%u", &temp)) {
+               fprintf(stderr, "Syntax error while parsing delay at line %d\n",
+                       line_num);
+               return false; /* syntax error */
+       }
+
+       if (temp > UINT16_MAX) {
+               fprintf(stderr, "Command X delay too high at line %d - %dms\n",
+                       line_num, temp);
+               return false;
+       }
+
+       *delay = (uint16_t)temp;
+       return true;
+
+}
+
+/*
+ * Returns:
+ *      0: success
+ *      1: EOF
+ *     -1: Parse error
+ */
+static int get_byte(uint8_t *byte)
+{
+       char *tok;
+       unsigned int temp;
+
+       tok = strtok(NULL, " \t\r\n");
+       if (!tok)
+               return 1; /*end of line or file */
+
+       if ((strlen(tok) != 2) || (sscanf(tok, "%2x", &temp) != 1)) {
+                       fprintf(stderr, "Syntax error at line %d, token - %s,"
+                               " temp - %x\n", line_num, tok, temp);
+                       return -1; /* syntax error */
+       }
+
+       *byte = (uint8_t)temp;
+
+       return 0;       /* success */
+}
+
+static bool get_addr_n_reg(cmd_t *cmd)
+{
+       if (get_byte(&cmd->addr))
+               return false;
+
+       if (get_byte(&cmd->reg))
+               return false;
+
+       return true;
+}
+
+static bool get_data_bytes(cmd_t *cmd)
+{
+       int ret, i = 0;
+       cmd->data_len = 0;
+
+       do {
+               ret = get_byte(&cmd->data.bytes[i++]);
+               if (ret == -1)
+                       return false;
+       } while ((ret == 0) && (i <= CMD_MAX_DATA_SIZE));
+
+       if (ret == 0) {
+               fprintf(stderr, "More than allowed number of data bytes at"
+                       " line %d, data_len %d, i %d\n", cmd->line_num,
+                       cmd->data_len, i);
+               return false;
+       }
+
+       cmd->data_len = i - 1;
+
+       return true;
+}
+
+static bool get_line(FILE *bqfs_file, char **buffer)
+{
+       int c;
+       int i = 0;
+       bool ret = true;
+       char *buf;
+
+       buf = malloc(MAX_LINE_LEN);
+       line_num++;
+
+       while (1) {
+               c = fgetc(bqfs_file);
+
+               if (feof(bqfs_file)) {
+#ifdef BQPROG_DEBUG
+                       fprintf(stdout, "EOF\n");
+#endif
+                       break;
+               } else if (ferror(bqfs_file)) {
+                       fprintf(stderr, "File read error\n");
+                       ret = false;
+                       break;
+               }
+
+               if (((c == '\r') || (c == '\n') || (c == '\t')
+                       || (c == ' ')) && (i == 0)) {
+                       /*
+                        * Skip leading white space, if any, at the beginning
+                        * of the line because this interferes with strtok
+                        */
+                       fprintf(stderr, "Leading whitespace at line %d\n",
+                               line_num);
+                       if (c == '\n')
+                               line_num++;
+                       continue;       /* blank line, let's continue */
+               } else if (c == '\n') {
+                       /* We've reached end of line */
+                       break;
+               }
+
+               buf[i++] = c;
+
+               if (i == MAX_LINE_LEN) {
+                       /*
+                        * Re-allocate in case the line is longer than
+                        * expected
+                        */
+                       buf = realloc(buf, MAX_LINE_LEN * 2);
+                       fprintf(stderr, "Line %d longer than expected,"
+                               " reallocating..\n", line_num);
+               } else if (i == MAX_LINE_LEN * 2) {
+                       /*
+                        * The line is already twice the expected maximum length
+                        * - maybe the bqfs/dffs needs to be fixed
+                        */
+                       fprintf(stderr, "Line %d too long, abort parsing..\n",
+                               line_num);
+                       ret = false;
+                       break;
+               }
+       }
+
+       *buffer = buf;
+       buf[i] = '\0';
+
+       if (i < 1)
+               ret = false;
+
+       return ret;
+}
+
+static bool get_cmd(FILE *bqfs_file, cmd_t *cmd)
+{
+       char *res;
+       char *tok;
+       char *buf = NULL;
+       int ret;
+
+       if (!get_line(bqfs_file, &buf))
+               goto error;
+
+       cmd->line_num = line_num;
+       tok = strtok(buf, ":");
+       if (!tok || (strlen(tok) != 1)) {
+               fprintf(stderr, "Error parsing command at line %d tok=%s"
+                       " buf=%s", line_num, tok, buf);
+               goto error;
+       }
+
+       switch (tok[0]) {
+       case 'R':
+       case 'r':
+               cmd->cmd_type = CMD_R;
+               if (!get_addr_n_reg(cmd))
+                       goto error;
+               break;
+       case 'W':
+       case 'w':
+               cmd->cmd_type = CMD_W;
+               if (!get_addr_n_reg(cmd))
+                       goto error;
+               if (!get_data_bytes(cmd))
+                       goto error;
+               break;
+       case 'C':
+       case 'c':
+               cmd->cmd_type = CMD_C;
+               if (!get_addr_n_reg(cmd))
+                       goto error;
+               if (!get_data_bytes(cmd))
+                       goto error;
+               break;
+       case 'X':
+       case 'x':
+               cmd->cmd_type = CMD_X;
+               if (!get_delay(&cmd->data.delay))
+                       goto error;
+               break;
+       default:
+               fprintf(stderr, "No command or unexpected command at"
+                       " line %d tok=\"%s\" buf=\"%s\"",
+                       line_num, tok, buf);
+               goto error;
+       }
+
+       print_cmd(cmd);
+       free(buf);
+       return true;
+
+error:
+       cmd->cmd_type = CMD_INVALID;
+       free(buf);
+       return false;
+}
+
+int bqfs_flash(char *fname)
+{
+       int poll_intvl = -1;
+       int i2c_file = -1;
+       int poll_file = -1;
+       FILE *bqfs_file = NULL;
+       char *line = NULL;
+       cmd_t *cmd = NULL;
+       int ret = 0;
+       char buf[20];
+
+       bqfs_file = fopen(fname, "r");
+       if (!bqfs_file) {
+               usage("--flash");
+               ret = -1;
+               goto end;
+       }
+
+       poll_intvl = read_bq_poll_intvl();
+       /* Turn off polling */
+       if (!write_bq_poll_intvl(0)) {
+               fprintf(stderr, "Failed to stop driver polling\n");
+               ret = -1;
+               goto end;
+       }
+
+       i2c_file = open(I2C_DEV, O_RDWR);
+       if (i2c_file < 0) {
+               fprintf(stderr, "Failed to open I2C device %s\n", I2C_DEV);
+               ret = -1;
+               goto end;
+       }
+
+       cmd = malloc(sizeof(cmd_t));
+
+       while (get_cmd(bqfs_file, cmd) && execute_cmd(i2c_file, cmd))
+               fputc('.', stdout);
+
+       if (feof(bqfs_file)) {
+               fprintf(stdout, "\n%s programmed successfully!\n", fname);
+               ret = 0;
+       } else {
+               fprintf(stdout, "\nprogramming %s failed!!\n", fname);
+               ret = -1;
+       }
+
+end:
+       if (poll_intvl >= 0) {
+               if (!write_bq_poll_intvl(poll_intvl))
+                       fprintf(stderr, "Failed to restore driver polling\n");
+       }
+
+       if (poll_file >= 0)
+               close(poll_file);
+
+       if (i2c_file >= 0)
+               close(i2c_file);
+
+       if (bqfs_file)
+               fclose(bqfs_file);
+
+       if (cmd)
+               free(cmd);
+
+       return ret;
+}
+
+int main(int argc, char **argv)
+{
+       if ((argc >= 3) && (strncmp(argv[1], "--flash", 7) == 0)) {
+               return bqfs_flash(argv[2]);
+       } else {
+               usage(argv[0]);
+               return -1;
+       }
+}