Linux GPIO rotary encoder as volume control

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












I've written a kernel module (tested on Raspberry Pi) to use a rotary encoder as a volume control.



The Linux source tree already contains a driver for rotary encoders so I've just piggybacked off of that to do the hard work, essentially my driver converts the events from that driver into volume up and down keypresses.



The GitHub repo also contains setup/build instructions etc.



However the code in question is reproduced below:



#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/device.h>

MODULE_AUTHOR("James Kent");
MODULE_DESCRIPTION("rotary encoder as volume control module");
MODULE_ALIAS("rotary-volume");
MODULE_LICENSE("GPL");

static char *devicename = "rotary@4";
module_param(devicename, charp, 0);
MODULE_PARM_DESC(devicename, "name of rotary input device");

static int reltype = REL_MISC; // 0x09
module_param(reltype, int, 0);
MODULE_PARM_DESC(reltype, "type of relative event to listen for");

static int count_per_press = 10;
module_param(count_per_press, int, 0);
MODULE_PARM_DESC(count_per_press, "event count before a press is generated");

static struct input_dev *button_dev;

static void send_key(int key)
input_report_key(button_dev, key, 1);
input_sync(button_dev);
input_report_key(button_dev, key, 0);
input_sync(button_dev);


int count = 0;

static void rotary_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
// printk(KERN_DEBUG pr_fmt("Event. Dev: %s, Type: %d, Code: %d, Value: %dn"), dev_name(&handle->dev->dev), type, code, value);
if (type == EV_REL)
if (code == reltype) (inc < 0 && count > 0)) // if change of direction reset count
count = 0;

for (i=0; i!=value; i+=inc)
count += inc;
if (abs(count) >= count_per_press)
send_key( (inc > 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN);
count = 0;






static int rotary_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
struct input_handle *handle;
int error;

handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;

handle->dev = dev;
handle->handler = handler;
handle->name = "gpio_volume";

error = input_register_handle(handle);
if (error)
goto err_free_handle;

error = input_open_device(handle);
if (error)
goto err_unregister_handle;

printk(KERN_DEBUG pr_fmt("Connected device: %s (%s at %s)n"),
dev_name(&dev->dev),
dev->name ?: "unknown",
dev->phys ?: "unknown");

return 0;

err_unregister_handle:
input_unregister_handle(handle);
err_free_handle:
kfree(handle);
return error;


bool startsWith(const char *pre, const char *str)
size_t lenpre = strlen(pre),
lenstr = strlen(str);
return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;


// keep record of match and remove record is disconnect
bool matched = false;

static bool rotary_match(struct input_handler *handler, struct input_dev *dev)
if (matched)
return false;
matched = startsWith(devicename, dev->name);
return matched;


static void rotary_disconnect(struct input_handle *handle)
printk(KERN_DEBUG pr_fmt("Disconnected device: %sn"), dev_name(&handle->dev->dev));

input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
matched = false;


static const struct input_device_id rotary_ids =
.driver_info = 1 , /* Matches all devices */
, /* Terminating zero entry */
;

MODULE_DEVICE_TABLE(input, rotary_ids);

static struct input_handler rotary_handler =
.event = rotary_event,
.match = rotary_match,
.connect = rotary_connect,
.disconnect = rotary_disconnect,
.name = "rotary_volume",
.id_table = rotary_ids,
;

static int __init button_init(void) BIT_MASK(EV_REP);
set_bit(KEY_VOLUMEDOWN, button_dev->keybit);
set_bit(KEY_VOLUMEUP, button_dev->keybit);

for (i=KEY_ESC; i<=KEY_KPDOT; i++) // add a load of extra keys
set_bit(i, button_dev->keybit);


error = input_register_device(button_dev);
if (error)
printk(KERN_ERR pr_fmt("Failed to register devicen"));
goto err_free_dev;

return 0;
err_free_dev:
input_free_device(button_dev);
return error;


static int __init rotary_volume_init(void)
int error = button_init();
if (count_per_press < 1) // sanitise input
count_per_press = 1;
if (error == 0)
if (input_register_handler(&rotary_handler)==0)
printk(KERN_INFO pr_fmt("loaded.n"));
return 0;
else
input_unregister_device(button_dev);
input_free_device(button_dev);


return error;


static void __exit rotary_volume_exit(void)
input_unregister_device(button_dev);
input_free_device(button_dev);
input_unregister_handler(&rotary_handler);


module_init(rotary_volume_init);
module_exit(rotary_volume_exit);


One area I'm keen to improve is the driver/device matching, because the rotary driver doesn't implement any bus, vendor, product or version info (all 0's). I haven't been able to find a better way of selecting the device other than by name matching. but I'm keen to see if there is anywhere else this could be improved.







share|improve this question



























    up vote
    4
    down vote

    favorite












    I've written a kernel module (tested on Raspberry Pi) to use a rotary encoder as a volume control.



    The Linux source tree already contains a driver for rotary encoders so I've just piggybacked off of that to do the hard work, essentially my driver converts the events from that driver into volume up and down keypresses.



    The GitHub repo also contains setup/build instructions etc.



    However the code in question is reproduced below:



    #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

    #include <linux/slab.h>
    #include <linux/module.h>
    #include <linux/input.h>
    #include <linux/init.h>
    #include <linux/device.h>

    MODULE_AUTHOR("James Kent");
    MODULE_DESCRIPTION("rotary encoder as volume control module");
    MODULE_ALIAS("rotary-volume");
    MODULE_LICENSE("GPL");

    static char *devicename = "rotary@4";
    module_param(devicename, charp, 0);
    MODULE_PARM_DESC(devicename, "name of rotary input device");

    static int reltype = REL_MISC; // 0x09
    module_param(reltype, int, 0);
    MODULE_PARM_DESC(reltype, "type of relative event to listen for");

    static int count_per_press = 10;
    module_param(count_per_press, int, 0);
    MODULE_PARM_DESC(count_per_press, "event count before a press is generated");

    static struct input_dev *button_dev;

    static void send_key(int key)
    input_report_key(button_dev, key, 1);
    input_sync(button_dev);
    input_report_key(button_dev, key, 0);
    input_sync(button_dev);


    int count = 0;

    static void rotary_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
    // printk(KERN_DEBUG pr_fmt("Event. Dev: %s, Type: %d, Code: %d, Value: %dn"), dev_name(&handle->dev->dev), type, code, value);
    if (type == EV_REL)
    if (code == reltype) (inc < 0 && count > 0)) // if change of direction reset count
    count = 0;

    for (i=0; i!=value; i+=inc)
    count += inc;
    if (abs(count) >= count_per_press)
    send_key( (inc > 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN);
    count = 0;






    static int rotary_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
    struct input_handle *handle;
    int error;

    handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
    if (!handle)
    return -ENOMEM;

    handle->dev = dev;
    handle->handler = handler;
    handle->name = "gpio_volume";

    error = input_register_handle(handle);
    if (error)
    goto err_free_handle;

    error = input_open_device(handle);
    if (error)
    goto err_unregister_handle;

    printk(KERN_DEBUG pr_fmt("Connected device: %s (%s at %s)n"),
    dev_name(&dev->dev),
    dev->name ?: "unknown",
    dev->phys ?: "unknown");

    return 0;

    err_unregister_handle:
    input_unregister_handle(handle);
    err_free_handle:
    kfree(handle);
    return error;


    bool startsWith(const char *pre, const char *str)
    size_t lenpre = strlen(pre),
    lenstr = strlen(str);
    return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;


    // keep record of match and remove record is disconnect
    bool matched = false;

    static bool rotary_match(struct input_handler *handler, struct input_dev *dev)
    if (matched)
    return false;
    matched = startsWith(devicename, dev->name);
    return matched;


    static void rotary_disconnect(struct input_handle *handle)
    printk(KERN_DEBUG pr_fmt("Disconnected device: %sn"), dev_name(&handle->dev->dev));

    input_close_device(handle);
    input_unregister_handle(handle);
    kfree(handle);
    matched = false;


    static const struct input_device_id rotary_ids =
    .driver_info = 1 , /* Matches all devices */
    , /* Terminating zero entry */
    ;

    MODULE_DEVICE_TABLE(input, rotary_ids);

    static struct input_handler rotary_handler =
    .event = rotary_event,
    .match = rotary_match,
    .connect = rotary_connect,
    .disconnect = rotary_disconnect,
    .name = "rotary_volume",
    .id_table = rotary_ids,
    ;

    static int __init button_init(void) BIT_MASK(EV_REP);
    set_bit(KEY_VOLUMEDOWN, button_dev->keybit);
    set_bit(KEY_VOLUMEUP, button_dev->keybit);

    for (i=KEY_ESC; i<=KEY_KPDOT; i++) // add a load of extra keys
    set_bit(i, button_dev->keybit);


    error = input_register_device(button_dev);
    if (error)
    printk(KERN_ERR pr_fmt("Failed to register devicen"));
    goto err_free_dev;

    return 0;
    err_free_dev:
    input_free_device(button_dev);
    return error;


    static int __init rotary_volume_init(void)
    int error = button_init();
    if (count_per_press < 1) // sanitise input
    count_per_press = 1;
    if (error == 0)
    if (input_register_handler(&rotary_handler)==0)
    printk(KERN_INFO pr_fmt("loaded.n"));
    return 0;
    else
    input_unregister_device(button_dev);
    input_free_device(button_dev);


    return error;


    static void __exit rotary_volume_exit(void)
    input_unregister_device(button_dev);
    input_free_device(button_dev);
    input_unregister_handler(&rotary_handler);


    module_init(rotary_volume_init);
    module_exit(rotary_volume_exit);


    One area I'm keen to improve is the driver/device matching, because the rotary driver doesn't implement any bus, vendor, product or version info (all 0's). I haven't been able to find a better way of selecting the device other than by name matching. but I'm keen to see if there is anywhere else this could be improved.







    share|improve this question























      up vote
      4
      down vote

      favorite









      up vote
      4
      down vote

      favorite











      I've written a kernel module (tested on Raspberry Pi) to use a rotary encoder as a volume control.



      The Linux source tree already contains a driver for rotary encoders so I've just piggybacked off of that to do the hard work, essentially my driver converts the events from that driver into volume up and down keypresses.



      The GitHub repo also contains setup/build instructions etc.



      However the code in question is reproduced below:



      #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

      #include <linux/slab.h>
      #include <linux/module.h>
      #include <linux/input.h>
      #include <linux/init.h>
      #include <linux/device.h>

      MODULE_AUTHOR("James Kent");
      MODULE_DESCRIPTION("rotary encoder as volume control module");
      MODULE_ALIAS("rotary-volume");
      MODULE_LICENSE("GPL");

      static char *devicename = "rotary@4";
      module_param(devicename, charp, 0);
      MODULE_PARM_DESC(devicename, "name of rotary input device");

      static int reltype = REL_MISC; // 0x09
      module_param(reltype, int, 0);
      MODULE_PARM_DESC(reltype, "type of relative event to listen for");

      static int count_per_press = 10;
      module_param(count_per_press, int, 0);
      MODULE_PARM_DESC(count_per_press, "event count before a press is generated");

      static struct input_dev *button_dev;

      static void send_key(int key)
      input_report_key(button_dev, key, 1);
      input_sync(button_dev);
      input_report_key(button_dev, key, 0);
      input_sync(button_dev);


      int count = 0;

      static void rotary_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
      // printk(KERN_DEBUG pr_fmt("Event. Dev: %s, Type: %d, Code: %d, Value: %dn"), dev_name(&handle->dev->dev), type, code, value);
      if (type == EV_REL)
      if (code == reltype) (inc < 0 && count > 0)) // if change of direction reset count
      count = 0;

      for (i=0; i!=value; i+=inc)
      count += inc;
      if (abs(count) >= count_per_press)
      send_key( (inc > 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN);
      count = 0;






      static int rotary_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
      struct input_handle *handle;
      int error;

      handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
      if (!handle)
      return -ENOMEM;

      handle->dev = dev;
      handle->handler = handler;
      handle->name = "gpio_volume";

      error = input_register_handle(handle);
      if (error)
      goto err_free_handle;

      error = input_open_device(handle);
      if (error)
      goto err_unregister_handle;

      printk(KERN_DEBUG pr_fmt("Connected device: %s (%s at %s)n"),
      dev_name(&dev->dev),
      dev->name ?: "unknown",
      dev->phys ?: "unknown");

      return 0;

      err_unregister_handle:
      input_unregister_handle(handle);
      err_free_handle:
      kfree(handle);
      return error;


      bool startsWith(const char *pre, const char *str)
      size_t lenpre = strlen(pre),
      lenstr = strlen(str);
      return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;


      // keep record of match and remove record is disconnect
      bool matched = false;

      static bool rotary_match(struct input_handler *handler, struct input_dev *dev)
      if (matched)
      return false;
      matched = startsWith(devicename, dev->name);
      return matched;


      static void rotary_disconnect(struct input_handle *handle)
      printk(KERN_DEBUG pr_fmt("Disconnected device: %sn"), dev_name(&handle->dev->dev));

      input_close_device(handle);
      input_unregister_handle(handle);
      kfree(handle);
      matched = false;


      static const struct input_device_id rotary_ids =
      .driver_info = 1 , /* Matches all devices */
      , /* Terminating zero entry */
      ;

      MODULE_DEVICE_TABLE(input, rotary_ids);

      static struct input_handler rotary_handler =
      .event = rotary_event,
      .match = rotary_match,
      .connect = rotary_connect,
      .disconnect = rotary_disconnect,
      .name = "rotary_volume",
      .id_table = rotary_ids,
      ;

      static int __init button_init(void) BIT_MASK(EV_REP);
      set_bit(KEY_VOLUMEDOWN, button_dev->keybit);
      set_bit(KEY_VOLUMEUP, button_dev->keybit);

      for (i=KEY_ESC; i<=KEY_KPDOT; i++) // add a load of extra keys
      set_bit(i, button_dev->keybit);


      error = input_register_device(button_dev);
      if (error)
      printk(KERN_ERR pr_fmt("Failed to register devicen"));
      goto err_free_dev;

      return 0;
      err_free_dev:
      input_free_device(button_dev);
      return error;


      static int __init rotary_volume_init(void)
      int error = button_init();
      if (count_per_press < 1) // sanitise input
      count_per_press = 1;
      if (error == 0)
      if (input_register_handler(&rotary_handler)==0)
      printk(KERN_INFO pr_fmt("loaded.n"));
      return 0;
      else
      input_unregister_device(button_dev);
      input_free_device(button_dev);


      return error;


      static void __exit rotary_volume_exit(void)
      input_unregister_device(button_dev);
      input_free_device(button_dev);
      input_unregister_handler(&rotary_handler);


      module_init(rotary_volume_init);
      module_exit(rotary_volume_exit);


      One area I'm keen to improve is the driver/device matching, because the rotary driver doesn't implement any bus, vendor, product or version info (all 0's). I haven't been able to find a better way of selecting the device other than by name matching. but I'm keen to see if there is anywhere else this could be improved.







      share|improve this question













      I've written a kernel module (tested on Raspberry Pi) to use a rotary encoder as a volume control.



      The Linux source tree already contains a driver for rotary encoders so I've just piggybacked off of that to do the hard work, essentially my driver converts the events from that driver into volume up and down keypresses.



      The GitHub repo also contains setup/build instructions etc.



      However the code in question is reproduced below:



      #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

      #include <linux/slab.h>
      #include <linux/module.h>
      #include <linux/input.h>
      #include <linux/init.h>
      #include <linux/device.h>

      MODULE_AUTHOR("James Kent");
      MODULE_DESCRIPTION("rotary encoder as volume control module");
      MODULE_ALIAS("rotary-volume");
      MODULE_LICENSE("GPL");

      static char *devicename = "rotary@4";
      module_param(devicename, charp, 0);
      MODULE_PARM_DESC(devicename, "name of rotary input device");

      static int reltype = REL_MISC; // 0x09
      module_param(reltype, int, 0);
      MODULE_PARM_DESC(reltype, "type of relative event to listen for");

      static int count_per_press = 10;
      module_param(count_per_press, int, 0);
      MODULE_PARM_DESC(count_per_press, "event count before a press is generated");

      static struct input_dev *button_dev;

      static void send_key(int key)
      input_report_key(button_dev, key, 1);
      input_sync(button_dev);
      input_report_key(button_dev, key, 0);
      input_sync(button_dev);


      int count = 0;

      static void rotary_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
      // printk(KERN_DEBUG pr_fmt("Event. Dev: %s, Type: %d, Code: %d, Value: %dn"), dev_name(&handle->dev->dev), type, code, value);
      if (type == EV_REL)
      if (code == reltype) (inc < 0 && count > 0)) // if change of direction reset count
      count = 0;

      for (i=0; i!=value; i+=inc)
      count += inc;
      if (abs(count) >= count_per_press)
      send_key( (inc > 0) ? KEY_VOLUMEUP : KEY_VOLUMEDOWN);
      count = 0;






      static int rotary_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)
      struct input_handle *handle;
      int error;

      handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
      if (!handle)
      return -ENOMEM;

      handle->dev = dev;
      handle->handler = handler;
      handle->name = "gpio_volume";

      error = input_register_handle(handle);
      if (error)
      goto err_free_handle;

      error = input_open_device(handle);
      if (error)
      goto err_unregister_handle;

      printk(KERN_DEBUG pr_fmt("Connected device: %s (%s at %s)n"),
      dev_name(&dev->dev),
      dev->name ?: "unknown",
      dev->phys ?: "unknown");

      return 0;

      err_unregister_handle:
      input_unregister_handle(handle);
      err_free_handle:
      kfree(handle);
      return error;


      bool startsWith(const char *pre, const char *str)
      size_t lenpre = strlen(pre),
      lenstr = strlen(str);
      return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;


      // keep record of match and remove record is disconnect
      bool matched = false;

      static bool rotary_match(struct input_handler *handler, struct input_dev *dev)
      if (matched)
      return false;
      matched = startsWith(devicename, dev->name);
      return matched;


      static void rotary_disconnect(struct input_handle *handle)
      printk(KERN_DEBUG pr_fmt("Disconnected device: %sn"), dev_name(&handle->dev->dev));

      input_close_device(handle);
      input_unregister_handle(handle);
      kfree(handle);
      matched = false;


      static const struct input_device_id rotary_ids =
      .driver_info = 1 , /* Matches all devices */
      , /* Terminating zero entry */
      ;

      MODULE_DEVICE_TABLE(input, rotary_ids);

      static struct input_handler rotary_handler =
      .event = rotary_event,
      .match = rotary_match,
      .connect = rotary_connect,
      .disconnect = rotary_disconnect,
      .name = "rotary_volume",
      .id_table = rotary_ids,
      ;

      static int __init button_init(void) BIT_MASK(EV_REP);
      set_bit(KEY_VOLUMEDOWN, button_dev->keybit);
      set_bit(KEY_VOLUMEUP, button_dev->keybit);

      for (i=KEY_ESC; i<=KEY_KPDOT; i++) // add a load of extra keys
      set_bit(i, button_dev->keybit);


      error = input_register_device(button_dev);
      if (error)
      printk(KERN_ERR pr_fmt("Failed to register devicen"));
      goto err_free_dev;

      return 0;
      err_free_dev:
      input_free_device(button_dev);
      return error;


      static int __init rotary_volume_init(void)
      int error = button_init();
      if (count_per_press < 1) // sanitise input
      count_per_press = 1;
      if (error == 0)
      if (input_register_handler(&rotary_handler)==0)
      printk(KERN_INFO pr_fmt("loaded.n"));
      return 0;
      else
      input_unregister_device(button_dev);
      input_free_device(button_dev);


      return error;


      static void __exit rotary_volume_exit(void)
      input_unregister_device(button_dev);
      input_free_device(button_dev);
      input_unregister_handler(&rotary_handler);


      module_init(rotary_volume_init);
      module_exit(rotary_volume_exit);


      One area I'm keen to improve is the driver/device matching, because the rotary driver doesn't implement any bus, vendor, product or version info (all 0's). I haven't been able to find a better way of selecting the device other than by name matching. but I'm keen to see if there is anywhere else this could be improved.









      share|improve this question












      share|improve this question




      share|improve this question








      edited Apr 4 at 16:37









      200_success

      123k14142399




      123k14142399









      asked Apr 4 at 15:23









      James Kent

      1212




      1212

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191259%2flinux-gpio-rotary-encoder-as-volume-control%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191259%2flinux-gpio-rotary-encoder-as-volume-control%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Will my employers contract hold up in court?