“TI’s OMAP-L138 processor is a low-power applications processor based on an ARM926EJ-S and a C674x DSP core”. It provides significantly low power and has been widely used in the industry. In this article I will show you how to write a Linux driver for the GPIO device on a board based on this processor. We will use the AD5420 DAC (Digital to Analog) converter for this demonstration.
Before writing any code, let’s learn something about the hardware background. As we can see from the datasheet of AD5420, to make it work, all we need to do is to control the three special pins (suppose all pins have been connected correctly), labeled as LATCH, SCLK, and SDIN. As we all know, the GPIO peripheral provides general-purpose pins that can be configured as either inputs or outputs. The OMAP-L138 processor has up to 9 banks of GPIO pins, with each bank containing 16 pins. In this project, we used the 3nd bank of pins to connect with the DAC converter. They are GPIO2_10, GPIO2_11, and GPIO2_12 respectively.
First of all, we have to configure the PINMUX. The OMAP-L138 development kit has already provided an example. We just need to modify two files. In the file “/mach-davinci/include/mach/mux.h”, we will add DA850_GPIO2_10, DA850_GPIO2_11, and DA850_GPIO2_12 to the “enum davinci_da850_index” section. And in the file “arch/arm/mach-davinci/da850.c”, we add the following codes:
MUX_CFG(DA850, GPIO2_10, 5, 20, 15, 8, false) // connected with SCLK MUX_CFG(DA850, GPIO2_11, 5, 16, 15, 8, false) // connected with LATCH MUX_CFG(DA850, GPIO2_12, 5, 12, 15, 8, false) // connected with SDIN
The format of the MUX_CFG is (for a thorough understanding, we can check the processor’s technical reference manual):
DA850 -> SOC name GPIO2_10 -> Pin desc 5 -> PINMUX5 20 -> Offset value of PINMUX5 15 -> Mask default value 8 -> Mode false -> for debugging
We also need to check that the GPIO driver is enabled in the Linux kernel. Then we rebuild the kernel.
Here we can write the actual device drive. A Linux drive consists of three main operations, there are “ioctl”, “read”, and “write”. And we also should implement the “init_module” and “cleanup_module” functions. And we’d better provide the statement of “MODULE_LICENSE”, ” MODULE_DESCRIPTION ” and “MODULE_AUTHOR” at the end of the source code. The following is the complete source code. Some comments have been added to make it readable.
[pre]/* * Linux driver for AD5420 * * 12-Bit, Serial Input, 4 mA to 20 mA * * The system contains 6 devices, so we will use the daisy-chain mode * */ #include <linux/module.h> #include <linux/version.h> #include <linux/delay.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/completion.h> #include <mach/cputype.h> #include <mach/hardware.h> #include <mach/mux.h> #include <asm/gpio.h> #include <asm/uaccess.h> #include <linux/fs.h> #define DEVICE_NAME "/dev/myAD5420" // the name of the device #define DEVICE_MAJOR 253 // major number of the device #define MAX_DEVICE_NUM 6 // 6 AD5420 chips in the daisy-chain mode #define SCLK_PIN 42 // connected to GPIO 2[10] #define LATCH_PIN 43 // connected to GPIO 2[11] #define SDIN_PIN 44 // connected to GPIO 2[12] #define CMD_BIT_NUM 8 // 8 bits #define DATA_BIT_NUM 16 // 16 bits #define TOTAL_PIN_NUM 3 // 3 pins used static int GPIO_PIN_NUMS[TOTAL_PIN_NUM] = {42,43,44}; // 2*16 + 10, 2*16 + 11, 2*16 + 12 struct data_unit{ unsigned char device_index; // between [0,MAX_DEVICE_NUM-1] unsigned int out_value; // 12 bits valid }; static unsigned int global_value_array[MAX_DEVICE_NUM]; static int ad5420_ioctl(struct inode *inode, struct file *file, unsigned int command, unsigned long args); static ssize_t ad5420_read(struct file *, char *, size_t, loff_t *); static ssize_t ad5420_write(struct file *, char *, size_t, loff_t *); static struct file_operations fops = { ioctl: ad5420_ioctl, read: ad5420_read, write: ad5420_write }; void InitDevice(void); void WriteUnit(unsigned int out_value); void ShiftCommand(unsigned char command); void ShiftData(unsigned int data); void OutPutData(); int init_module() { int i,status,major; // configure the pins for(i = 0; i < TOTAL_PIN_NUM; ++i) { status = davinci_cfg_reg(GPIO_PIN_NUMS[i]); if (status < 0) { printk("Pin could not be muxed for GPIO functionality %d\n", GPIO_PIN_NUMS[i]); // in the driver we use printk return status; } } // request GPIO resource char gpio_pin_name[100]; for(i = 0; i < TOTAL_PIN_NUM; ++i) { sprintf(gpio_pin_name, "gpio_%d", GPIO_PIN_NUMS[i]); status = gpio_request(GPIO_PIN_NUMS[i], gpio_pin_name); if (status < 0) { printk("Can not open GPIO %d\n", GPIO_PIN_NUMS[i]); return status; } } // set GPIO's input/output directions for(i = 0; i < TOTAL_PIN_NUM; ++i) gpio_direction_output(GPIO_PIN_NUMS[i] ,0); // set initial values for(i = 0; i < TOTAL_PIN_NUM; ++i) gpio_set_value(GPIO_PIN_NUMS[i] ,0); InitDevice(); // register the device major = register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &fops); if (major < 0) { printk ("Registering the character device failed with %d\n", major); return major; } // clean the global value array for(i = 0; i < TOTAL_PIN_NUM; ++i) global_value_array[i] = 0; return 0; } void cleanup_module(void) { int i; // release GPIO resource for(i = 0; i < TOTAL_PIN_NUM; ++i) gpio_free(GPIO_PIN_NUMS[i]); // unregister the device unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME); } // AD5420 can not read, so we let it empty static ssize_t ad5420_read(struct file *filp,char *buf, size_t count,loff_t *f_ops) { return 0; } // output all values in the daisy-chain static ssize_t ad5420_write(struct file *filp,char *buf, size_t count,loff_t *f_ops) { int i; unsigned int current_value; gpio_set_value(LATCH_PIN, 0); gpio_set_value(SCLK_PIN, 0); ndelay(50000); for(i = 0; i < MAX_DEVICE_NUM; ++i) { current_value = global_value_array[i]; WriteUnit(current_value); } OutPutData(); return 1; } static int ad5420_ioctl(struct inode *inode, struct file *file, unsigned int command, unsigned long args) { int device_index_; struct data_unit current_unit; if (copy_from_user(¤t_unit,(const char *)args,sizeof(current_unit)) != 0) { printk("copy_from_user failed \n"); return 0; } device_index_ = current_unit.device_index; if ((device_index_ < 0) || (device_index_ > (MAX_DEVICE_NUM - 1))) { printk("device index should between [0,5] \n"); return 0; } global_value_array[device_index_] = current_unit.out_value; return 1; } // learn the write mode timing diagram from the datasheet void ShiftCommand(unsigned char command) { int i; unsigned char command_; unsigned char command_flag; command_ = command; for(i = 0; i < CMD_BIT_NUM; ++i) { command_flag = command_ & 0x80; if (0 == command_flag) gpio_set_value(SDIN_PIN ,0); else gpio_set_value(SDIN_PIN ,1); ndelay(50000); gpio_set_value(SCLK_PIN ,1); ndelay(50000); gpio_set_value(SCLK_PIN ,0); ndelay(50000); command_ = command_ << 1; } } void ShiftData(unsigned int data) { int i; unsigned int data_; unsigned int data_flag; data_ = data; for(i = 0; i < DATA_BIT_NUM; ++i) { data_flag = data_ & 0x8000; if (0 == data_flag) gpio_set_value(SDIN_PIN ,0); else gpio_set_value(SDIN_PIN ,1); ndelay(50000); gpio_set_value(SCLK_PIN ,1); ndelay(50000); gpio_set_value(SCLK_PIN ,0); ndelay(50000); data_ = data_ << 1; } } void OutPutData() { gpio_set_value(SDIN_PIN, 0); gpio_set_value(SCLK_PIN ,0); gpio_set_value(LATCH_PIN,1); ndelay(50000); gpio_set_value(LATCH_PIN,0); } void InitDevice(void) { int i; unsigned char command; unsigned int data; // reset, see the chip's datasheet for(i = 0; i < MAX_DEVICE_NUM; ++i) { gpio_set_value(LATCH_PIN,0); gpio_set_value(SCLK_PIN ,0); ndelay(50000); command = 0x56; ShiftCommand(command); data = 0x0001; ShiftData(data); // nop command = 0x00; ShiftCommand(command); data = 0x0000; ShiftData(data); OutPutData(); ndelay(1000000); } } void WriteUnit(unsigned int out_value) { unsigned char command; command = 0x01; ShiftCommand(command); ShiftData(out_value); } MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("AD5420 driver"); MODULE_AUTHOR("hlding");[/pre]
In order to use our device driver, we should run these two commands:insmod AD5420.ko mknod /dev/myAD5420 c 253 0And here is our tiny test program:
[pre]#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/types.h> #define MAX_DEVICE_NUM 6 // 6 AD5420 chips in the daisy-chain mode #define DEVICE_NAME "/dev/myAD5420" // the name of the device #define SET_CHANNEL_DATA 1 struct data_unit{ unsigned char device_index; // between [0,MAX_DEVICE_NUM-1] unsigned int out_value; // 12 bits valid }; unsigned int test_value_array[MAX_DEVICE_NUM]= {0x7f,0x7f,0x7f,0x7f,0x7f,0x7f}; int main(int argc, char *argv[]) { int i, ret; struct data_unit current_unit; int device_handle = -1; // open the device handle device_handle = open(DEVICE_NAME, O_RDWR, 0); if (device_handle < 0) { printf("open the device failed!\n"); return -1; } // prepare data for(i = 0; i < MAX_DEVICE_NUM-1; ++i) { current_unit.device_index = i; current_unit.out_value = test_value_array[i]; ret = ioctl(device_handle, SET_CHANNEL_DATA, ¤t_unit); if (0 == ret) { printf("ioctl failed!\n"); close(device_handle); return -1; } } // output ret = write(device_handle, NULL, NULL); // close the device handle close(device_handle); return 0; }[/pre]
References
OMAP-L138 C6000 DSP+ARM Processor Technical Reference Manual
AD5410_5420 data sheet
Click to access AD5410_5420.pdf
OMAP-L138 Software Design Guide
http://processors.wiki.ti.com/index.php/OMAP-L138_Software_Design_Guide#Linux_Drivers:Linux Kernel and Driver Development Training