GPIO access from kernelspace

Started by iw2lsi, March 13, 2013, 11:31:43 PM

Previous topic - Next topic

iw2lsi

Hi

  anyone has already try to drive a GPIO (for example to toggle pin37/LED1/cpupin12) from kernelspace ?

I would like to toggle it from a kernel module (using high resolution timers), but miss some info on how to access the gpio itself... should I use sunxi_gpio_set() ? with which parms ?

  thanks a lot for any hints

           Giampaolo

iw2lsi


Dear John

   I've search... but found nothing...

I'm talking about reading/setting a GPIO from kernel-space using the already-made gpio infrastructure, not from userspace using procfs or a memory-mapped device...

could I use kernel functions as:

- gpio_is_valid(gpio_num)
- gpio_request(gpio_num, gpio_label)
- gpio_direction_output(gpio_num, true);
- gpio_direction_input(gpio_num);
- gpio_set_value(gpio_num, value);

??? how can I find the gpio_num ?

as a first try, I want to toggle olinuxino green LED...

as my script.fex looks like this:

[gpio_para]
gpio_used = 1
gpio_num = 15
gpio_pin_1 = port:PB03<1><default><default><default>
gpio_pin_2 = port:PB04<1><default><default><default>
gpio_pin_3 = port:PB10<1><default><default><default>
gpio_pin_4 = port:PE04<0><default><default><default>
gpio_pin_5 = port:PE05<0><default><default><default>
gpio_pin_6 = port:PE06<0><default><default><default>
gpio_pin_7 = port:PE07<0><default><default><default>
gpio_pin_8 = port:PE08<1><default><default><default>
gpio_pin_9 = port:PE09<1><default><default><default>
gpio_pin_10 = port:PE10<1><default><default><default>
gpio_pin_11 = port:PE11<1><default><default><default>
gpio_pin_12 = port:PG09<1><default><default><default>      <<<<< olinuxino green LED
gpio_pin_13 = port:PG10<1><default><default><default>
gpio_pin_14 = port:PG11<1><default><default><default>
gpio_pin_15 = port:PB02<1><default><default><0>

I call the function gpio_is_valid(12) from kernelspace but it return false.

also... I found just now that there are two GPIO drivers... drivers/gpio/gpio-sunxi.c and drivers/misc/sun4i-gpio.c

  thanks a lot

       giampaolo



hhornbacher

Hey maybe my posts / drivers help you understand the gpio api:

https://www.olimex.com/forum/index.php?topic=967.msg4522#msg4522 --> 1-Wire Master Driver using GPIO
https://www.olimex.com/forum/index.php?topic=967.0 --> DHT22 platform driver GPIO interface to another 1-wire similar protocol to read humidity/temperature data

vinifr

#3
hi,

gpio-sunxi driver for 3.0: https://github.com/linux-sunxi/linux-sunxi/blob/stage/sunxi-3.0/drivers/gpio/gpio-sunxi.c

gpio-sunxi driver for 3.4: https://github.com/linux-sunxi/linux-sunxi/blob/stage/sunxi-3.4/drivers/gpio/gpio-sunxi.c

Ops  ;D, this driver has already been added to the master branch sunxi-3.0, look for in the directory: source-path/linux-sunxi/drivers/gpio/. If there is not gpio-sunxi.c, then you just need to update the kernel.

Lioric

Please bear in mind that I have not yet received the A13 board (the usb to UART cable is in back order), so I have not looked at the kernel code yet, but maybe you need to first setup the pin as a GPIO, probably something like this (as used by sunxi driver):

__u32 reg_val = 0;
__u32 pin_idx = pin >> 3;
void *raddr = addr + (((port)-1)*0x24 + ((pin_idx)<<2) + 0x00);
reg_val = readl(raddr);
reg_val &= ~(0x07 << (((pin - (pin_idx<<3))<<2)));
reg_val |= 1 << (((pin - (pin_idx<<3))<<2)); ****// Set pin to Output mode****
writel(reg_val, raddr);


As per the examples, it seems that you need to first "get the pin" before actually working with it. See the sunxi_gpio_request method from the driver for more details.

This seems to be backed by the Olimex blog phyton sample:

Quote>>> GPIO.init()                           #init GPIOs
>>> GPIO.getcfg(GPIO.PIN37)               #config PIN37 as GPIO ****(probably can be traced to the above)****
>>> GPIO.setcfg(GPIO.PIN37, GPIO.OUTPUT)  #config PIN37 as output
....


iw2lsi

Hi

  thanks you all for the help... I'm looking deeper at the dummy GPIO driver (sun4i-gpio.c) at the moment
  not sure, but it seems a good point to start with.
  working directly with memory locations could be another (good) way...

  I'll be back ASAP

        thanks again

            Giampaolo

vinifr

#6
Quote from: iw2lsi on March 14, 2013, 10:38:02 PM
Hi

  thanks you all for the help... I'm looking deeper at the dummy GPIO driver (sun4i-gpio.c) at the moment
  not sure, but it seems a good point to start with.
  working directly with memory locations could be another (good) way...

  I'll be back ASAP

        thanks again

            Giampaolo

hi iw2lsi, sun4i-gpio.c is a ancient driver and was replaced by gpio-sunxi.c.  ;) You got find it in source code?

iw2lsi


Hello vinifr

   I'm going to check it right now... in the meantime, I got my first (and VERY VERY dirty) led toggle from kernel space modifying the gpio dummy...

   here is the original Tom Cubie code modified by me to toggle:

/* driver/misc/sun4i-gpio.c
*
*  Copyright (C) 2011 Allwinner Technology Co.Ltd
*   Tom Cubie <tangliang@allwinnertech.com>
*
www.allwinnertech.com
*
*  An ugly sun4i gpio driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/pm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>



// iw2lsi
#include <linux/hrtimer.h>
#include <linux/ktime.h>




#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif

#include <linux/miscdevice.h>
#include <linux/device.h>

#include <plat/sys_config.h>

//#undef DEBUG_SUN4I
#define DEBUG_SUN4I

#ifdef DEBUG_SUN4I
#define sun4i_gpio_dbg(x...)   printk(x)
#else
#define sun4i_gpio_dbg(x...)
#endif

struct sun4i_gpio_data {
   int status;
   unsigned gpio_handler;
   script_gpio_set_t info;
   char name[8];
  #ifdef CONFIG_HAS_EARLYSUSPEND
   struct early_suspend early_suspend;
  #endif
};

static int sun4i_gpio_num;
static struct sun4i_gpio_data *psun4i_gpio;
static struct device_attribute *pattr;














// iw2lsi
#define MS_TO_NS(x) (x * 1E6L)

static struct hrtimer hr_timer;
struct sun4i_gpio_data *gpio_i12=NULL;

// ----------------------------------------------------------------------------------------------------
// hrtimer_restart
// ----------------------------------------------------------------------------------------------------

enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer)

{
   static int toggle=0;
   ktime_t now;
   ktime_t ktime;

   unsigned long delay_in_ms = 10L;

   ktime = ktime_set(0, MS_TO_NS(delay_in_ms));

// printk("my_hrtimer_callback called (%ld).\n", jiffies);


// DANGER
   if (toggle) toggle=0; else toggle=1;

   gpio_write_one_pin_value(gpio_i12->gpio_handler, toggle, NULL);




// now = hrtimer_cb_get_time(timeri);
   now = ktime_get();

   hrtimer_forward(&hr_timer, now, ktime);

// return HRTIMER_NORESTART;
   return HRTIMER_RESTART;
}

// ----------------------------------------------------------------------------------------------------
// init_mytimer
// ----------------------------------------------------------------------------------------------------

int init_mytimer(void)

{
   ktime_t ktime;
   unsigned long delay_in_ms = 500L;

   printk(KERN_INFO "iw2lsi init_timer called\n");

   printk("HR Timer module installing\n");

   ktime = ktime_set(0, MS_TO_NS(delay_in_ms));

   hrtimer_init(&hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);

   hr_timer.function = &my_hrtimer_callback;

   printk("Starting timer to fire in %ldms (%ld)\n", delay_in_ms, jiffies);

   hrtimer_start(&hr_timer, ktime, HRTIMER_MODE_REL);

   return 0;
}

// ----------------------------------------------------------------------------------------------------
// cancel_mytimer
// ----------------------------------------------------------------------------------------------------

void cancel_mytimer(void)

{
   int ret;

   printk(KERN_INFO "iw2lsi cleanup_module() called\n");

   ret = hrtimer_cancel(&hr_timer);

   if (ret) printk("The timer was still in use...\n");

   printk("HR Timer module uninstalled\n");
}






















static void set_sun4i_gpio_status(struct sun4i_gpio_data *sun4i_gpio,  int on)

{
   if (on){
      sun4i_gpio_dbg("\n-on-");
      gpio_write_one_pin_value(sun4i_gpio->gpio_handler, 1, NULL);
      }else{
      sun4i_gpio_dbg("\n-off-");
      gpio_write_one_pin_value(sun4i_gpio->gpio_handler, 0, NULL);
      }
}

static ssize_t sun4i_gpio_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)

{
   unsigned long data;
   int i,error;
   struct sun4i_gpio_data *gpio_i = psun4i_gpio;

   error = strict_strtoul(buf, 10, &data);

   if (error) return error;

// ciclo per tutte le porte fino a che non trovo quella da settare
   for (i = 0; i < sun4i_gpio_num; i++){

       sun4i_gpio_dbg("attr->attr.name: %s\n", attr->attr.name);
       sun4i_gpio_dbg("gpio_i->name: %s\n", gpio_i->name);

    // se la porta e' quella da settare, la setto ed esco
       if (!strcmp(attr->attr.name, gpio_i->name)){
          set_sun4i_gpio_status(gpio_i, data);
          break;
          }

       gpio_i++;
       }

   return count;
}

static ssize_t sun4i_gpio_enable_show(struct device *dev, struct device_attribute *attr, char *buf)

{
   int i;
   int data = EGPIO_FAIL;
   struct sun4i_gpio_data *gpio_i = psun4i_gpio;

// cicla per tutte le porte fino a che non trova quella da leggere
   for (i = 0; i < sun4i_gpio_num; i++){

       sun4i_gpio_dbg("attr->attr.name: %s\n", attr->attr.name);
       sun4i_gpio_dbg("gpio_i->name: %s\n", gpio_i->name);

    // se la porta e' quella da leggere, la leggo e mi fermo
       if (!strcmp(attr->attr.name, gpio_i->name)) {
     data = gpio_read_one_pin_value(gpio_i->gpio_handler, NULL);
     sun4i_gpio_dbg("handler: %u\n", gpio_i->gpio_handler);
     sun4i_gpio_dbg("data: %d\n", data);
     break;
     }

       gpio_i++;
       }

   if (data != EGPIO_FAIL){
      return sprintf(buf, "%d\n", data);
      }else{
      return sprintf(buf, "error\n");
      }
}

static struct attribute *sun4i_gpio_attributes[256] = {
   NULL
};

static struct attribute_group sun4i_gpio_attribute_group = {
   .name = "pin",
   .attrs = sun4i_gpio_attributes
};

static int sun4i_gpio_open(struct inode *inode, struct file *file)

{
   pr_info("sun4i_gpio open\n");
   return 0;
}

ssize_t sun4i_gpio_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)

{
   pr_info("sun4i_gpio write\n");
   return 0;
}

static int sun4i_gpio_release(struct inode *inode, struct file *file)

{
   gpio_release(psun4i_gpio->gpio_handler, 0);
   kfree(psun4i_gpio);
   return 0;
}

static const struct file_operations sun4i_gpio_fops = {
   .open      = sun4i_gpio_open,
   .write      = sun4i_gpio_write,
   .release   = sun4i_gpio_release
};

static struct miscdevice sun4i_gpio_dev = {
   .minor =   MISC_DYNAMIC_MINOR,
   .name =      "sun4i-gpio",
   .fops =      &sun4i_gpio_fops
};

static int __init sun4i_gpio_init(void)

{
   int err;
   int i;
   int sun4i_gpio_used = 0;
   struct sun4i_gpio_data *gpio_i;
   struct device_attribute *attr_i;
   char pin[16];

   pr_info("sun4i gpio driver init\n");

   err = script_parser_fetch("gpio_para", "gpio_used", &sun4i_gpio_used, sizeof(sun4i_gpio_used)/sizeof(int));

   if (err){
         pr_err("%s script_parser_fetch \"gpio_para\" \"gpio_used\" error\n", __FUNCTION__);
      goto exit;
      }

   if (!sun4i_gpio_used) {
      pr_err("%s sun4i_gpio is not used in config\n", __FUNCTION__);
      err = -1;
      goto exit;
      }

   err = script_parser_fetch("gpio_para", "gpio_num", &sun4i_gpio_num, sizeof(sun4i_gpio_num)/sizeof(int));

   if (err) {
      pr_err("%s script_parser_fetch \"gpio_para\" \"gpio_num\" error\n", __FUNCTION__);
      goto exit;
        }

   sun4i_gpio_dbg("sun4i_gpio_num: %d\n", sun4i_gpio_num);

   if (!sun4i_gpio_num) {
      pr_err("%s sun4i_gpio_num is none\n", __FUNCTION__);
      err = -1;
      goto exit;
      }

   err = misc_register(&sun4i_gpio_dev);

   if (err){
      pr_err("%s register sun4i_gpio as misc device error\n", __FUNCTION__);
      goto exit;
      }

     // alloca la memoria necessaria per contenere tante strutture sun4i_gpio_data quanto sono le GPIO dichiarate
   psun4i_gpio = kzalloc(sizeof(struct sun4i_gpio_data) * sun4i_gpio_num, GFP_KERNEL);

     // alloca la memoria necessaria per il sysfs ?
   pattr = kzalloc(sizeof(struct device_attribute) * sun4i_gpio_num, GFP_KERNEL);

     // DANGER potrei aver allocato la prima e non la seconda!
   if (!psun4i_gpio || !pattr) {
      pr_err("%s kzalloc failed\n", __FUNCTION__);
      err = -ENOMEM;
      goto exit;
      }

     // puntatore alla gpio n-esima (partendo da 0)
   gpio_i = psun4i_gpio;
   attr_i = pattr;

   for (i=0; i<sun4i_gpio_num; i++){

         // creo una stringa nel formato previsto dal file script.fex (es: gpio_pin_12)
         sprintf(pin, "gpio_pin_%d", i+1);

       sun4i_gpio_dbg("pin: %s\n", pin);

         // tento di ricavare i dati dal file fex compilato ?
       err = script_parser_fetch("gpio_para", pin, (int *)&gpio_i->info, sizeof(script_gpio_set_t));

       if (err){
          pr_err("%s script_parser_fetch \"gpio_para\" \"%s\" error\n", __FUNCTION__, pin);
          break;
          }

         // richiede l'handler per il pin dato e lo imposta nella struttura associata alla porta n-esima
       gpio_i->gpio_handler = gpio_request_ex("gpio_para", pin);

       sun4i_gpio_dbg("gpio handler: %u\n", gpio_i->gpio_handler);

       if (!gpio_i->gpio_handler){
          pr_err("%s can not get \"gpio_para\" \"%s\" gpio handler, already used by others?", __FUNCTION__, pin);
          break;
          }

       sun4i_gpio_dbg("%s: port:%d, portnum:%d\n", pin, gpio_i->info.port, gpio_i->info.port_num);

    // imposto il nomenel formato pa1, pb2 etc...
       sprintf(gpio_i->name, "p%c%d", 'a'+gpio_i->info.port-1, gpio_i->info.port_num);

       sun4i_gpio_dbg("psun4i_gpio->name: %s\n\n", gpio_i->name);




         // DANGER per ora mi memorizzo un riferimento al gpio_pin_12
         // DANGER e' veramente una boiata!
            if (i==11) gpio_i12=gpio_i;




    // imposto gli attibuti del gruppo in sysfs ?
       sysfs_attr_init(&attr_i->attr);
       attr_i->attr.name = gpio_i->name;
       attr_i->attr.mode = S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH;
       attr_i->show = sun4i_gpio_enable_show;
       attr_i->store = sun4i_gpio_enable_store;
       sun4i_gpio_attributes = &attr_i->attr;

       gpio_i++;
       attr_i++;
       }

     // creo un gruppo in sysfs
   sysfs_create_group(&sun4i_gpio_dev.this_device->kobj, &sun4i_gpio_attribute_group);



    // DANGER creo e avvio il timer
       init_mytimer();


exit:

   return err;
}

static void __exit sun4i_gpio_exit(void)

{
   sun4i_gpio_dbg("bye, sun4i_gpio exit\n");

   misc_deregister(&sun4i_gpio_dev);

   sysfs_remove_group(&sun4i_gpio_dev.this_device->kobj, &sun4i_gpio_attribute_group);


// DANGER rilascio il timer
   cancel_mytimer();



   kfree(psun4i_gpio);
   kfree(pattr);
}

module_init(sun4i_gpio_init);
module_exit(sun4i_gpio_exit);

MODULE_DESCRIPTION("a simple sun4i_gpio driver");
MODULE_AUTHOR("Tom Cubie");
MODULE_LICENSE("GPL");

olimex

please stick strictly to technical discussion and stop the personal attacks or I will take measures

lorenzo

Hi Giampaolo,

the best way to do what you want is, as vinifr correctly reported, to use gpio-sunxi driver functions. I think you can find it in sunxi-3.4 branch. This driver implements all the functions that you need to drive the GPIOs inside the kernel sapce. To understand how to register and use a GPIO you can refer to this document in the kernel src:
Documentation/gpio.txt

You need to define in the script.bin file the gpio, as you correctly have done, but without gpio-sunxi driver you won't be able to use gpio functions.

If you want to use IRQ for SUN5I search in sunxi google group these patches, they will help to understand how to use gpiolib:

[PATCH] SUN5I GPIO EINT definition and fix to irq search function
[PATCH] Sunxi spi board IRQ setting with GPIO management

cheers
Lorenzo

iw2lsi


Hello Lorendo...

   yes, you are right... today I got also a PM from Alexandr Shutko (the gpio/gpio-sunxi.c author) saying the same... don't know exacly why but it seems I start with looking at the wrong driver...

  thanks you all for the help

         Giampaolo