1. 添加主控設備支持
i2c主控制器也是一個設備,只是在內核被虛擬成了一個平臺設備,平臺設備在內核中已經被定義了,定義在plat-s3c/dev-i2c0.c中:
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
那麼我只需要將這個平臺設備添加到mini2440_devices這個數組裏面,最終這個平臺設備將被註冊到內核中。
2. 添加主控驅動支持
主控制器驅動爲drivers/i2c/busses/i2c-s3c2410.c,那麼我們只需要配置一下就可以了:
Device Drivers ---> <*> I2C support ---> I2C Hardware Bus support ---> <*> S3C2410 I2C Driver
3. at24c08 i2c設備的註冊
這裏使用i2c_register_board_info()函數來註冊i2c設備,首先是使用I2C_BOARD_INFO宏來定義i2c設備信息:
static struct at24_platform_data at24_platdata = {
.byte_len = 8192,
.page_size = 16,
};
static struct i2c_board_info mini2440_i2c_devices[] = {
{
I2C_BOARD_INFO("24c08", 0x50),
.platform_data = &at24_platdata,
}
};
mini2440上at24c08的i2c地址爲0x50,並且是掛載到i2c0上的,在mini2440_machine_init去完成註冊:i2c_register_board_info(0, mini2440_i2c_devices, ARRAY_SIZE(mini2440_i2c_devices));
其中的第一個參數需要注意,爲i2c的總線號。
最後還需要注意別忘了包含這兩個頭文件:
<linux/i2c.h>
<linux/i2c/at24.h>
4. at24c08驅動支持
驅動代碼就用默認的drivers/misc/eeprom/at24.c,配置如下:
Device Drivers ---> [*] Misc devices ---> EEPROM support ---> -*- I2C EEPROMs from most vendors
5. 驗證
那麼怎麼去驗證我們添加的i2c驅動和i2c設置是匹配成功的了呢,進入到這個目錄下:/sys/bus/i2c/drivers/at24,然後可以看到一個文件0-0050,說明i2c驅動和i2c設備是匹配成功了的。
//2016.3.27 add
linux自帶的eeprom驅動程序可能不便於測試,爲此,我寫了個簡單的i2c驅動用來測試at24c08,代碼如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
struct at24_data {
struct cdev cdev;
struct i2c_client *client;
};
int at24_read_block(struct i2c_client *client,
u8 addr, u8 len, u8 *values)
{
return i2c_smbus_read_i2c_block_data(client, addr, len, values);
}
int at24_write_block(struct i2c_client *client,
u8 addr, u8 len, u8 *values)
{
return i2c_smbus_write_i2c_block_data(client, addr, len, values);
}
#define BLOCK_MAX 32
static size_t at24_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct at24_data *at24 = file->private_data;
int num;
u8 block[BLOCK_MAX];
if (count > BLOCK_MAX)
count = BLOCK_MAX;
num = at24_read_block(at24->client, *pos, count, block);
if (copy_to_user(buf, block, num)) {
return -EFAULT;
} else {
*pos += num;
return num;
}
}
static size_t at24_write(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct at24_data *at24 = file->private_data;
int num;
u8 block[BLOCK_MAX];
if (count > BLOCK_MAX)
count = BLOCK_MAX;
if (copy_from_user(block, buf, count)) {
return -EFAULT;
} else {
num = at24_write_block(at24->client, *pos, count, block);
*pos += num;
return num;
}
}
static size_t at24_open(struct inode *inode, struct file *file)
{
struct at24_data *at24;
at24 = container_of(inode->i_cdev, struct at24_data, cdev);
file->private_data = at24;
return 0;
}
static size_t at24_close(struct inode *inode, struct file *file)
{
file->private_data = NULL;
return 0;
}
static struct file_operations at24_fops = {
.open = at24_open,
.release = at24_close,
.read = at24_read,
.write = at24_write,
};
static int at24_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct at24_data *at24;
int retval = 0;
dev_t dev_id;
at24 = kzalloc(sizeof(struct at24_data), GFP_KERNEL);
if (!at24) {
return -ENOMEM;
}
at24->client = client;
retval = alloc_chrdev_region(&dev_id, 0, 1, "at24c08");
if (retval < 0) {
dev_err(&client->dev,
"unable to allocate char device region\n");
goto err_out;
}
cdev_init(&at24->cdev, &at24_fops);
retval = cdev_add(&at24->cdev, dev_id, 1);
if (retval < 0) {
dev_err(&client->dev,
"unable to register char device\n");
goto err_free_region;
}
i2c_set_clientdata(client, at24);
return 0;
err_free_region:
unregister_chrdev_region(dev_id, 1);
err_out:
kfree(at24);
return -1;
}
static int at24_remove(struct i2c_client *client)
{
struct at24_data *at24 = i2c_get_clientdata(client);
cdev_del(&at24->cdev);
kfree(at24);
unregister_chrdev_region(at24->cdev.dev, 1);
return 0;
}
static struct i2c_device_id at24_ids[] = {
{ "at24c08", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, at24_ids);
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24c08",
},
.id_table = at24_ids,
.probe = at24_probe,
.remove = at24_remove,
};
static int __init at24_init(void)
{
return i2c_add_driver(&at24_driver);
}
static void __exit at24_exit(void)
{
i2c_del_driver(&at24_driver);
}
module_init(at24_init);
module_exit(at24_exit);
編譯好了之後,首先insmod,然後mknod,測試app程序如下:#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
//#define TEST_WRITE 1
#define TEST_READ 1
int main(int argc, char *argv[])
{
int fd;
int i;
char buf[8];
if (argc < 2)
exit(1);
fd = open(argv[1], O_RDWR);
if (fd < 0)
exit(1);
#ifdef TEST_WRITE
for (i = 0; i < sizeof(buf); i++)
buf[i] = i;
write(fd, buf, sizeof(buf));
#endif
#ifdef TEST_READ
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf));
for (i = 0; i < sizeof(buf); i++)
printf("%d ", buf[i]);
printf("\n");
#endif
close(fd);
exit(0);
}
最後測試得即使掉電之後也能正確讀取到寫入的數據,說明程序是ok的。
// 2016-05-15 add
另一個i2c read函數
int at24_read_array(struct i2c_client *client,
u8 addr, u8 len, u8 *vals)
{
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = &addr
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = len,
.buf = vals
}
};
int ret;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret < 0)
printk("i2c_transfer error\n");
return ret;
}
這裏使用i2c_transfer來操作i2c,注意i2c_transfer的返回值,正確返回的是傳輸i2c_msg的個數。