设备树学习之(八)eeprom

 

转载地址:https://blog.csdn.net/lizuobin2/article/details/54565121

开发板:tiny4412SDK + S702 + 4GB Flash 

要移植的内核版本:Linux-4.4.0 (支持device tree) 
u-boot版本:友善之臂自带的 U-Boot 2010.12 
busybox版本:busybox 1.25

目标: 
驱动 tiny4412 底板上的 i2c eeprom ,使用字符设备进行读写。

原理图: 
设备树学习之(八)eeprom
设备地址为 0x50

设备树:

&i2c_0{
    [email protected] {//它对应于driverid_table中的name
        compatible = "tiny4412,eeprom";
        reg = <0x50>;
    };
};

代码:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/delay.h>

#define uchar unsigned char
#define mydebug() printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__)
/*
    24AA025E48
    2K bit 只有低 1K bit可用
    因此可以使用的范围 0 - 128 byte
    一页 16bytes
    支持写单字节,整页写操作
    支持单字节读,连续读取
*/


static int major;
static struct class *class;
static struct i2c_client *at24cxx_client;

/* 传入: buf[0] : addr
 * 输出: buf[1] : len
 */
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t count, loff_t *off)
{
    int ret, i;
    unsigned char addr, len, data[2];
    unsigned char *readbuf;
    struct i2c_msg msg[2];

    if (count != 2)
    {
        printk("%s count invalid \n", __func__);
        return -EINVAL;
    }

    ret = copy_from_user(data, buf, 2);

    if (ret < 0)
    {
        printk("%s copy_from_user error\n", __func__);
    }

    addr = data[0];
    len  = data[1];
    readbuf = kzalloc(len, GFP_KERNEL);

    if (addr + len - 1 >= 128)
    {
        printk("%s write addr len invalid \n", __func__);
        return -EINVAL;
    }

    if (len == 0)
    {
        return 0;
    }
    else
    {
        readbuf[0] = i2c_smbus_read_byte_data(at24cxx_client, addr);
        mdelay(20);
        addr += 1;
        /* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
        msg[0].addr  = at24cxx_client->addr;    /* 目的 */
        msg[0].buf   = &addr;                   /* 源 */
        msg[0].len   = 1;                       /* 地址=1 byte */
        msg[0].flags = 0;                       /* 表示写 */
        /* 然后启动读操作 */
        msg[1].addr  = at24cxx_client->addr;    /* 源 */
        msg[1].buf   = readbuf + 1;             /* 目的 */
        msg[1].len   = len - 1;                 /* 数据=1 byte */
        msg[1].flags = I2C_M_RD;                /* 表示读 */
        ret = i2c_transfer(at24cxx_client->adapter, msg, 2);

        if (ret != 2)
        {
            printk("%s i2c_transfer error \n", __func__);
            return -EINVAL;
        }
    }

    if (data < 0)
    {
        printk("%s data read  error\n", __func__);
    }

    ret = copy_to_user(buf + 2, readbuf, len);

    if (ret < 0)
    {
        printk("%s copy_from_user error\n", __func__);
    }

    kfree(readbuf);
    return count;
}

static void calHead(uchar align, uchar start, uchar len, uchar *hstart, uchar *hlen)
{
    if (start % align + len <= align)    //长度很短,不跨段
    {
        *hlen   = len;
        *hstart = start;
        return;
    }

    if (start % align == 0) //没有零散头部
    {
        *hlen   = 0;
        *hstart = start;
    }
    else                    //有零散头部
    {
        //(start % align)           范围 0 - align-1
        //align - (start % align);  范围 1 - align,start:0 <-> align
        *hlen   = align - (start % align);
        *hstart = start;
    }
}

static void calMiddle(uchar align, uchar start, uchar len, uchar *mstart, uchar *mlen, uchar *num)
{
    uchar hstart, hlen;
    calHead(align, start, len, &hstart, &hlen);
    *mstart = hstart + hlen;
    *num    = (len - hlen) / align;
    *mlen   = ((len - hlen) / align) * align;
}

static void calEnd(uchar align, uchar start, uchar len, uchar *estart, uchar *elen)
{
    uchar hstart, hlen, mstart, mlen, num;
    calHead(align, start, len, &hstart, &hlen);
    calMiddle(align, start, len, &mstart, &mlen, &num);
    *estart = mstart + mlen;
    *elen   = len - hlen - mlen;
}

/* buf[0] : addr_start
 * buf[1] : len
 * buf[n] : data
 */
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
    int ret, i;
    unsigned char addr, len;
    unsigned char pagebuf[17];
    unsigned char hstart, hlen, mstart, mlen, num, estart, elen;
    unsigned char *data = kzalloc(count, GFP_KERNEL);
    struct i2c_msg msg;

    if (count < 3)
    {
        printk("%s count invalid \n", __func__);
        return -EINVAL;
    }

    ret = copy_from_user(data, buf, count);

    if (ret < 0)
    {
        printk("%s copy_from_user error\n", __func__);
    }

    addr = data[0];
    len  = data[1];
    printk("addr %d len %d\n", addr, len);

    if (addr + len - 1 >= 128)
    {
        printk("%s write addr len invalid \n", __func__);
        return -EINVAL;
    }

    calHead(  16, addr, len, &hstart, &hlen);
    calMiddle(16, addr, len, &mstart, &mlen, &num);
    calEnd(   16, addr, len, &estart, &elen);

    for (i = hstart; i < hstart + hlen; i++)
    {
        if (i2c_smbus_write_byte_data(at24cxx_client, i, data[2 + i - addr]) < 0)
        {
            printk("%s i2c_smbus_write_byte_data %d \n", __func__, i);
            return -EINVAL;
        }

        mdelay(5);
    }

    for (i = mstart; i < mstart + mlen; i += 16)
    {
        memset(pagebuf, i, 1);  //第一个字节为要写入的地址
        memcpy(pagebuf + 1, data + 2 + i - addr, 16);
        msg.addr  = at24cxx_client->addr;       /* 设备地址 */
        msg.buf   = pagebuf;                    /* 源 */
        msg.len   = 17;                         /* 地址+数据=17 byte */
        msg.flags = 0;                          /* 表示写 */
        ret = i2c_transfer(at24cxx_client->adapter, &msg, 1);

        if (ret != 1)
        {
            printk("%s i2c_transfer error \n", __func__);
            return -EINVAL;
        }

        mdelay(5);
    }

    for (i = estart; i < estart + elen; i++)
    {
        if (i2c_smbus_write_byte_data(at24cxx_client, i, data[2 + i - addr]) < 0)
        {
            printk("%s i2c_smbus_write_byte_data %d \n", __func__, i);
            return -EINVAL;
        }

        mdelay(5);
    }

    kfree(data);
    return count;
}

static struct file_operations at24cxx_fops =
{
    .owner = THIS_MODULE,
    .read  = at24cxx_read,
    .write = at24cxx_write,
};

static int at24cxx_probe(struct i2c_client *client,
                         const struct i2c_device_id *id)
{
    at24cxx_client = client;
    //printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    major = register_chrdev(0, "at24cxx", &at24cxx_fops);
    class = class_create(THIS_MODULE, "at24cxx");
    device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
    return 0;
}

static int at24cxx_remove(struct i2c_client *client)
{
    //printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    device_destroy(class, MKDEV(major, 0));
    class_destroy(class);
    unregister_chrdev(major, "at24cxx");
    return 0;
}

static const struct i2c_device_id at24cxx_id_table[] =
{
    { "eeprom", 0 },
    {}
};


/* 1. 分配/设置i2c_driver */
static struct i2c_driver at24cxx_driver =
{
    .driver = {
        .name   = "eeprom",
        .owner  = THIS_MODULE,
    },
    .probe      = at24cxx_probe,
    .remove     = at24cxx_remove,
    .id_table   = at24cxx_id_table,
};

static int at24cxx_drv_init(void)
{
    /* 2. 注册i2c_driver */
    i2c_add_driver(&at24cxx_driver);
    return 0;
}

static void at24cxx_drv_exit(void)
{
    i2c_del_driver(&at24cxx_driver);
}


module_init(at24cxx_drv_init);
module_exit(at24cxx_drv_exit);
MODULE_LICENSE("GPL");
;