Linux Driver : i2c-gpio
背景
在学习高通平台的有关知识,看到一篇博客中介绍了GPIO模拟I2C设备,觉得挺有意思的。
跟了一下代码,发现这种模拟方式已经有了一套内核框架。刚好,学习这个驱动有助于加深理解:
1、i2c 子系统框架:
- i2c_adapter对象实现了一组通过一个i2c控制器发送消息的所有信息,包括时序,地址等等, 即封装了i2c控制器的"控制信息"。它被i2c主机驱动创建,通过clien域和i2c_client和i2c_driver相连, 这样设备端驱动就可以通过其中的方法以及i2c物理控制器来和一个i2c总线的物理设备进行交互
- i2c_algorithm描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调,以完成i2c的读写。
- i2c_client描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。
- i2c_driver描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连
- i2c_msg描述一个在设备端和主机端之间进行流动的数据,在设备驱动中打包并通过i2c_transfer()发送。相当于skbuf之于网络设备,urb之于USB设备。
2、gpio子系统api的使用
3、Linux 平台设备驱动
内核:4.9
介绍
在drivers/i2c/busses
下包含各种I2C总线驱动,其中就包括了使用GPIO模拟I2C总线的驱动i2c-gpio.c。
驱动分析
i2c-gpio.c实现了gpio模拟I2C总线的驱动。总线也是个设备,在这里将总线当作平台设备处理,那驱动当然是平台设备驱动。
注册与注销
没有什么好说的,它的初始化和注销函数就是注册和注销一个平台设备驱动。
static int __init i2c_gpio_init(void)
{
int ret;
ret = platform_driver_register(&i2c_gpio_driver);
if (ret)
printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
return ret;
}
subsys_initcall(i2c_gpio_init);
static void __exit i2c_gpio_exit(void)
{
platform_driver_unregister(&i2c_gpio_driver);
}
module_exit(i2c_gpio_exit);
platform_driver
直接看它的platform_driver结构i2c_gpio_driver
#if defined(CONFIG_OF)
static const struct of_device_id i2c_gpio_dt_ids[] = {
{ .compatible = "i2c-gpio", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_gpio_dt_ids);
#endif
static struct platform_driver i2c_gpio_driver = {
.driver = {
.name = "i2c-gpio",
.of_match_table = of_match_ptr(i2c_gpio_dt_ids),
},
.probe = i2c_gpio_probe,
.remove = i2c_gpio_remove,
};
平台驱动设备放在arch/arm/mach-xxxx/board-xxx.c中,例如:
static struct i2c_gpio_platform_data i2c_gpio_adapter_data = {
.sda_pin = PINID_GPMI_D05,
.scl_pin = PINID_GPMI_D04,
.udelay = 5, //100KHz
.timeout = 100,
.sda_is_open_drain = 1,
.scl_is_open_drain = 1,
};
static struct platform_device i2c_gpio = {
.name = "i2c-gpio",
.id = 0,
.dev = {
.platform_data = &i2c_gpio_adapter_data,
.release = mxs_nop_release,
},
};
在这里 struct platform_device
结构中的 name字段要和 struct platform_driver中 driver字段中 name字段要相同,因为平台总线就是通过这个来判断设备和驱动是否匹配的。
注意这里的 id将它赋值了 0,后面会提到这个id是用来软件上的一个总线号(nr)。
i2c_gpio_adapter_data
platform_device
这个结构里面还包含一个最重要的数据 i2c_gpio_adapter_data
,它的原型为是struct i2c_gpio_platform_data
类型:
定义在
include/linux/i2c-gpio.h
中。
/**
* struct i2c_gpio_platform_data - Platform-dependent data for i2c-gpio
* @sda_pin: GPIO pin ID to use for SDA
* @scl_pin: GPIO pin ID to use for SCL
* @udelay: signal toggle delay. SCL frequency is (500 / udelay) kHz
* @timeout: clock stretching timeout in jiffies. If the slave keeps
* SCL low for longer than this, the transfer will time out.
* @sda_is_open_drain: SDA is configured as open drain, i.e. the pin
* isn't actively driven high when setting the output value high.
* gpio_get_value() must return the actual pin state even if the
* pin is configured as an output.
* @scl_is_open_drain: SCL is set up as open drain. Same requirements
* as for sda_is_open_drain apply.
* @scl_is_output_only: SCL output drivers cannot be turned off.
*/
struct i2c_gpio_platform_data {
unsigned int sda_pin;
unsigned int scl_pin;
int udelay;
int timeout;
unsigned int sda_is_open_drain:1;
unsigned int scl_is_open_drain:1;
unsigned int scl_is_output_only:1;
};
这个结构体主要描述gpio模拟i2c总线时的一些必要信息:
- sda_pin和scl_pin表示使用哪两个IO管脚来模拟I2C总线
- udelay和timeout分别为它的时钟频率和超时时间
- sda_is_open_drain和scl_is_open_drain表示sda、scl这两个管脚是否是开漏(opendrain)电路,如果是则为1
- scl_is_output_only表示scl这个管脚是否只是作为输出,如果是设置为1。
probe
回到驱动中,看最重要的i2c_gpio_probe。
static int i2c_gpio_probe(struct platform_device *pdev)
{
struct i2c_gpio_private_data *priv;
//
struct i2c_gpio_platform_data *pdata;
struct i2c_algo_bit_data *bit_data;
// i2c适配器
struct i2c_adapter *adap;
unsigned int sda_pin, scl_pin;
int ret;
/* First get the GPIO pins; if it fails, we'll defer the probe. */
if (pdev->dev.of_node) {
ret = of_i2c_gpio_get_pins(pdev->dev.of_node,
&sda_pin, &scl_pin);
if (ret)
return ret;
} else {
if (!dev_get_platdata(&pdev->dev))
return -ENXIO;
pdata = dev_get_platdata(&pdev->dev);
sda_pin = pdata->sda_pin;
scl_pin = pdata->scl_pin;
}
ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* Try again later */
return ret;
}
ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* Try again later */
return ret;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
adap = &priv->adap;
bit_data = &priv->bit_data;
pdata = &priv->pdata;
if (pdev->dev.of_node) {
pdata->sda_pin = sda_pin;
pdata->scl_pin = scl_pin;
of_i2c_gpio_get_props(pdev->dev.of_node, pdata);
} else {
memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));
}
if (pdata->sda_is_open_drain) {
gpio_direction_output(pdata->sda_pin, 1);
bit_data->setsda = i2c_gpio_setsda_val;
} else {
gpio_direction_input(pdata->sda_pin);
bit_data->setsda = i2c_gpio_setsda_dir;
}
if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
gpio_direction_output(pdata->scl_pin, 1);
bit_data->setscl = i2c_gpio_setscl_val;
} else {
gpio_direction_input(pdata->scl_pin);
bit_data->setscl = i2c_gpio_setscl_dir;
}
if (!pdata->scl_is_output_only)
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda;
if (pdata->udelay)
bit_data->udelay = pdata->udelay;
else if (pdata->scl_is_output_only)
bit_data->udelay = 50; /* 10 kHz */
else
bit_data->udelay = 5; /* 100 kHz */
if (pdata->timeout)
bit_data->timeout = pdata->timeout;
else
bit_data->timeout = HZ / 10; /* 100 ms */
bit_data->data = pdata;
adap->owner = THIS_MODULE;
if (pdev->dev.of_node)
strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
else
snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
adap->nr = pdev->id;
ret = i2c_bit_add_numbered_bus(adap);
if (ret)
return ret;
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
pdata->sda_pin, pdata->scl_pin,
pdata->scl_is_output_only
? ", no clock stretching" : "");
return 0;
}
获取platform数据
1、pdata = dev_get_platdata(&pdev->dev);
正是我们在平台设备结构中定义的数据:我们能够从其中获取到gpio模拟i2c总线时的一些必要信息。
申请有关的资源
接下来使用gpio_request去申请这个两个GPIO管脚,申请的目的是为了防止管脚冲突。
ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* Try again later */
return ret;
}
ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");
if (ret) {
if (ret == -EINVAL)
ret = -EPROBE_DEFER; /* Try again later */
return ret;
}
2、注意到这里使用kzalloc申请了一个struct i2c_gpio_private_data
对象
static int i2c_gpio_probe(struct platform_device *pdev)
{
struct i2c_gpio_private_data *priv;
struct i2c_gpio_platform_data *pdata;
struct i2c_algo_bit_data *bit_data;
//
unsigned int sda_pin, scl_pin;
int ret;
// ...
// 申请 i2c_gpio_private_data,其中包含了 i2c_adapter i2c_algo_bit_data 这两个成员
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
adap = &priv->adap;
bit_data = &priv->bit_data;
pdata = &priv->pdata;
主要是为了struct i2c_adapter
、struct i2c_algo_bit_data
这2个成员。
// drivers/i2c/busses/i2c-gpio.c
struct i2c_gpio_private_data {
struct i2c_adapter adap;
struct i2c_algo_bit_data bit_data;
struct i2c_gpio_platform_data pdata;
};
- struct i2c_adapter:在I2C子系统中,I2C适配器使用结构
struct i2c_adapter
描述,代表一条实际的I2C总线。
定义在i
nclude/linux/i2c.h
中
struct i2c_adapter {
struct module *owner;
unsigned int id;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
u8 level; /* nesting level for lockdep */
struct mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
};
- struct i2c_algo_bit_data:用来定义对I2C-引脚的操作方式
定义在include/linux/i2c-algo-bit.h中
struct i2c_algo_bit_data {
void *data; /* private data for lowlevel routines */
void (*setsda) (void *data, int state);
void (*setscl) (void *data, int state);
int (*getsda) (void *data);
int (*getscl) (void *data);
int (*pre_xfer) (struct i2c_adapter *);
void (*post_xfer) (struct i2c_adapter *);
/* local settings */
int udelay; /* half clock cycle time in us,
minimum 2 us for fast-mode I2C,
minimum 5 us for standard-mode I2C and SMBus,
maximum 50 us for SMBus */
int timeout; /* in jiffies */
};
3、将所需数据中保存到设备中
pdata = &priv->pdata;
if (pdev->dev.of_node) {
pdata->sda_pin = sda_pin;
pdata->scl_pin = scl_pin;
of_i2c_gpio_get_props(pdev->dev.of_node, pdata);
} else {
memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));
}
4、然后是根据struct i2c_gpio_platform_data结构中定义的数据对struct i2c_algo_bit_data
规定具体操作I2C引脚的方法。
if (pdata->sda_is_open_drain) {
gpio_direction_output(pdata->sda_pin, 1);
bit_data->setsda = i2c_gpio_setsda_val;
} else {
gpio_direction_input(pdata->sda_pin);
bit_data->setsda = i2c_gpio_setsda_dir;
}
if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
gpio_direction_output(pdata->scl_pin, 1);
bit_data->setscl = i2c_gpio_setscl_val;
} else {
gpio_direction_input(pdata->scl_pin);
bit_data->setscl = i2c_gpio_setscl_dir;
}
if (!pdata->scl_is_output_only)
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda;
配置I2C时钟属性
接下来是I2C时钟频率和超时设置:如果在struct i2c_gpio_platform_data结构中定义了值,那么就采用定义的值,否则就采用默认的值。
if (pdata->udelay)
bit_data->udelay = pdata->udelay;
else if (pdata->scl_is_output_only)
bit_data->udelay = 50; /* 10 kHz */
else
bit_data->udelay = 5; /* 100 kHz */
if (pdata->timeout)
bit_data->timeout = pdata->timeout;
else
bit_data->timeout = HZ / 10; /* 100 ms */
然后是对struct i2c_adapter结构的一些赋值操作:
指定它的父设备为这里的平台设备,前面在平台设备中定义的一个id(值为0),这里用到了,赋给了struct i2c_adapter中的nr成员,这个值表示总线号。
这里的总线号和硬件无关,只是在软件上的区分。
bit_data->data = pdata;
adap->owner = THIS_MODULE;
if (pdev->dev.of_node)
strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
else
snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
adap->nr = pdev->id;
i2c_bit_add_numbered_bus
到了最后的主角:i2c_bit_add_numbered_bus
定义在
drivers/i2c/algos/i2c-algo-bit.c
int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
}
EXPORT_SYMBOL(i2c_bit_add_numbered_bus);
i2c_bit_add_numbered_bus
实际上是调用了__i2c_bit_add_bus
,同时传入了i2c_add_numbered_adapter
作为参数。
__i2c_bit_add_bus
函数原型:
/*
* registering functions to load algorithms at runtime
*/
static int __i2c_bit_add_bus(struct i2c_adapter *adap,
int (*add_adapter)(struct i2c_adapter *))
{
struct i2c_algo_bit_data *bit_adap = adap->algo_data;
int ret;
// ...
/* register new adapter to i2c module... */
adap->algo = &i2c_bit_algo; // 添加这个设备的读写实现方法
adap->retries = 3;
if (bit_adap->getscl == NULL)
adap->quirks = &i2c_bit_quirk_no_clk_stretch;
// 添加适配器,以i2c 总线的方式进行添加
ret = add_adapter(adap);
if (ret < 0)
return ret;
// ...
return 0;
}
1、添加:指定 该类的对象algo是i2c_adapter的一个域,其中的
adap->algo= &i2c_bit_algo;
来看这个结构定义
// include/linux/i2c.h
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE) // 从设备相关,先不纠结
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
/* -----exported algorithm data: ------------------------------------- */
const struct i2c_algorithm i2c_bit_algo = {
.master_xfer = bit_xfer,
.functionality = bit_func,
};
EXPORT_SYMBOL(i2c_bit_algo);
除了注释以外,只是指定了adapter的行为:
- master_xfer指定为主机的数据传输方式(包括读与写):master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调,以完成i2c的读写
- functionality:告知这个adapter能够支持的操作。
i2c数据传输
具体来看bit_xfer这个函数,这个函数和I2C协议相关,因此理解的前提是搞懂I2C协议。
根据I2C协议规定,要先发送起始信号,才能开始进行数据的传输,最后数据传输完成后发送停止信号。
static int bit_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg msgs[], int num)
{
struct i2c_msg *pmsg;
struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
int i, ret;
unsigned short nak_ok;
// ...
i2c_start(adap);
for (i = 0; i < num; i++) {
pmsg = &msgs[i];
nak_ok = pmsg->flags & I2C_M_IGNORE_NAK;
if (!(pmsg->flags & I2C_M_NOSTART)) {
if (i) {
bit_dbg(3, &i2c_adap->dev, "emitting "
"repeated start condition\n");
i2c_repstart(adap);
}
ret = bit_doAddress(i2c_adap, pmsg);
if ((ret != 0) && !nak_ok) {
bit_dbg(1, &i2c_adap->dev, "NAK from "
"device addr 0x%02x msg #%d\n",
msgs[i].addr, i);
goto bailout;
}
}
if (pmsg->flags & I2C_M_RD) {
/* read bytes into buffer*/
ret = readbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n",
ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EIO;
goto bailout;
}
} else {
/* write bytes from buffer */
ret = sendbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n",
ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EIO;
goto bailout;
}
}
}
ret = i;
bailout:
bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
i2c_stop(adap);
if (adap->post_xfer)
adap->post_xfer(i2c_adap);
return ret;
}
发送起始信号
i2c_start(adap);
看这个函数前,先看I2C协议怎么定义起始信号的:起始信号就是在SCL为高电平期间,SDA从高到低的跳变。
再来看代码是怎么实现的
/* --- setting states on the bus with the right timing: --------------- */
#define setsda(adap, val) adap->setsda(adap->data, val)
#define setscl(adap, val) adap->setscl(adap->data, val)
#define getsda(adap) adap->getsda(adap->data)
#define getscl(adap) adap->getscl(adap->data)
static void i2c_start(struct i2c_algo_bit_data *adap)
{
/* assert: scl, sda are high */
setsda(adap, 0);
udelay(adap->udelay);
scllo(adap);
}
注意,这些 setsda和 setscl这些都是使用的总线的函数,在这里是使用的 i2c-gpio.c中定义的函数,例如:
// i2c-gpio.c
/*
* Toggle SDA by changing the output value of the pin. This is only
* valid for pins configured as open drain (i.e. setting the value
* high effectively turns off the output driver.)
*/
static void i2c_gpio_setsda_val(void *data, int state)
{
struct i2c_gpio_platform_data *pdata = data;
gpio_set_value(pdata->sda_pin, state);
}
static int i2c_gpio_probe(struct platform_device *pdev)
{
// ...
if (pdata->sda_is_open_drain) {
gpio_direction_output(pdata->sda_pin, 1);
bit_data->setsda = i2c_gpio_setsda_val;
} else {
gpio_direction_input(pdata->sda_pin);
bit_data->setsda = i2c_gpio_setsda_dir;
}
// ...
}
循环处理
往下是个大的for循环:判断pmsg->flags设置而采取不同的行动。
回顾一下i2c_msg原型
// include/linux/i2c.h
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ //表示10位设备地址
#define I2C_M_RD 0x0001 /* read data, from slave to master */ // 读标志
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */ // 无起始信号标志
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ //忽略应答信号标志
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
struct i2c_msg {
__u16 addr; /* I2C设备地址 */
__u16 flags; /* 标志位 */
__u16 len; /* 数据的长度 */
__u8 *buf; /* 数据 */
};
有了上面的基础,再看看下面的for就简单了:
// num代表有几个 struct i2c_msg
for (i = 0; i < num; i++) {
pmsg = &msgs[i];
nak_ok = pmsg->flags & I2C_M_IGNORE_NAK;
if (!(pmsg->flags & I2C_M_NOSTART)) {
if (i) {
bit_dbg(3, &i2c_adap->dev, "emitting "
"repeated start condition\n");
i2c_repstart(adap);
}
ret = bit_doAddress(i2c_adap, pmsg);
if ((ret != 0) && !nak_ok) {
bit_dbg(1, &i2c_adap->dev, "NAK from "
"device addr 0x%02x msg #%d\n",
msgs[i].addr, i);
goto bailout;
}
}
if (pmsg->flags & I2C_M_RD) {
/* read bytes into buffer*/
ret = readbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n",
ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EIO;
goto bailout;
}
} else {
/* write bytes from buffer */
ret = sendbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n",
ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EIO;
goto bailout;
}
}
}
ret = i;
我们来看看这3个if,分别实现了:发送地址、发送或读取字节数据
发送地址
if (!(pmsg->flags & I2C_M_NOSTART)) {
if (i) {
bit_dbg(3, &i2c_adap->dev, "emitting "
"repeated start condition\n");
i2c_repstart(adap);
}
ret = bit_doAddress(i2c_adap, pmsg);
if ((ret != 0) && !nak_ok) {
bit_dbg(1, &i2c_adap->dev, "NAK from "
"device addr 0x%02x msg #%d\n",
msgs[i].addr, i);
goto bailout;
}
}
判断这个设备是否定义了I2C_M_NOSTART标志,这个标志主要用于读写操作
- 写:不必重新发送起始信号和设备地址。
- 读:要调用
i2c_repstart
这个函数去重新发送起始信号,调用bit_doAddress
函数去重新构造设备地址字节。
重新构造设备地址字节
/* doAddress initiates the transfer by generating the start condition (in
* try_address) and transmits the address in the necessary format to handle
* reads, writes as well as 10bit-addresses.
* returns:
* 0 everything went okay, the chip ack'ed, or IGNORE_NAK flag was set
* -x an error occurred (like: -ENXIO if the device did not answer, or
* -ETIMEDOUT, for example if the lines are stuck...)
*/
static int bit_doAddress(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
unsigned short flags = msg->flags;
unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK;
struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
unsigned char addr;
int ret, retries;
retries = nak_ok ? 0 : i2c_adap->retries;
if (flags & I2C_M_TEN) { // 判断是否是10位地址
// ...
} else { /* normal 7bit address */
addr = msg->addr << 1;
if (flags & I2C_M_RD)
addr |= 1;
if (flags & I2C_M_REV_DIR_ADDR)
addr ^= 1;
ret = try_address(i2c_adap, addr, retries);
if ((ret != 1) && !nak_ok)
return -ENXIO;
}
return 0;
}
1、这里先做了一个判断, 10位设备地址和 7位设备地址分别做不同的处理,通常一条 I2C总线上不会挂那么多 I2C设备,所以 10位地址不常用,直接看对 7位地址的处理。
2、 struct i2c_msg中 addr中是真正的设备地址,而这里发送的 addr高 7位才是设备地址,最低位为读写位。
- 如果为读,最低位为 1
- 如果为写,最低位为 0。
3、所以要将 struct i2c_msg中 addr向左移 1位,再根据读写情况,对最低位进行置位/清零。
4、最后调用 try_address函数将这个地址字节发送出去。
地址的发送
这里负责发送,以及超时重传
/* try_address tries to contact a chip for a number of
* times before it gives up.
* return values:
* 1 chip answered
* 0 chip did not answer
* -x transmission error
*/
static int try_address(struct i2c_adapter *i2c_adap,
unsigned char addr, int retries)
{
struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
int i, ret = 0;
for (i = 0; i <= retries; i++) {
// 输出字节
ret = i2c_outb(i2c_adap, addr);
if (ret == 1 || i == retries)
break;
bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
i2c_stop(adap);
udelay(adap->udelay);
yield();
bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");
i2c_start(adap);
}
if (i && ret)
bit_dbg(1, &i2c_adap->dev, "Used %d tries to %s client at "
"0x%02x: %s\n", i + 1,
addr & 1 ? "read from" : "write to", addr >> 1,
ret == 1 ? "success" : "failed, timeout?");
return ret;
}
最主要的就是调用i2c_outb发送一个字节,retries为重复次数,看前面adap->retries= 3;
如果发送失败,也就是设备没有给出应答信号,那就发送停止信号,发送起始信号,再发送这个地址字节,这就叫retries。
来看这个具体的i2c_outb函数
/* send a byte without start cond., look for arbitration,
check ackn. from slave */
/* returns:
* 1 if the device acknowledged
* 0 if the device did not ack
* -ETIMEDOUT if an error occurred (while raising the scl line)
*/
static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c)
{
int i;
int sb;
int ack;
struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
/* assert: scl is low */
for (i = 7; i >= 0; i--) {
sb = (c >> i) & 1;
setsda(adap, sb);
udelay((adap->udelay + 1) / 2);
if (sclhi(adap) < 0) { /* timed out */
bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
"timeout at bit #%d\n", (int)c, i);
return -ETIMEDOUT;
}
scllo(adap);
}
sdahi(adap);
if (sclhi(adap) < 0) { /* timeout */
bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
"timeout at ack\n", (int)c);
return -ETIMEDOUT;
}
/* read ack: SDA should be pulled down by slave, or it may
* NAK (usually to report problems with the data we wrote).
*/
ack = !getsda(adap); /* ack: sda is pulled low -> success */
bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c,
ack ? "A" : "NA");
scllo(adap);
return ack;
/* assert: scl is low (sda undef) */
}
这个函数有两个参数,一个是structi2c_adapter代表I2C主机,一个是发送的字节数据。那么I2C是怎样将一个字节数据发送出去的呢,那再来看看协议。
首先是发送字节数据的最高位,在时钟为高电平期间将一位数据发送出去,最后是发送字节数据的最低位。
发送完成之后,我们需要一个ACK信号,要不然我怎么知道发送成功没有:
ACK信号就是在第九个时钟周期时数据线为低,所以在一个字节数据传送完成后,还要将数据线拉高,我们看程序中就是这一句sdahi(adap);等待这个ACK信号的到来,这样一个字节数据就发送完成。
回到bit_xfer函数中,前面只是将设备地址字节发送出去了,那么接下来就是该读写数据了。
读取字节
先看读
if (pmsg->flags & I2C_M_RD) { // 如果是读则调用readbytes函数去读
/* read bytes into buffer*/
ret = readbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n",
ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EIO;
goto bailout;
}
} else //... 如果是写则调用sendbytes去写
注意:这里的数据包括操作设备的基地址。
static int readbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
int inval;
int rdcount = 0; /* counts bytes read */
unsigned char *temp = msg->buf;
int count = msg->len;
const unsigned flags = msg->flags;
while (count > 0) {
// 读取一个字节
inval = i2c_inb(i2c_adap);
if (inval >= 0) {
*temp = inval;
rdcount++;
} else { /* read timed out */
break;
}
temp++;
count--;
/* Some SMBus transactions require that we receive the
transaction length as the first read byte. */
if (rdcount == 1 && (flags & I2C_M_RECV_LEN)) {
if (inval <= 0 || inval > I2C_SMBUS_BLOCK_MAX) {
if (!(flags & I2C_M_NO_RD_ACK))
acknak(i2c_adap, 0);
dev_err(&i2c_adap->dev, "readbytes: invalid "
"block length (%d)\n", inval);
return -EREMOTEIO;
}
/* The original count value accounts for the extra
bytes, that is, either 1 for a regular transaction,
or 2 for a PEC transaction. */
count += inval;
msg->len += inval;
}
bit_dbg(2, &i2c_adap->dev, "readbytes: 0x%02x %s\n",
inval,
(flags & I2C_M_NO_RD_ACK)
? "(no ack/nak)"
: (count ? "A" : "NA"));
if (!(flags & I2C_M_NO_RD_ACK)) {
inval = acknak(i2c_adap, count);
if (inval < 0)
return inval;
}
}
return rdcount;
}
其中一个大的while循环,调用i2c_inb去读一个字节,count为数据的长度,单位为多少个字节。
那就来看i2c_inb函数。按位读取,就可以了。
static int i2c_inb(struct i2c_adapter *i2c_adap)
{
/* read byte via i2c port, without start/stop sequence */
/* acknowledge is sent in i2c_read. */
int i;
unsigned char indata = 0;
struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
/* assert: scl is low */
sdahi(adap);
for (i = 0; i < 8; i++) {
if (sclhi(adap) < 0) { /* timeout */
bit_dbg(1, &i2c_adap->dev, "i2c_inb: timeout at bit "
"#%d\n", 7 - i);
return -ETIMEDOUT;
}
indata *= 2;
if (getsda(adap))
indata |= 0x01;
setscl(adap, 0);
udelay(i == 7 ? adap->udelay / 2 : adap->udelay);
}
/* assert: scl is low */
return indata;
}
发送字节
else {
/* write bytes from buffer */
ret = sendbytes(i2c_adap, pmsg);
if (ret >= 1)
bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n",
ret, ret == 1 ? "" : "s");
if (ret < pmsg->len) {
if (ret >= 0)
ret = -EIO;
goto bailout;
}
再来看 sendbytes函数
static int sendbytes(struct i2c_adapter *i2c_adap, struct i2c_msg *msg)
{
const unsigned char *temp = msg->buf;
int count = msg->len;
unsigned short nak_ok = msg->flags & I2C_M_IGNORE_NAK;
int retval;
int wrcount = 0;
while (count > 0) {
retval = i2c_outb(i2c_adap, *temp);
/* OK/ACK; or ignored NAK */
if ((retval > 0) || (nak_ok && (retval == 0))) {
count--;
temp++;
wrcount++;
/* A slave NAKing the master means the slave didn't like
* something about the data it saw. For example, maybe
* the SMBus PEC was wrong.
*/
} else if (retval == 0) {
dev_err(&i2c_adap->dev, "sendbytes: NAK bailout.\n");
return -EIO;
/* Timeout; or (someday) lost arbitration
*
* FIXME Lost ARB implies retrying the transaction from
* the first message, after the "winning" master issues
* its STOP. As a rule, upper layer code has no reason
* to know or care about this ... it is *NOT* an error.
*/
} else {
dev_err(&i2c_adap->dev, "sendbytes: error %d\n",
retval);
return retval;
}
}
return wrcount;
}
也是一个大的while循环,同发送地址字节一样,也是调用i2c_outb去发送一个字节,count也是数据长度。类似的代码就不再赘述了。
发送停止信号
还是回到bit_xfer函数,数据传输完成后,调用i2c_stop函数发送停止信号。
bailout:
bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
i2c_stop(adap);
if (adap->post_xfer)
adap->post_xfer(i2c_adap);
return ret;
我们看停止信号函数怎么去实现的。
static void i2c_stop(struct i2c_algo_bit_data *adap)
{
/* assert: scl is low */
sdalo(adap);
sclhi(adap);
setsda(adap, 1);
udelay(adap->udelay);
}
根据i2c的协议,停止信号就是在时钟为高电平期间,数据线从低到高的跳变。
在i2c_stop
中,执行的顺序也是先将数据线拉低,将时钟线拉高,最后将数据拉高,这样就够成了一个停止信号。
i2c_add_numbered_adapter
刚刚说了,i2c_bit_add_numbered_bus
实际上是调用了__i2c_bit_add_bus
,同时传入了i2c_add_numbered_adapter
作为参数。
在__i2c_bit_add_bus
中的添加适配器的操作是由i2c_add_numbered_adapter
完成的。
所以现在看看另外一个函数调用i2c_add_numbered_adapter。
在i2c_add_numbered_adapter中,向i2c总线上添加了一个adapter。
// drivers/i2c/i2c-core.c
/**
* i2c_add_adapter - declare i2c adapter, use dynamic bus number
* @adapter: the adapter to add
* Context: can sleep
*
* This routine is used to declare an I2C adapter when its bus number
* doesn't matter or when its bus number is specified by an dt alias.
* Examples of bases when the bus number doesn't matter: I2C adapters
* dynamically added by USB links or PCI plugin cards.
*
* When this returns zero, a new bus number was allocated and stored
* in adap->nr, and the specified adapter became available for clients.
* Otherwise, a negative errno value is returned.
*/
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
// ...
adapter->nr = id;
return i2c_register_adapter(adapter);
}
EXPORT_SYMBOL(i2c_add_adapter);
/**
* __i2c_add_numbered_adapter - i2c_add_numbered_adapter where nr is never -1
* @adap: the adapter to register (with adap->nr initialized)
* Context: can sleep
*
* See i2c_add_numbered_adapter() for details.
*/
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
int id;
// ...
return i2c_register_adapter(adap);
}
/**
* i2c_add_numbered_adapter - declare i2c adapter, use static bus number
* @adap: the adapter to register (with adap->nr initialized)
* Context: can sleep
*
* This routine is used to declare an I2C adapter when its bus number
* matters. For example, use it for I2C adapters from system-on-chip CPUs,
* or otherwise built in to the system's mainboard, and where i2c_board_info
* is used to properly configure I2C devices.
*
* If the requested bus number is set to -1, then this function will behave
* identically to i2c_add_adapter, and will dynamically assign a bus number.
*
* If no devices have pre-been declared for this bus, then be sure to
* register the adapter before any dynamically allocated ones. Otherwise
* the required bus ID may not be available.
*
* When this returns zero, the specified adapter became available for
* clients using the bus number provided in adap->nr. Also, the table
* of I2C devices pre-declared using i2c_register_board_info() is scanned,
* and the appropriate driver model device nodes are created. Otherwise, a
* negative errno value is returned.
*/
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
if (adap->nr == -1) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
return __i2c_add_numbered_adapter(adap);
}
EXPORT_SYMBOL_GPL(i2c_add_numbered_adapter);
无论adap->nr
为多少,__i2c_add_numbered_adapter
与``i2c_add_adapter最终都调用了
i2c_register_adapter`注册这条 I2C总线:
// i2c-core.c
static int i2c_register_adapter(struct i2c_adapter *adap)
{
int res = -EINVAL;
// ...
if (!adap->lock_ops)
adap->lock_ops = &i2c_adapter_lock_ops;
// ...
/* Set default timeout to 1 second if not already set */
if (adap->timeout == 0) adap->timeout = HZ;
// 初始化,并注册这个I2C总线设备
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
// ...
#ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
#endif
/* create pre-declared device nodes */
of_i2c_register_devices(adap);
i2c_acpi_register_devices(adap);
i2c_acpi_install_space_handler(adap);
// 扫描板级信息(由于 nr = 0 ,因此一点会执行)
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
// ...
return 0;
out_list:
// ...
return res;
}
看内核代码有时就会这样,会陷入内核代码的汪洋大海中,而拔不出来,直接后果是最后都忘记看这段代码的目的,丧失继续看下去的信心(勿忘初心的重要性)。所以为了避免这样情况出现,所以最好在开始看代码的时候要明确目标,我通过这段代码到底要了解什么东西,主干要抓住,其它枝叶就不要看了。
关于i2c注册设备比较关键的有下面3点。
1. 注册这个I2C总线设备
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
i2c的总线类型 i2c_bus_type的类型为bus_type
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};
看一下它的 match函数:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
// ...
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
这个 match函数主要用来匹配我们的 I2C设备和 I2C驱动的,如果匹配成功,最后会调用驱动的 probe函数,来看它如何匹配的。
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
就是判断I2C设备的name字段和驱动中id_table中定义的name字段是否相等。
2.往这条总线上添加设备
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
struct i2c_devinfo *devinfo;
down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr
&& !i2c_new_device(adapter,
&devinfo->board_info))
dev_err(&adapter->dev,
"Can't create device at 0x%02x\n",
devinfo->board_info.addr);
}
up_read(&__i2c_board_lock);
}
遍历 __i2c_board_list
这条链表。判断要将新的i2c适配器添加到哪里。
nr就是 i2c总线的总线号,这里可以理解为是在往这条总线上添加设备。
所以,如果我们要向 I2C注册一个 I2C设备的话,直接向 __i2c_board_list添加一个设备信息就可以了,先来看这个设备信息结构是怎么定义的。
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
int irq;
};
定义这样一个信息呢一般使用一个宏 I2C_BOARD_INFO
dev_type为设备的名字,前面也说了,这个name一定要和I2C驱动相同。addr为设备的地址。
/**
* I2C_BOARD_INFO - macro used to list an i2c device and its address
* @dev_type: identifies the device type
* @dev_addr: the device's address on the bus.
*
* This macro initializes essential fields of a struct i2c_board_info,
* declaring what has been provided on a particular board. Optional
* fields (such as associated irq, or device-specific platform_data)
* are provided using conventional syntax.
*/
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
定义了这样一组信息之后呢,接下来当然是往链表添加这些信息了。
int __init
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);
}
up_write(&__i2c_board_lock);
return status;
}
第一个参数呢需要注意,它是 I2C总线号,一定要和具体的 I2C总线对应。我们看又定义了这样一个结构 struct i2c_devinfo。
最后是调用list_add_tail往__i2c_board_list这条链表添加设备信息。
然后是i2c_new_device
struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
struct i2c_client *client;
int status;
/*为I2C设备申请内存*/
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return NULL;
/*指定I2C设备的总线*/
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr; /*I2C设备地址*/
client->irq = info->irq;
strlcpy(client->name, info->type, sizeof(client->name));
/*检查这个地址有没有被设备占用*/
/* Check for address business */
status = i2c_check_addr(adap, client->addr);
if (status)
goto out_err;
client->dev.parent = &client->adapter->dev; /*指定设备的父设备*/
client->dev.bus = &i2c_bus_type; /*指定设备的总线类型*/
client->dev.type = &i2c_client_type;
dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
client->addr);
status = device_register(&client->dev); /*注册设备*/
if (status)
goto out_err;
dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));
return client;
out_err:
dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
"(%d)\n", client->name, client->addr, status);
kfree(client);
return NULL;
这个函数的功能是新建一个I2C设备并注册它,在I2C子系统中,I2C设备使用结构structi2c_client描述,那么首先要申请内存空间,I2C设备的主机是谁,必须知道挂载到哪条总线上的,然后就是一些赋值操作,最后就是注册设备,那么这个设备就实实在在的挂在到这条总线上了,这也是新的I2C设备注册方式。
3.i2c_do_add_adapter
你看说着说着就跑远了
static int i2c_do_add_adapter(struct device_driver *d, void *data)
{
struct i2c_driver *driver = to_i2c_driver(d);
struct i2c_adapter *adap = data;
/* Detect supported devices on that bus, and instantiate them */
i2c_detect(adap, driver);
/* Let legacy drivers scan this bus for matching devices */
if (driver->attach_adapter) {
/* We ignore the return code; if it fails, too bad */
driver->attach_adapter(adap);
}
return 0;
}
前面通过 i2c_scan_static_board_info往 I2C总线上添加设备是新的方式,而这里调用每个 I2C设备驱动的 attach_adapter函数,然后在 attach_adapter函数中去实现设备的注册,这是老的方式, i2c-dev.c中就是采用的这种方式。至此,总线这块就看完了。
Linux Driver : i2c-gpio的更多相关文章
- linux driver ------ GPIO的驱动编写和调用
判断哪些文件被编译进内核: 1.通过 make menuconfig 查看 2.比如查看gpio类型的文件,输入 ls drivers/gpio/*.o,有生成.o文件表示被编译进内核 在编写驱动程序 ...
- TQ2440学习笔记——Linux上I2C驱动的两种实现方法(1)
作者:彭东林 邮箱:pengdonglin137@163.com 内核版本:Linux-3.14 u-boot版本:U-Boot 2015.04 硬件:TQ2440 (NorFlash:2M Na ...
- linux下i2c的驱动架构分析和应用
i2c在linux下的代码在/driver/i2c下面,总体代码如下所示: i2c-core.c 这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口. i2c-dev.c 实现 ...
- 【驱动】linux下I2C驱动架构全面分析
I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...
- Linux driver 板级文件跟踪一般方法
/*********************************************************************************** * Linux driver ...
- linux内核I2C子系统学习(三)
写设备驱动: 四部曲: 构建i2c_driver 注册i2c_driver 构建i2c_client ( 第一种方法:注册字符设备驱动.第二种方法:通过板文件的i2c_board_info填充,然后注 ...
- linux下I2C驱动架构全面分析【转】
本文转载自:http://blog.csdn.net/wangpengqi/article/details/17711165 I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一 ...
- 嵌入式Linux内核I2C子系统详解
1.1 I2C总线知识 1.1.1 I2C总线物理拓扑结构 I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成.通信原理是通过对SCL和SDA线高 ...
- Linux 下操作GPIO(两种方法,驱动和mmap)(转载)
目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...
- Linux 下操作gpio(两种方法,驱动和mmap)
目前我所知道的在linux下操作GPIO有两种方法: 1. 编写驱动,这当然要熟悉linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据 ...
随机推荐
- C语言实验1
#include<stdio.h> #include<stdlib.h> int main() { printf(" o\n"); printf(" ...
- go语言package使用
近期接触go感觉package包之间引用很麻烦,很绕圈子.下面一起理一理这个package咋用 关于package: 1.不限于一个文件,可以多个文件组成一个package 2.不要求package的 ...
- 2021“MINIEYE杯”中国大学生算法设计超级联赛(1)
2021"MINIEYE杯"中国大学生算法设计超级联赛(1) 1001 Mod, Or and Everything 题意: 对于每次输入的数字n,求(n%1)|...(n%n)的 ...
- M3U8下载器加嗅探浏览器
M3U8下载器太多了,随便一抓一大把,没什么新奇的. 下载地址: https://www.zhaimaojun.cn/P/%e8%a7%86%e9%a2%91%e7%bd%91%e7%ab%99%e5 ...
- NVCC编译选项含义解析
NVCC编译 nvcc 是cuda程序的编译器. 1. 编译阶段 用于指定编译阶段最基本的编译参数. -c: 同gcc,只预处理.编译和汇编为.o文件,不link -lib:生成一个库文件,windo ...
- ansible(10)--ansible的systemd模块
1. systemd模块 功能:管理服务启动与停止,与 service 模块用法一致: 主要参数如下: 参数 说明 name 指定需要控制的服务名称 state 指定服务状态,其值可以为stopped ...
- 使用XCA自制CA证书并签发https证书
序言 本文目的是使公司内网部署的Web可以使用https的方式访问 现有部署的系统有用域名访问,有用IP访问,但都是用http的方式 所以打算在公司内网部署统一的CA证书,并可以自己签发对应的域名和I ...
- ChannelInboundHandlerAdapter 类
在 ChannelInboundHandlerAdapter 类中,除了 channelActive 和 channelRead 方法之外,还有其他方法用于处理不同类型的入站事件.以下是这些方法的解释 ...
- 跨域问题服务端解决办法 Request header field Authorization is not allowed by Access-Control-Allow-Headers
跨域问题服务端解决办法 一般在入口文件加 header('Access-Control-Allow-Origin:*');// 响应类型header('Access-Control-Allow-Met ...
- Swift全局变量的线程安全分析
一.示例代码 import UIKit let obj = TestObj() class TestObj { init() { print("\(type(of: self)) init& ...