【驱动大全】

驱动大全

一、IIC

1.IIC-dev分析

在这里插入图片描述

在这里插入图片描述
i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断
    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
  • 使用id_table来判断
    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_driver跟i2c_client匹配成功后,就调用i2c_driver.probe函数。

  // 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
  # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
  
  // 删除一个i2c_client
  # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

1.1dts

&i2c1 {
		ap3216c@1e {
			compatible = "lite-on,ap3216c";
			reg = <0x1e>;
		};
};

1.2ap3216c_drv.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>

static int major = 0;
static struct class *ap3216c_class;
static struct i2c_client *ap3216c_client;

static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kernel_buf[6];
	int val;
	
	if (size != 6)
		return -EINVAL;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xA); /* read IR */
	kernel_buf[0] = val & 0xff;
	kernel_buf[1] = (val>>8) & 0xff;
	
	val = i2c_smbus_read_word_data(ap3216c_client, 0xC); /* read 光强 */
	kernel_buf[2] = val & 0xff;
	kernel_buf[3] = (val>>8) & 0xff;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xE); /* read 距离 */
	kernel_buf[4] = val & 0xff;
	kernel_buf[5] = (val>>8) & 0xff;
	
	err = copy_to_user(buf, kernel_buf, size);
	return size;
}


static int ap3216c_open (struct inode *node, struct file *file)
{
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
	/* delay for reset */
	mdelay(20);
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
	mdelay(250);
	return 0;
}


static struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open  = ap3216c_open,
	.read  = ap3216c_read,
};

static const struct of_device_id of_match_ids_ap3216c[] = {
	{ .compatible = "lite-on,ap3216c",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id ap3216c_ids[] = {
	{ "ap3216c",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ap3216c_client = client;
	
	/* register_chrdev */
	major = register_chrdev(0, "ap3216c", &ap3216c_ops);

	ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
	device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */

	return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(ap3216c_class, MKDEV(major, 0));
	class_destroy(ap3216c_class);
	
	/* unregister_chrdev */
	unregister_chrdev(major, "ap3216c");

	return 0;
}

static struct i2c_driver i2c_ap3216c_driver = {
	.driver = {
		.name = "ap3216c",
		.of_match_table = of_match_ids_ap3216c,
	},
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.id_table = ap3216c_ids,
};

static int __init i2c_driver_ap3216c_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return i2c_add_driver(&i2c_ap3216c_driver);
}
module_init(i2c_driver_ap3216c_init);

static void __exit i2c_driver_ap3216c_exit(void)
{
	i2c_del_driver(&i2c_ap3216c_driver);
}
module_exit(i2c_driver_ap3216c_exit);

MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");

1.3ap3216c_client.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/property.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
#include <linux/uaccess.h>
#include <linux/fs.h>


#if 1
static struct i2c_client *ap3216c_client;

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;

	static struct i2c_board_info board_info = {
	  I2C_BOARD_INFO("ap3216c", 0x1e),
	};

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0);
	ap3216c_client = i2c_new_device(adapter, &board_info);
	i2c_put_adapter(adapter);
	return 0;
}

#else

static struct i2c_client *ap3216c_client;

/* Addresses to scan */
static const unsigned short normal_i2c[] = {
	0x1e, I2C_CLIENT_END
};

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;
	struct i2c_board_info board_info;
	memset(&board_info, 0, sizeof(struct i2c_board_info));
	strscpy(board_info.type, "ap3216c", sizeof(board_info.type));
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0);
	ap3216c_client = i2c_new_probed_device(adapter, &board_info,
						   normal_i2c, NULL);
	i2c_put_adapter(adapter);
	return 0;
}
#endif

module_init(i2c_client_ap3216c_init);

static void __exit i2c_client_ap3216c_exit(void)
{
	i2c_unregister_device(ap3216c_client);
}
module_exit(i2c_client_ap3216c_exit);

MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");

1.4ap3216c_drv_test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[6];
	int len;
	

	/* 2. 打开文件 */
	fd = open("/dev/ap3216c", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	len = read(fd, buf, 6);		
	printf("APP read : ");
	for (len = 0; len < 6; len++)
		printf("%02x ", buf[len]);
	printf("\n");
	
	close(fd);
	
	return 0;
}

2.IIC_adapter

在这里插入图片描述
在这里插入图片描述

分配、设置、注册一个i2c_adpater结构体:

  • i2c_adpater的核心是i2c_algorithm

  • i2c_algorithm的核心是master_xfer函数

  • 分配

    struct i2c_adpater *adap = kzalloc(sizeof(struct i2c_adpater), GFP_KERNEL);
    
  • 设置

    adap->owner = THIS_MODULE;
    adap->algo = &stm32f7_i2c_algo;
    
  • 注册:i2c_add_adapter/i2c_add_numbered_adapter

    ret = i2c_add_adapter(adap);          // 不管adap->nr原来是什么,都动态设置adap->nr
    ret = i2c_add_numbered_adapter(adap); // 如果adap->nr == -1 则动态分配nr; 否则使用该nr   
    
  • 反注册

    i2c_del_adapter(adap);
    

2.1 dts

/ {
	i2c-bus-virtual {
		 compatible = "100ask,i2c-bus-virtual";
	};
};

2.2 i2c_adapter_drv.c

#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
//#include <linux/i2c-algo-bit.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
//#include <linux/platform_data/i2c-gpio.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

static struct i2c_adapter *g_adapter;

static unsigned char eeprom_buffer[512];
static int eeprom_cur_addr = 0;
static void eeprom_emulate_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
	int i;
	if (msg->flags & I2C_M_RD)
	{
		for (i = 0; i < msg->len; i++)
		{
			msg->buf[i] = eeprom_buffer[eeprom_cur_addr++];
			if (eeprom_cur_addr == 512)
				eeprom_cur_addr = 0;
		}
	}
	else
	{
		if (msg->len >= 1)
		{
			eeprom_cur_addr = msg->buf[0];
			for (i = 1; i < msg->len; i++)
			{
				eeprom_buffer[eeprom_cur_addr++] = msg->buf[i];
				if (eeprom_cur_addr == 512)
					eeprom_cur_addr = 0;
			}
		}
	}
}

static int i2c_bus_virtual_master_xfer(struct i2c_adapter *i2c_adap,
		    struct i2c_msg msgs[], int num)
{
	int i;

	// emulate eeprom , addr = 0x50
	for (i = 0; i < num; i++)
	{
		if (msgs[i].addr == 0x50)
		{
			eeprom_emulate_xfer(i2c_adap, &msgs[i]);
		}
		else
		{
			i = -EIO;
			break;
		}
	}
	
	return i;
}

static u32 i2c_bus_virtual_func(struct i2c_adapter *adap)
{
	return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |
	       I2C_FUNC_SMBUS_READ_BLOCK_DATA |
	       I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
	       I2C_FUNC_PROTOCOL_MANGLING;
}


const struct i2c_algorithm i2c_bus_virtual_algo = {
	.master_xfer   = i2c_bus_virtual_master_xfer,
	.functionality = i2c_bus_virtual_func,
};


static int i2c_bus_virtual_probe(struct platform_device *pdev)
{
	/* get info from device tree, to set i2c_adapter/hardware  */
	
	/* alloc, set, register i2c_adapter */
	g_adapter = kzalloc(sizeof(*g_adapter), GFP_KERNEL);

	g_adapter->owner = THIS_MODULE;
	g_adapter->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	g_adapter->nr = -1;
	snprintf(g_adapter->name, sizeof(g_adapter->name), "i2c-bus-virtual");

	g_adapter->algo = &i2c_bus_virtual_algo;

	i2c_add_adapter(g_adapter); // i2c_add_numbered_adapter(g_adapter);
	
	return 0;
}

static int i2c_bus_virtual_remove(struct platform_device *pdev)
{
	i2c_del_adapter(g_adapter);
	return 0;
}
static const struct of_device_id i2c_bus_virtual_dt_ids[] = {
	{ .compatible = "100ask,i2c-bus-virtual", },
	{ /* sentinel */ }
};

static struct platform_driver i2c_bus_virtual_driver = {
	.driver		= {
		.name	= "i2c-gpio",
		.of_match_table	= of_match_ptr(i2c_bus_virtual_dt_ids),
	},
	.probe		= i2c_bus_virtual_probe,
	.remove		= i2c_bus_virtual_remove,
};


static int __init i2c_bus_virtual_init(void)
{
	int ret;

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ret = platform_driver_register(&i2c_bus_virtual_driver);
	if (ret)
		printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);

	return ret;
}
module_init(i2c_bus_virtual_init);

static void __exit i2c_bus_virtual_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	platform_driver_unregister(&i2c_bus_virtual_driver);
}
module_exit(i2c_bus_virtual_exit);

MODULE_AUTHOR("www.100ask.net");
MODULE_LICENSE("GPL");

2.3 使用i2c-tools测试

在开发板上执行,命令如下:

  • 列出I2C总线

    i2cdetect -l
    

    结果类似下列的信息:

    i2c-1   i2c             21a4000.i2c                             I2C adapter
    i2c-4   i2c             i2c-bus-virtual                         I2C adapter
    i2c-0   i2c             21a0000.i2c                             I2C adapter
    

    注意:不同的板子上,i2c-bus-virtual的总线号可能不一样,上问中总线号是4。

  • 检查虚拟总线下的I2C设备

    // 假设虚拟I2C BUS号为4
    [root@100ask:~]# i2cdetect -y -a 4
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    
  • 读写模拟的EEPROM

    // 假设虚拟I2C BUS号为4
    [root@100ask:~]# i2cset -f -y 4 0x50 0 0x55   // 往0地址写入0x55
    [root@100ask:~]# i2cget -f -y 4 0x50 0        // 读0地址
    0x55
    

二、Input子系统

1.input_app

1.1input_read.c


#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>


/* ./01_get_input_info /dev/input/event0 noblock */
int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	struct input_event event;
	
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	
	if (argc < 2)
	{
		printf("Usage: %s <dev> [noblock]\n", argv[0]);
		return -1;
	}

	if (argc == 3 && !strcmp(argv[2], "noblock"))
	{
		fd = open(argv[1], O_RDWR | O_NONBLOCK);
	}
	else
	{
		fd = open(argv[1], O_RDWR);
	}
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	while (1)
	{
		len = read(fd, &event, sizeof(event));
		if (len == sizeof(event))
		{
			printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
		}
		else
		{
			printf("read err %d\n", len);
		}
	}
	return 0;
}

1.2 input_read_poll.c

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>

/* ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int ret;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	struct input_event event;
	struct pollfd fds[1];
	nfds_t nfds = 1;
	
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	
	if (argc != 2)
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	while (1)
	{
		fds[0].fd = fd;
		fds[0].events  = POLLIN;
		fds[0].revents = 0;
		ret = poll(fds, nfds, 5000);
		if (ret > 0)
		{
			if (fds[0].revents == POLLIN)
			{
				while (read(fd, &event, sizeof(event)) == sizeof(event))
				{
					printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
				}
			}
		}
		else if (ret == 0)
		{
			printf("time out\n");
		}
		else
		{
			printf("poll err\n");
		}
	}

	return 0;
}

1.3input_read_select.c

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>


/* ./01_get_input_info /dev/input/event0 */
int main(int argc, char **argv)
{
	int fd;
	int err;
	int len;
	int ret;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	struct input_event event;
	int nfds;
	struct timeval tv;
	fd_set readfds;
	
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	
	if (argc != 2)
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	while (1)
	{
		/* 设置超时时间 */
		tv.tv_sec  = 5;
		tv.tv_usec = 0;
		
		/* 想监测哪些文件? */
		FD_ZERO(&readfds);    /* 先全部清零 */	
		FD_SET(fd, &readfds); /* 想监测fd */
		
		/* 函数原型为:
			int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
         * 我们为了"read"而监测, 所以只需要提供readfds
		 */
		nfds = fd + 1; /* nfds 是最大的文件句柄+1, 注意: 不是文件个数, 这与poll不一样 */ 
		ret = select(nfds, &readfds, NULL, NULL, &tv);
		if (ret > 0)  /* 有文件可以提供数据了 */
		{
			/* 再次确认fd有数据 */
			if (FD_ISSET(fd, &readfds))
			{
				while (read(fd, &event, sizeof(event)) == sizeof(event))
				{
					printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
				}
			}
		}
		else if (ret == 0)  /* 超时 */
		{
			printf("time out\n");
		}
		else   /* -1: error */
		{
			printf("select err\n");
		}
	}

	return 0;
}

1.4input_read_fasync.c

#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int fd;

void my_sig_handler(int sig)
{
	struct input_event event;
	while (read(fd, &event, sizeof(event)) == sizeof(event))
	{
		printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);		
	}
}

/* ./05_input_read_fasync /dev/input/event0 */
int main(int argc, char **argv)
{
	int err;
	int len;
	int ret;
	int i;
	unsigned char byte;
	int bit;
	struct input_id id;
	unsigned int evbit[2];
	unsigned int flags;
	int count = 0;
	
	char *ev_names[] = {
		"EV_SYN ",
		"EV_KEY ",
		"EV_REL ",
		"EV_ABS ",
		"EV_MSC ",
		"EV_SW	",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"NULL ",
		"EV_LED ",
		"EV_SND ",
		"NULL ",
		"EV_REP ",
		"EV_FF	",
		"EV_PWR ",
		};
	if (argc != 2)
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 注册信号处理函数 */
	signal(SIGIO, my_sig_handler);
	
	/* 打开驱动程序 */
	fd = open(argv[1], O_RDWR | O_NONBLOCK);
	if (fd < 0)
	{
		printf("open %s err\n", argv[1]);
		return -1;
	}

	err = ioctl(fd, EVIOCGID, &id);
	if (err == 0)
	{
		printf("bustype = 0x%x\n", id.bustype );
		printf("vendor	= 0x%x\n", id.vendor  );
		printf("product = 0x%x\n", id.product );
		printf("version = 0x%x\n", id.version );
	}

	len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
	if (len > 0 && len <= sizeof(evbit))
	{
		printf("support ev type: ");
		for (i = 0; i < len; i++)
		{
			byte = ((unsigned char *)evbit)[i];
			for (bit = 0; bit < 8; bit++)
			{
				if (byte & (1<<bit)) {
					printf("%s ", ev_names[i*8 + bit]);
				}
			}
		}
		printf("\n");
	}

	/* 把APP的进程号告诉驱动程序 */
	fcntl(fd, F_SETOWN, getpid());
	
	/* 使能"异步通知" */
	flags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC);
	
	while (1)
	{
		printf("main loop count = %d\n", count++);
		sleep(2);
	}
	return 0;
}

三、GPIO

  • List item
    Active-High and Active-Low
    以LED为例,需要设置GPIO电平。但是有些电路可能是高电平点亮LED,有些是低电平点亮LED。
    可以使用如下代码:
gpiod_set_value(gpio, 1);  // 输出高电平点亮LED
gpiod_set_value(gpio, 0);  // 输出低电平点亮LED

对应同一个目标:点亮LED,对于不同的LED,就需要不同的代码,原因在于上面的代码中1、0表示的是"物理值"。
如果能使用"逻辑值",同样的逻辑值在不同的配置下输出对应的物理值,就可以保持代码一致,比如:

gpiod_set_value(gpio, 1);  // 输出逻辑1
                           // 在Active-High的情况下它会输出高电平
                           // 在Active-Low的情况下它会输出低电平

1.自带_GPIO

1.1dts

    myled{
        compatible ="sch,myled";
        gpios=<&gpio5  3  GPIO_ACTIVE_LOW>;
    };

1.2Makefile

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o led_app led_app.c
	
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f led_app
	
obj-m	+= led_drv.o

1.3led_drv.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>

struct gpio_desc *led_pin;
/* 主设备号     */
static int major = 0;
static struct class *gpio_class;

static int gpio_drv_open(struct inode *node, struct file *file)
{
	 printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
     gpiod_direction_output(led_pin,0);
	 return 0;
}

static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	char status;
	int err;
	printk("%s %s line %d\n",__FILE__,__FUNCTION__,__LINE__);
    if(size!=1)
      return -1;
	err=copy_from_user(&status, buf, 1);
	gpiod_set_value(led_pin,status);
	return 0;		
}

static struct file_operations gpio_led_drv = {
	.owner	 = THIS_MODULE,
	.open    = gpio_drv_open,
	.write   = gpio_drv_write,
};


/* 在入口函数 */
static int gpio_drv_probe(struct platform_device *pdev)
{
    printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	//获取引脚
	led_pin= gpiod_get(&pdev->dev, NULL, 0);
	
	/* 注册file_operations 	*/
	major = register_chrdev(0, "sch", &gpio_led_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "led_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "sch");
		return PTR_ERR(gpio_class);
	}
    /* 自动创建设备节点          /dev/led */
	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "myled"); 
	return 0;
}

static int  gpio_drv_remove(struct platform_device *pdev)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "sch");
	gpiod_put(led_pin);
    return 0;
}

static const struct of_device_id gpio_dt_ids[] = {
        { .compatible = "sch,myled", },
        { /* sentinel */ }
};

static struct platform_driver gpio_platform_driver = {
	.driver = {
		.name = "gpio_platform_driver_led",
		.of_match_table = gpio_dt_ids,
	},
	.probe  = gpio_drv_probe,
	.remove = gpio_drv_remove,
};

static int __init gpio_drv_init(void)
{
	/* 注册platform_driver */
	return platform_driver_register(&gpio_platform_driver);
}

static void __exit gpio_drv_exit(void)
{
	/* 反注册platform_driver */
	platform_driver_unregister(&gpio_platform_driver);
}

module_init(gpio_drv_init);
module_exit(gpio_drv_exit);

MODULE_LICENSE("GPL");

1.4led_app.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
 * ./ledtest /dev/myled on
 * ./ledtest /dev/myled off
 */
int main(int argc, char * * argv)
{
   int fd;
   char buf;
   
   if(argc!=3)
   {
     printf("please use %s <dev> <on/off>",argv[0]);
     return -1;
   }

  fd=open(argv[1], O_RDWR);
  if (fd == -1)
  {
	 printf("can not open file %s\n", argv[1]);
	 return -1;
  }

  if(strcmp(argv[2],"on")==0)
  {
  	   buf=1;
	   write(fd, &buf, 1);
  }
  else if(strcmp(argv[2],"off")==0)
  {
  	  buf=0;
      write(fd, &buf, 1);
  }
  else
  {
	 printf("please use %s <dev> <on/off>\n",argv[0]);
	 return -1;
  }

  close(fd);
  return 0;
}

2.虚拟_GPIO

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1dts

/ {
    gpio_virt: virtual_gpiocontroller {
        compatible = "sch,virtual_gpio";
        gpio-controller;
        、、gpio-cells = <2>;//2个描述
        ngpios = <4>;//分配4个引脚
    };

    myled {
        compatible = "sch,leddrv";
        led-gpios = <&gpio_virt 2 GPIO_ACTIVE_LOW>;
    };
};

2.2virtual_gpio_driver.c


#include <linux/module.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/driver.h>
#include <linux/slab.h>
#include <linux/regmap.h>

static struct gpio_chip * g_virt_gpio;
static int g_gpio_val = 0;

static const struct of_device_id virtual_gpio_of_match[] = {
	{ .compatible = "100ask,virtual_gpio", },
	{ },
};

static int virt_gpio_direction_output(struct gpio_chip *gc,
		unsigned offset, int val)
{
	printk("set pin %d as output %s\n", offset, val ? "high" : "low");
	return 0;
}

static int virt_gpio_direction_input(struct gpio_chip *chip,
					unsigned offset)
{
	printk("set pin %d as input\n", offset);
	return 0;
}


static int virt_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
	int val;
	val = (g_gpio_val & (1<<offset)) ? 1 : 0;
	printk("get pin %d, it's val = %d\n", offset, val);
	return val;
}

static void virt_gpio_set_value(struct gpio_chip *gc,
		unsigned offset, int val)
{
	printk("set pin %d as %d\n", offset, val);
	if (val)
		g_gpio_val |= (1 << offset);
	else
		g_gpio_val &= ~(1 << offset);
}

static int virtual_gpio_probe(struct platform_device *pdev)
{
	int ret;
	unsigned int val;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	/* 1. 分配gpio_chip */
	g_virt_gpio = devm_kzalloc(&pdev->dev, sizeof(*g_virt_gpio), GFP_KERNEL);
	
	/* 2. 设置gpio_chip */
	
	/* 2.1 设置函数 */
	g_virt_gpio->label = pdev->name;
	g_virt_gpio->direction_output = virt_gpio_direction_output;
	g_virt_gpio->direction_input  = virt_gpio_direction_input;
	g_virt_gpio->get = virt_gpio_get_value;
	g_virt_gpio->set = virt_gpio_set_value;
	
	g_virt_gpio->parent = &pdev->dev;
	g_virt_gpio->owner = THIS_MODULE;
	
	/* 2.2 设置base、ngpio值 */
	g_virt_gpio->base = -1;
	ret = of_property_read_u32(pdev->dev.of_node, "ngpios", &val);
	g_virt_gpio->ngpio = val;
	
	/* 3. 注册gpio_chip */
	ret = devm_gpiochip_add_data(&pdev->dev, g_virt_gpio, NULL);
	
	return 0;
}
static int virtual_gpio_remove(struct platform_device *pdev)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


static struct platform_driver virtual_gpio_driver = {
	.probe		= virtual_gpio_probe,
	.remove		= virtual_gpio_remove,
	.driver		= {
		.name	= "100ask_virtual_gpio",
		.of_match_table = of_match_ptr(virtual_gpio_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_gpio_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_gpio_driver);
}


/* 2. 出口函数 */
static void __exit virtual_gpio_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_gpio_driver);
}

module_init(virtual_gpio_init);
module_exit(virtual_gpio_exit);

MODULE_LICENSE("GPL");

在这里插入图片描述
在这里插入图片描述

四、interrupt

在这里插入图片描述
在这里插入图片描述

1.链式中断控制器驱动程序编写

1.1 链式中断控制器的重要函数和结构体

1.1.1 回顾处理流程

为方便描述,假设下级的链式中断控制器就是GPIO控制器。
在这里插入图片描述

沿着中断的处理流程,GIC之下的中断控制器涉及这4个重要部分:handleB、GPIO Domain、handleC、irq_chip

  • handleB:处理GIC 33号中断,handleB由GPIO驱动提供

    • 屏蔽GIC 33号中断:调用irq_dataA的irq_chip的函数,irq_dataA由GIC驱动提供
    • 细分并处理某个GPIO中断:
      • 读取GPIO寄存器得到hwirq,通过GPIO Domain转换为virq,假设是102
      • 调用irq_desc[102].handle_irq,即handleC
    • 清除GIC 33号中断:调用irq_dataA的irq_chip的函数,由GIC驱动提供
  • handleC:处理GPIO 2号中断,handleC由GPIO驱动提供

    • 屏蔽GPIO 2号中断:调用irq_dataB的irq_chip的函数,由GPIO驱动提供
    • 处理:调用actions链表中用户注册的函数
    • 清除GPIO 2号中断:调用irq_dataB的irq_chip的函数,由GPIO驱动提供
1.2.1 irq_domain的核心作用

怎么把handleB、GPIO Domain、handleC、irq_chip这4个结构体组织起来,irq_domain是核心。

我们从使用中断的流程来讲解。

  • 在设备树里指定使用哪个中断

        gpio_keys_100ask {
            compatible = "100ask,gpio_key";
    		interrupt-parent = <&gpio5>;
    		interrupts = <3 IRQ_TYPE_EDGE_BOTH>,
        };
    
  • 内核解析、处理设备树的中断信息

    • 根据interrupt-parent找到驱动程序注册的irq_domain

    • 使用irq_domain.ops中的translate或xlate函数解析设备树,得到hwirq和type

    • 分配/找到irq_desc,得到virq

      • 把(hwirq, virq)的关系存入irq_domain
      • 把virq存入platform_device的resource中
    • 使用irq_domain.ops中的alloc或map函数进行设置

      • 可能是替换irq_desc[virq].handle_irq函数
      • 可能是替换irq_desc[virq].irq_data,里面有irq_chip
  • 用户的驱动程序注册中断

    • 从platform_device的resource中得到中断号virq
    • request_irq(virq, …, func)
  • 发生中断、处理中断:处理流程见上面。

1.2 硬件模型

下图中列出了链式中断控制器、层级中断控制器,本节课程只涉及左边的链式中断控制器。

内核中有各类中断控制器的驱动程序,它们涉及的硬件过于复杂,从这些杂乱的代码中去讲清楚中断体系,比较难。

我们实现一些虚拟的中断控制器,如下图所示。

实际板子中,我们可以通过按键触发中断。

对于这些虚拟的中断控制器,我们没有真实按键,通过devmem指令写GIC的PENDING寄存器触发中断。

在这里插入图片描述

1.3 编程

会涉及2个驱动程序:虚拟的中断控制器驱动程序,按键驱动程序,以及对应的设备树。

2. legacy方式代码的上机实验

2.1 确定中断号n

在这里插入图片描述

查看芯片手册,选择一个保留的、未使用的GIC SPI中断即可。

2.1.1 IMX6ULL

看芯片手册第3章:

在这里插入图片描述

看上图,选择122号中断,它是SPI里的122号中断,GIC里的编号是(32+122)=154。

2.1.2 STM32MP157

看芯片手册第21.2节:

在这里插入图片描述
看上图,选择210号中断,它是SPI里的210号中断,GIC里的编号是(32+210)=242。

2.2 怎么触发中断

可以通过devmem命令直接写GIC的PENDING寄存区。

在这里插入图片描述

GICD_ISPENDRn有多个寄存器,每个寄存器中每一位对应一个GIC中断,写入1就可以触发该中断。

写哪一个GICD_ISPENDRn寄存器?写哪一位?使用下列公式来确定:

在这里插入图片描述

查看内核设备树文件imx6ull.dtsi、stm32mp151.dtsi,可以知道:

  • IMX6ULL的GIC Distributor 地址是:0x00a01000
  • 在这里插入图片描述
  • STM32MP157的GIC Distributor 地址是:0xa0021000
  • 在这里插入图片描述
芯片SPI中断号GIC中断号n,bitGICD_ISPENDRn地址命令
IMX6LLL1221544,260xa01210devmem 0xa01210 32 0x4000000
STM32MP1572102427,180xa002121cdevmem 0xa002121c 32 0x40000

2.3 上机实验

2.3.1 设置工具链
1. STM32MP157
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
2. IMX6ULL
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
2.3.2 编译、替换设备树
1. STM32MP157
  • 修改arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts,添加如下代码:

    / {
    	virtual_intc: virtual_intc_100ask {
    		compatible = "100ask,virtual_intc";
    		
    		interrupt-controller;
    		#interrupt-cells = <2>;
    
    		interrupt-parent = <&intc>;
    		interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>;
    		
    	};
    	
        gpio_keys_100ask {
            compatible = "100ask,gpio_key";
    		interrupt-parent = <&virtual_intc>;
    		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
    		             <1 IRQ_TYPE_LEVEL_HIGH>,
    					 <2 IRQ_TYPE_LEVEL_HIGH>,
    					 <3 IRQ_TYPE_LEVEL_HIGH>;
        };
    };
    
  • 编译设备树:
    在Ubuntu的STM32MP157内核目录下执行如下命令,
    得到设备树文件:arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb

    make dtbs
    
  • 复制到NFS目录:

    $ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/
    
  • 开发板上挂载NFS文件系统

    • vmware使用NAT(假设windowsIP为192.168.1.100)

      [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
      192.168.1.100:/home/book/nfs_rootfs /mnt
      
    • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137

      [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      
  • 更新设备树

    [root@100ask:~]# mount  /dev/mmcblk2p2  /boot
    [root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot
    [root@100ask:~]# sync
    
  • 重启开发板

2. IMX6ULL
  • 修改arch/arm/boot/dts/100ask_imx6ull-14x14.dts,添加如下代码:

    / {
    	virtual_intc: virtual_intc_100ask {
    		compatible = "100ask,virtual_intc";
    		
    		interrupt-controller;
    		#interrupt-cells = <2>;
    
    		interrupt-parent = <&intc>;
    		interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
    		
    	};
    	
        gpio_keys_100ask {
            compatible = "100ask,gpio_key";
    		interrupt-parent = <&virtual_intc>;
    		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
    		             <1 IRQ_TYPE_LEVEL_HIGH>,
    					 <2 IRQ_TYPE_LEVEL_HIGH>,
    					 <3 IRQ_TYPE_LEVEL_HIGH>;
        };
    };
    
  • 编译设备树:
    在Ubuntu的IMX6ULL内核目录下执行如下命令,
    得到设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dtb

    make dtbs
    
  • 复制到NFS目录:

    $ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
    
  • 开发板上挂载NFS文件系统

    • vmware使用NAT(假设windowsIP为192.168.1.100)

      [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
      192.168.1.100:/home/book/nfs_rootfs /mnt
      
    • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137

      [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      
    • 更新设备树

      [root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
      [root@100ask:~]# sync
      
  • 重启开发板

2.3.3 编译、安装驱动程序
  • 编译:

    • 在Ubuntu上
    • 修改04_virtual_int_controller_legacy_ok中的Makefile,指定内核路径KERN_DIR,在执行make命令即可。
  • 安装:

    • 在开发板上

    • 挂载NFS,复制文件,insmod,类似如下命令:

      mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      // 对于IMX6ULL,想看到驱动打印信息,需要先执行
      echo "7 4 1 7" > /proc/sys/kernel/printk
      
      insmod -f /mnt/virtual_int_controller.ko
      // 安装virtual_int_controller之后即可进入/sys/kernel/irq目录查看分配的中断号
      
      insmod -f /mnt/gpio_key_drv.ko
      cat /proc/interrupts
      
      // 触发中断
      devmem 0xa01210 32 0x4000000 // imx6ull
      devmem 0xa002121c 32 0x40000 // stm32mp157
      
  • 观察内核打印的信息

2.4 virtual_int_controller.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/random.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
/* FIXME: for gpio_get_value() replace this with direct register read */
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/bug.h>
#include <linux/random.h>

static struct irq_domain *virtual_intc_domain;

static int virtual_intc_get_hwirq(void)
{
	return get_random_int() & 0x3;
}

static void virtual_intc_irq_handler(struct irq_desc *desc)
{
	/* 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	int hwirq;
	
	struct irq_chip *chip = irq_desc_get_chip(desc);

	chained_irq_enter(chip, desc);

	/* a. 分辨中断 */
	hwirq = virtual_intc_get_hwirq();

	/* b. 调用irq_desc[].handle_irq(handleC) */
	generic_handle_irq(irq_find_mapping(virtual_intc_domain, hwirq));

	chained_irq_exit(chip, desc);
}

static void virtual_intc_irq_ack(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_mask(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_mask_ack(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_unmask(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_eoi(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static struct irq_chip virtual_intc_irq_chip = {
	.name			= "100ask_virtual_intc",
	.irq_ack	   = virtual_intc_irq_ack	   ,
	.irq_mask	   = virtual_intc_irq_mask	   ,
	.irq_mask_ack  = virtual_intc_irq_mask_ack ,
	.irq_unmask    = virtual_intc_irq_unmask   ,
	.irq_eoi	   = virtual_intc_irq_eoi	   ,
};

static int virtual_intc_irq_map(struct irq_domain *h, unsigned int virq,
			  irq_hw_number_t hw)
{
	/* 1. 给virq提供处理函数
	 * 2. 提供irq_chip用来mask/unmask中断
	 */
	
	irq_set_chip_data(virq, h->host_data);
	//irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_edge_irq); /* handle_edge_irq就是handleC */
	irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_level_irq); /* handle_level_irq就是handleC */
	//irq_set_nested_thread(virq, 1);
	//irq_set_noprobe(virq);

	return 0;
}


static const struct irq_domain_ops virtual_intc_domain_ops = {
	.xlate = irq_domain_xlate_onetwocell,
	.map   = virtual_intc_irq_map,
};

static int virtual_intc_probe(struct platform_device *pdev)
{	
	struct device_node *np = pdev->dev.of_node;
	int irq_to_parent;
	int irq_base;
	
	/* 1. virutal intc 会向GIC发出n号中断 */
	/* 1.1 从设备树里获得virq_n */
	irq_to_parent = platform_get_irq(pdev, 0);
	printk("virtual_intc_probe irq_to_parent = %d\n", irq_to_parent);
	
	/* 1.2 设置它的irq_desc[].handle_irq, 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	irq_set_chained_handler_and_data(irq_to_parent, virtual_intc_irq_handler, NULL);

	
	/* 2. 分配/设置/注册一个irq_domain */
	irq_base = irq_alloc_descs(-1, 0, 4, numa_node_id());
	printk("virtual_intc_probe irq_base = %d\n", irq_base);

	virtual_intc_domain = irq_domain_add_legacy(np, 4, irq_base, 0,
					     &virtual_intc_domain_ops, NULL);
	
	return 0;
}
static int virtual_intc_remove(struct platform_device *pdev)
{
	return 0;
}



static const struct of_device_id virtual_intc_of_match[] = {
	{ .compatible = "100ask,virtual_intc", },
	{ },
};


static struct platform_driver virtual_intc_driver = {
	.probe		= virtual_intc_probe,
	.remove		= virtual_intc_remove,
	.driver		= {
		.name	= "100ask_virtual_intc",
		.of_match_table = of_match_ptr(virtual_intc_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_intc_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_intc_driver);
}


/* 2. 出口函数 */
static void __exit virtual_intc_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_intc_driver);
}

module_init(virtual_intc_init);
module_exit(virtual_intc_exit);

MODULE_LICENSE("GPL");

2.5 gpio_key_drv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>


struct gpio_key{
	char name[100];
	int irq;
	int cnt;
} ;

static struct gpio_key gpio_keys_100ask[100];

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	

	printk("gpio_key_isr %s cnt %d\n", gpio_key->name, gpio_key->cnt++);
	
	return IRQ_HANDLED;
}

/* 1. 从platform_device获得GPIO
 * 2. gpio=>irq
 * 3. request_irq
 */
static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	int i = 0;
	int irq;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);


	while (1)
	{
		irq = platform_get_irq(pdev, i);
		if (irq <= 0)
			break;
		gpio_keys_100ask[i].irq = irq;
		sprintf(gpio_keys_100ask[i].name, "100as_virtual_key%d", i);
		
		err = devm_request_irq(&pdev->dev, gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpio_keys_100ask[i].name, &gpio_keys_100ask[i]);
		printk("devm_request_irq %d for %s, err = %d\n", irq, gpio_keys_100ask[i].name, err);
		i++;
	}
        
    return 0;
    
}

static int gpio_key_remove(struct platform_device *pdev)
{
    return 0;
}


static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

/* 1. 定义platform_driver */
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe,
    .remove     = gpio_key_remove,
    .driver     = {
        .name   = "100ask_gpio_key",
        .of_match_table = ask100_keys,
    },
};

/* 2. 在入口函数注册platform_driver */
static int __init gpio_key_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
    err = platform_driver_register(&gpio_keys_driver); 
	
	return err;
}

/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *     卸载platform_driver
 */
static void __exit gpio_key_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver);
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(gpio_key_init);
module_exit(gpio_key_exit);

MODULE_LICENSE("GPL");

3.链式中断控制器驱动程序编写_linear方式

3.1 两种方式的对比

linear、legacy方式,都是用来编写链式中断控制器驱动程序,它们的关系如下表所示。

legacylinear
函数irq_domain_add_legacyirq_domain_add_linear
irq_desc一次性分配完用到再分配
(hwirq,virq)domain->linear_revmap[hwirq] = irq_data->irq;同左边

3.2 编程

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/random.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
/* FIXME: for gpio_get_value() replace this with direct register read */
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/bug.h>
#include <linux/random.h>

static struct irq_domain *virtual_intc_domain;

static int virtual_intc_get_hwirq(void)
{
	return get_random_int() & 0x3;
}

static void virtual_intc_irq_handler(struct irq_desc *desc)
{
	/* 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	int hwirq;
	
	struct irq_chip *chip = irq_desc_get_chip(desc);

	chained_irq_enter(chip, desc);

	/* a. 分辨中断 */
	hwirq = virtual_intc_get_hwirq();

	/* b. 调用irq_desc[].handle_irq(handleC) */
	generic_handle_irq(irq_find_mapping(virtual_intc_domain, hwirq));

	chained_irq_exit(chip, desc);
}

static void virtual_intc_irq_ack(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_mask(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_mask_ack(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_unmask(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static void virtual_intc_irq_eoi(struct irq_data *data)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}

static struct irq_chip virtual_intc_irq_chip = {
	.name			= "100ask_virtual_intc",
	.irq_ack	   = virtual_intc_irq_ack	   ,
	.irq_mask	   = virtual_intc_irq_mask	   ,
	.irq_mask_ack  = virtual_intc_irq_mask_ack ,
	.irq_unmask    = virtual_intc_irq_unmask   ,
	.irq_eoi	   = virtual_intc_irq_eoi	   ,
};

static int virtual_intc_irq_map(struct irq_domain *h, unsigned int virq,
			  irq_hw_number_t hw)
{
	/* 1. 给virq提供处理函数
	 * 2. 提供irq_chip用来mask/unmask中断
	 */
	
	irq_set_chip_data(virq, h->host_data);
	//irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_edge_irq); /* handle_edge_irq就是handleC */
	irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_level_irq); /* handle_level_irq就是handleC */
	//irq_set_nested_thread(virq, 1);
	//irq_set_noprobe(virq);

	return 0;
}


static const struct irq_domain_ops virtual_intc_domain_ops = {
	.xlate = irq_domain_xlate_onetwocell,
	.map   = virtual_intc_irq_map,
};

static int virtual_intc_probe(struct platform_device *pdev)
{	
	struct device_node *np = pdev->dev.of_node;
	int irq_to_parent;
	//int irq_base;
	
	/* 1. virutal intc 会向GIC发出n号中断 */
	/* 1.1 从设备树里获得virq_n */
	irq_to_parent = platform_get_irq(pdev, 0);
	printk("virtual_intc_probe irq_to_parent = %d\n", irq_to_parent);
	
	/* 1.2 设置它的irq_desc[].handle_irq, 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	irq_set_chained_handler_and_data(irq_to_parent, virtual_intc_irq_handler, NULL);

	
	/* 2. 分配/设置/注册一个irq_domain */
	//irq_base = irq_alloc_descs(-1, 0, 4, numa_node_id());
	//printk("virtual_intc_probe irq_base = %d\n", irq_base);

	/* Usage:
	 *  a. dts: 定义使用哪个hwirq
	 *  b. 内核解析设备树时分配irq_desc,得到virq
	 *  c. (hwirq, virq) ==>存入domain
	 */

	virtual_intc_domain = irq_domain_add_linear(np, 4, 
					     &virtual_intc_domain_ops, NULL);
	
	return 0;
}
static int virtual_intc_remove(struct platform_device *pdev)
{
	return 0;
}



static const struct of_device_id virtual_intc_of_match[] = {
	{ .compatible = "100ask,virtual_intc", },
	{ },
};


static struct platform_driver virtual_intc_driver = {
	.probe		= virtual_intc_probe,
	.remove		= virtual_intc_remove,
	.driver		= {
		.name	= "100ask_virtual_intc",
		.of_match_table = of_match_ptr(virtual_intc_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_intc_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_intc_driver);
}


/* 2. 出口函数 */
static void __exit virtual_intc_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_intc_driver);
}

module_init(virtual_intc_init);
module_exit(virtual_intc_exit);

MODULE_LICENSE("GPL");



3.3 上机实验

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.层级中断控制器驱动程序编写

参考资料:

1. 层级中断控制器的重要函数和结构体

4.1 回顾处理流程

为方便描述,假设下级的层级中断控制器就是GPIO控制器。

下图中:

  • handleA、irq_dataA由GIC驱动提供

  • irq_dataB由GPIO驱动提供,不需要handleB
    在这里插入图片描述

  • 假设GPIO模块下有4个引脚,都可以产生中断,分别链接到GIC的100~103号中断

  • GPIO就是一个层级中断控制器

  • 对于GPIO模块中0~3这四个hwirq,分配四个irq_desc,用到时再分配

  • 假设这4个irq_desc的序号为234~237

    • 在GIC domain中记录(100,234) (101,235)(102,236) (103,237)
    • 在GPIO domain中记录(0,234) (1,235)(2,236) (3,237)
  • 对于KEY,注册中断时就是:request_irq(236, ...)

  • 按下KEY时:

    • 程序从GIC中读取寄存器知道发生了102号中断,通过GIC irq_domain可以知道virq为236
    • 处理virq 236号中断:调用irq_desc[236].handle_irq,即handleA
      • mask/ack中断:
        • 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB
          • 它会调用父级irq_dataA->irq_chip的函数
      • 调用irq_desc[236].action链表中用户注册的函数
      • unmask中断:
        • 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB
          • 它会调用父级irq_dataA->irq_chip的函数
1.2 irq_domain的核心作用

怎么把handleA、GIC Domain和GPIO Domain、irq_chipA和irq_chipB这4个结构体组织起来,irq_domain是核心。

为方便描述,我们把上图中的层级中断控制器当做GPIO控制器。

我们从使用中断的流程来讲解。

  • 在设备树里指定使用哪个中断

        gpio_keys_100ask {
            compatible = "100ask,gpio_key";
    		interrupt-parent = <&gpio5>;
    		interrupts = <3 IRQ_TYPE_EDGE_BOTH>,
        };
    
  • 内核解析、处理设备树的中断信息

    • 根据interrupt-parent找到驱动程序注册的GPIO irq_domain
    • GPIO irq_domain对设备树的解析
      • 使用GPIO irq_domain.ops中的translate或xlate函数解析设备树,得到hwirq和type
      • 分配/找到irq_desc,得到virq
        • 把(hwirq, virq)的关系存入GPIO irq_domain
        • 把virq存入platform_device的resource中
      • 修改得到对应的GIC_hwirq,调用父级GIC irq_domain继续解析
        • 把(GIC_hwirq, virq)的关系存入GIC irq_domain
      • 注意:对于同一个硬件中断,它在两个irq_domain里的virq是相同的,hwirq可能不一样。
    • GPIO irq_domain对设备树的设置
      • 使用GPIO irq_domain.ops中的alloc函数进行设置
        • 替换irq_desc[virq].irq_data,里面有irq_chip改为irq_chipB,即GPIO的irq_chip
        • 调用父级GIC irq_domain的alloc继续设置
          • 设置irq_desc[virq].handle_irq为GIC的handle_irq,即上图中的handleA
  • 用户的驱动程序注册中断

    • 从platform_device的resource中得到中断号virq
    • request_irq(virq, …, func)
  • 发生中断、处理中断:处理流程见上面。

2. 硬件模型

下图中列出了链式中断控制器、层级中断控制器,本节课程之设计右边的层级中断控制器。

内核中有各类中断控制器的驱动程序,它们涉及的硬件过于复杂,从这些杂乱的代码中去讲清楚中断体系,比较难。

我们实现一些虚拟的中断控制器,如下图所示。

实际板子中,我们可以通过按键触发中断。

对于这些虚拟的中断控制器,我们没有真实按键,通过devmem指令写GIC的PENDING寄存器触发中断。

在这里插入图片描述

4.3 编程(virtual_int_controller.c)

会涉及2个驱动程序:虚拟的中断控制器驱动程序,按键驱动程序,以及对应的设备树。

虚拟的中断控制器驱动程序中,涉及2个递归处理。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/random.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
/* FIXME: for gpio_get_value() replace this with direct register read */
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/bug.h>


static struct irq_domain *virtual_intc_domain;
static u32 upper_hwirq_base;

static int virtual_intc_domain_translate(struct irq_domain *d,
				    struct irq_fwspec *fwspec,
				    unsigned long *hwirq,
				    unsigned int *type)
{
	if (is_of_node(fwspec->fwnode)) {
		if (fwspec->param_count != 2)
			return -EINVAL;

		*hwirq = fwspec->param[0];
		*type = fwspec->param[1];
		return 0;
	}

	return -EINVAL;
}

static void virtual_intc_irq_unmask(struct irq_data *d)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	irq_chip_unmask_parent(d);
}

static void virtual_intc_irq_mask(struct irq_data *d)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	irq_chip_mask_parent(d);
}

static void virtual_intc_irq_eoi(struct irq_data *d)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	irq_chip_eoi_parent(d);
}

static struct irq_chip virtual_intc_chip = {
	.name			= "virtual_intc",
	.irq_mask		= virtual_intc_irq_mask,
	.irq_unmask 	= virtual_intc_irq_unmask,
	.irq_eoi        = virtual_intc_irq_eoi,
};


static int virtual_intc_domain_alloc(struct irq_domain *domain,
				  unsigned int irq,
				  unsigned int nr_irqs, void *data)
{
	struct irq_fwspec *fwspec = data;
	struct irq_fwspec parent_fwspec;
	irq_hw_number_t hwirq;
	int i;
	
	/* 设置irq_desc[irq] */

	/* 1. 设置irq_desc[irq].irq_data, 里面含有virtual_intc irq_chip */
	hwirq = fwspec->param[0];
	for (i = 0; i < nr_irqs; i++)
		irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
					      &virtual_intc_chip, NULL);

	/* 2. 设置irq_desc[irq].handle_irq,  来自GIC */
	parent_fwspec.fwnode = domain->parent->fwnode;
	parent_fwspec.param_count = 3;
	parent_fwspec.param[0]    = 0; //GIC_SPI;
	parent_fwspec.param[1]    = fwspec->param[0] + upper_hwirq_base;
	parent_fwspec.param[2]    = fwspec->param[1];
	
	return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
					  &parent_fwspec);
}

static const struct irq_domain_ops virtual_intc_domain_ops = {
	.translate	= virtual_intc_domain_translate,
	.alloc		= virtual_intc_domain_alloc,
};

static int virtual_intc_probe(struct platform_device *pdev)
{	

	struct irq_domain *parent_domain;
	struct device_node *parent;
	int err;
		

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = of_property_read_u32(pdev->dev.of_node, "upper_hwirq_base", &upper_hwirq_base);

	parent = of_irq_find_parent(pdev->dev.of_node);
	parent_domain = irq_find_host(parent);


	/* 分配/设置/注册irq_domain */
	virtual_intc_domain = irq_domain_add_hierarchy(parent_domain, 0, 4,
					  pdev->dev.of_node, &virtual_intc_domain_ops,
					  NULL);
	
	
	return 0;
}
static int virtual_intc_remove(struct platform_device *pdev)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


static const struct of_device_id virtual_intc_of_match[] = {
	{ .compatible = "100ask,virtual_intc", },
	{ },
};


static struct platform_driver virtual_intc_driver = {
	.probe		= virtual_intc_probe,
	.remove		= virtual_intc_remove,
	.driver		= {
		.name	= "100ask_virtual_intc",
		.of_match_table = of_match_ptr(virtual_intc_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_intc_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_intc_driver);
}


/* 2. 出口函数 */
static void __exit virtual_intc_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_intc_driver);
}

module_init(virtual_intc_init);
module_exit(virtual_intc_exit);

MODULE_LICENSE("GPL");



4.3.1 alloc的递归处理

在这里插入图片描述

4.3.2 irq_chip的递归处理

在这里插入图片描述

5.层级中断控制器驱动程序上机实验

1. 确定中断号n

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-peWit9aR-1681093781109)(pic/08_Interrupt/076_virtual_intc_hardware.png)]

查看芯片手册,选择一个保留的、未使用的GIC SPI中断即可。

5.1 IMX6ULL

看芯片手册第3章:
在这里插入图片描述

看上图,选择122号中断,它是SPI里的122号中断,GIC里的编号是(32+122)=154。

5.1.2 STM32MP157

看芯片手册第21.2节:
在这里插入图片描述

看上图,选择210号中断,它是SPI里的210号中断,GIC里的编号是(32+210)=242。

5.2 怎么触发中断

可以通过devmem命令直接写GIC的PENDING寄存区。
在这里插入图片描述

GICD_ISPENDRn有多个寄存器,每个寄存器中每一位对应一个GIC中断,写入1就可以触发该中断。

写哪一个GICD_ISPENDRn寄存器?写哪一位?使用下列公式来确定:
在这里插入图片描述

查看内核设备树文件imx6ull.dtsi、stm32mp151.dtsi,可以知道:

  • IMX6ULL的GIC Distributor 地址是:0x00a01000

在这里插入图片描述

  • STM32MP157的GIC Distributor 地址是:0xa0021000
    在这里插入图片描述
芯片SPI中断号GIC中断号n,bitGICD_ISPENDRn地址命令
IMX6LLL1221544,260xa01210devmem 0xa01210 32 0x4000000
1231554,270xa01210devmem 0xa01210 32 0x8000000
1241564,280xa01210devmem 0xa01210 32 0x10000000
1251574,290xa01210devmem 0xa01210 32 0x20000000
STM32MP1572102427,180xa002121cdevmem 0xa002121c 32 0x40000
2112437,190xa002121cdevmem 0xa002121c 32 0x80000
2122447,200xa002121cdevmem 0xa002121c 32 0x100000
2132457,210xa002121cdevmem 0xa002121c 32 0x200000

3. 上机实验

3.1 设置工具链
1. STM32MP157
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_stm32mp157_pro-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
2. IMX6ULL
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
3.2 编译、替换设备树
1. STM32MP157
  • 修改arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dts,添加如下代码:

    / {
       virtual_intc: virtual_intc_100ask {
            compatible = "100ask,virtual_intc";
    
            interrupt-controller;
            #interrupt-cells = <2>;
    
            interrupt-parent = <&intc>;
            //upper_hwirq_base = <122>;  // imx6ull
            upper_hwirq_base = <210>;  // stm32mp157
        };
    
        gpio_keys_100ask {
            compatible = "100ask,gpio_key";
            interrupt-parent = <&virtual_intc>;
            interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
                         <1 IRQ_TYPE_LEVEL_HIGH>,
                         <2 IRQ_TYPE_LEVEL_HIGH>,
                         <3 IRQ_TYPE_LEVEL_HIGH>;
        };
    };
    
  • 编译设备树:
    在Ubuntu的STM32MP157内核目录下执行如下命令,
    得到设备树文件:arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb

    make dtbs
    
  • 复制到NFS目录:

    $ cp arch/arm/boot/dts/stm32mp157c-100ask-512d-lcd-v1.dtb ~/nfs_rootfs/
    
  • 开发板上挂载NFS文件系统

    • vmware使用NAT(假设windowsIP为192.168.1.100)

      [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
      192.168.1.100:/home/book/nfs_rootfs /mnt
      
    • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137

      [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      
  • 更新设备树

    [root@100ask:~]# mount  /dev/mmcblk2p2  /boot
    [root@100ask:~]# cp /mnt/stm32mp157c-100ask-512d-lcd-v1.dtb /boot
    [root@100ask:~]# sync
    
  • 重启开发板

2. IMX6ULL
  • 修改arch/arm/boot/dts/100ask_imx6ull-14x14.dts,添加如下代码:

    / {
       virtual_intc: virtual_intc_100ask {
            compatible = "100ask,virtual_intc";
    
            interrupt-controller;
            #interrupt-cells = <2>;
    
            interrupt-parent = <&intc>;
            upper_hwirq_base = <122>;  // imx6ull
            //upper_hwirq_base = <210>;  // stm32mp157
        };
    
        gpio_keys_100ask {
            compatible = "100ask,gpio_key";
            interrupt-parent = <&virtual_intc>;
            interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
                         <1 IRQ_TYPE_LEVEL_HIGH>,
                         <2 IRQ_TYPE_LEVEL_HIGH>,
                         <3 IRQ_TYPE_LEVEL_HIGH>;
        };
    
    };
    
  • 编译设备树:
    在Ubuntu的IMX6ULL内核目录下执行如下命令,
    得到设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dtb

    make dtbs
    
  • 复制到NFS目录:

    $ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
    
  • 开发板上挂载NFS文件系统

    • vmware使用NAT(假设windowsIP为192.168.1.100)

      [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
      192.168.1.100:/home/book/nfs_rootfs /mnt
      
    • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137

      [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      
    • 更新设备树

      [root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
      [root@100ask:~]# sync
      
  • 重启开发板

3.3 编译、安装驱动程序
  • 编译:

    • 在Ubuntu上
    • 修改07_virtual_int_controller_hierarchy_ok中的Makefile,指定内核路径KERN_DIR,在执行make命令即可。
  • 安装:

    • 在开发板上

    • 挂载NFS,复制文件,insmod,类似如下命令:

      mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
      // 对于IMX6ULL,想看到驱动打印信息,需要先执行
      echo "7 4 1 7" > /proc/sys/kernel/printk
      
      insmod -f /mnt/virtual_int_controller.ko
      // 安装virtual_int_controller之后即可进入/sys/kernel/irq目录查看分配的中断号
      
      insmod -f /mnt/gpio_key_drv.ko
      cat /proc/interrupts
      
      // 触发中断
      // 对于IMX6ULL
      devmem 0xa01210 32 0x4000000
      devmem 0xa01210 32 0x8000000
      devmem 0xa01210 32 0x10000000
      devmem 0xa01210 32 0x20000000
      
      // 对于stm32mp157
      devmem 0xa002121c 32 0x40000 
      devmem 0xa002121c 32 0x80000
      devmem 0xa002121c 32 0x100000  // 它不能触发中断,可能是被占用了
      devmem 0xa002121c 32 0x200000
      
  • 观察内核打印的信息

六、USART

1.编写虚拟UART驱动程序_实现数据传输

1.1 虚拟UART的驱动组成

在这里插入图片描述

1.2 虚拟UART的数据流程

在这里插入图片描述

为了做实验,我们还要创建一个虚拟文件:/proc/virt_uart_buf

  • 要发数据给虚拟串口时,执行:echo “xxx” > /proc/virt_uart_buf
  • 要读取虚拟串口的数据时,执行:cat /proc/virt_uart_buf

1.3 实现/proc文件

参考/proc/cmdline,怎么找到它对应的驱动?在Linux内核源码下执行以下命令搜索:

grep "cmdline" * -nr | grep proc

得到:

fs/proc/cmdline.c:26:   proc_create("cmdline", 0, NULL, &cmdline_proc_fops);

1.4 触发中断

使用如下函数:

int irq_set_irqchip_state(unsigned int irq, enum irqchip_irq_state which,
			  bool val);

怎么找到它的?在中断子系统中,我们知道往GIC寄存器GICD_ISPENDRn写入某一位就可以触发中断。内核代码中怎么访问这些寄存器?
drivers\irqchip\irq-gic.c中可以看到irq_chip中的"irq_set_irqchip_state"被用来设置中断状态:

static struct irq_chip gic_chip = {
	.irq_mask		= gic_mask_irq,
	.irq_unmask		= gic_unmask_irq,
	.irq_eoi		= gic_eoi_irq,
	.irq_set_type		= gic_set_type,
	.irq_get_irqchip_state	= gic_irq_get_irqchip_state,
	.irq_set_irqchip_state	= gic_irq_set_irqchip_state, /* 2. 继续搜"irq_set_irqchip_state" */
	.flags			= IRQCHIP_SET_TYPE_MASKED |
				  IRQCHIP_SKIP_SET_WAKE |
				  IRQCHIP_MASK_ON_SUSPEND,
};

static int gic_irq_set_irqchip_state(struct irq_data *d,
				     enum irqchip_irq_state which, bool val)
{
	u32 reg;

	switch (which) {
	case IRQCHIP_STATE_PENDING:
		reg = val ? GIC_DIST_PENDING_SET : GIC_DIST_PENDING_CLEAR; /* 1. 找到寄存器 */
		break;

	case IRQCHIP_STATE_ACTIVE:
		reg = val ? GIC_DIST_ACTIVE_SET : GIC_DIST_ACTIVE_CLEAR;
		break;

	case IRQCHIP_STATE_MASKED:
		reg = val ? GIC_DIST_ENABLE_CLEAR : GIC_DIST_ENABLE_SET;
		break;

	default:
		return -EINVAL;
	}

	gic_poke_irq(d, reg);
	return 0;
}

继续搜"irq_set_irqchip_state",在drivers\irqchip\irq-gic.c中可以看到:

int irq_set_irqchip_state(unsigned int irq, enum irqchip_irq_state which,
			  bool val)
{
    ......
}

EXPORT_SYMBOL_GPL(irq_set_irqchip_state);

以后就可与使用如下代码触发某个中断:

irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING, 1);

2 编写虚拟UART驱动程序_调试

2.1 实验流程

在这里插入图片描述

2.2 编译、替换设备树
  • 修改arch/arm/boot/dts/100ask_imx6ull-14x14.dts,添加如下代码:

    / {
    	virtual_uart: virtual_uart_100ask {
    		compatible = "100ask,virtual_uart";
    		
    		interrupt-parent = <&intc>;
    		interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;
    		
    	};
    
    };
    
  • 编译设备树:
    在Ubuntu的IMX6ULL内核目录下执行如下命令,
    得到设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dtb

    make dtbs
    
  • 复制到NFS目录:

    $ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
    
  • 开发板上挂载NFS文件系统

    • vmware使用NAT(假设windowsIP为192.168.2.100)

      [root@100ask:~]# mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 
      192.168.2.100:/home/book/nfs_rootfs /mnt
      
    • vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.2.137

      [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.2.137:/home/book/nfs_rootfs /mnt
      
    • 更新设备树

      [root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
      [root@100ask:~]# sync
      
  • 重启开发板

2.3 编译、安装驱动程序
  • 编译:

    • 在Ubuntu上
    • 修改07_virtual_uart_driver_ok中的Makefile,指定内核路径KERN_DIR,在执行make命令即可。
  • 安装:

    • 在开发板上

    • 挂载NFS,复制文件,insmod,类似如下命令:

      mount -t nfs -o nolock,vers=3 192.168.2.137:/home/book/nfs_rootfs /mnt
      // 对于IMX6ULL,想看到驱动打印信息,需要先执行
      echo "7 4 1 7" > /proc/sys/kernel/printk
      
      insmod -f /mnt/virtual_uart.ko
      
      
  • 观察内核打印的信息

2. 2 调试

根据框架、数据流程来调试:

在这里插入图片描述

2.2.1 virtual_uart.c

#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/platform_device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/rational.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>

#include <asm/irq.h>

#define BUF_LEN  1024
#define NEXT_PLACE(i) ((i+1)&0x3FF)

static struct uart_port	*virt_port;
static unsigned char txbuf[BUF_LEN];
static int tx_buf_r = 0;
static int tx_buf_w = 0;

static unsigned char rxbuf[BUF_LEN];
static int rx_buf_w = 0;

static struct proc_dir_entry *uart_proc_file;


static struct uart_driver virt_uart_drv = {
	.owner          = THIS_MODULE,
	.driver_name    = "VIRT_UART",
	.dev_name       = "ttyVIRT",
	.major          = 0,
	.minor          = 0,
	.nr             = 1,
};


/* circle buffer */


static int is_txbuf_empty(void)
{
	return tx_buf_r == tx_buf_w;
}

static int is_txbuf_full(void)
{
	return NEXT_PLACE(tx_buf_w) == tx_buf_r;
}

static int txbuf_put(unsigned char val)
{
	if (is_txbuf_full())
		return -1;
	txbuf[tx_buf_w] = val;
	tx_buf_w = NEXT_PLACE(tx_buf_w);
	return 0;
}

static int txbuf_get(unsigned char *pval)
{
	if (is_txbuf_empty())
		return -1;
	*pval = txbuf[tx_buf_r];
	tx_buf_r = NEXT_PLACE(tx_buf_r);
	return 0;
}

static int txbuf_count(void)
{
	if (tx_buf_w >= tx_buf_r)
		return tx_buf_w - tx_buf_r;
	else
		return BUF_LEN + tx_buf_w - tx_buf_r;
}



ssize_t virt_uart_buf_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	/* 把txbuf中的数据copy_to_user */
	int cnt = txbuf_count();
	int i;
	unsigned char val;
	int ret;

	cnt = (cnt > size)? size: cnt;

	for (i = 0; i < cnt; i++)
	{
		txbuf_get(&val);
		ret = copy_to_user(buf+i, &val, 1);
	}

	return cnt;
}

static ssize_t virt_uart_buf_write (struct file *file, const char __user *buf, size_t size, loff_t *off)
{
	int ret;
	
	/* get data */
	ret = copy_from_user(rxbuf, buf, size);
	rx_buf_w = size;

	/* 模拟产生RX中断 */
	irq_set_irqchip_state(virt_port->irq, IRQCHIP_STATE_PENDING, 1);
	
	return size;
}

static const struct file_operations virt_uart_buf_fops = {
	.read		= virt_uart_buf_read,
	.write		= virt_uart_buf_write,
};


static unsigned int virt_tx_empty(struct uart_port *port)
{
	/* 因为要发送的数据瞬间存入buffer */
	return 1;
}


/*
 * interrupts disabled on entry
 */
static void virt_start_tx(struct uart_port *port)
{
	struct circ_buf *xmit = &port->state->xmit;

	while (!uart_circ_empty(xmit) &&
	       !uart_tx_stopped(port)) {
		/* send xmit->buf[xmit->tail]
		 * out the port here */

		/* 把circ buffer中的数据全部存入txbuf */

		//txbuf[tx_buf_w++] =  xmit->buf[xmit->tail];
		txbuf_put(xmit->buf[xmit->tail]);
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;
	}

   if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
	   uart_write_wakeup(port);

}


static void
virt_set_termios(struct uart_port *port, struct ktermios *termios,
		  struct ktermios *old)
{
   return;
}

static int virt_startup(struct uart_port *port)
{
   return 0;
}

static void virt_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}

static unsigned int virt_get_mctrl(struct uart_port *port)
{
   return 0;
}

static void virt_stop_tx(struct uart_port *port)
{
}

static void virt_stop_rx(struct uart_port *port)
{
}

static void virt_shutdown(struct uart_port *port)
{
}

static const char *virt_type(struct uart_port *port)
{
   return "100ASK_VIRT_UART";
}


static const struct uart_ops virt_pops = {
   .tx_empty   = virt_tx_empty,
   .set_mctrl  = virt_set_mctrl,
   .get_mctrl  = virt_get_mctrl,
   .stop_tx    = virt_stop_tx,
   .start_tx   = virt_start_tx,
   .stop_rx    = virt_stop_rx,
   //.enable_ms    = imx_enable_ms,
   //.break_ctl    = imx_break_ctl,
   .startup    = virt_startup,
   .shutdown   = virt_shutdown,
   //.flush_buffer = imx_flush_buffer,
   .set_termios    = virt_set_termios,
   .type	   = virt_type,
   //.config_port  = imx_config_port,
   //.verify_port  = imx_verify_port,
};



static irqreturn_t virt_uart_rxint(int irq, void *dev_id)
{
	struct uart_port *port = dev_id;
	struct tty_port *tport = &port->state->port;
	unsigned long flags;
	int i;
	
	spin_lock_irqsave(&port->lock, flags);

	for (i = 0; i < rx_buf_w; i++) {
		port->icount.rx++;
	
		/* get data from hardware/rxbuf */

		/* put data to ldisc */
		tty_insert_flip_char(tport, rxbuf[i], TTY_NORMAL);
	}
	rx_buf_w = 0;

	spin_unlock_irqrestore(&port->lock, flags);
	tty_flip_buffer_push(tport);
	
	return IRQ_HANDLED;
}


static int virtual_uart_probe(struct platform_device *pdev)
{	
	int rxirq;
	int ret;

	/* create proc file */	
	uart_proc_file = proc_create("virt_uart_buf", 0, NULL, &virt_uart_buf_fops);
	
	//uart_add_one_port(struct uart_driver * drv, struct uart_port * uport);

	/* 从设备树获得硬件信息 */
	rxirq = platform_get_irq(pdev, 0);
	
	/* 分配设置注册uart_port */
	virt_port = devm_kzalloc(&pdev->dev, sizeof(*virt_port), GFP_KERNEL);

	virt_port->dev = &pdev->dev;
	virt_port->iotype = UPIO_MEM;
	virt_port->irq = rxirq;
	virt_port->fifosize = 32;
	virt_port->ops = &virt_pops;
	virt_port->flags = UPF_BOOT_AUTOCONF;
	virt_port->type = PORT_8250;

	ret = devm_request_irq(&pdev->dev, rxirq, virt_uart_rxint, 0,
				   dev_name(&pdev->dev), virt_port);
	
	return uart_add_one_port(&virt_uart_drv, virt_port);
	
}
static int virtual_uart_remove(struct platform_device *pdev)
{
	uart_remove_one_port(&virt_uart_drv, virt_port);
	proc_remove(uart_proc_file);
	return 0;
}



static const struct of_device_id virtual_uart_of_match[] = {
	{ .compatible = "100ask,virtual_uart", },
	{ },
};


static struct platform_driver virtual_uart_driver = {
	.probe		= virtual_uart_probe,
	.remove		= virtual_uart_remove,
	.driver		= {
		.name	= "100ask_virtual_uart",
		.of_match_table = of_match_ptr(virtual_uart_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_uart_init(void)
{	
	int ret;

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	ret = uart_register_driver(&virt_uart_drv);

	if (ret)
		return ret;
	
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_uart_driver);
}


/* 2. 出口函数 */
static void __exit virtual_uart_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_uart_driver);

	
	uart_unregister_driver(&virt_uart_drv);
}

module_init(virtual_uart_init);
module_exit(virtual_uart_exit);

MODULE_LICENSE("GPL");

2.2.2 serial_send_recv.c

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>

/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
	struct termios newtio,oldtio;
	
	if ( tcgetattr( fd,&oldtio) != 0) { 
		perror("SetupSerial 1");
		return -1;
	}
	
	bzero( &newtio, sizeof( newtio ) );
	newtio.c_cflag |= CLOCAL | CREAD; 
	newtio.c_cflag &= ~CSIZE; 

	newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/
	newtio.c_oflag  &= ~OPOST;   /*Output*/

	switch( nBits )
	{
	case 7:
		newtio.c_cflag |= CS7;
	break;
	case 8:
		newtio.c_cflag |= CS8;
	break;
	}

	switch( nEvent )
	{
	case 'O':
		newtio.c_cflag |= PARENB;
		newtio.c_cflag |= PARODD;
		newtio.c_iflag |= (INPCK | ISTRIP);
	break;
	case 'E': 
		newtio.c_iflag |= (INPCK | ISTRIP);
		newtio.c_cflag |= PARENB;
		newtio.c_cflag &= ~PARODD;
	break;
	case 'N': 
		newtio.c_cflag &= ~PARENB;
	break;
	}

	switch( nSpeed )
	{
	case 2400:
		cfsetispeed(&newtio, B2400);
		cfsetospeed(&newtio, B2400);
	break;
	case 4800:
		cfsetispeed(&newtio, B4800);
		cfsetospeed(&newtio, B4800);
	break;
	case 9600:
		cfsetispeed(&newtio, B9600);
		cfsetospeed(&newtio, B9600);
	break;
	case 115200:
		cfsetispeed(&newtio, B115200);
		cfsetospeed(&newtio, B115200);
	break;
	default:
		cfsetispeed(&newtio, B9600);
		cfsetospeed(&newtio, B9600);
	break;
	}
	
	if( nStop == 1 )
		newtio.c_cflag &= ~CSTOPB;
	else if ( nStop == 2 )
		newtio.c_cflag |= CSTOPB;
	
	newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
	newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: 
	                         * 比如VMIN设为10表示至少读到10个数据才返回,
	                         * 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
	                         * 假设VTIME=1,表示: 
	                         *    10秒内一个数据都没有的话就返回
	                         *    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
	                         */

	tcflush(fd,TCIFLUSH);
	
	if((tcsetattr(fd,TCSANOW,&newtio))!=0)
	{
		perror("com set error");
		return -1;
	}
	//printf("set done!\n");
	return 0;
}

int open_port(char *com)
{
	int fd;
	//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
	fd = open(com, O_RDWR|O_NOCTTY);
    if (-1 == fd){
		return(-1);
    }
	
	  if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
	  {
			printf("fcntl failed!\n");
			return -1;
	  }
  
	  return fd;
}


/*
 * ./serial_send_recv <dev>
 */
int main(int argc, char **argv)
{
	int fd;
	int iRet;
	char c;

	/* 1. open */

	/* 2. setup 
	 * 115200,8N1
	 * RAW mode
	 * return data immediately
	 */

	/* 3. write and read */
	
	if (argc != 2)
	{
		printf("Usage: \n");
		printf("%s </dev/ttySAC1 or other>\n", argv[0]);
		return -1;
	}

	fd = open_port(argv[1]);
	if (fd < 0)
	{
		printf("open %s err!\n", argv[1]);
		return -1;
	}

	iRet = set_opt(fd, 115200, 8, 'N', 1);
	if (iRet)
	{
		printf("set port err!\n");
		return -1;
	}

	printf("Enter a char: ");
	while (1)
	{
		scanf("%c", &c);
		iRet = write(fd, &c, 1);
		iRet = read(fd, &c, 1);
		if (iRet == 1)
			printf("get: %02x %c\n", c, c);
		else
			printf("can not get data\n");
	}

	return 0;
}

七、SPI协议介绍

1. SPI硬件知识

1.1 硬件连线

在这里插入图片描述
引脚含义如下:

引脚含义
DO(MOSI)Master Output, Slave Input,
SPI主控用来发出数据,SPI从设备用来接收数据
DI(MISO)Master Input, Slave Output,
SPI主控用来接受数据,SPI从设备用来发送数据
SCKSerial Clock,时钟
CSChip Select,芯片选择引脚

1.2 SPI控制器内部结构

这个图等我们看完后面的SPI协议,再回过头来讲解:

在这里插入图片描述

2. SPI协议

2.1 传输示例

假设现在主控芯片要传输一个0x56数据给SPI Flash,时序如下:
在这里插入图片描述
首先CS0先拉低选中SPI Flash,0x56的二进制就是0b0101 0110,因此在每个SCK时钟周期,DO输出对应的电平。
SPI Flash会在每个时钟周期的上升沿读取D0上的电平。

2.2 SPI模式

在SPI协议中,有两个值来确定SPI的模式。
CPOL:表示SPICLK的初始电平,0为电平,1为高电平
CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿

CPOLCPHA模式含义
000SPICLK初始电平为低电平,在第一个时钟沿采样数据
011SPICLK初始电平为低电平,在第二个时钟沿采样数据
102SPICLK初始电平为高电平,在第一个时钟沿采样数据
113SPICLK初始电平为高电平,在第二个时钟沿采样数据

我们常用的是模式0和模式3,因为它们都是在上升沿采样数据,不用去在乎时钟的初始电平是什么,只要在上升沿采集数据就行。

极性选什么?格式选什么?通常去参考外接的模块的芯片手册。比如对于OLED,查看它的芯片手册时序部分:
在这里插入图片描述

SCLK的初始电平我们并不需要关心,只要保证在上升沿采样数据就行。
在这里插入图片描述
在这里插入图片描述

3. SPI_DAC模块上机实验

3.1 原理图

IMX6ULL:
在这里插入图片描述

3.2 连接

1.2.1 IMX6ULL

DAC模块接到IMX6ULL扩展板的SPI_A插座上:

在这里插入图片描述

2. 编写设备树

确认SPI时钟最大频率:
在这里插入图片描述

T = 25 + 25 = 50ns
F = 20000000 = 20MHz

设备树如下:

    dac: dac {
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <20000000>;
    };

2.1 IMX6ULL

在这里插入图片描述

DAC模块接在这个插座上,那么要在设备树里spi1的节点下创建子节点。

代码在arch/arm/boot/dts/100ask_imx6ull-14x14.dtb中,如下:

&ecspi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi1>;

    fsl,spi-num-chipselects = <2>;
    cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
    status = "okay";

    dac: dac {
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <20000000>;
    };
};

3. 编译替换设备树

3.1 IMX6ULL

3.1.1 设置工具链
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
 export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
3.1.2 编译、替换设备树
  • 编译设备树:
    在Ubuntu的IMX6ULL内核目录下执行如下命令,
    得到设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dtb

    make dtbs
    
  • 复制到NFS目录:

    $ cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs/
    
  • 开发板上挂载NFS文件系统

    [root@100ask:~]#  mount -t nfs -o nolock,vers=3 192.168.1.137:/home/book/nfs_rootfs /mnt
    
  • 更新设备树

    [root@100ask:~]# cp /mnt/100ask_imx6ull-14x14.dtb /boot
    [root@100ask:~]# sync
    
  • 重启开发板

3.2 编译spidev驱动

首先要确定内核中已经含有spidev。在内核目录下执行make menuconfig,查看是否有改驱动,如下图:

-> Device Drivers
  -> SPI support (SPI [=y]) 
    < >   User mode SPI device driver support  

如果User mode SPI device driver support前面不是<Y><M>,可以输入M表示把它编译为模块。

  • 如果已经是<Y>,则不用再做其他事情。
  • 如果你设置为<M>,在内核目录下执行make modules,把生成的drivers/spi/spidev.ko复制到NFS目录备用

3.5 dac_test.c



/* 参考: tools\spi\spidev_fdx.c */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>

/* dac_test /dev/spidevB.D <val> */

int main(int argc, char **argv)
{
	int fd;
	unsigned int val;
	struct spi_ioc_transfer	xfer[1];
	int status;

	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	
	
	if (argc != 3)
	{
		printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
		return 0;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		printf("can not open %s\n", argv[1]);
		return 1;
	}

	val = strtoul(argv[2], NULL, 0);
	val <<= 2;     /* bit0,bit1 = 0b00 */
	val &= 0xFFC;  /* 只保留10bit */

	tx_buf[1] = val & 0xff;
	tx_buf[0] = (val>>8) & 0xff;	

	memset(xfer, 0, sizeof xfer);


	xfer[0].tx_buf = tx_buf;
	xfer[0].rx_buf = rx_buf;
	xfer[0].len = 2;
	
	status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
	if (status < 0) {
		printf("SPI_IOC_MESSAGE\n");
		return -1;
	}

	/* 打印 */
	val = (rx_buf[0] << 8) | (rx_buf[1]);
	val >>= 2;
	printf("Pre val = %d\n", val);
	
	
	return 0;
}


3.4 编译APP

arm-buildroot-linux-gnueabihf-gcc  -o  dac_test  dac_test.c

3.4上机实验

如果spidev没有被编译进内核,那么先执行:

insmod spidev.ko

确定设备节点:

ls /dev/spidev*

假设设备节点为/dev/spidev0.0,执行测试程序:

./dac_test  /dev/spidev0.0  500
./dac_test  /dev/spidev0.0  600
./dac_test  /dev/spidev0.0  1000

4.编写DAC驱动

&ecspi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi1>;

    fsl,spi-num-chipselects = <2>;
    cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
    status = "okay";

    dac: dac {
        compatible = "spidev";
        reg = <0>;
        spi-max-frequency = <20000000>;
    };
};

4.1 dac_drv.c

/*
 * Simple synchronous userspace interface to SPI devices
 *
 * Copyright (C) 2006 SWAPP
 *	Andrea Paterniani <a.paterniani@swapp-eng.it>
 * Copyright (C) 2007 David Brownell (simplification, cleanup)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/uaccess.h>

#define SPI_IOC_WR 123

/*-------------------------------------------------------------------------*/

static struct spi_device *dac;
static int major;

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int val;
	int err;
	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	

	struct spi_message	msg;
	struct spi_transfer	xfer[1];
	int status;

	memset(&xfer[0], 0, sizeof(xfer));
	
	/* copy_from_user */
	err = copy_from_user(&val, (const void __user *)arg, sizeof(int));

	printk("spidev_ioctl get val from user: %d\n", val);

	/* 发起SPI传输:     */

	/* 1. 把val修改为正确的格式 */
	val <<= 2;     /* bit0,bit1 = 0b00 */
	val &= 0xFFC;  /* 只保留10bit */

	tx_buf[1] = val & 0xff;
	tx_buf[0] = (val>>8) & 0xff;	

	/* 2. 发起SPI传输同时写\读 */
	/* 2.1 构造transfer
	 * 2.2 加入message
	 * 2.3 调用spi_sync
	 */
	xfer[0].tx_buf = tx_buf;
	xfer[0].rx_buf = rx_buf;
	xfer[0].len = 2;

	spi_message_init(&msg);
	spi_message_add_tail(&xfer[0], &msg);
	
	status = spi_sync(dac, &msg);

	/* 3. 修改读到的数据的格式 */
	val = (rx_buf[0] << 8) | (rx_buf[1]);
	val >>= 2;

	/* copy_to_user */
	err = copy_to_user((void __user *)arg, &val, sizeof(int));
	
	return 0;
}


static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.unlocked_ioctl = spidev_ioctl,
};

/*-------------------------------------------------------------------------*/

/* The main reason to have this class is to make mdev/udev create the
 * /dev/spidevB.C character device nodes exposing our userspace API.
 * It also simplifies memory management.
 */

static struct class *spidev_class;

static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "100ask,dac" },
	{},
};


/*-------------------------------------------------------------------------*/

static int spidev_probe(struct spi_device *spi)
{
	/* 1. 记录spi_device */
	dac = spi;

	/* 2. 注册字符设备 */
	major = register_chrdev(0, "100ask_dac", &spidev_fops);
	spidev_class = class_create(THIS_MODULE, "100ask_dac");
	device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_dac");	

	return 0;
}

static int spidev_remove(struct spi_device *spi)
{
	/* 反注册字符设备 */
	device_destroy(spidev_class, MKDEV(major, 0));
	class_destroy(spidev_class);
	unregister_chrdev(major, "100ask_dac");

	return 0;
}

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"100ask_spi_dac_drv",
		.of_match_table = of_match_ptr(spidev_dt_ids),
	},
	.probe =	spidev_probe,
	.remove =	spidev_remove,

	/* NOTE:  suspend/resume methods are not necessary here.
	 * We don't do anything except pass the requests to/from
	 * the underlying controller.  The refrigerator handles
	 * most issues; the controller driver handles the rest.
	 */
};

/*-------------------------------------------------------------------------*/

static int __init spidev_init(void)
{
	int status;

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
	}
	return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	spi_unregister_driver(&spidev_spi_driver);
}

module_exit(spidev_exit);
MODULE_LICENSE("GPL");

4.2 dac_test.c

/* 参考: tools\spi\spidev_fdx.c */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>

#define SPI_IOC_WR 123

/* dac_test /dev/100ask_dac <val> */

int main(int argc, char **argv)
{
	int fd;
	unsigned int val;
	int status;

	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	
	
	if (argc != 3)
	{
		printf("Usage: %s /dev/100ask_dac <val>\n", argv[0]);
		return 0;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		printf("can not open %s\n", argv[1]);
		return 1;
	}

	val = strtoul(argv[2], NULL, 0);

	status = ioctl(fd, SPI_IOC_WR, &val);
	if (status < 0) {
		printf("SPI_IOC_WR\n");
		return -1;
	}

	/* 打印 */
	printf("Pre val = %d\n", val);

	return 0;
}

5.编写OLED驱动_上机实验

5.1. 原理图

在这里插入图片描述

无论是使用IMX6ULL开发板还是STM32MP157开发板,都有类似的扩展板。把OLED模块接到扩展板的SPI_A插座上,如下:

在这里插入图片描述

5.2 编写设备树

在这里插入图片描述

DC引脚使用GPIO4_20,也需在设备树里指定。

设备树如下:arch/arm/boot/dts/100ask_imx6ull-14x14.dts

&ecspi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi1>;

    fsl,spi-num-chipselects = <2>;
    cs-gpios = <&gpio4 26 GPIO_ACTIVE_LOW>, <&gpio4 24 GPIO_ACTIVE_LOW>;
    status = "okay";

    oled: oled {
        compatible = "100ask,oled";
        reg = <0>;
        spi-max-frequency = <10000000>;
        dc-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>; 
    };
};

5.3 oled_drv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>


#define OLED_IOC_INIT 			123
#define OLED_IOC_SET_POS 		124

//为0 表示命令,为1表示数据
#define OLED_CMD 	0
#define OLED_DATA 	1



/*-------------------------------------------------------------------------*/

static struct spi_device *oled;
static int major;
static struct gpio_desc *dc_gpio;

static void dc_pin_init(void)
{
	gpiod_direction_output(dc_gpio, 1);
}

static void oled_set_dc_pin(int val)
{
	gpiod_set_value(dc_gpio, val);
}

static void spi_write_datas(const unsigned char *buf, int len)
{
	spi_write(oled, buf, len);
}


/**********************************************************************
	 * 函数名称: oled_write_cmd
	 * 功能描述: oled向特定地址写入数据或者命令
	 * 输入参数:@uc_data :要写入的数据
	 			@uc_cmd:为1则表示写入数据,为0表示写入命令
	 * 输出参数:无
	 * 返 回 值: 无
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/04		 V1.0	  芯晓		  创建
 ***********************************************************************/
static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd)
{
	if(uc_cmd==0)
	{
		oled_set_dc_pin(0);
	}
	else
	{
		oled_set_dc_pin(1);//拉高,表示写入数据
	}
	spi_write_datas(&uc_data, 1);//写入
}


/**********************************************************************
	 * 函数名称: oled_init
	 * 功能描述: oled_init的初始化,包括SPI控制器得初始化
	 * 输入参数:无
	 * 输出参数: 初始化的结果
	 * 返 回 值: 成功则返回0,否则返回-1
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
static int oled_init(void)
{
	oled_write_cmd_data(0xae,OLED_CMD);//关闭显示

	oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column address
	oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address

	oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line

	oled_write_cmd_data(0xB0,OLED_CMD);//设置page address

	oled_write_cmd_data(0x81,OLED_CMD);// contract control
	oled_write_cmd_data(0x66,OLED_CMD);//128

	oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap

	oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse

	oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio
	oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64

	oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction

	oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset
	oled_write_cmd_data(0x00,OLED_CMD);//

	oled_write_cmd_data(0xd5,OLED_CMD);//set osc division
	oled_write_cmd_data(0x80,OLED_CMD);//

	oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period
	oled_write_cmd_data(0x1f,OLED_CMD);//

	oled_write_cmd_data(0xda,OLED_CMD);//set com pins
	oled_write_cmd_data(0x12,OLED_CMD);//

	oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh
	oled_write_cmd_data(0x30,OLED_CMD);//

	oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable 
	oled_write_cmd_data(0x14,OLED_CMD);//

	oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay on

	return 0;
}		  			 		  						  					  				 	   		  	  	 	  

//坐标设置
/**********************************************************************
	 * 函数名称: OLED_DIsp_Set_Pos
	 * 功能描述:设置要显示的位置
	 * 输入参数:@ x :要显示的column address
	 			@y :要显示的page address
	 * 输出参数: 无
	 * 返 回 值: 
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
static void OLED_DIsp_Set_Pos(int x, int y)
{ 	oled_write_cmd_data(0xb0+y,OLED_CMD);
	oled_write_cmd_data((x&0x0f),OLED_CMD); 
	oled_write_cmd_data(((x&0xf0)>>4)|0x10,OLED_CMD);
}   	      	   			 

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int x, y;
	
	/* 根据cmd操作硬件 */
	switch (cmd)
	{
		case OLED_IOC_INIT: /* init */
		{
			dc_pin_init();
			oled_init();
			break;
		}

		case OLED_IOC_SET_POS: /* set pos */
		{
			x = arg & 0xff;
			y = (arg >> 8) & 0xff;
			OLED_DIsp_Set_Pos(x, y);
			break;
		}

	}

	return 0;
}

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	char *ker_buf;
	int err;

	ker_buf = kmalloc(count, GFP_KERNEL);
	err = copy_from_user(ker_buf, buf, count);
	
	oled_set_dc_pin(1);//拉高,表示写入数据
	spi_write_datas(ker_buf, count);
	kfree(ker_buf);
	return count;
}

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.write =	spidev_write,
	.unlocked_ioctl = spidev_ioctl,
};

/*-------------------------------------------------------------------------*/

/* The main reason to have this class is to make mdev/udev create the
 * /dev/spidevB.C character device nodes exposing our userspace API.
 * It also simplifies memory management.
 */

static struct class *spidev_class;

static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "100ask,oled" },
	{},
};


/*-------------------------------------------------------------------------*/

static int spidev_probe(struct spi_device *spi)
{
	/* 1. 记录spi_device */
	oled = spi;

	/* 2. 注册字符设备 */
	major = register_chrdev(0, "100ask_oled", &spidev_fops);
	spidev_class = class_create(THIS_MODULE, "100ask_oled");
	device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_oled");	

	/* 3. 获得GPIO引脚 */
	dc_gpio = gpiod_get(&spi->dev, "dc", 0);

	return 0;
}

static int spidev_remove(struct spi_device *spi)
{
	gpiod_put(dc_gpio);
	
	/* 反注册字符设备 */
	device_destroy(spidev_class, MKDEV(major, 0));
	class_destroy(spidev_class);
	unregister_chrdev(major, "100ask_oled");

	return 0;
}

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"100ask_spi_oled_drv",
		.of_match_table = of_match_ptr(spidev_dt_ids),
	},
	.probe =	spidev_probe,
	.remove =	spidev_remove,

	/* NOTE:  suspend/resume methods are not necessary here.
	 * We don't do anything except pass the requests to/from
	 * the underlying controller.  The refrigerator handles
	 * most issues; the controller driver handles the rest.
	 */
};

/*-------------------------------------------------------------------------*/

static int __init spidev_init(void)
{
	int status;

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
	}
	return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
	spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);

MODULE_LICENSE("GPL");


5.4 spi_oled.c

arm-buildroot-linux-gnueabihf-gcc  -o  spi_oled  spi_oled.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>

#include "font.h"

#define OLED_IOC_INIT 			123
#define OLED_IOC_SET_POS 		124


//为0 表示命令,为1表示数据
#define OLED_CMD 	0
#define OLED_DATA 	1

static int fd_spidev;
static int dc_pin_num;


void OLED_DIsp_Set_Pos(int x, int y);

void oled_write_datas(const unsigned char *buf, int len)
{
	write(fd_spidev, buf, len);
}

		  			 		  						  					  				 	   		  	  	 	  		  			 		  						  					  				 	   		  	  	 	  

/**********************************************************************
	 * 函数名称: OLED_DIsp_Clear
	 * 功能描述: 整个屏幕显示数据清0
	 * 输入参数:无
	 * 输出参数: 无
	 * 返 回 值: 
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
void OLED_DIsp_Clear(void)  
{
    unsigned char x, y;
	char buf[128];

	memset(buf, 0, 128);
	
    for (y = 0; y < 8; y++)
    {
        OLED_DIsp_Set_Pos(0, y);
        oled_write_datas(buf, 128);
    }
}

/**********************************************************************
	 * 函数名称: OLED_DIsp_All
	 * 功能描述: 整个屏幕显示全部点亮,可以用于检查坏点
	 * 输入参数:无
	 * 输出参数:无 
	 * 返 回 值:
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
void OLED_DIsp_All(void)  
{
    unsigned char x, y;
	char buf[128];

	memset(buf, 0xff, 128);
	
    for (y = 0; y < 8; y++)
    {
        OLED_DIsp_Set_Pos(0, y);
        oled_write_datas(buf, 128);
    }
}

//坐标设置
/**********************************************************************
	 * 函数名称: OLED_DIsp_Set_Pos
	 * 功能描述:设置要显示的位置
	 * 输入参数:@ x :要显示的column address
	 			@y :要显示的page address
	 * 输出参数: 无
	 * 返 回 值: 
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
void OLED_DIsp_Set_Pos(int x, int y)
{ 	
	ioctl(fd_spidev, OLED_IOC_SET_POS, x  | (y << 8));
}   	      	   			 
/**********************************************************************
	  * 函数名称: OLED_DIsp_Char
	  * 功能描述:在某个位置显示字符 1-9
	  * 输入参数:@ x :要显示的column address
		 			@y :要显示的page address
		 			@c :要显示的字符的ascii码
	  * 输出参数: 无
	  * 返 回 值: 
	  * 修改日期		版本号	  修改人 	   修改内容
	  * -----------------------------------------------
	  * 2020/03/15		  V1.0	   芯晓		   创建
***********************************************************************/
void OLED_DIsp_Char(int x, int y, unsigned char c)
{
	int i = 0;
	/* 得到字模 */
	const unsigned char *dots = oled_asc2_8x16[c - ' '];

	/* 发给OLED */
	OLED_DIsp_Set_Pos(x, y);
	/* 发出8字节数据 */
	//for (i = 0; i < 8; i++)
	//	oled_write_cmd_data(dots[i], OLED_DATA);
	oled_write_datas(&dots[0], 8);

	OLED_DIsp_Set_Pos(x, y+1);
	/* 发出8字节数据 */
	//for (i = 0; i < 8; i++)
		//oled_write_cmd_data(dots[i+8], OLED_DATA);
	oled_write_datas(&dots[8], 8);
}


/**********************************************************************
	 * 函数名称: OLED_DIsp_String
	 * 功能描述: 在指定位置显示字符串
	 * 输入参数:@ x :要显示的column address
		 			@y :要显示的page address
		 			@str :要显示的字符串
	 * 输出参数: 无
	 * 返 回 值: 无
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
***********************************************************************/
void OLED_DIsp_String(int x, int y, char *str)
{
	unsigned char j=0;
	while (str[j])
	{		
		OLED_DIsp_Char(x, y, str[j]);//显示单个字符
		x += 8;
		if(x > 127)
		{
			x = 0;
			y += 2;
		}//移动显示位置
		j++;
	}
}
/**********************************************************************
	 * 函数名称: OLED_DIsp_CHinese
	 * 功能描述:在指定位置显示汉字
	 * 输入参数:@ x :要显示的column address
		 			@y :要显示的page address
		 			@chr :要显示的汉字,三个汉字“百问网”中选择一个
	 * 输出参数: 无
	 * 返 回 值: 无
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/

void OLED_DIsp_CHinese(unsigned char x,unsigned char y,unsigned char no)
{      			    
	unsigned char t,adder=0;
	OLED_DIsp_Set_Pos(x,y);	
    for(t=0;t<16;t++)
	{//显示上半截字符	
		oled_write_datas(&hz_1616[no][t*2], 1);
		adder+=1;
    }	
	OLED_DIsp_Set_Pos(x,y+1);	
    for(t=0;t<16;t++)
	{//显示下半截字符
		oled_write_datas(&hz_1616[no][t*2+1], 1);
		adder+=1;
    }					
}
/**********************************************************************
	 * 函数名称: OLED_DIsp_Test
	 * 功能描述: 整个屏幕显示测试
	 * 输入参数:无
	 * 输出参数: 无
	 * 返 回 值: 无
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
void OLED_DIsp_Test(void)
{ 	
	int i;
	
	OLED_DIsp_String(0, 0, "wiki.100ask.net");
	OLED_DIsp_String(0, 2, "book.100ask.net");
	OLED_DIsp_String(0, 4, "bbs.100ask.net");
	
	for(i = 0; i < 3; i++)
	{   //显示汉字 百问网
		OLED_DIsp_CHinese(32+i*16, 6, i);
	}
} 

/* spi_oled /dev/100ask_oled */
int main(int argc, char **argv)
{	
	if (argc != 2)
	{
		printf("Usage: %s /dev/100ask_oled\n", argv[0]);
		return -1;
	}

	fd_spidev = open(argv[1], O_RDWR);
	if (fd_spidev < 0) {
		printf("open %s err\n", argv[1]);
		return -1;
	}

	ioctl(fd_spidev, OLED_IOC_INIT);
	OLED_DIsp_Clear();
	OLED_DIsp_Test();

	return 0;
}

5.5 上机实验

如果spidev没有被编译进内核,那么先执行:

insmod oled_drv.ko

确定设备节点:

ls /dev/100ask_oled

假设设备节点为/dev/100ask_oled,执行测试程序:

./spi_oled  /dev/100ask_oled

6. 使用Framebuffer改造OLED驱动

6.1 思路

在这里插入图片描述

假设OLED的每个像素使用1位数据表示:

  • Linux Framebuffer中byte0对应OLED上第1行的8个像素
  • OLED显存中byte0对应OLED上第1列的8个像素

为了兼容基于Framebuffer的程序,驱动程序中分配一块Framebuffer,APP直接操作Framebuffer。

驱动程序周期性地把Framebuffer中的数据搬移到OLED显存上。

怎么搬移?

发给OLED线程的byte0、1、2、3、4、5、6、7怎么构造出来?

  • 它们来自Framebuffer的byte0、16、32、48、64、80、96、112
  • OLED的byte0,由Framebuffer的这8个字节的bit0组合得到
  • OLED的byte1,由Framebuffer的这8个字节的bit1组合得到
  • OLED的byte2,由Framebuffer的这8个字节的bit2组合得到
  • OLED的byte3,由Framebuffer的这8个字节的bit3组合得到
  • ……

6.2 编程

6.2.1 Framebuffer编程

分配、设置、注册fb_info结构体。

  • 分配fb_info
  • 设置fb_info
    • fb_var
    • fb_fix
  • 注册fb_info
  • 硬件操作
6.2.2 数据搬移

创建内核线程,周期性地把Framebuffer中的数据通过SPI发送给OLED。

创建内核线程:

  • 参考文件include\linux\kthread.h

  • 参考文章:https://blog.csdn.net/qq_37858386/article/details/115573565

  • kthread_create:创建内核线程,线程处于"停止状态",要运行它需要执行wake_up_process

  • kthread_run:创建内核线程,并马上让它处于"运行状态"

  • kernel_thread

6.3 oled_drv.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>

#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/uaccess.h>
#include <linux/gpio/consumer.h>

#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>

#define OLED_IOC_INIT 			123
#define OLED_IOC_SET_POS 		124

//为0 表示命令,为1表示数据
#define OLED_CMD 	0
#define OLED_DATA 	1

static struct fb_info *myfb_info;

static unsigned int pseudo_palette[16];

static struct task_struct *oled_thread;

static unsigned char *oled_buf; //[1024];

/* from pxafb.c */
static inline unsigned int chan_to_field(unsigned int chan,
					 struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

static int mylcd_setcolreg(unsigned regno,
			       unsigned red, unsigned green, unsigned blue,
			       unsigned transp, struct fb_info *info)
{
	unsigned int val;

	/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",
		   regno, red, green, blue); */

	switch (info->fix.visual) {
	case FB_VISUAL_TRUECOLOR:
		/* true-colour, use pseudo-palette */

		if (regno < 16) {
			u32 *pal = info->pseudo_palette;

			val  = chan_to_field(red,   &info->var.red);
			val |= chan_to_field(green, &info->var.green);
			val |= chan_to_field(blue,  &info->var.blue);

			pal[regno] = val;
		}
		break;

	default:
		return 1;	/* unknown type */
	}

	return 0;
}


static struct fb_ops myfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= mylcd_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};



/*-------------------------------------------------------------------------*/

static struct spi_device *oled;
static int major;
static struct gpio_desc *dc_gpio;

static void dc_pin_init(void)
{
	gpiod_direction_output(dc_gpio, 1);
}

static void oled_set_dc_pin(int val)
{
	gpiod_set_value(dc_gpio, val);
}

static void spi_write_datas(const unsigned char *buf, int len)
{
	spi_write(oled, buf, len);
}


/**********************************************************************
	 * 函数名称: oled_write_cmd
	 * 功能描述: oled向特定地址写入数据或者命令
	 * 输入参数:@uc_data :要写入的数据
	 			@uc_cmd:为1则表示写入数据,为0表示写入命令
	 * 输出参数:无
	 * 返 回 值: 无
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/04		 V1.0	  芯晓		  创建
 ***********************************************************************/
static void oled_write_cmd_data(unsigned char uc_data,unsigned char uc_cmd)
{
	if(uc_cmd==0)
	{
		oled_set_dc_pin(0);
	}
	else
	{
		oled_set_dc_pin(1);//拉高,表示写入数据
	}
	spi_write_datas(&uc_data, 1);//写入
}


/**********************************************************************
	 * 函数名称: oled_init
	 * 功能描述: oled_init的初始化,包括SPI控制器得初始化
	 * 输入参数:无
	 * 输出参数: 初始化的结果
	 * 返 回 值: 成功则返回0,否则返回-1
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
static int oled_init(void)
{
	oled_write_cmd_data(0xae,OLED_CMD);//关闭显示

	oled_write_cmd_data(0x00,OLED_CMD);//设置 lower column address
	oled_write_cmd_data(0x10,OLED_CMD);//设置 higher column address

	oled_write_cmd_data(0x40,OLED_CMD);//设置 display start line

	oled_write_cmd_data(0xB0,OLED_CMD);//设置page address

	oled_write_cmd_data(0x81,OLED_CMD);// contract control
	oled_write_cmd_data(0x66,OLED_CMD);//128

	oled_write_cmd_data(0xa1,OLED_CMD);//设置 segment remap

	oled_write_cmd_data(0xa6,OLED_CMD);//normal /reverse

	oled_write_cmd_data(0xa8,OLED_CMD);//multiple ratio
	oled_write_cmd_data(0x3f,OLED_CMD);//duty = 1/64

	oled_write_cmd_data(0xc8,OLED_CMD);//com scan direction

	oled_write_cmd_data(0xd3,OLED_CMD);//set displat offset
	oled_write_cmd_data(0x00,OLED_CMD);//

	oled_write_cmd_data(0xd5,OLED_CMD);//set osc division
	oled_write_cmd_data(0x80,OLED_CMD);//

	oled_write_cmd_data(0xd9,OLED_CMD);//ser pre-charge period
	oled_write_cmd_data(0x1f,OLED_CMD);//

	oled_write_cmd_data(0xda,OLED_CMD);//set com pins
	oled_write_cmd_data(0x12,OLED_CMD);//

	oled_write_cmd_data(0xdb,OLED_CMD);//set vcomh
	oled_write_cmd_data(0x30,OLED_CMD);//

	oled_write_cmd_data(0x8d,OLED_CMD);//set charge pump disable 
	oled_write_cmd_data(0x14,OLED_CMD);//

	oled_write_cmd_data(0xaf,OLED_CMD);//set dispkay on

	return 0;
}		  			 		  						  					  				 	   		  	  	 	  

//坐标设置
/**********************************************************************
	 * 函数名称: OLED_DIsp_Set_Pos
	 * 功能描述:设置要显示的位置
	 * 输入参数:@ x :要显示的column address
	 			@y :要显示的page address
	 * 输出参数: 无
	 * 返 回 值: 
	 * 修改日期 	   版本号	 修改人		  修改内容
	 * -----------------------------------------------
	 * 2020/03/15		 V1.0	  芯晓		  创建
 ***********************************************************************/
static void OLED_DIsp_Set_Pos(int x, int y)
{ 	oled_write_cmd_data(0xb0+y,OLED_CMD);
	oled_write_cmd_data((x&0x0f),OLED_CMD); 
	oled_write_cmd_data(((x&0xf0)>>4)|0x10,OLED_CMD);
}   	      	   			 


static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int x, y;
	
	/* 根据cmd操作硬件 */
	switch (cmd)
	{
		case OLED_IOC_INIT: /* init */
		{
			dc_pin_init();
			oled_init();
			break;
		}

		case OLED_IOC_SET_POS: /* set pos */
		{
			x = arg & 0xff;
			y = (arg >> 8) & 0xff;
			OLED_DIsp_Set_Pos(x, y);
			break;
		}

	}

	return 0;
}

static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	char *ker_buf;
	int err;

	ker_buf = kmalloc(count, GFP_KERNEL);
	err = copy_from_user(ker_buf, buf, count);
	
	oled_set_dc_pin(1);//拉高,表示写入数据
	spi_write_datas(ker_buf, count);
	kfree(ker_buf);
	return count;
}

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.write =	spidev_write,
	.unlocked_ioctl = spidev_ioctl,
};

/*-------------------------------------------------------------------------*/

/* The main reason to have this class is to make mdev/udev create the
 * /dev/spidevB.C character device nodes exposing our userspace API.
 * It also simplifies memory management.
 */

static struct class *spidev_class;

static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "100ask,oled" },
	{},
};



static int oled_thread_func(void *param)
{
	unsigned char *p[8];
	unsigned char data[8];
	int i;
	int j;
	int line;
	int bit;
	unsigned char byte;
	unsigned char *fb  = myfb_info->screen_base;
	int k;
	
	while (!kthread_should_stop()) 
	{
		/* 1. 从Framebuffer得到数据 */
		/* 2. 转换格式 */
		k = 0;
		for (i = 0; i < 8; i++)
		{
			for (line = 0; line < 8; line++)
				p[line] = &fb[i*128 + line * 16];
			
			for (j = 0; j < 16; j++)
			{
				for (line = 0; line < 8; line++)
				{
					data[line] = *p[line];
					p[line] += 1;
				}

				for (bit = 0; bit < 8; bit++)
				{
					byte =  (((data[0]>>bit) & 1) << 0) |
							(((data[1]>>bit) & 1) << 1) |
							(((data[2]>>bit) & 1) << 2) |
							(((data[3]>>bit) & 1) << 3) |
							(((data[4]>>bit) & 1) << 4) |
							(((data[5]>>bit) & 1) << 5) |
							(((data[6]>>bit) & 1) << 6) |
							(((data[7]>>bit) & 1) << 7);

					oled_buf[k++] = byte;
				}
				
			}
		}
		

		/* 3. 通过SPI发送给OLED */
		for (i = 0; i < 8; i++)
		{
			OLED_DIsp_Set_Pos(0, i);
			oled_set_dc_pin(1);
			spi_write_datas(&oled_buf[i*128], 128);
		}
		
		
		/* 4. 休眠一会 */
		schedule_timeout_interruptible(HZ);
	}
	return 0;
}


/*-------------------------------------------------------------------------*/

static int spidev_probe(struct spi_device *spi)
{
	dma_addr_t phy_addr;
	
	/* 1. 记录spi_device */
	oled = spi;

	/* 2. 注册字符设备 */
	major = register_chrdev(0, "100ask_oled", &spidev_fops);
	spidev_class = class_create(THIS_MODULE, "100ask_oled");
	device_create(spidev_class, NULL, MKDEV(major, 0), NULL, "100ask_oled");	

	/* 3. 获得GPIO引脚 */
	dc_gpio = gpiod_get(&spi->dev, "dc", 0);

	

	/* A. 分配fb_info */
	myfb_info = framebuffer_alloc(0, NULL);

	/* B. 设置fb_info */
	/* B.1 var : LCD分辨率、颜色格式 */
	myfb_info->var.xres_virtual = myfb_info->var.xres = 128;
	myfb_info->var.yres_virtual = myfb_info->var.yres = 64;
	
	myfb_info->var.bits_per_pixel = 1;  /* rgb565 */	

	/* B.2 fix */
	strcpy(myfb_info->fix.id, "100ask_oled");
	myfb_info->fix.smem_len = myfb_info->var.xres * myfb_info->var.yres * myfb_info->var.bits_per_pixel / 8;

	myfb_info->flags |= FBINFO_MODULE; /* 禁止显示LOGO */


	/* fb的虚拟地址 */
	myfb_info->screen_base = dma_alloc_wc(NULL, myfb_info->fix.smem_len, &phy_addr,
					 GFP_KERNEL);
	myfb_info->fix.smem_start = phy_addr;  /* fb的物理地址 */
	
	myfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
	myfb_info->fix.visual = FB_VISUAL_MONO10;

	myfb_info->fix.line_length = myfb_info->var.xres * myfb_info->var.bits_per_pixel / 8;	

	/* c. fbops */
	myfb_info->fbops = &myfb_ops;
	myfb_info->pseudo_palette = pseudo_palette;


	/* C. 注册fb_info */
	register_framebuffer(myfb_info);

	/* D. 创建内核线程 */
	oled_buf = kmalloc(1024, GFP_KERNEL);
	dc_pin_init();
	oled_init();
	
 	oled_thread = kthread_run(oled_thread_func, NULL, "oled_kthead");

	return 0;
}

static int spidev_remove(struct spi_device *spi)
{
	kthread_stop(oled_thread);
	kfree(oled_buf);
	
	/* A. 反注册fb_info */
	unregister_framebuffer(myfb_info);

	/* B. 释放内存 */
	dma_free_wc(NULL, myfb_info->fix.smem_len, myfb_info->screen_base,
		    myfb_info->fix.smem_start);

	/* C. 释放fb_info */
	framebuffer_release(myfb_info);
	
	gpiod_put(dc_gpio);
	
	/* 反注册字符设 */
	device_destroy(spidev_class, MKDEV(major, 0));
	class_destroy(spidev_class);
	unregister_chrdev(major, "100ask_oled");

	return 0;
}

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"100ask_spi_oled_drv",
		.of_match_table = of_match_ptr(spidev_dt_ids),
	},
	.probe =	spidev_probe,
	.remove =	spidev_remove,

	/* NOTE:  suspend/resume methods are not necessary here.
	 * We don't do anything except pass the requests to/from
	 * the underlying controller.  The refrigerator handles
	 * most issues; the controller driver handles the rest.
	 */
};

/*-------------------------------------------------------------------------*/

static int __init spidev_init(void)
{
	int status;

	status = spi_register_driver(&spidev_spi_driver);
	if (status < 0) {
	}
	return status;
}
module_init(spidev_init);

static void __exit spidev_exit(void)
{
	spi_unregister_driver(&spidev_spi_driver);
}
module_exit(spidev_exit);

MODULE_LICENSE("GPL");


6.4 spi_oled.c

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <wchar.h>
#include <sys/ioctl.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;


/**********************************************************************
 * 函数名称: lcd_put_pixel
 * 功能描述: 在LCD指定位置上输出指定颜色(描点)
 * 输入参数: x坐标,y坐标,颜色
 * 输出参数: 无
 * 返 回 值: 会
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2020/05/12	     V1.0	  zh(angenao)	      创建
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
	unsigned short *pen_16;	
	unsigned int *pen_32;	

	unsigned int red, green, blue;	
	int bit;

	pen_16 = (unsigned short *)pen_8;
	pen_32 = (unsigned int *)pen_8;

	switch (var.bits_per_pixel)
	{
		case 1:
		{
			pen_8 = fbmem+y*line_width+x/8;
			bit = x & 7;
			if (color)
				*pen_8 = *pen_8 | (1<<bit);
			else
				*pen_8 = *pen_8 & ~(1<<bit);
			break;
		}
		case 8:
		{
			*pen_8 = color;
			break;
		}
		case 16:
		{
			/* 565 */
			red   = (color >> 16) & 0xff;
			green = (color >> 8) & 0xff;
			blue  = (color >> 0) & 0xff;
			color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
			*pen_16 = color;
			break;
		}
		case 32:
		{
			*pen_32 = color;
			break;
		}
		default:
		{
			printf("can't surport %dbpp\n", var.bits_per_pixel);
			break;
		}
	}
}

/**********************************************************************
 * 函数名称: draw_bitmap
 * 功能描述: 根据bitmap位图,在LCD指定位置显示汉字
 * 输入参数: x坐标,y坐标,位图指针
 * 输出参数: 无
 * 返 回 值: 无
 * 修改日期        版本号     修改人	      修改内容
 * -----------------------------------------------
 * 2020/05/12	     V1.0	  zh(angenao)	      创建
 ***********************************************************************/ 
void
draw_bitmap( FT_Bitmap*  bitmap,
             FT_Int      x,
             FT_Int      y)
{
	FT_Int  i, j, p, q;
	FT_Int  x_max = x + bitmap->width;
	FT_Int  y_max = y + bitmap->rows;

	//printf("x = %d, y = %d\n", x, y);

	for ( j = y, q = 0; j < y_max; j++, q++ )
	{
		for ( i = x, p = 0; i < x_max; i++, p++ )
		{
			if ( i < 0      || j < 0       ||
				i >= var.xres || j >= var.yres )
			continue;

			//image[j][i] |= bitmap->buffer[q * bitmap->width + p];
			lcd_put_pixel(i, j, bitmap->buffer[q * bitmap->width + p]);
		}
	}
}


int main(int argc, char **argv)
{
	wchar_t *chinese_str = L"繁";

	FT_Library	  library;
	FT_Face 	  face;
	int error;
    FT_Vector     pen;
	FT_GlyphSlot  slot;
	int font_size = 24;
	FT_Matrix	  matrix;				  /* transformation matrix */
	double		  angle;

	if (argc < 3)
	{
		printf("Usage : %s </dev/fbX> <font_file> <angle> [font_size]\n", argv[0]);
		return -1;
	}

	angle  = ( 1.0* strtoul(argv[3], NULL, 0) / 360 ) * 3.14159 * 2;	   /* use 25 degrees	 */

	if (argc == 5)
		font_size = strtoul(argv[4], NULL, 0);
		
	fd_fb = open(argv[1], O_RDWR);
	if (fd_fb < 0)
	{
		printf("can't open %s\n", argv[1]);
		return -1;
	}

	if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
	{
		printf("can't get var\n");
		return -1;
	}

	line_width  = var.xres * var.bits_per_pixel / 8;
	pixel_width = var.bits_per_pixel / 8;
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

	/* 清屏: 全部设为黑色 */
	memset(fbmem, 0, screen_size);

	/* 显示矢量字体 */
	error = FT_Init_FreeType( &library );			   /* initialize library */
	/* error handling omitted */
	
	error = FT_New_Face( library, argv[2], 0, &face ); /* create face object */
	/* error handling omitted */	
	slot = face->glyph;

	FT_Set_Pixel_Sizes(face, font_size, 0);

	/* 确定座标:
	 */
	pen.x = 0;
	pen.y = 0;

	/* set up matrix */
	matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
	matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
	matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
	matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L );

    /* set transformation */
    FT_Set_Transform( face, &matrix, &pen);

    /* load glyph image into the slot (erase previous one) */
    error = FT_Load_Char( face, chinese_str[0], FT_LOAD_RENDER );
	if (error)
	{
		printf("FT_Load_Char error\n");
		return -1;
	}
	
    draw_bitmap( &slot->bitmap,
                 var.xres/2,
                 var.yres/2);

	return 0;	
}

6.5 调试

配置内核,把下列配置项去掉:
在这里插入图片描述

7.老方法编写的SPI_Master驱动程序

7.1 怎么编写SPI_Master驱动

7.1.1 编写设备树

在设备树中,对于SPI Master,必须的属性如下:

  • #address-cells:这个SPI Master下的SPI设备,需要多少个cell来表述它的片选引脚
  • #size-cells:必须设置为0
  • compatible:根据它找到SPI Master驱动

可选的属性如下:

  • cs-gpios:SPI Master可以使用多个GPIO当做片选,可以在这个属性列出那些GPIO
  • num-cs:片选引脚总数

其他属性都是驱动程序相关的,不同的SPI Master驱动程序要求的属性可能不一样。

在SPI Master对应的设备树节点下,每一个子节点都对应一个SPI设备,这个SPI设备连接在该SPI Master下面。

这些子节点中,必选的属性如下:

  • compatible:根据它找到SPI Device驱动
  • reg:用来表示它使用哪个片选引脚
  • spi-max-frequency:必选,该SPI设备支持的最大SPI时钟

可选的属性如下:

  • spi-cpol:这是一个空属性(没有值),表示CPOL为1,即平时SPI时钟为低电平
  • spi-cpha:这是一个空属性(没有值),表示CPHA为1),即在时钟的第2个边沿采样数据
  • spi-cs-high:这是一个空属性(没有值),表示片选引脚高电平有效
  • spi-3wire:这是一个空属性(没有值),表示使用SPI 三线模式
  • spi-lsb-first:这是一个空属性(没有值),表示使用SPI传输数据时先传输最低位(LSB)
  • spi-tx-bus-width:表示有几条MOSI引脚;没有这个属性时默认只有1条MOSI引脚
  • spi-rx-bus-width:表示有几条MISO引脚;没有这个属性时默认只有1条MISO引脚
  • spi-rx-delay-us:单位是毫秒,表示每次读传输后要延时多久
  • spi-tx-delay-us:单位是毫秒,表示每次写传输后要延时多久
        vitural_spi_master {
                compatible = "100ask,virtual_spi_master";
                status = "okay";
                cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;
                num-chipselects = <1>;
                #address-cells = <1>;
                #size-cells = <0>;

                virtual_spi_dev: virtual_spi_dev@0 {
                        compatible = "spidev";
                        reg = <0>;
                        spi-max-frequency = <100000>;
                };
        };

在这里插入图片描述

7.2 virtual_spi_master.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>

static struct spi_master *g_virtual_master;
static struct work_struct g_virtual_ws;

static const struct of_device_id spi_virtual_dt_ids[] = {
	{ .compatible = "100ask,virtual_spi_master", },
	{ /* sentinel */ }
};


static void spi_virtual_work(struct work_struct *work)
{
	struct spi_message *mesg;
	
	while (!list_empty(&g_virtual_master->queue)) {
		mesg = list_entry(g_virtual_master->queue.next, struct spi_message, queue);
		list_del_init(&mesg->queue);
		
		/* 假装硬件传输已经完成 */

		mesg->status = 0;
		if (mesg->complete)
			mesg->complete(mesg->context);
	}	
}

static int spi_virtual_transfer(struct spi_device *spi, struct spi_message *mesg)
{
#if 0	
	/* 方法1: 直接实现spi传输 */
	/* 假装传输完成, 直接唤醒 */
	mesg->status = 0;
	mesg->complete(mesg->context);
	return 0;
	
#else
	/* 方法2: 使用工作队列启动SPI传输、等待完成 */
	/* 把消息放入队列 */
	mesg->actual_length = 0;
	mesg->status = -EINPROGRESS;
	list_add_tail(&mesg->queue, &spi->master->queue);
	
	/* 启动工作队列 */
	schedule_work(&g_virtual_ws);
	
	/* 直接返回 */
	return 0;
#endif	
}


static int spi_virtual_probe(struct platform_device *pdev)
{
	struct spi_master *master;
	int ret;
	
	/* 分配/设置/注册spi_master */
	g_virtual_master = master = spi_alloc_master(&pdev->dev, 0);
	if (master == NULL) {
		dev_err(&pdev->dev, "spi_alloc_master error.\n");
		return -ENOMEM;
	}

	master->transfer = spi_virtual_transfer;
	INIT_WORK(&g_virtual_ws, spi_virtual_work);

	master->dev.of_node = pdev->dev.of_node;
	ret = spi_register_master(master);
	if (ret < 0) {
		printk(KERN_ERR "spi_register_master error.\n");
		spi_master_put(master);
		return ret;
	}

	return 0;
}

static int spi_virtual_remove(struct platform_device *pdev)
{
	/* 反注册spi_master */
	spi_unregister_master(g_virtual_master);
	return 0;
}


static struct platform_driver spi_virtual_driver = {
	.probe = spi_virtual_probe,
	.remove = spi_virtual_remove,
	.driver = {
		.name = "virtual_spi",
		.of_match_table = spi_virtual_dt_ids,
	},
};

static int virtual_master_init(void)
{
	return platform_driver_register(&spi_virtual_driver);
}

static void virtual_master_exit(void)
{
	platform_driver_unregister(&spi_virtual_driver);
}

module_init(virtual_master_init);
module_exit(virtual_master_exit);

MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");

7.3 spi_test.c

/* 参考: tools\spi\spidev_fdx.c */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>

/* dac_test /dev/spidevB.D <val> */

int main(int argc, char **argv)
{
	int fd;
	unsigned int val;
	struct spi_ioc_transfer	xfer[1];
	int status;

	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	
	
	if (argc != 3)
	{
		printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
		return 0;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		printf("can not open %s\n", argv[1]);
		return 1;
	}

	val = strtoul(argv[2], NULL, 0);
	val <<= 2;     /* bit0,bit1 = 0b00 */
	val &= 0xFFC;  /* 只保留10bit */

	tx_buf[1] = val & 0xff;
	tx_buf[0] = (val>>8) & 0xff;	

	memset(xfer, 0, sizeof xfer);

	xfer[0].tx_buf = tx_buf;
	xfer[0].rx_buf = rx_buf;
	xfer[0].len = 2;
	
	status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
	if (status < 0) {
		printf("SPI_IOC_MESSAGE %d\n", errno);
		return -1;
	}

	/* 打印 */
	val = (rx_buf[0] << 8) | (rx_buf[1]);
	val >>= 2;
	printf("Pre val = %d\n", val);
	
	return 0;
}

8.新方法编写的SPI_Master驱动程序

8.1 修改设备树

修改arch/arm/boot/dts/100ask_imx6ull-14x14.dts中,如下:

        virtual_spi_master {
                compatible = "100ask,virtual_spi_master";
                status = "okay";
                cs-gpios = <&gpio4 27 GPIO_ACTIVE_LOW>;
                num-chipselects = <1>;
                #address-cells = <1>;
                #size-cells = <0>;

                virtual_spi_dev: virtual_spi_dev@0 {
                        compatible = "spidev";
                        reg = <0>;
                        spi-max-frequency = <100000>;
                };
        };

8.2 virtual_spi_master.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_bitbang.h>

static struct spi_master *g_virtual_master;
static struct spi_bitbang *g_virtual_bitbang;
static struct completion g_xfer_done;


static const struct of_device_id spi_virtual_dt_ids[] = {
	{ .compatible = "100ask,virtual_spi_master", },
	{ /* sentinel */ }
};

/* xxx_isr() { complete(&g_xfer_done)  } */

static int spi_virtual_transfer(struct spi_device *spi,
				struct spi_transfer *transfer)
{
	int timeout;

#if 1	
	/* 1. init complete */
	reinit_completion(&g_xfer_done);

	/* 2. 启动硬件传输 */
	complete(&g_xfer_done);

	/* 3. wait for complete */
	timeout = wait_for_completion_timeout(&g_xfer_done,
					      100);
	if (!timeout) {
		dev_err(&spi->dev, "I/O Error in PIO\n");
		return -ETIMEDOUT;
	}
#endif
	return transfer->len;
}

static void	spi_virtual_chipselect(struct spi_device *spi, int is_on)
{
}


static int spi_virtual_probe(struct platform_device *pdev)
{
	struct spi_master *master;
	int ret;
	
	/* 分配/设置/注册spi_master */
	g_virtual_master = master = spi_alloc_master(&pdev->dev, sizeof(struct spi_bitbang));
	if (master == NULL) {
		dev_err(&pdev->dev, "spi_alloc_master error.\n");
		return -ENOMEM;
	}

	g_virtual_bitbang = spi_master_get_devdata(master);

	init_completion(&g_xfer_done);

	/* 怎么设置spi_master?
	 * 1. spi_master使用默认的函数
	 * 2. 分配/设置 spi_bitbang结构体: 主要是实现里面的txrx_bufs函数
	 * 3. spi_master要能找到spi_bitbang
	 */
	g_virtual_bitbang->master = master;
	g_virtual_bitbang->txrx_bufs  = spi_virtual_transfer;
	g_virtual_bitbang->chipselect = spi_virtual_chipselect;
	master->dev.of_node = pdev->dev.of_node;

#if 0
	ret = spi_register_master(master);
	if (ret < 0) {
		printk(KERN_ERR "spi_register_master error.\n");
		spi_master_put(master);
		return ret;
	}
#else
	ret = spi_bitbang_start(g_virtual_bitbang);
	if (ret) {
		printk("bitbang start failed with %d\n", ret);
		return ret;
	}

#endif

	return 0;
}

static int spi_virtual_remove(struct platform_device *pdev)
{
#if 0	
	/* 反注册spi_master */
	spi_unregister_master(g_virtual_master);
#endif
	spi_bitbang_stop(g_virtual_bitbang);
	spi_master_put(g_virtual_master);
	return 0;
}


static struct platform_driver spi_virtual_driver = {
	.probe = spi_virtual_probe,
	.remove = spi_virtual_remove,
	.driver = {
		.name = "virtual_spi",
		.of_match_table = spi_virtual_dt_ids,
	},
};

static int virtual_master_init(void)
{
	return platform_driver_register(&spi_virtual_driver);
}

static void virtual_master_exit(void)
{
	platform_driver_unregister(&spi_virtual_driver);
}

module_init(virtual_master_init);
module_exit(virtual_master_exit);

MODULE_DESCRIPTION("Virtual SPI bus driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.100ask.net");

8.3 spi_test.c

/* 参考: tools\spi\spidev_fdx.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <errno.h>

/* dac_test /dev/spidevB.D <val> */

int main(int argc, char **argv)
{
	int fd;
	unsigned int val;
	struct spi_ioc_transfer	xfer[1];
	int status;

	unsigned char tx_buf[2];	
	unsigned char rx_buf[2];	
	
	if (argc != 3)
	{
		printf("Usage: %s /dev/spidevB.D <val>\n", argv[0]);
		return 0;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0) {
		printf("can not open %s\n", argv[1]);
		return 1;
	}

	val = strtoul(argv[2], NULL, 0);
	val <<= 2;     /* bit0,bit1 = 0b00 */
	val &= 0xFFC;  /* 只保留10bit */

	tx_buf[1] = val & 0xff;
	tx_buf[0] = (val>>8) & 0xff;	

	memset(xfer, 0, sizeof xfer);

	xfer[0].tx_buf = tx_buf;
	xfer[0].rx_buf = rx_buf;
	xfer[0].len = 2;
	
	status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
	if (status < 0) {
		printf("SPI_IOC_MESSAGE %d\n", errno);
		return -1;
	}

	/* 打印 */
	val = (rx_buf[0] << 8) | (rx_buf[1]);
	val >>= 2;
	printf("Pre val = %d\n", val);
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自然醒欧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值