MT6572平台加入呼吸灯功能——编写linux驱动

一)编写驱动核心源代码。

    即编写linux驱动程序,运行于内核空间的代码。这部分基本上和android没什么关系,完全按照linux驱动编程格式来的。包含一个*.c,一个*.h(可有可无),一个Kconfig。一个Makefile。

    进入kernel/drivers/目录,新建breath_leds目录,进入该目录,新建breath_leds.c:

#include <linux/module.h>
#include <linux/init.h>

#include <linux/fs.h>  
#include <linux/cdev.h>
#include <linux/device.h>   
#include <linux/pci.h>
#include <asm/uaccess.h>   
#include <linux/kernel.h>

#include <mach/mt_gpio.h>
#include <linux/delay.h>

#define DEV_NAME "breath_leds"   //设备名称
#define DEV_COUNT 1  //设备文件数量
#define BREATH_LEDS_MAJOR 0  //默认主设备号
#define BREATH_LEDS_MINOR 1  //默认次设备号

#define IIC_ADDR      0xa8   //i2c 地址
#define SN3112_EN     GPIO141
#define SN3112_SCL    GPIO102
#define SN3112_SDA    GPIO138

#define LED1_3_CON_REG   0x13
#define LED4_9_CON_REG   0x14
#define LED10_12_CON_REG 0x15

#define SET_SCL_PIN_HIGH() (mt_set_gpio_out(SN3112_SCL, GPIO_OUT_ONE))
#define SET_SCL_PIN_LOW()  (mt_set_gpio_out(SN3112_SCL, GPIO_OUT_ZERO))
#define SET_SDA_PIN_HIGH() (mt_set_gpio_out(SN3112_SDA, GPIO_OUT_ONE))
#define SET_SDA_PIN_LOW()  (mt_set_gpio_out(SN3112_SDA, GPIO_OUT_ZERO))

static ssize_t breath_leds_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);

static int major = BREATH_LEDS_MAJOR; //主设备号
static int minor = BREATH_LEDS_MINOR; //次设备号

static dev_t dev_num; //设备号
static struct class *leds_class = NULL;  //struct class

//通过i2c发送一个字节
static void send_iic_byte(unsigned short data)
{
    unsigned short b_mask;
    unsigned short i;

    b_mask = 0x80;   // 1 << 7 : first send MSB
    for (i=0; i<8; i++)
    {
        if ((b_mask & data) != 0)
         {
             SET_SDA_PIN_HIGH();
         }
        else
         {
             SET_SDA_PIN_LOW();
         }
        udelay(1);
        SET_SCL_PIN_HIGH();
        udelay(2);
        SET_SCL_PIN_LOW();
        udelay(1);
        b_mask >>= 1;
    }
}

//往sn3112寄存器reg_addr中写入数据data
static unsigned short write_iic_reg(unsigned short reg_addr, unsigned short data)
{
    unsigned short ack = 0;  //for read iic ack
    unsigned short iic_addr = IIC_ADDR & 0xfe;

    //start condition
    SET_SCL_PIN_HIGH();
    SET_SDA_PIN_HIGH();
    udelay(1);
    SET_SDA_PIN_LOW();
    udelay(1);
    SET_SCL_PIN_LOW();
    udelay(1);

    //send iic addr
    send_iic_byte(iic_addr);

    //read ack
    SET_SCL_PIN_HIGH();
    udelay(1);
    SET_SDA_PIN_LOW();
    //ack = iic_read_ack();
    udelay(3);
    SET_SCL_PIN_LOW();
    udelay(1);
    
    //send reg addr
    send_iic_byte(reg_addr);

    //read ack
    SET_SCL_PIN_HIGH();
    udelay(1);
    SET_SDA_PIN_LOW();
    //ack = iic_read_ack();
    udelay(3);
    SET_SCL_PIN_LOW();
    udelay(1);

    //send data
    send_iic_byte(data);

    //read ack
    SET_SCL_PIN_HIGH();
    udelay(1);
    SET_SDA_PIN_LOW();
    //ack = iic_read_ack();
    udelay(3);
    SET_SCL_PIN_LOW();
    udelay(1);

    //stop condition
    SET_SCL_PIN_HIGH();
    udelay(1);
    SET_SDA_PIN_HIGH();
    udelay(1);

    return ack;
}

//每次写入数据后手动刷新
static void refresh_leds(void)
{
    write_iic_reg(0x16, 0x00);
}

static void turn_on_sn3112(void)
{
    //硬开启
    //mt_set_gpio_out(SN3112_EN, GPIO_OUT_ONE);
    //软开启
    write_iic_reg(0x00, 0x01); 
}

static void turn_off_sn3112(void)
{
    //硬关断
    //mt_set_gpio_out(SN3112_EN, GPIO_OUT_ZERO);
    //软关断    
    write_iic_reg(0x00, 0x00); 
}

static int param_level = 0xff;

//初始化呼吸灯控制ic sn3112
static void initial_sn3112(void)
{
    int i;

    //使能sn3112
    mt_set_gpio_mode(SN3112_EN, GPIO_MODE_GPIO);
    mt_set_gpio_dir(SN3112_EN, GPIO_DIR_OUT);
    mt_set_gpio_out(SN3112_EN, GPIO_OUT_ONE);

    //配置时钟线为输出模式
    mt_set_gpio_mode(SN3112_SCL, GPIO_MODE_GPIO);
    mt_set_gpio_dir(SN3112_SCL, GPIO_DIR_OUT);

    //配置数据线为输出模式
    mt_set_gpio_mode(SN3112_SDA, GPIO_MODE_GPIO);
    mt_set_gpio_dir(SN3112_SDA, GPIO_DIR_OUT);

    write_iic_reg(0x00,0x01);   //设置sn3112工作于标准模式
    //12路灯全开
    write_iic_reg(LED1_3_CON_REG,0x38);
    write_iic_reg(LED4_9_CON_REG,0x3f);
    write_iic_reg(LED10_12_CON_REG,0x07);

    //设置12路灯初始亮度,控制等亮度的寄存器位0x04~0x0f
    for (i=0x04; i<0x10; i++)
    {
        write_iic_reg(i, param_level);
        refresh_leds();
    }
}

static struct file_operations dev_fops =
{
    .owner = THIS_MODULE,
    .write = breath_leds_write,
};

//rec_data[0]:亮度值0~255;rec_data[1]的bit0~3:哪一路led 1~12,bit7:是否打开sn3112,1为打开,0为关闭
static unsigned char rec_data[2];
//每路led对应的led值
static int pwm_reg_index[12] = {0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

static ssize_t breath_leds_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    memset(rec_data, 0, 2);  //清零
    if (copy_from_user(rec_data, buf, 2))
    {
        return -EFAULT;
    }

    if ((rec_data[1] & 0x80) != 0)  
    {
        turn_on_sn3112();  //开启sn3112

        unsigned short level = rec_data[0];
        unsigned short reg_index = (rec_data[1] & 0x0f) - 1;  //数组索引从0开始

        write_iic_reg(pwm_reg_index[reg_index], level);
        refresh_leds();
    }
    else
    {
        turn_off_sn3112();
    }

    return count;
}

//定义cdev结构体,描述字符设备
static struct cdev leds_cdev;

//创建设备文件(/dev/breath_leds)
static int leds_create_device(void)
{
    int ret = 0;
    int err = 0;

    //初始化cdev成员,并建立cdev和file_operations之间的联系
    cdev_init(&leds_cdev, &dev_fops);
    leds_cdev.owner = dev_fops.owner; //cedv_init中没有指定适用模块,故需另指定

    if (major > 0)   //主设备号大于0,通过指定设备号的方式注册字符设备
    {
        dev_num = MKDEV(major, minor);   //获取设备号
        err = register_chrdev_region(dev_num, DEV_COUNT, DEV_NAME);
        if (err < 0)
        {
            printk("wming : register_chrdev_region() failed\n");
            return err;
        }
    }
    else
    {
        err = alloc_chrdev_region(&leds_cdev.dev, minor, DEV_COUNT, DEV_NAME); //通过自动分配方式注册字符设备,minor这里表示起始次设备号
        if (err < 0)
        {
            printk("wming : alloc_chrdev_region() failed\n");
            return err;
        }
        major = MAJOR(leds_cdev.dev);  //获取主设备号
        minor = MINOR(leds_cdev.dev);  //获取从设备号
        dev_num = leds_cdev.dev;       //获取设备号
    }

    //将字符设备添加到内核的字符设备数组中
    ret = cdev_add(&leds_cdev, dev_num, DEV_COUNT);
    //创建struct class
    leds_class = class_create(THIS_MODULE, DEV_NAME);
    //创建设备文件
    device_create(leds_class, NULL, dev_num, NULL, DEV_NAME);

    return ret;
}

static int __init breath_leds_init(void)
{
    int ret;
    ret = leds_create_device();
    initial_sn3112();
    return ret;
}

static void __exit breath_leds_exit(void)
{
    device_destroy(leds_class, dev_num);  //销毁字符设备
    if (leds_class)
    {
        class_destroy(leds_class);   //销毁class结构体
    }
    unregister_chrdev_region(dev_num, DEV_COUNT); //注销字符设备区
}

module_init(breath_leds_init);
module_exit(breath_leds_exit);
module_param(param_level, int, S_IRUGO | S_IWUSR);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wming");
MODULE_ALIAS("breath_leds");
MODULE_DESCRIPTION("breathing leds");
由于是单向控制,这里我们只完成了模拟I2C的写通信函数,而没有实现读函数。另外这里我们只使用了软关断,硬件一直处于开启状态。

这里归纳下创建LED驱动的设备文件五步骤:

第一步:使用cdev_init函数初始化cdev

    描述设备文件需要一个cdev结构体:

struct cdev {
    struct kobject kobj;     //封装设备文件的对象
    struct module *owner;    //指向内核模块指针
    const struct file_operations *ops;   //指向file_operations结构体的指针
    struct list_head list;   //指向上一个和下一个cdev结构体的指针
    dev_t dev;   //dev_t是int型,表示设备号,前12位表示主设备号,后20位表示次设备号
    unsigned int count;   //请求的连接设备编号范围,在建立多个设备文件的时候使用
};

    cdev的大多数成员变量并不需要我们自己去初始化,只要调用cdev_init就能初始化大部分cdev成员变量,cdev_init函数代码:

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    memset(cdev, 0, sizeof *cdev);  
    INIT_LIST_HEAD(&cdev->list); //初始化首尾指针
    kobject_init(&cdev->kobj, &ktype_cdev_default);   //初始化设备文件对象
    cdev->ops = fops;   //关联file_operations
}

可以看出,cdev.owner变量并没有在该函数中初始化,所以cdev.owner需要自己初始化。

第二步:指定设备号

    如果要直接指定设备号,需要使用register_chrdev_region函数注册字符设备区域,这样虽然比较直观,但是如果主设备号和次设备号已经存在,建立设备文件就会失败。而使用alloc_chrdev_region函数会自动分配一个未使用的主设备号。alloc_chrdev_region函数原型如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
其中,dev表示设备号指针,即分配的设备号存储地址,baseminor值用来决定怎么分配次设备号,如baseminor为10,则分配的第一个设备文件的次设备号为10,count表示分配的次设备号范围,即分配几个次设备号,如count为3,baseminor为10,则会分配三个次设备号(10、11、12),name表示设备文件名称。

第三步:使用cdev_add函数将字符设备添加到内核中的字符设备数组中

    cdev_add函数将字符设备添加到probes数组中,probes数组中保存着已建立的字符设备。

第四步:使用class_create宏创建struct class

    struct class 包含了一些与设备文件有关的变量以及一些回调函数指针变量。这一步是为下一步创建设备文件做准备。

第五步:使用device_create函数创建设备文件

    最终会生成 /dev/breath_leds 节点。


二)配置Kconfig

    在breath_leds目录下新建Kconfig:

config BREATH_LEDS
	tristate "breath leds driver"
	default y
tristate表示在编译内核时,breath_leds模块支持三种编译方法:模块,内建和不编译。y表示内建,即编译进内核。


三)配置Makefile

   1, 在breath_leds目录下新建Makefile:

obj-$(CONFIG_BREATH_LEDS) := breath_leds.o
#obj-y := breath_leds.o
    2,在breath_leds父目录下的Makefile中,加入:

obj-$(CONFIG_BREATH_LEDS)	+= breath_leds/
这样编译时才能编译到该驱动


四)配置系统的audoconfig

    打开mediatek/config/$project/autoconfig/kconfig/project,加入

CONFIG_BREATH_LEDS=y

MTK自加的驱动模块基本上都没带Kconfig,难道是在该文件中统一指定CONFIG_XXX ?


五)编译

    ./mk $project n k bootimage

即可,打开手机文件系统,可看到/dev/breath_leds设备文件。

   

   


  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值