刚学i2c子系统时写的程序,一个简单的参考~~~
注意,gpio的硬件地址信息需要根据实际填写~~
gpio_i2c.h文件
#ifndef _HIK_GPIO_I2C_H
#define _HIK_GPIO_I2C_H
int init_gpio(void);
void exit_gpio(void);
long gpio_tmp_read_data(struct i2c_client *client, unsigned char reg_addr);
long gpio_tmp_write_data(struct i2c_client *client, unsigned char reg_addr, unsigned long data);
#endif
gpio_i2c.c文件
#include <linux/fs.h>
#include <linux/uaccess.h>
#include<linux/delay.h>
#include <linux/i2c.h>
#define SBREG_BAR 0xFD000000 /*bios provide*/
#define SZ_64K 0x00010000
#define GPP_COMM0_BASE (SBREG_BAR + (0xAF << 16)) /*GPP_A/B*/
#define GPP_COMM1_BASE (SBREG_BAR + (0xAE << 16)) /*GPP_C/D/E/F/G/H*/
#define CLEAR_PAD_MODE (~(0b111 << 10))
#define SET_PAD_MODE_IS_GPIO (0b000 << 10)
#define SET_RX_TX_MODE (~(0b11 << 8))
#define C_GROUP_OFFSET_ADDR 0x400
#define PIN(n) (C_GROUP_OFFSET_ADDR + n*8)
#define C0 0
#define C1 1
#define RX_BIT 1
#define TX_BIT 0
#define W_FLAG 0
#define R_FLAG 1
#define SDA_IN(PIN_REG) (*PIN_REG = *PIN_REG | (1 << 8))
#define SDA_OUT(PIN_REG) (*PIN_REG = *PIN_REG & (~(1 << 8)))
#define SET_GPIO(PIN_REG, val) (val?(*PIN_REG = *PIN_REG | (0b1 << TX_BIT)) \
: (*PIN_REG = *PIN_REG & (~(0b1 << TX_BIT))))
#define GET_GPIO(PIN_REG) ((*PIN_REG & (1 << RX_BIT))?1:0)
static volatile unsigned char *base = NULL;
static volatile unsigned int *CLK = NULL;
static volatile unsigned int *SDA = NULL;
static unsigned int slk_old_reg_value = 0;
static unsigned int sda_old_reg_value = 0;
int init_gpio(void)
{
base = ioremap(GPP_COMM1_BASE, SZ_64K);
if (NULL == base)
{
return -EINVAL;
printk("ioremap failed!\n");
}
CLK = (unsigned int *)(base + PIN(C0));
SDA = (unsigned int *)(base + PIN(C1));
return 0;
}
void exit_gpio(void)
{
CLK = NULL;
SDA = NULL;
iounmap(base);
}
static void gpio_i2c_start(void)
{
SDA_OUT(SDA);
mb();//配置引脚方向与设置电平之间加入内存屏障
SET_GPIO(SDA, 1);
ndelay(10);//少量延时,等待信号稳定
SET_GPIO(CLK, 1);
udelay(2);//TMP75时序要求重复开始信号间隔最少1300ns
SET_GPIO(SDA, 0);
udelay(2);//TMP75时序要求SDA拉低后最少600ns才能拉低CLK产生start信号
SET_GPIO(CLK, 0);
}
static void gpio_i2c_stop(void)
{
SDA_OUT(SDA);
SET_GPIO(CLK, 0);
ndelay(10);//少量延时,等待信号稳定
SET_GPIO(SDA, 0);
udelay(2);
SET_GPIO(CLK, 1);
udelay(2);//TMP75时序要求CLK拉高后最少600ns才能拉高SDA产生stop信号
SET_GPIO(SDA, 1);
udelay(2);//TMP75时序要求开始信号与结束信号最少间隔600ns
}
static int gpio_i2c_wait_ack(void)
{
unsigned char cut = 0;
SDA_IN(SDA);
udelay(2);//CLK最少保持1300ns低电平时间
SET_GPIO(CLK, 1);
udelay(1);
while (GET_GPIO(SDA))
{
cut++;
if (cut > 250)
{
SET_GPIO(CLK, 0);
ndelay(10);
gpio_i2c_stop();
return -1;
}
ndelay(10);
}
ndelay(600);//CLK最少保持600ns高电平时间
SET_GPIO(CLK, 0);
return 0;
}
static void gpio_i2c_ack(void)
{
SET_GPIO(CLK, 0);
SDA_OUT(SDA);
udelay(2);
SET_GPIO(SDA, 0);//SDA为0表示ACK
udelay(2);
SET_GPIO(CLK, 1);//高电平时间最少保持600ns
udelay(2);
SET_GPIO(CLK, 0);
}
static void gpio_i2c_nack(void)
{
SET_GPIO(CLK, 0);
SDA_OUT(SDA);
udelay(2);
SET_GPIO(SDA, 1);//SDA为1表示NACK
udelay(2);
SET_GPIO(CLK, 1);//高电平时间最少保持600ns
udelay(2);
SET_GPIO(CLK, 0);
}
static void gpio_i2c_send_bety(unsigned char data)
{
unsigned char i;
SDA_OUT(SDA);
SET_GPIO(CLK, 0);
ndelay(100);
for (i = 0; i < 8; i++)
{
if (data & 0x80)
{
SET_GPIO(SDA, 1);
}
else
{
SET_GPIO(SDA, 0);
}
data = data << 1;
udelay(2);//低电平时间最少保持1300ns
SET_GPIO(CLK, 1);//高电平时间最少保持600ns
udelay(2);
SET_GPIO(CLK, 0);
}
}
static unsigned char gpio_i2c_read_bety(void)
{
unsigned char data = 0;
unsigned char i;
SDA_IN(SDA);
for (i = 0; i < 8; i++)
{
udelay(2);
SET_GPIO(CLK, 1);
data = data << 1;
if (GET_GPIO(SDA))
{
data |= 1;
}
udelay(2);
SET_GPIO(CLK, 0);
}
return data;
}
long gpio_tmp_read_data(struct i2c_client *client, unsigned char reg_addr)
{
int ret = 0;
long data = 0;
unsigned char addr = (unsigned char)(client->addr);
addr = (addr << 1) | W_FLAG;
slk_old_reg_value = *CLK;
sda_old_reg_value = *SDA;
*CLK = *CLK & CLEAR_PAD_MODE & SET_RX_TX_MODE;
mb();//内存屏障,防止指令重排,确保能设置成GPIO模式
*CLK = *CLK | SET_PAD_MODE_IS_GPIO;
*SDA = *SDA & CLEAR_PAD_MODE & SET_RX_TX_MODE;
mb();//内存屏障,防止指令重排,确保能设置成GPIO模式
*SDA = *SDA | SET_PAD_MODE_IS_GPIO;
gpio_i2c_start();
gpio_i2c_send_bety(addr);
ret = gpio_i2c_wait_ack();
gpio_i2c_send_bety(reg_addr);
ret = gpio_i2c_wait_ack();
gpio_i2c_start();
addr |= R_FLAG;
gpio_i2c_send_bety(addr);
ret = gpio_i2c_wait_ack();
data = gpio_i2c_read_bety();
gpio_i2c_ack();
data |= (gpio_i2c_read_bety() << 8);
gpio_i2c_nack();
gpio_i2c_stop();
*CLK = slk_old_reg_value;
*SDA = sda_old_reg_value;
if (ret)
{
data = -EINVAL;;
}
return data;
}
long gpio_tmp_write_data(struct i2c_client *client, unsigned char reg_addr, unsigned long data)
{
long ret = 0;
unsigned char addr = (unsigned char)(client->addr);
addr = (addr << 1) | W_FLAG;
slk_old_reg_value = *CLK;
sda_old_reg_value = *SDA;
*CLK = *CLK & CLEAR_PAD_MODE & SET_RX_TX_MODE;
*CLK = *CLK | SET_PAD_MODE_IS_GPIO;
*SDA = *SDA & CLEAR_PAD_MODE & SET_RX_TX_MODE;
*SDA = *SDA | SET_PAD_MODE_IS_GPIO;
gpio_i2c_start();
gpio_i2c_send_bety(addr);
ret = gpio_i2c_wait_ack();
gpio_i2c_send_bety(reg_addr);
ret = gpio_i2c_wait_ack();
gpio_i2c_send_bety((data) & 0xff);
ret = gpio_i2c_wait_ack();
gpio_i2c_send_bety((data >> 8) & 0xff);
ret = gpio_i2c_wait_ack();
gpio_i2c_stop();
*CLK = slk_old_reg_value;
*SDA = sda_old_reg_value;
if (ret)
{
ret = -EINVAL;;
}
return ret;
}
这里由tmp75传感器注册字符设备,进而提供给上层使用。
tmp75_gpio_i2c.c文件。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include "gpio_i2c.h"
#define MY_I2C_MAGIC_NUMBER 'k'
#define GET_TEMP _IO(MY_I2C_MAGIC_NUMBER, 0)
#define GET_CONFIG _IO(MY_I2C_MAGIC_NUMBER, 1)
#define GET_TLOW_REG _IO(MY_I2C_MAGIC_NUMBER, 2)
#define GET_THIGH_REG _IO(MY_I2C_MAGIC_NUMBER, 3)
#define SET_CONFIG _IO(MY_I2C_MAGIC_NUMBER, 4)
#define SET_TLOW_REG _IO(MY_I2C_MAGIC_NUMBER, 5)
#define SET_THIGH_REG _IO(MY_I2C_MAGIC_NUMBER, 6)
#define TMP_REG_ADDR 0x00
#define TMP_CFIG_ADDR 0x01
#define TMP_TLOW_ADDR 0x02
#define TMP_THIGH_ADDR 0x03
#define DEV_NAME "my_i2c"
#define NAME_SIZE 32
#define NINOR_MAX_NUM 4095
#define DEV_ADD_NUM 1
static int my_debug = 1;
module_param(my_debug, int, 0644);
MODULE_PARM_DESC(my_debug, "enable debugging informationg");
#define debug_printk(ftm, args...) if(my_debug){printk(ftm, ##args);}
unsigned char TMP_ADDR[] = {TMP_REG_ADDR, TMP_CFIG_ADDR, \
TMP_TLOW_ADDR, TMP_THIGH_ADDR, \
TMP_CFIG_ADDR, TMP_TLOW_ADDR, TMP_THIGH_ADDR};
struct my_tmp75_device
{
struct i2c_client *client;
struct cdev tmp75_cdev;
struct mutex my_mutex;
char name[NAME_SIZE];
};
struct tmp75_i2c_drv {
dev_t dev;
struct class *my_i2c_class;
unsigned int dev_count;
unsigned int minor_count;
};
static struct tmp75_i2c_drv my_i2c_drv = {
.dev = 0,
.my_i2c_class = NULL,
.dev_count = 0,
.minor_count = 0,
};
static int my_tmp75_open(struct inode *inode, struct file *filp)
{
struct my_tmp75_device *tmp75_device;
tmp75_device = container_of(inode->i_cdev, struct my_tmp75_device, tmp75_cdev);
filp->private_data = tmp75_device;
return 0;
}
static long my_tmp75_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret = 0;
unsigned long data;
struct my_tmp75_device *tmp75_device;
tmp75_device = filp->private_data;
if ((_IOC_TYPE(cmd) != MY_I2C_MAGIC_NUMBER))
{
printk("%s:pid = %d, command type [%c] error!\n", __func__, current->pid, _IOC_TYPE(cmd));
return -EINVAL;
}
//加锁,防止多线程操作
mutex_lock(&tmp75_device->my_mutex);
switch (_IOC_NR(cmd))
{
case _IOC_NR(GET_TEMP):
case _IOC_NR(GET_CONFIG):
case _IOC_NR(GET_TLOW_REG):
case _IOC_NR(GET_THIGH_REG):
{
//ret = tmp_read_data(tmp75_device->client, TMP_ADDR[_IOC_NR(cmd)]);
ret = gpio_tmp_read_data(tmp75_device->client, TMP_ADDR[_IOC_NR(cmd)]);
if (ret > 0)
{
data = (unsigned long)ret;
ret = copy_to_user((void *)arg, &data, sizeof(data));
}
break;
}
case _IOC_NR(SET_CONFIG):
case _IOC_NR(SET_TLOW_REG):
case _IOC_NR(SET_THIGH_REG):
{
ret = copy_from_user(&data, (void *)arg, sizeof(data));
if (0 == ret)
{
//ret = tmp_write_data(tmp75_device->client, TMP_ADDR[_IOC_NR(cmd)], data);
ret = gpio_tmp_write_data(tmp75_device->client, TMP_ADDR[_IOC_NR(cmd)], data);
}
break;
}
default:
{
printk("%s:pid = %d, command error!\n", __func__, current->pid);
ret = -EINVAL;
break;
}
}
mutex_unlock(&tmp75_device->my_mutex);
if (ret < 0)
{
debug_printk("read/write failed!\n");
}
return ret;
}
//i2c设备的操作方法集
static const struct file_operations my_tmp75_fops = {
.owner = THIS_MODULE,
.open = my_tmp75_open,
.unlocked_ioctl = my_tmp75_ioctl,
};
//驱动与设备的匹配列表
static const struct i2c_device_id my_tmp75_ids[] = {
{.name = "my_tmp75", 0},
{.name = "my_tmp175", 1},
{ },
};
MODULE_DEVICE_TABLE(i2c, my_tmp75_ids);
static int my_tmp75_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct my_tmp75_device *tmp75_dev;
struct device *my_i2c_class_dev;
dev_t tmep_dev;
int ret = 0;
//devm_kzalloc分配的托管内存,当设备或驱动卸载时自动释放
tmp75_dev = devm_kzalloc(&client->dev, sizeof(struct my_tmp75_device), GFP_KERNEL);
if (NULL == tmp75_dev)
{
printk("devm_kzalloc fail!\n");
return -ENOMEM;
}
tmp75_dev->client = client;
mutex_init(&(tmp75_dev->my_mutex));
//设置设备节点名称,根据设备名称、总线号、设备地址来设置,不会相同
sprintf(tmp75_dev->name, "%s-%d-0x%x", client->name, client->adapter->nr, client->addr);
i2c_set_clientdata(client, tmp75_dev);//设置tmp75_dev为client中dev的私有数据
//如果是第一次注册字符驱动,则自动分配设备号,否则在原来主设备号基础上增加新设备
if (my_i2c_drv.dev)//如果设备号为空,表示第一次注册
{
tmep_dev = MKDEV(MAJOR(my_i2c_drv.dev), MINOR(my_i2c_drv.dev) + my_i2c_drv.minor_count);
ret = register_chrdev_region(tmep_dev, DEV_ADD_NUM, tmp75_dev->name);
}
else
{
ret = alloc_chrdev_region(&my_i2c_drv.dev, 0, DEV_ADD_NUM, tmp75_dev->name);
tmep_dev = my_i2c_drv.dev;
}
if (ret)//字符驱动注册失败
{
printk("failed to char device region,minor_count = %d\n", my_i2c_drv.minor_count);
goto char_region_fail;
}
cdev_init(&tmp75_dev->tmp75_cdev, &my_tmp75_fops);
ret = cdev_add(&tmp75_dev->tmp75_cdev, tmep_dev, DEV_ADD_NUM);
if (ret)
{
printk("cdev_add failed\n");
goto cdev_add_fail;
}
if (NULL == my_i2c_drv.my_i2c_class)//如果设备类指针为空,表示第一次注册
{
my_i2c_drv.my_i2c_class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(my_i2c_drv.my_i2c_class))
{
ret = PTR_ERR(my_i2c_drv.my_i2c_class);
printk("class_create failed\n");
goto class_create_fail;
}
}
//每一次设备与驱动匹配成功,都创建设备节点
my_i2c_class_dev = device_create(my_i2c_drv.my_i2c_class, \
NULL, tmep_dev, NULL, tmp75_dev->name);
if (IS_ERR(my_i2c_class_dev))
{
ret = PTR_ERR(my_i2c_class_dev);
printk("device_create failed\n");
goto device_create_fail;
}
ret = init_gpio();
if (ret)
{
printk("init_gpio fail!\n");
}
my_i2c_drv.dev_count++;//记录当前存活设备
//记录次设备号,用于下一次注册设备,最多不超过4095个次设备
//my_i2c_drv.minor_count = (++my_i2c_drv.minor_count)&NINOR_MAX_NUM;
my_i2c_drv.minor_count = (my_i2c_drv.minor_count + 1)&NINOR_MAX_NUM;
debug_printk("register %s successed! dev_count = %d,minor_count = %d\n", \
tmp75_dev->name, my_i2c_drv.dev_count, my_i2c_drv.minor_count);
return 0;
device_create_fail:
if (!my_i2c_drv.dev_count)
{
class_destroy(my_i2c_drv.my_i2c_class);
}
my_i2c_drv.my_i2c_class = NULL;
class_create_fail:
cdev_del(&tmp75_dev->tmp75_cdev);
cdev_add_fail:
unregister_chrdev_region(tmep_dev, DEV_ADD_NUM);
char_region_fail:
devm_kfree(&client->dev, tmp75_dev);
tmp75_dev = NULL;
return ret;
}
static int my_tmp75_remove(struct i2c_client *client)
{
struct my_tmp75_device *tmp75_dev;
tmp75_dev = i2c_get_clientdata(client);
exit_gpio();
my_i2c_drv.dev_count--;//每卸载一个设备,则设备存活计数减1
device_destroy(my_i2c_drv.my_i2c_class, tmp75_dev->tmp75_cdev.dev);
if (!my_i2c_drv.dev_count)
{
class_destroy(my_i2c_drv.my_i2c_class);
my_i2c_drv.my_i2c_class = NULL;
}
cdev_del(&tmp75_dev->tmp75_cdev);
unregister_chrdev_region(tmp75_dev->tmp75_cdev.dev, DEV_ADD_NUM);
debug_printk("%sx is removed!\n", tmp75_dev->name);
return 0;
}
struct i2c_driver my_tmp75_driver = {
.driver = {
.name = "my_i2c",
.owner = THIS_MODULE,
},
.probe = my_tmp75_probe,
.remove = my_tmp75_remove,
.id_table = my_tmp75_ids,
};
static int __init my_tmp75_driver_init(void)
{
return i2c_add_driver(&my_tmp75_driver);
}
static void __exit my_tmp75_driver_exit(void)
{
i2c_del_driver(&my_tmp75_driver);
}
module_init(my_tmp75_driver_init);
module_exit(my_tmp75_driver_exit);
//module_i2c_driver(my_tmp75_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("caodongwang");
MODULE_DESCRIPTION("This i2c driver for my tmp75!");
随意写个测试程序就好了~~~