본문 바로가기
device driver

charter device driver - 7. i2c platform driver 만들기

by jsh91 2023. 5. 28.
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/uaccess.h>

struct ioexp_dev {
	struct i2c_client * client;
	struct miscdevice ioexp_miscdevice;
	char name[8];
};

static ssize_t ioexp_read_file(struct file *file, char __user *userbuf,
                               size_t count, loff_t *ppos)
{
	int ret, recv_size, size;
	char buf[10] = {0,};
	char recv_buf[10] ={0,};
	struct ioexp_dev * ioexp;

	ioexp = container_of(file->private_data,
			     struct ioexp_dev, 
			     ioexp_miscdevice);

	/* 
	ioexp->client를 사용하여 ioexp로 부터 data를 읽는다.
	이를 위해 prob() 함수에서 "ioexp->client = client;" 코드가 적용
	 */

	/*
	int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
	client: 데이터를 받을 I2C 슬레이브 장치를 나타내는 i2c_client 구조체의 포인터입니다.
	buf: 수신한 데이터를 저장할 버퍼입니다.
	count: 버퍼에 저장할 데이터의 바이트 수를 나타냅니다.
 	함수는 성공적으로 수신된 바이트 수를 반환하거나, 에러가 발생하면 음수 errno 코드를 반환
	*/
	ret = i2c_master_recv(ioexp->client,recv_buf, 1 );
	
	if (ret < 0)
		return -EFAULT;


	/*만약 cat /proc/ioexp 할 경우 ppos를 사용하여 처음 읽었을 때만 user 영역에 데이타 전달*/
	if(*ppos == 0)
	{
		pr_info("read data from i2c device : [%x]\n",recv_buf[0] );
	/* 
      읽은 값은 char 정수 값이다 
	  하지만 user 에 data를 전달하기 위해서 문자로 변환한다
	 */
		size = sprintf(buf, "%02x", recv_buf[0]);
		buf[size] = '\n';
		
		if(copy_to_user(userbuf, buf, size+1)){
			pr_info("Failed to send data to user space\n");
			return -EFAULT;
		}
		
		*ppos+=(size+1);
		return size+1;
	}

	/*만약 cat /proc/ioexp 할 경우 2번째 읽을때 종료하기 위해 0을 return 한다.*/
	return 0;
}

static ssize_t ioexp_write_file(struct file *file, const char __user *userbuf,
                                   size_t count, loff_t *ppos)
{
	int ret;
	unsigned long val;
	char buf[4] ={0,};
	char send_buf[2] = {0,};
	struct ioexp_dev * ioexp;

	ioexp = container_of(file->private_data,
			     struct ioexp_dev, 
			     ioexp_miscdevice);

	dev_info(&ioexp->client->dev, 
		 "ioexp_write_file entered on %s\n", ioexp->name);

	dev_info(&ioexp->client->dev,
		 "we have written %zu characters\n", count); 

	if(copy_from_user(buf, userbuf, count)) {
		dev_err(&ioexp->client->dev, "Bad copied value\n");
		return -EFAULT;
	}

	buf[count-1] = '\0';


	/*
	int kstrtoul(const char *s(from), unsigned int base, unsigned long *res(to));
	문자열을 숫자로 변환
	- s는 변환할 문자열입니다.
    - base는 문자열의 기수입니다. (예를 들어, 10은 십진수, 16은 16진수 등)
    - res는 결과 unsigned long 값을 저장할 변수의 포인터
	kstrtoul 함수는 성공 시 0을 반환하며, 에러 발생 시 에러 코드를 반환합니다. 
	변환된 unsigned long 값은 res가 가리키는 변수에 저장
	*/
	ret = kstrtoul(buf, 0, &val);
	if (ret)
		return -EINVAL;

	dev_info(&ioexp->client->dev, "the value is %x\n", val);

	send_buf[0] = val;
	
	/*
	int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
	client: 데이터를 보낼 I2C 슬레이브 장치를 나타내는 i2c_client 구조체의 포인터입니다.
    buf: 전송할 데이터를 가지고 있는 버퍼입니다.
    count: 버퍼에 있는 데이터의 바이트 수를 나타냅니다.
	*/
	ret = i2c_master_send(ioexp->client,send_buf,1);
	if (ret < 0)
		dev_err(&ioexp->client->dev, "failed to write data\n");

	dev_info(&ioexp->client->dev, 
		 "ioexp_write_file exited on %s\n", ioexp->name);

	return count;
}

static const struct file_operations ioexp_fops = {
	.owner = THIS_MODULE,
	.read = ioexp_read_file,
	.write = ioexp_write_file,
};

static int ioexp_probe(struct i2c_client * client,
		const struct i2c_device_id * id)
{
	static int counter = 0;
	int ret =0;
	struct ioexp_dev * ioexp;

	/*void *devm_kzalloc(struct device *dev, size_t size, gfp_t flags)*/
	ioexp = devm_kzalloc(&client->dev, sizeof(struct ioexp_dev), GFP_KERNEL);

	/* 
	i2c_set_clientdata(to, from) 함수는 from data를 to(i2c_client)->dev.driver_data 에 저장한다
	dev_set_drvdata() 함수와 동일
	  */
	i2c_set_clientdata(client,ioexp);

	/* 
	struct ioexp_dev에서도 이 I2C 클라이언트를 참조
	struct ioexp_dev와 관련된 코드 어디에서든 I2C 클라이언트에 대한 액세스를 쉽게 접근 가능
	*/
	ioexp->client = client;

	sprintf(ioexp->name, "ioexp%02d", counter++); 
	dev_info(&client->dev, 
		 "ioexp_probe is entered on %s\n", ioexp->name);

	ioexp->ioexp_miscdevice.name = ioexp->name;
	ioexp->ioexp_miscdevice.minor = MISC_DYNAMIC_MINOR;
	ioexp->ioexp_miscdevice.fops = &ioexp_fops;

	ret =  misc_register(&ioexp->ioexp_miscdevice);

	if(ret==0)
	dev_info(&client->dev, 
		 "ioexp_probe is exited on %s\n", ioexp->name);
	else
	dev_info(&client->dev, 
		 "ioexp_probe is error on %s\n", ioexp->name);

	return 0;
}

static int ioexp_remove(struct i2c_client * client)
{
	struct ioexp_dev * ioexp;

	
	/* dev_get_drvdata() 함수와 동일 */
	ioexp = i2c_get_clientdata(client);

	dev_info(&client->dev, 
		 "ioexp_remove is entered on %s\n", ioexp->name);

	misc_deregister(&ioexp->ioexp_miscdevice);

	dev_info(&client->dev, 
		 "ioexp_remove is exited on %s\n", ioexp->name);

	return 0;
}

static const struct of_device_id ioexp_dt_ids[] = {
	{ .compatible = "arrow,ioexp", },
	{ }
};
MODULE_DEVICE_TABLE(of, ioexp_dt_ids);

static const struct i2c_device_id i2c_ids[] = {
	{ .name = "ioexp_ids_name", },
	{ }
};

/* i2c device 이므로 i2c로 작성*/
MODULE_DEVICE_TABLE(i2c, i2c_ids);

static struct i2c_driver ioexp_driver = {
	.driver = {
		.name = "ioexp_driver_name",
		.owner = THIS_MODULE,
		.of_match_table = ioexp_dt_ids,
	},
	.probe = ioexp_probe,
	.remove = ioexp_remove,
	.id_table = i2c_ids,
};

/* insmod 시 i2c를 바로 등록 */
module_i2c_driver(ioexp_driver);

MODULE_LICENSE("GPL");

 

device tree

  &i2c1 {
      pinctrl-names = "default";
      pinctrl-0 = <&i2c1_pins>;
      clock-frequency = <100000>;
     status = "okay";


     ioexp@26 {
          compatible = "arrow,ioexp";
          reg = <0x26>;
     };

예제에서 사용한 i2d device는 PCF8574 이다

output 단자는 8개이다

data 8bit 로 output 출력이 결정된다

ex)

0b10010001 -> output port : 1,5,8 

 

 i2cdetect -y 명령어는 사용하면 현재 i2c bus의 상태를 확인 할 수 있다

board@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

i2c device가 연결되어 있다면 아래처럼 숫자로 표시가 된다.

root@raspberrypi:/home/board# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- 26 -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

insmod 1_io_expander.ko

insmod를 하면 module에서 사용하는 addres에 UU(Userland Usage)로 표시된다.

root@raspberrypi:/home/board# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- UU -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

 

 

insmod 1_io_expander.ko

[  188.700887] ioexp_driver_name 1-0026: ioexp_probe is entered on ioexp00
[  188.704149] ioexp_driver_name 1-0026: ioexp_probe is exited on ioexp00

 

echo 200 > /dev/ioexp00

[  230.281363] ioexp_driver_name 1-0026: ioexp_write_file entered on ioexp00
[  230.281386] ioexp_driver_name 1-0026: we have written 4 characters
[  230.281404] ioexp_driver_name 1-0026: the value is c8
[  230.281673] ioexp_driver_name 1-0026: ioexp_write_file exited on ioexp00

 

cat /dev/ioexp00

c8

 

 

root@raspberrypi:/home/board# ls -l /dev/ioexp00
crw------- 1 root root 10, 60 May 27 19:44 /dev/ioexp00

 

 

rmmod 1_io_expander.ko

[  391.122952] ioexp_driver_name 1-0026: ioexp_remove is entered on ioexp00
[  391.126103] ioexp_driver_name 1-0026: ioexp_remove is exited on ioexp00

 

 

root@raspberrypi:/# find ./ -name *ioexp_driver_name*
./sys/bus/i2c/drivers/ioexp_driver_name
./sys/module/1_io_expander/drivers/i2c:ioexp_driver_name

 

root@raspberrypi:/# find ./ -name *ioexp*
./sys/devices/virtual/misc/ioexp00
./sys/class/misc/ioexp00
./sys/firmware/devicetree/base/soc/i2c@7e804000/ioexp@26
./sys/bus/i2c/drivers/ioexp_driver_name
./sys/module/1_io_expander/drivers/i2c:ioexp_driver_name
./dev/ioexp00

댓글