树莓派 -- 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

posted @ 2018-07-14 23:19  feiwatson  阅读(674)  评论(0编辑  收藏  举报