2020年9月12日 星期六

LP5560 - Single-LED Driver With Single-Wire Control


這是一顆我們俗稱的呼吸燈,因為預設的行為就像呼吸一樣,由滅到緩慢至亮,再由亮緩慢至滅。可以透過Single-Wire控制其行為,作法大同小異。這裡就稍微筆記一下。

6.6 Single-Wire Interface Timing Requirements

這裡描述了每個階段的high/low以及min/max時間,比如TC_ON > 20us,TC_OF > 30us之類的,簡略的code如下
#define SHORT_DELAY 10
#define TC_ON (20 + SHORT_DELAY)
#define TC_OFF (30 + SHORT_DELAY)
#define T_ENTER (500 + 10*SHORT_DELAY)
#define T_BLANK (1500 - 500)

// T-ENTER 
GPIO(CTRL_PIN, high);
us_delay(TC_ON);
GPIO(CTRL_PIN, low);
us_delay(TC_OFF);

GPIO(CTRL_PIN, high);
us_delay(TC_ON);
GPIO(CTRL_PIN, low);
us_delay(TC_OFF + T_ENTER + T_BLANK); // over T-BLANK period


7.3.3.4 Entering Follow Mode

這裡描述Entering Follow Mode的指令結構,基本上就是,"Training start command" + "Blank Period" + "Follow Mode command" + "Training end command"
#define SHORT_DELAY 10
#define TC_ON (20 + SHORT_DELAY)
#define TC_OFF (30 + SHORT_DELAY)
#define T_ENTER (500 + SHORT_DELAY)
#define T_BLANK (1500 - 500)
#define TCAL (350 + 10 * SHORT_DELAY))
#define TT_OFF (200 + 10 * SHORT_DELAY)
#define TIMEOUT (127 * TCAL)

static void training_start_command(void)
{
  int i = 0;
  while (i++ < 2) {
    GPIO(CTRL_PIN, high);
    us_delay(TC_ON);
    GPIO(CTRL_PIN, low);
    us_delay(TC_OFF);
  }
  us_delay(T_ENTER + T_BLANK); // over T-BLANK period due to 
}

static void follow_mode(uint32_t i, uint32_t r1, uint32_t on1, uint32_t f1, uint32_t off1)
{
  // C
  GPIO(CTRL_PIN, high);
  us_delay(TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // I
  GPIO(CTRL_PIN, high);
  us_delay(i * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // r1
  GPIO(CTRL_PIN, high);
  us_delay(r1 * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // on1
  GPIO(CTRL_PIN, high);
  us_delay(on1 * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);

  // off1
  GPIO(CTRL_PIN, high);
  us_delay(off1 * TCAL);
  GPIO(CTRL_PIN, low);
  us_delay(TT_OFF);
}

static void training_start_command(void)
{
  int i = 0;
  while (i++ < 3) {
    GPIO(CTRL_PIN, high);
    us_delay(TC_ON);
    GPIO(CTRL_PIN, low);
    us_delay(TC_OFF);
  }
  // more than 127 × TCAL time this is interpreted as timeout.
  us_delay(TIMEOUT );
}





2020年9月6日 星期日

Linux Kernel(20.3)- Creating an input device driver


這個範例用最簡單的input device driver來解釋必要的部分,首先用input_allocate_device()取得"struct input_dev",接著填入必要欄位,如evbit,這個範例是只會產生EV_KEY event type,且key/event code只有KEY_UP(103)與KEY_DOWN(108),之後呼叫input_register_device(btn_dev)向系統註冊一個input device。
我用QEMU跑,沒有實際的硬體可以驅動該input device,所以我在sys底下創建一個sysfs_report_key讓input device送event。
#include <linux/module.h>
#include <linux/init.h>

#include <linux/input.h>
#include <linux/device.h>
#include <linux/sysfs.h>

static struct input_dev *btn_dev;

static ssize_t sysfs_report_key_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n)
{
  int key, value;
  if (sscanf(buf, "%d %d", &key, &value) != 2) {
    printk("invalid format:%s", buf);
    return n;
  }
  printk("key:%d, value:%d", key, value);
  input_report_key(btn_dev, key, value);
  input_sync(btn_dev);
  return n;
}

static DEVICE_ATTR_WO(sysfs_report_key);
static int __init button_init(void)
{
  int ret;

  btn_dev = input_allocate_device();
  if (btn_dev == NULL) {
    printk(KERN_ERR "Not enough memory\n");
    return -ENOMEM;
  }

  btn_dev->name = "brook-input-dev";
  btn_dev->evbit[0] = BIT(EV_KEY);
  set_bit(KEY_UP, btn_dev->keybit);
  set_bit(KEY_DOWN, btn_dev->keybit);

  ret = input_register_device(btn_dev);
  if (ret) {
    dev_err(&(btn_dev->dev), "Failed to register device\n");
    goto err_free_dev;
  }

  /* used for send event from user-space. please ignore it */
  ret = device_create_file(&(btn_dev->dev), &dev_attr_sysfs_report_key);
  if (ret) {
    dev_err(&(btn_dev->dev), "cannot create sysfs attribute\n");
    goto err_unreg_dev;
  }

  return 0;

err_unreg_dev:
  input_unregister_device(btn_dev);
err_free_dev:
  input_free_device(btn_dev);
  return ret;
}

static void __exit button_exit(void)
{
  input_unregister_device(btn_dev);
  input_free_device(btn_dev);
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");


執行結果
/ # insmod inputdev.ko
[60145.219561] inputdev: loading out-of-tree module taints kernel.
[60145.277803] input: brook-input-dev as /devices/virtual/input/input3
/ # ./input_test /dev/input/event1 &

送出KEY_UP(103)後,input_test會收到event。
/ # echo '103 1' > /sys/devices/virtual/input/input3/sysfs_report_key
type:1, code:103, val:1
type:0, code:0, val:0

重複的送出KEY_UP(103),input_test不會收到event。
/ # echo '103 1' > /sys/devices/virtual/input/input3/sysfs_report_key
[60174.829756] key:103, value:1
/ # echo '103 0' > /sys/devices/virtual/input/input3/sysfs_report_key
[60182.965543] key:103, value:1
type:1, code:103, val:0
/ # type:0, code:0, val:0

Key value只有0/1。
/ # echo '103 2' > /sys/devices/virtual/input/input3/sysfs_report_key
[60189.286751] key:103, value:0
type:1, code:103, val:1
/ # type:0, code:0, val:0

其他的key/event code是不會被送出。
/ # echo '105 2' > /sys/devices/virtual/input/input3/sysfs_report_key
[60194.705307] key:103, value:2
/ # echo '108 2' > /sys/devices/virtual/input/input3/sysfs_report_key
[60201.887535] key:105, value:2
type:1, code:108, val:1
type:0, code:0, val:0


基本上,所有的input_report_xxx()/input_sync底層都是呼叫input_event,input_report_key的value只有0/1。
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_KEY, code, !!value);
}

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_REL, code, value);
}

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_ABS, code, value);
}

static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_FF_STATUS, code, value);
}

static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)
{
  input_event(dev, EV_SW, code, !!value);
}

static inline void input_sync(struct input_dev *dev)
{
  input_event(dev, EV_SYN, SYN_REPORT, 0);
}


input_event() 在送出key/event code之前,會先判斷送的event type與當初device設定的是否一致。
void input_event(struct input_dev *dev,
         unsigned int type, unsigned int code, int value)
{
  unsigned long flags;

  if (is_event_supported(type, dev->evbit, EV_MAX)) {
    spin_lock_irqsave(&dev->event_lock, flags);
    input_handle_event(dev, type, code, value);
    spin_unlock_irqrestore(&dev->event_lock, flags);
  }
}

重複的KEY value是不會被設成INPUT_PASS_TO_HANDLERS,也就是INPUT_IGNORE_EVENT。
static void input_handle_event(struct input_dev *dev,
                   unsigned int type, unsigned int code, int value)
{
  int disposition = input_get_disposition(dev, type, code, &value);

  ...
}

static int input_get_disposition(struct input_dev *dev,
              unsigned int type, unsigned int code, int *pval)
{
  int disposition = INPUT_IGNORE_EVENT;
  int value = *pval;

  switch (type) {
  ...
  case EV_KEY:
    if (is_event_supported(code, dev->keybit, KEY_MAX)) {
      /* auto-repeat bypasses state updates */
      if (value == 2) {
        disposition = INPUT_PASS_TO_HANDLERS;
        break;
      }

      if (!!test_bit(code, dev->key) != !!value) {
        __change_bit(code, dev->key);
        disposition = INPUT_PASS_TO_HANDLERS;
      }

    }
    break;
  ...
  }
  
  *pval = value;
  return disposition;
}


    參考資料:
  • Documentation/input/input-programming.rst



2020年9月5日 星期六

Linux Kernel(20.2)- uinput module


uinput是kernel module透過寫入/dev/uinput從userpace模擬input device裝置,並且驅動特定的event。
其kernel configuration的path為Input device support -> Miscellaneous devices -> User level driver support
設定簡單,可以參考以下範例大概就知道如何操控了。
#include <stdio.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#include <linux/input.h>

#include <linux/uinput.h>

void emit(int fd, int type, int code, int val)
{
  struct input_event ie;

  ie.type = type;
  ie.code = code;
  ie.value = val;
  /* timestamp values below are ignored */
  ie.time.tv_sec = 0;
  ie.time.tv_usec = 0;

  write(fd, &ie, sizeof(ie));
}

int main(void)
{
  struct uinput_setup usetup;

  int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

  /*
   * The ioctls below will enable the device that is about to be
   * created, to pass key events, in this case the space key.
   **/
  ioctl(fd, UI_SET_EVBIT, EV_KEY);
  ioctl(fd, UI_SET_KEYBIT, KEY_SPACE);

  memset(&usetup, 0, sizeof(usetup));
  usetup.id.bustype = BUS_USB;
  usetup.id.vendor = 0x1234;    /* sample vendor */
  usetup.id.product = 0x5678;   /* sample product */
  strcpy(usetup.name, "Example device");

  ioctl(fd, UI_DEV_SETUP, &usetup);
  ioctl(fd, UI_DEV_CREATE);

  /*
   * On UI_DEV_CREATE the kernel will create the device node for this
   * device. We are inserting a pause here so that userspace has time
   * to detect, initialize the new device, and can start listening to
   * the event, otherwise it will not notice the event we are about
   * to send. This pause is only needed in our example code!
   **/
  printf("will report the event after 10 sec\n");
  sleep(10);

  /* Key press, report the event, send key release, and report again */
  emit(fd, EV_KEY, KEY_SPACE, 1);
  emit(fd, EV_SYN, SYN_REPORT, 0);
  emit(fd, EV_KEY, KEY_SPACE, 0);
  emit(fd, EV_SYN, SYN_REPORT, 0);

  /*
   * Give userspace some time to read the events before we destroy the
   * device with UI_DEV_DESTOY.
   **/
  sleep(1);

  ioctl(fd, UI_DEV_DESTROY);
  close(fd);

  return 0;
}


執行結果,input_test為先前章節的user space程式,uinput為該章節範例,執行uinput之後,會在/dev/input/長出對應的device node。
/ # zcat /proc/config.gz | grep UINPUT
CONFIG_INPUT_UINPUT=y
/ # ./uinput &
/ # [   99.433163] input: Example device as /devices/virtual/input/input4
/ # ls /dev/input/
event0  event1
/ # ./input_test /dev/input/event1
will report the event after 10 sec
type:1, code:57, val:1
type:0, code:0, val:0
type:1, code:57, val:0
type:0, code:0, val:0


    參考資料:
  • Documentation/input/uinput.rst



熱門文章