树莓派 -- i2c学习
硬件平台
- RaspberryPi-3B+
- Pioneer600外扩版
i2c芯片为DS3231,adddress 0x68
首先来看一下i2ctool的使用
i2ctool 使用
https://i2c.wiki.kernel.org/index.php/I2C_Tools
https://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git/tree/
i2cdetect
总线扫描
pi@raspberrypi:~ $ i2cdetect -l
i2c-1 i2c bcm2835 I2C adapter I2C adapter
设备扫描
pi@raspberrypi:~ $ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
i2cdump
Usage: i2cdump [-f] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (byte, default)
w (word)
W (word on even register addresses)
s (SMBus block)
i (I2C block)
c (consecutive byte)
Append p for SMBus PEC
pi@raspberrypi:/dev $ i2cdump -y -r 0x00-0x0f 1 0x68
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 27 48 14 05 19 06 15 00 00 00 00 00 00 00 1c 88 'H?????.......??
i2c控制器
processor 外设中一般会集成i2c控制器。 i2c控制器在驱动模型中又称为i2c_adapter.
在raspberryPi中查看到i2c-adapter如下
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ ls
delete_device device i2c-dev name new_device of_node power subsystem uevent
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ cat name
bcm2835 I2C adapter
i2c-adapter的驱动是基于platform bus的
总线驱动
pi@raspberrypi:/sys/bus/platform/drivers/i2c-bcm2835 $ ls
3f804000.i2c bind module uevent unbind
总线设备
pi@raspberrypi:/sys/bus/platform/devices/3f804000.i2c $ ls
driver driver_override i2c-1 modalias of_node power subsystem uevent
pi@raspberrypi:/dev $ ls | grep i2c
i2c-1
driver
相关源码drivers\i2c\busses\i2c-bcm2835.c
下面就只列出probe函数
/*
* BCM2835 master mode driver
*/
static int bcm2835_i2c_probe(struct platform_device *pdev)
{
struct bcm2835_i2c_dev *i2c_dev;
struct resource *mem, *irq;
int ret;
struct i2c_adapter *adap;
i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
if (!i2c_dev)
return -ENOMEM;
platform_set_drvdata(pdev, i2c_dev);
i2c_dev->dev = &pdev->dev;
init_completion(&i2c_dev->completion);
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c_dev->regs = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(i2c_dev->regs))
return PTR_ERR(i2c_dev->regs);
i2c_dev->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(i2c_dev->clk)) {
if (PTR_ERR(i2c_dev->clk) != -EPROBE_DEFER)
dev_err(&pdev->dev, "Could not get clock\n");
return PTR_ERR(i2c_dev->clk);
}
ret = of_property_read_u32(pdev->dev.of_node, "clock-frequency",
&i2c_dev->bus_clk_rate);
if (ret < 0) {
dev_warn(&pdev->dev,
"Could not read clock-frequency property\n");
i2c_dev->bus_clk_rate = 100000;
}
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!irq) {
dev_err(&pdev->dev, "No IRQ resource\n");
return -ENODEV;
}
i2c_dev->irq = irq->start;
ret = request_irq(i2c_dev->irq, bcm2835_i2c_isr, IRQF_SHARED,
dev_name(&pdev->dev), i2c_dev);
if (ret) {
dev_err(&pdev->dev, "Could not request IRQ\n");
return -ENODEV;
}
adap = &i2c_dev->adapter;
i2c_set_adapdata(adap, i2c_dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_DEPRECATED;
strlcpy(adap->name, "bcm2835 I2C adapter", sizeof(adap->name));
adap->algo = &bcm2835_i2c_algo;
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
adap->quirks = &bcm2835_i2c_quirks;
bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, 0);
ret = i2c_add_adapter(adap);
if (ret)
free_irq(i2c_dev->irq, i2c_dev);
return ret;
}
static const struct of_device_id bcm2835_i2c_of_match[] = {
{ .compatible = "brcm,bcm2835-i2c" },
{},
};
MODULE_DEVICE_TABLE(of, bcm2835_i2c_of_match);
static struct platform_driver bcm2835_i2c_driver = {
.probe = bcm2835_i2c_probe,
.remove = bcm2835_i2c_remove,
.driver = {
.name = "i2c-bcm2835",
.of_match_table = bcm2835_i2c_of_match,
},
};
module_platform_driver(bcm2835_i2c_driver);
MODULE_AUTHOR("Stephen Warren <swarren@wwwdotorg.org>");
MODULE_DESCRIPTION("BCM2835 I2C bus adapter");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:i2c-bcm2835");
可以看到在probe函数中调用了i2c_add_adapter来注册i2c-adapter到i2ccore.
设备树中的i2c-adapter设备
查看源码arch\arm\boot\dts\bcm283x.dtsi,
i2c1: i2c@7e804000 {
compatible = "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
interrupts = <2 21>;
clocks = <&clocks BCM2835_CLOCK_VPU>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
i2c外设
添加rtc设备
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ echo ds3231 0x68 | sudo tee new_device
ds3231 0x68
在目录下多了一个1-0068
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ ls
1-0068 device name of_node subsystem
delete_device i2c-dev new_device power uevent
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1 $ cd 1-0068/
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068 $ ls
driver hwmon modalias name power rtc subsystem uevent
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068 $ cat name
ds3231
查看1-0068
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068 $ cd rtc/
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068/rtc $ ls
rtc0
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068/rtc $ cd rtc0/
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068/rtc/rtc0 $ ls
date device max_user_freq power subsystem uevent
dev hctosys name since_epoch time
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068/rtc/rtc0 $ cat name
rtc-ds1307 1-0068
pi@raspberrypi:/sys/class/i2c-adapter/i2c-1/1-0068/rtc/rtc0 $
查看/dev/rtc0
pi@raspberrypi:/dev $ ls -la |grep rtc
lrwxrwxrwx 1 root root 4 Jul 14 13:43 rtc -> rtc0
crw------- 1 root root 253, 0 Jul 14 13:43 rtc0
测试
pi@raspberrypi:~ $ sudo hwclock
2000-01-01 00:07:24.525370+0000
pi@raspberrypi:~ $ sudo hwclock --debug
hwclock from util-linux 2.29.2
Using the /dev interface to the clock.
Assuming hardware clock is kept in UTC time.
Waiting for clock tick...
/dev/rtc does not have interrupt functions. Waiting in loop for time from /dev/rtc to change
...got clock tick
Time read from Hardware Clock: 2000/01/01 00:07:40
Hw clock time : 2000/01/01 00:07:40 = 946685260 seconds since 1969
Time since last adjustment is 946685260 seconds
Calculated Hardware Clock drift is 0.000000 seconds
2000-01-01 00:07:39.551217+0000
添加rtc设备分析
i2c设备的实例化有多种方式,在上文中,采用的通过sysfs的方式来实例化。
通过sysfs实例化i2c设备
源码drivers\i2c\i2c-core-base.c,中
/*
* Let users instantiate I2C devices through sysfs. This can be used when
* platform initialization code doesn't contain the proper data for
* whatever reason. Also useful for drivers that do device detection and
* detection fails, either because the device uses an unexpected address,
* or this is a compatible device with different ID register values.
*
* Parameter checking may look overzealous, but we really don't want
* the user to provide incorrect parameters.
*/
static ssize_t
i2c_sysfs_new_device(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_adapter *adap = to_i2c_adapter(dev);
struct i2c_board_info info;
struct i2c_client *client;
char *blank, end;
int res;
memset(&info, 0, sizeof(struct i2c_board_info));
blank = strchr(buf, ' ');
if (!blank) {
dev_err(dev, "%s: Missing parameters\n", "new_device");
return -EINVAL;
}
if (blank - buf > I2C_NAME_SIZE - 1) {
dev_err(dev, "%s: Invalid device name\n", "new_device");
return -EINVAL;
}
memcpy(info.type, buf, blank - buf);
/* Parse remaining parameters, reject extra parameters */
res = sscanf(++blank, "%hi%c", &info.addr, &end);
if (res < 1) {
dev_err(dev, "%s: Can't parse I2C address\n", "new_device");
return -EINVAL;
}
if (res > 1 && end != '\n') {
dev_err(dev, "%s: Extra parameters\n", "new_device");
return -EINVAL;
}
if ((info.addr & I2C_ADDR_OFFSET_TEN_BIT) == I2C_ADDR_OFFSET_TEN_BIT) {
info.addr &= ~I2C_ADDR_OFFSET_TEN_BIT;
info.flags |= I2C_CLIENT_TEN;
}
if (info.addr & I2C_ADDR_OFFSET_SLAVE) {
info.addr &= ~I2C_ADDR_OFFSET_SLAVE;
info.flags |= I2C_CLIENT_SLAVE;
}
client = i2c_new_device(adap, &info);
if (!client)
return -EINVAL;
/* Keep track of the added device */
mutex_lock(&adap->userspace_clients_lock);
list_add_tail(&client->detected, &adap->userspace_clients);
mutex_unlock(&adap->userspace_clients_lock);
dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
info.type, info.addr);
return count;
}
通过上面的函数可以看出,在命令
echo ds3231 0x68 | sudo tee new_device
中,ds3231被传递到i2c_board_info 的type中, 然后调用i2c_new_device
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
const char *dev_name;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
const struct property_entry *properties;
const struct resource *resources;
unsigned int num_resources;
int irq;
};
源码drivers\rtc\rtc-ds1307.c中
static const struct i2c_device_id ds1307_id[] = {
{ "ds1307", ds_1307 },
{ "ds1308", ds_1308 },
{ "ds1337", ds_1337 },
{ "ds1338", ds_1338 },
{ "ds1339", ds_1339 },
{ "ds1388", ds_1388 },
{ "ds1340", ds_1340 },
{ "ds1341", ds_1341 },
{ "ds3231", ds_3231 },
{ "m41t0", m41t0 },
{ "m41t00", m41t00 },
{ "mcp7940x", mcp794xx },
{ "mcp7941x", mcp794xx },
{ "pt7c4338", ds_1307 },
{ "rx8025", rx_8025 },
{ "isl12057", ds_1337 },
{ "rx8130", rx_8130 },
{ }
};
enum ds_type {
ds_1307,
ds_1308,
ds_1337,
ds_1338,
ds_1339,
ds_1340,
ds_1341,
ds_1388,
ds_3231,
m41t0,
m41t00,
mcp794xx,
rx_8025,
rx_8130,
last_ds_type /* always last */
/* rs5c372 too? different address... */
};
Reference
https://blog.csdn.net/lizuobin2/article/details/51694574
https://www.linuxidc.com/Linux/2014-05/101649.htm
https://blog.csdn.net/qq_33160790/article/details/69048520