驱动大全
一、IIC
1.IIC-dev分析
i2c_driver表明能支持哪些设备:
- 使用of_match_table来判断
- 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
- 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
- i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
- 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
- 使用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,bit | GICD_ISPENDRn地址 | 命令 |
---|---|---|---|---|---|
IMX6LLL | 122 | 154 | 4,26 | 0xa01210 | devmem 0xa01210 32 0x4000000 |
STM32MP157 | 210 | 242 | 7,18 | 0xa002121c | devmem 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方式,都是用来编写链式中断控制器驱动程序,它们的关系如下表所示。
legacy | linear | |
---|---|---|
函数 | irq_domain_add_legacy | irq_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].irq_data->irq_chip的函数,即irq_dataB
- 调用irq_desc[236].action链表中用户注册的函数
- unmask中断:
- 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB
- 它会调用父级irq_dataA->irq_chip的函数
- 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB
- mask/ack中断:
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
- 使用GPIO irq_domain.ops中的alloc函数进行设置
- 根据
-
用户的驱动程序注册中断
- 从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,bit | GICD_ISPENDRn地址 | 命令 |
---|---|---|---|---|---|
IMX6LLL | 122 | 154 | 4,26 | 0xa01210 | devmem 0xa01210 32 0x4000000 |
123 | 155 | 4,27 | 0xa01210 | devmem 0xa01210 32 0x8000000 | |
124 | 156 | 4,28 | 0xa01210 | devmem 0xa01210 32 0x10000000 | |
125 | 157 | 4,29 | 0xa01210 | devmem 0xa01210 32 0x20000000 | |
STM32MP157 | 210 | 242 | 7,18 | 0xa002121c | devmem 0xa002121c 32 0x40000 |
211 | 243 | 7,19 | 0xa002121c | devmem 0xa002121c 32 0x80000 | |
212 | 244 | 7,20 | 0xa002121c | devmem 0xa002121c 32 0x100000 | |
213 | 245 | 7,21 | 0xa002121c | devmem 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从设备用来发送数据 |
SCK | Serial Clock,时钟 |
CS | Chip 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为第二个时钟沿
CPOL | CPHA | 模式 | 含义 |
---|---|---|---|
0 | 0 | 0 | SPICLK初始电平为低电平,在第一个时钟沿采样数据 |
0 | 1 | 1 | SPICLK初始电平为低电平,在第二个时钟沿采样数据 |
1 | 0 | 2 | SPICLK初始电平为高电平,在第一个时钟沿采样数据 |
1 | 1 | 3 | SPICLK初始电平为高电平,在第二个时钟沿采样数据 |
我们常用的是模式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;
}