转载地址: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 ,使用字符设备进行读写。
原理图:
设备地址为 0x50
设备树:
&i2c_0{
eeprom@50 {//它对应于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");