text: /***************************************************************************
   *             __________               __   ___.
   *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
   *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
   *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
   *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
   *                     \/            \/     \/    \/            \/
   * $Id$
   *
   * Copyright (C) 2016 Amaury Pouly
   *
   * 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 2
   * of the License, or (at your option) any later version.
   *
   * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
   * KIND, either express or implied.
   *
   ****************************************************************************/
  #include "nwz_lib.h"
  #include "nwz_db.h"
  
  int nwz_run(const char *file, const char *args[], bool wait)
  {
      pid_t child_pid = fork();
      if(child_pid != 0)
      {
          if(wait)
          {
              int status;
              waitpid(child_pid, &status, 0);
              return status;
          }
          else
              return 0;
      }
      else
      {
          execvp(file, (char * const *)args);
          _exit(1);
      }
  }
  
  char *nwz_run_pipe(const char *file, const char *args[], int *status)
  {
      int pipe_fds[2];
      pipe(pipe_fds);
      pid_t child_pid = fork();
      if(child_pid == 0)
      {
          dup2(pipe_fds[1], 1); /* redirect stdout */
          dup2(pipe_fds[1], 2); /* redirect stderr */
          close(pipe_fds[0]); /* close reading */
          close(pipe_fds[1]); /* close writing */
          execvp(file, (char * const *)args);
          _exit(1);
      }
      else
      {
          close(pipe_fds[1]); /* close writing */
          char buffer[1024];
          char *output = malloc(1);
          ssize_t count;
          size_t size = 0;
          while((count = read(pipe_fds[0], buffer, sizeof(buffer))) > 0)
          {
              output = realloc(output, size + count + 1);
              memcpy(output + size, buffer, count);
              size += count;
          }
          close(pipe_fds[0]);
          output[size] = 0;
          waitpid(child_pid, status, 0);
          return output;
      }
  }
  
  void nwz_lcdmsg(bool clear, int x, int y, const char *msg)
  {
      const char *path_lcdmsg = "/usr/local/bin/lcdmsg";
      const char *args[16];
      int index = 0;
      char locate[32];
      args[index++] = "lcdmsg";
      if(clear)
          args[index++] = "-c";
      args[index++] = "-f";
      args[index++] = "/usr/local/bin/font_08x12.bmp";
      args[index++] = "-l";
      sprintf(locate, "%d,%d", x, y);
      args[index++] = locate;
      args[index++] = msg;
      args[index++] = NULL;
      /* wait for lcdmsg to finish to avoid any race conditions in framebuffer
       * accesses */
      nwz_run(path_lcdmsg, args, true);
  }
  
  void nwz_lcdmsgf(bool clear, int x, int y, const char *format, ...)
  {
      char buffer[1024];
      va_list args;
      va_start(args, format);
      vsprintf(buffer, format, args);
      va_end(args);
      nwz_lcdmsg(clear, x, y, buffer);
  }
  
  #define NWZ_COLOR_RGB(col) \
      NWZ_COLOR_RED(col), NWZ_COLOR_GREEN(col), NWZ_COLOR_BLUE(col)
  
  void nwz_display_clear(nwz_color_t color)
  {
      const char *path_display = "/usr/local/bin/display";
      const char *args[16];
      int index = 0;
      char col[32];
      args[index++] = "display";
      args[index++] = "lcd";
      args[index++] = "clear";
      sprintf(col, "%d,%d,%d", NWZ_COLOR_RGB(color));
      args[index++] = col;
      args[index++] = NULL;
      /* wait for lcdmsg to finish to avoid any race conditions in framebuffer
       * accesses */
      nwz_run(path_display, args, true);
  }
  
  void nwz_display_text(int x, int y, bool big_font, nwz_color_t foreground_col,
      nwz_color_t background_col, int alpha, const char *text)
  {
      const char *path_display = "/usr/local/bin/display";
      const char *args[16];
      int index = 0;
      char fg[32],bg[32], pos[32], transp[16];
      args[index++] = "display";
      args[index++] = "lcd";
      args[index++] = "text";
      sprintf(pos, "%d,%d", x, y);
      args[index++] = pos;
      if(big_font)
          args[index++] = "/usr/local/bin/font_14x24.bmp";
      else
          args[index++] = "/usr/local/bin/font_08x12.bmp";
      sprintf(fg, "%d,%d,%d", NWZ_COLOR_RGB(foreground_col));
      args[index++] = fg;
      sprintf(bg, "%d,%d,%d", NWZ_COLOR_RGB(background_col));
      args[index++] = bg;
      sprintf(transp, "%d", alpha);
      args[index++] = transp;
      args[index++] = text;
      args[index++] = NULL;
      /* wait for lcdmsg to finish to avoid any race conditions in framebuffer
       * accesses */
      nwz_run(path_display, args, true);
  }
  
  void nwz_display_text_center(int width, int y, bool big_font, nwz_color_t fg,
      nwz_color_t bg, int alpha, const char *text)
  {
      int txt_w = NWZ_FONT_W(big_font) * strlen(text);
      nwz_display_text((width - txt_w) / 2, y, big_font, fg, bg, alpha, text);
  }
  
  void nwz_display_textf(int x, int y, bool big_font, nwz_color_t foreground_col,
      nwz_color_t background_col, int alpha, const char *fmt, ...)
  {
      char buffer[1024];
      va_list args;
      va_start(args, fmt);
      vsprintf(buffer, fmt, args);
      va_end(args);
      nwz_display_text(x, y, big_font, foreground_col, background_col, alpha, buffer);
  }
  
  void nwz_display_textf_center(int width, int y, bool big_font, nwz_color_t fg,
      nwz_color_t bg, int alpha, const char *fmt, ...)
  {
      char buffer[1024];
      va_list args;
      va_start(args, fmt);
      vsprintf(buffer, fmt, args);
      va_end(args);
      nwz_display_text_center(width, y, big_font, fg, bg, alpha, buffer);
  }
  
  void nwz_display_bitmap(int x, int y, const char *file, int left, int top,
      int width, int height, nwz_color_t key_col, int bmp_alpha)
  {
      const char *path_display = "/usr/local/bin/display";
      const char *args[16];
      int index = 0;
      char pos[32], topleft[32], dim[32], key[32], transp[16];
      args[index++] = "display";
      args[index++] = "lcd";
      args[index++] = "bitmap";
      sprintf(pos, "%d,%d", x, y);
      args[index++] = pos;
      args[index++] = file;
      sprintf(topleft, "%d,%d", left, top);
      args[index++] = topleft;
      sprintf(dim, "%d,%d", width, height);
      args[index++] = dim;
      if(key_col == NWZ_COLOR_NO_KEY)
          sprintf(key, "no");
      else
          sprintf(key, "%d,%d,%d", NWZ_COLOR_RGB(key_col));
      args[index++] = key;
      sprintf(transp, "%d", bmp_alpha);
      args[index++] = transp;
      args[index++] = NULL;
      /* wait for lcdmsg to finish to avoid any race conditions in framebuffer
       * accesses */
      nwz_run(path_display, args, true);
  }
  
  int nwz_input_open(const char *requested_name)
  {
      /* try all /dev/input/eventX, there can't a lot of them */
      for(int index = 0; index < 8; index++)
      {
          char buffer[32];
          sprintf(buffer, "/dev/input/event%d", index);
          int fd = open(buffer, O_RDWR);
          if(fd < 0)
              continue; /* try next one */
          /* query name */
          char name[256];
          if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) >= 0 &&
                  strcmp(name, requested_name) == 0)
              return fd;
          close(fd);
      }
      return -1;
  }
  
  int nwz_key_open(void)
  {
      return nwz_input_open(NWZ_KEY_NAME);
  }
  
  void nwz_key_close(int fd)
  {
      close(fd);
  }
  
  int nwz_key_get_hold_status(int fd)
  {
      unsigned long led_hold = 0;
      if(ioctl(fd, EVIOCGLED(sizeof(led_hold)), &led_hold) < 0)
          return -1;
      return led_hold;
  }
  
  int nwz_key_wait_event(int fd, long tmo_us)
  {
      return nwz_wait_fds(&fd, 1, tmo_us);
  }
  
  int nwz_key_read_event(int fd, struct input_event *evt)
  {
      int ret = read(fd, evt, sizeof(struct input_event));
      if(ret != sizeof(struct input_event))
          return -1;
      return 1;
  }
  
  int nwz_key_event_get_keycode(struct input_event *evt)
  {
      return evt->code & NWZ_KEY_MASK;
  }
  
  bool nwz_key_event_is_press(struct input_event *evt)
  {
      return evt->value == 0;
  }
  
  bool nwz_key_event_get_hold_status(struct input_event *evt)
  {
      return !!(evt->code & NWZ_KEY_HOLD_MASK);
  }
  
  static const char *nwz_keyname[NWZ_KEY_MASK + 1] =
  {
      [0 ... NWZ_KEY_MASK] = "unknown",
      [NWZ_KEY_PLAY] = "PLAY",
      [NWZ_KEY_RIGHT] = "RIGHT",
      [NWZ_KEY_LEFT] = "LEFT",
      [NWZ_KEY_UP] = "UP",
      [NWZ_KEY_DOWN] = "DOWN",
      [NWZ_KEY_ZAPPIN] = "ZAPPIN",
      [NWZ_KEY_AD0_6] = "AD0_6",
      [NWZ_KEY_AD0_7] = "AD0_7",
      [NWZ_KEY_NONE] = "NONE",
      [NWZ_KEY_VOL_DOWN] = "VOL DOWN",
      [NWZ_KEY_VOL_UP] = "VOL UP",
      [NWZ_KEY_BACK] = "BACK",
      [NWZ_KEY_OPTION] = "OPTION",
      [NWZ_KEY_BT] = "BT",
      [NWZ_KEY_AD1_5] = "AD1_5",
      [NWZ_KEY_AD1_6] = "AD1_6",
      [NWZ_KEY_AD1_7] = "AD1_7",
  };
  
  const char *nwz_key_get_name(int keycode)
  {
      if(keycode <0 || keycode > NWZ_KEY_MASK)
          return "invalid";
      else
          return nwz_keyname[keycode];
  }
  
  int nwz_fb_open(bool lcd)
  {
      return open(lcd ? NWZ_FB_LCD_DEV : NWZ_FB_TV_DEV, O_RDWR);
  }
  
  void nwz_fb_close(int fd)
  {
      close(fd);
  }
  
  int nwz_fb_get_brightness(int fd, struct nwz_fb_brightness *bl)
  {
      if(ioctl(fd, NWZ_FB_GET_BRIGHTNESS, bl) < 0)
          return -1;
      else
          return 1;
  }
  
  int nwz_fb_set_brightness(int fd, struct nwz_fb_brightness *bl)
  {
      if(ioctl(fd, NWZ_FB_SET_BRIGHTNESS, bl) < 0)
          return -1;
      else
          return 1;
  }
  
  int nwz_fb_set_page(int fd, int page)
  {
      /* set page mode to no transparency and no rotation */
      struct nwz_fb_image_info mode_info;
      mode_info.tc_enable = 0;
      mode_info.t_color = 0;
      mode_info.alpha = 0;
      mode_info.rot = 0;
      mode_info.page = page;
      mode_info.update = NWZ_FB_ONLY_2D_MODE;
      if(ioctl(fd, NWZ_FB_UPDATE, &mode_info) < 0)
          return -2;
      return 0;
  }
  
  int nwz_fb_set_standard_mode(int fd)
  {
      /* disable timer (apparently useless with LCD) */
      struct nwz_fb_update_timer update_timer;
      update_timer.timerflag = NWZ_FB_TIMER_OFF;
      update_timer.timeout = NWZ_FB_DEFAULT_TIMEOUT;
      if(ioctl(fd, NWZ_FB_UPDATE_TIMER, &update_timer) < 0)
          return -1;
      return nwz_fb_set_page(fd, 0);
  }
  
  int nwz_fb_get_resolution(int fd, int *x, int *y, int *bpp)
  {
      struct fb_var_screeninfo vinfo;
      if(ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0)
          return -1;
      if(x)
          *x = vinfo.xres;
      if(y)
          *y = vinfo.yres;
      if(bpp)
          *bpp = vinfo.bits_per_pixel;
      return 0;
  }
  
  void *nwz_fb_mmap(int fd, int offset, int size)
  {
      return mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)offset);
  }
  
  int nwz_adc_open(void)
  {
      return open(NWZ_ADC_DEV, O_RDONLY);
  }
  
  void nwz_adc_close(int fd)
  {
      close(fd);
  }
  
  static const char *nwz_adc_name[] =
  {
      [NWZ_ADC_VCCBAT] = "VCCBAT",
      [NWZ_ADC_VCCVBUS] = "VCCVBUS",
      [NWZ_ADC_ADIN3] = "ADIN3",
      [NWZ_ADC_ADIN4] = "ADIN4",
      [NWZ_ADC_ADIN5] = "ADIN5",
      [NWZ_ADC_ADIN6] = "ADIN6",
      [NWZ_ADC_ADIN7] = "ADIN7",
      [NWZ_ADC_ADIN8] = "ADIN8",
  };
  
  const char *nwz_adc_get_name(int ch)
  {
      return nwz_adc_name[ch];
  }
  
  int nwz_adc_get_val(int fd, int ch)
  {
      unsigned char val;
      if(ioctl(fd, NWZ_ADC_GET_VAL(ch), &val) < 0)
          return -1;
      else
          return val;
  }
  
  int nwz_ts_open(void)
  {
      return nwz_input_open(NWZ_TS_NAME);
  }
  
  void nwz_ts_close(int fd)
  {
      close(fd);
  }
  
  int nwz_ts_state_init(int fd, struct nwz_ts_state_t *state)
  {
      memset(state, 0, sizeof(struct nwz_ts_state_t));
      struct input_absinfo info;
      if(ioctl(fd, EVIOCGABS(ABS_X), &info) < 0)
          return -1;
      state->max_x = info.maximum;
      if(ioctl(fd, EVIOCGABS(ABS_Y), &info) < 0)
          return -1;
      state->max_y = info.maximum;
      if(ioctl(fd, EVIOCGABS(ABS_PRESSURE), &info) < 0)
          return -1;
      state->max_pressure = info.maximum;
      if(ioctl(fd, EVIOCGABS(ABS_TOOL_WIDTH), &info) < 0)
          return -1;
      state->max_tool_width = info.maximum;
      return 1;
  }
  
  int nwz_ts_state_update(struct nwz_ts_state_t *state, struct input_event *evt)
  {
      switch(evt->type)
      {
          case EV_SYN:
              return 1;
          case EV_REL:
              if(evt->code == REL_RX)
                  state->flick_x = evt->value;
              else if(evt->code == REL_RY)
                  state->flick_y = evt->value;
              else
                  return -1;
              state->flick = true;
              break;
          case EV_ABS:
              if(evt->code == ABS_X)
                  state->x = evt->value;
              else if(evt->code == ABS_Y)
                  state->y = evt->value;
              else if(evt->code == ABS_PRESSURE)
                  state->pressure = evt->value;
              else if(evt->code == ABS_TOOL_WIDTH)
                  state->tool_width = evt->value;
              else
                  return -1;
              break;
          case EV_KEY:
              if(evt->code == BTN_TOUCH)
                  state->touch = evt->value;
              else
                  return -1;
              break;
          default:
              return -1;
      }
      return 0;
  }
  
  int nwz_ts_state_post_syn(struct nwz_ts_state_t *state)
  {
      state->flick = false;
      return 1;
  }
  
  int nwz_ts_read_events(int fd, struct input_event *evts, int nr_evts)
  {
      int ret = read(fd, evts, nr_evts * sizeof(struct input_event));
      if(ret < 0)
          return -1;
      return ret / sizeof(struct input_event);
  }
  
  long nwz_wait_fds(int *fds, int nr_fds, long tmo_us)
  {
      fd_set rfds;
      struct timeval tv;
      struct timeval *tv_ptr = NULL;
      /* watch the input device */
      FD_ZERO(&rfds);
      int max_fd = 0;
      for(int i = 0; i < nr_fds; i++)
      {
          FD_SET(fds[i], &rfds);
          if(fds[i] > max_fd)
              max_fd = fds[i];
      }
      /* setup timeout */
      if(tmo_us >= 0)
      {
          tv.tv_sec = 0;
          tv.tv_usec = tmo_us;
          tv_ptr = &tv;
      }
      int ret = select(max_fd + 1, &rfds, NULL, NULL, tv_ptr);
      if(ret <= 0)
          return ret;
      long bitmap = 0;
      for(int i = 0; i < nr_fds; i++)
          if(FD_ISSET(fds[i], &rfds))
              bitmap |= 1 << i;
      return bitmap;
  }
  
  int nwz_power_open(void)
  {
      return open(NWZ_POWER_DEV, O_RDWR);
  }
  
  void nwz_power_close(int fd)
  {
      close(fd);
  }
  
  int nwz_power_get_status(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_STATUS, &status) < 0)
          return -1;
      return status;
  }
  
  static int nwz_power_adval_to_mv(int adval, int ad_base)
  {
      if(adval == -1)
          return -1;
      /* the AD base corresponds to the millivolt value if adval was 255 */
      return (adval * ad_base) / 255;
  }
  
  int nwz_power_get_vbus_adval(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_VBUS_ADVAL, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_vbus_voltage(int fd)
  {
      return nwz_power_adval_to_mv(nwz_power_get_vbus_adval(fd), NWZ_POWER_AD_BASE_VBUS);
  }
  
  int nwz_power_get_vbus_limit(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_VBUS_LIMIT, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_charge_switch(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_CHARGE_SWITCH, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_charge_current(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_CHARGE_CURRENT, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_battery_gauge(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_BAT_GAUGE, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_battery_adval(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_BAT_ADVAL, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_battery_voltage(int fd)
  {
      return nwz_power_adval_to_mv(nwz_power_get_battery_adval(fd), NWZ_POWER_AD_BASE_VBAT);
  }
  
  int nwz_power_get_vbat_adval(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_VBAT_ADVAL, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_vbat_voltage(int fd)
  {
      return nwz_power_adval_to_mv(nwz_power_get_vbat_adval(fd), NWZ_POWER_AD_BASE_VBAT);
  }
  
  int nwz_power_get_sample_count(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_SAMPLE_COUNT, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_vsys_adval(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_VSYS_ADVAL, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_get_vsys_voltage(int fd)
  {
      return nwz_power_adval_to_mv(nwz_power_get_vsys_adval(fd), NWZ_POWER_AD_BASE_VSYS);
  }
  
  int nwz_power_get_acc_charge_mode(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_GET_ACCESSARY_CHARGE_MODE, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_power_is_fully_charged(int fd)
  {
      int status;
      if(ioctl(fd, NWZ_POWER_IS_FULLY_CHARGED, &status) < 0)
          return -1;
      return status;
  }
  
  int nwz_pminfo_open(void)
  {
      return open(NWZ_PMINFO_DEV, O_RDONLY);
  }
  
  void nwz_pminfo_close(int fd)
  {
      close(fd);
  }
  
  unsigned int nwz_pminfo_get_factor(int fd)
  {
      unsigned int val;
      if(ioctl(fd, NWZ_PMINFO_GET_FACTOR, &val) < 0)
          return 0;
      else
          return val;
  }
  
  static unsigned long find_model_id(void)
  {
      /* try with the environment variable */
      const char *mid = getenv("ICX_MODEL_ID");
      if(mid == NULL)
          return 0;
      char *end;
      unsigned long v = strtoul(mid, &end, 0);
      if(*end)
          return 0;
      else
          return v;
  }
  
  unsigned long nwz_get_model_id(void)
  {
      static unsigned long model_id = 0xffffffff;
      if(model_id == 0xffffffff)
          model_id = find_model_id();
      return model_id;
  }
  
  const char *nwz_get_model_name()
  {
      for(int i = 0; i < NWZ_MODEL_COUNT; i++)
          if(nwz_model[i].mid == nwz_get_model_id())
              return nwz_model[i].name;
      return NULL;
  }
  
  static int find_series(void)
  {
      for(int i = 0; i < NWZ_SERIES_COUNT; i++)
          for(int j = 0; j < nwz_series[i].mid_count; j++)
              if(nwz_series[i].mid[j] == nwz_get_model_id())
                  return i;
      return -1;
  }
  
  int nwz_get_series(void)
  {
      static int series = -2;
      if(series == -2)
          series = find_series();
      return series;
  }
  
  static nwz_nvp_index_t *get_nvp_index(void)
  {
      static nwz_nvp_index_t *index = 0;
      if(index == 0)
      {
          int series = nwz_get_series();
          index = series < 0 ? 0 : nwz_series[series].nvp_index;
      }
      return index;
  }
  
  int nwz_nvp_read(enum nwz_nvp_node_t node, void *data)
  {
      int size = nwz_nvp[node].size;
      if(data == 0)
          return size;
      nwz_nvp_index_t *index = get_nvp_index();
      if(index == 0 || (*index)[node] == NWZ_NVP_INVALID)
          return -1;
      char nvp_path[32];
      snprintf(nvp_path, sizeof(nvp_path), "/dev/icx_nvp/%03d", (*index)[node]);
      int fd = open(nvp_path, O_RDONLY);
      if(fd < 0)
          return -1;
      int cnt = read(fd, data, size);
      close(fd);
      return cnt == size ? size : -1;
  }
  
  int nwz_nvp_write(enum nwz_nvp_node_t node, void *data)
  {
      int size = nwz_nvp[node].size;
      nwz_nvp_index_t *index = get_nvp_index();
      if(index == 0 || (*index)[node] == NWZ_NVP_INVALID)
          return -1;
      char nvp_path[32];
      snprintf(nvp_path, sizeof(nvp_path), "/dev/icx_nvp/%03d", (*index)[node]);
      int fd = open(nvp_path, O_WRONLY);
      if(fd < 0)
          return -1;
      int cnt = write(fd, data, size);
      close(fd);
      return cnt == size ? 0 : -1;
  }
  