Adding sysfs support to a driver

Posted on February 19, 2014
Tags: ,
by Sanchayan Maity

A month or so back I had given an example of how to add support for a device using the platform bus framework, which as such mainly showed how exactly platform framework worked and what it was used for. In the driver I wrote, I provided access to the required values using the ioctl calls. The ioctl() calls are not a recommended way of doing this. These days drivers provide the data and control to the user space through the sysfs interface. As the Linux Kernel Development book mentions “The sysfs file system is currently the place for implementing functionality previously reserved for ioctl() calls on device nodes or the procfs filesystem”. This post will show how to make possible what we tried to achieve in the

Write a platform device ADC driver using wm97xx codec

article in a much easier way using the sysfs interface. Please refer that article before reading further.

For learning about the sysfs interface and what it is, refer to the The Linux Device Model chapter of the Linux Device Drivers book or the Devices and Modules chapter of the Linux Kernel Development book by Robert Love.

Some other documentation can be found at the below links:

  1. http://lxr.free-electrons.com/source/Documentation/driver-model/device.txt?v=3.12;a=arm

  2. http://lxr.free-electrons.com/source/Documentation/filesystems/sysfs.txt

The core driver file to which we will make the changes is on the below link:

http://lxr.free-electrons.com/source/drivers/input/touchscreen/wm97xx-core.c

The below change was made to the probe function of the core driver file

if ( device_create_file(wm->dev, &dev_attr_adc_channel1) != 0 )
{
    printk(KERN_ALERT "Sysfs Attribute Creation failed for ADC Channel1\n");
}

if ( device_create_file(wm->dev, &dev_attr_adc_channel2) != 0 )
{
    printk(KERN_ALERT "Sysfs Attribute Creation failed for ADC Channel2\n");
}

if ( device_create_file(wm->dev, &dev_attr_adc_channel3) != 0 )
{
    printk(KERN_ALERT "Sysfs Attribute Creation failed for ADC Channel3\n");
}

if ( device_create_file(wm->dev, &dev_attr_adc_channel4) != 0 )
{
    printk(KERN_ALERT "Sysfs Attribute Creation failed for ADC Channel4\n");
}

The following was added to the core driver file to access the ADC data via sysfs attributes.

static ssize_t adc_channel1_show(struct device *child, struct device_attribute *attr, char *buf)
{
    struct wm97xx *wm = dev_get_drvdata(child);

    return sprintf(buf, "%d\n", wm97xx_read_aux_adc(wm, WM97XX_AUX_ID1));
}

static ssize_t adc_channel2_show(struct device *child, struct device_attribute *attr, char *buf)
{
    struct wm97xx *wm = dev_get_drvdata(child);

    return sprintf(buf, "%d\n", wm97xx_read_aux_adc(wm, WM97XX_AUX_ID2));
}

static ssize_t adc_channel3_show(struct device *child, struct device_attribute *attr, char *buf)
{
    struct wm97xx *wm = dev_get_drvdata(child);

    return sprintf(buf, "%d\n", wm97xx_read_aux_adc(wm, WM97XX_AUX_ID3));
}

static ssize_t adc_channel4_show(struct device *child, struct device_attribute *attr, char *buf)
{
    struct wm97xx *wm = dev_get_drvdata(child);

    return sprintf(buf, "%d\n", wm97xx_read_aux_adc(wm, WM97XX_AUX_ID4));
}

static DEVICE_ATTR(adc_channel1, 0644, adc_channel1_show, NULL);
static DEVICE_ATTR(adc_channel2, 0644, adc_channel2_show, NULL);
static DEVICE_ATTR(adc_channel3, 0644, adc_channel3_show, NULL);
static DEVICE_ATTR(adc_channel4, 0644, adc_channel4_show, NULL);

For cleanup, the attribute removal was done in the remove function as below.

device_remove_file(wm->dev, &dev_attr_adc_channel1);
device_remove_file(wm->dev, &dev_attr_adc_channel2);
device_remove_file(wm->dev, &dev_attr_adc_channel3);
device_remove_file(wm->dev, &dev_attr_adc_channel4);

After doing the above changes, the device attributes which we added show up in the sysfs tree, which can be used to get the ADC values.

If you compare this to what we tried to do previously, you can see how easy it is to get the ADC values as it allows a simple command like cat to access the value. Of course, you could also access the value in C code as well. Things will not always be simple like this. Here we already had a core driver, which allowed us to just add the necessary changes and get what we wanted.

Check out the below file which shows how sysfs support was added for PWM. This support landed in the 3.6 version with the following commit.

http://lwn.net/Articles/553755/

http://lxr.free-electrons.com/source/drivers/pwm/sysfs.c?v=3.12;a=arm

As such the sysfs support will be provided through the framework being used. For example, for devices like ADC, the Industrial IO framework will be used and you will have the sysfs support available through that framework.

Hopefully my earlier post and this one will give you an idea on how to add and use the sysfs interface.