【linux iic子系统】gpio模拟i2c(八)

9 篇文章 5 订阅

刚学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!");

随意写个测试程序就好了~~~

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LinuxI2C子系统是用于在Linux内核中管理和操作I2C总线的子系统。它提供了一组API和驱动程序,允许用户空间应用程序与连接到I2C总线上的设备进行通信。 在Linux中,I2C子系统由以下几个主要组件组成: 1. I2C核心驱动程序:这是I2C子系统的核心部分,负责管理I2C总线和设备的注册、协议处理等功能。它提供了一组API供其他驱动程序或用户空间应用程序使用。 2. I2C适配器驱动程序:这些驱动程序用于支持特定的硬件I2C适配器,如FPGA、SOC等。它们与I2C核心驱动程序紧密配合,负责将硬件特定的操作转换为通用的I2C操作。 3. I2C设备驱动程序:这些驱动程序用于支持连接到I2C总线上的具体设备。每个I2C设备都有一个对应的设备驱动程序,负责处理设备的初始化、通信协议等。在Linux中,这些设备驱动程序通常作为内核模块存在。 4. I2C工具和库:除了内核驱动程序外,Linux还提供了一些用户空间工具和库,用于与I2C设备进行交互。例如,`i2cdetect`工具用于检测I2C总线上的设备地址,`i2cget`和`i2cset`工具用于读取和写入I2C设备的寄存器值。 用户空间应用程序可以使用I2C子系统提供的API和工具来访问和控制连接到I2C总线上的设备。通过打开适当的设备节点文件,并使用相应的读写操作,可以向设备发送命令和数据,以及从设备读取响应和数据。 总而言之,LinuxI2C子系统提供了一套完整的解决方案,使用户能够方便地在Linux环境中操作和管理I2C设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值