背景

在学习高通平台的有关知识,看到一篇博客中介绍了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_adapterstruct 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总线。

定义在include/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的更多相关文章

  1. linux driver ------ GPIO的驱动编写和调用

    判断哪些文件被编译进内核: 1.通过 make menuconfig 查看 2.比如查看gpio类型的文件,输入 ls drivers/gpio/*.o,有生成.o文件表示被编译进内核 在编写驱动程序 ...

  2. TQ2440学习笔记——Linux上I2C驱动的两种实现方法(1)

    作者:彭东林 邮箱:pengdonglin137@163.com 内核版本:Linux-3.14 u-boot版本:U-Boot 2015.04 硬件:TQ2440 (NorFlash:2M   Na ...

  3. linux下i2c的驱动架构分析和应用

    i2c在linux下的代码在/driver/i2c下面,总体代码如下所示: i2c-core.c 这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口.    i2c-dev.c  实现 ...

  4. 【驱动】linux下I2C驱动架构全面分析

    I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...

  5. Linux driver 板级文件跟踪一般方法

    /*********************************************************************************** * Linux driver ...

  6. linux内核I2C子系统学习(三)

    写设备驱动: 四部曲: 构建i2c_driver 注册i2c_driver 构建i2c_client ( 第一种方法:注册字符设备驱动.第二种方法:通过板文件的i2c_board_info填充,然后注 ...

  7. linux下I2C驱动架构全面分析【转】

    本文转载自:http://blog.csdn.net/wangpengqi/article/details/17711165 I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一 ...

  8. 嵌入式Linux内核I2C子系统详解

    1.1 I2C总线知识 1.1.1  I2C总线物理拓扑结构     I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成.通信原理是通过对SCL和SDA线高 ...

  9. Linux 下操作GPIO(两种方法,驱动和mmap)(转载)

    目前我所知道的在Linux下操作GPIO有两种方法: 1.编写驱动,这当然要熟悉Linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据io ...

  10. Linux 下操作gpio(两种方法,驱动和mmap)

    目前我所知道的在linux下操作GPIO有两种方法: 1.  编写驱动,这当然要熟悉linux下驱动的编写方法和技巧,在驱动里可以使用ioremap函数获得GPIO物理基地址指针,然后使用这个指针根据 ...

随机推荐

  1. 密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg)

    密钥存储在过时的 trusted.gpg 密钥环中(/etc/apt/trusted.gpg) 问题: 解决: cd /etc/opt sudo cp trusted.gpg trusted.gpg. ...

  2. docker-compose 安装LNMP

    安装DNMP https://github.com/yeszao/dnmp.git https://blog.csdn.net/weixin_34038293/article/details/9427 ...

  3. java中调用exe程序和问题处理

    常规方法如下: public class RunExe { public static void main(String[] args) { try { // exe文件的完整路径 String fi ...

  4. IPv6 — 地址格式与寻址模式

    目录 文章目录 目录 前文列表 IPv6 的地址格式 站点前缀 地址生成方式 IPv6 地址的分类以及寻址模式 单播(Unicast)地址 Interface ID 全球唯一地址(Global Uni ...

  5. flask注册功能

    一个项目的简单结构划分 首先创建一个新项目 可以正常运行与访问 创建配置文件并添加配置. 将这里拆分到不同的文件中,让启动文件更加简洁. 创建一个apps包,导入配置模块,导入Flask,定义创建ap ...

  6. kubernetes 之Health Check 健康检查

    默认的健康检查 这里Pod的restartPolicy设置为OnFailure,默认为Always. [machangwei@mcwk8s-master ~]$ cat mcwHealthcheck. ...

  7. 导入使用es

    from django.shortcuts import render, HttpResponsefrom elasticsearch import Elasticsearchfrom elastic ...

  8. 统计学习:EM算法及其在高斯混合模型(GMM)中的应用

    1. EM算法的基本思想 我们在应用中所面对的数据有时是缺损的/观测不完全的[1][2].我们将数据分为: 可观测数据,用\(Y\)表示: 缺失数据,用\(Z\)表示; 完全数据,用\(X=(Y, Z ...

  9. echarts下划线实现

    echarts中无下划线实现,我采用图片填充文本块背景的方式实现 这是从 长空雁叫霜晨月 的博客中得到启发https://www.cnblogs.com/volodya/p/Echarts.html ...

  10. Python Pandas 数据分组

    在数据处理中,分箱.分组是一种常见的技术,用于将连续数据的间隔分组到"箱"或"桶"中.我们将讨论以下两种方法: 使用 Pandas 的 between 和 lo ...