历史上的今天
返回首页

历史上的今天

今天是:2024年10月21日(星期一)

正在发生

2021年10月21日 | mini2440 I2C驱动的分析与学习(一)

2021-10-21 来源:eefocus

花了近一个星期的时间学习I2C的驱动,将心得描述如下。I2C是一个典型的较简单的子系统,比较适合学习,因为I2C协议要比PCI等等简单得多。


在mini2440上用I2C连了一块EEPROM的芯片。因此,在mini2440上面说I2C的驱动,主要是说怎么样通过I2C在这块EERPOM上面读写数据。有点像是这个EEPROM的驱动。


先分析几个相关的结构体。其实有时觉得C语言就是以结构体为中心的,基本上把重要的结构体看明白了,程序也就看懂了。


struct s3c24xx_i2c {

spinlock_t lock;

wait_queue_head_t wait;

unsigned int suspended:1;

 

struct i2c_msg *msg;

unsigned int msg_num;

unsigned int msg_idx;

unsigned int msg_ptr;

 

unsigned int tx_setup;

unsigned int irq;

 

enum s3c24xx_i2c_state state;

unsigned long clkrate;

 

void __iomem *regs;

struct clk *clk;

struct device *dev;

struct resource *ioarea;

struct i2c_adapter adap;

 

#ifdef CONFIG_CPU_FREQ

struct notifier_block freq_transition;

#endif

};

这个结构体其实是代表着i2c所连着的设备。其中state代表设备的状态,resource代表设备相关的IO资源,dev是所有设备都包括的。adap其实代表的就是这个设备连着的I2C总线,I2C总线本身就是一个dev。因为在struct i2c_adapter里面包含有struct device结构体。如下:


/*

 * i2c_adapter is the structure used to identify a physical i2c bus along

 * with the access algorithms necessary to access it.

 */

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;

};

#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)

这里是一个典型的设备结构的嵌入。其中dev就是这个设备的主体。函数 static int i2c_register_adapter(struct i2c_adapter *adap) 实现设备的注册。i2c主要的就是这两个结构体。另外,还有几个结构体:struct i2c_driver i2c_client等接下来再分析。



 


首先从probe函数说起。


/* s3c24xx_i2c_probe

 *

 * called by the bus driver when a suitable device is found

*/

 

static int s3c24xx_i2c_probe(struct platform_device *pdev)

{

struct s3c24xx_i2c *i2c;

struct s3c2410_platform_i2c *pdata;

struct resource *res;

int ret;

 

pdata = pdev->dev.platform_data;

if (!pdata) {

dev_err(&pdev->dev, "no platform datan");

return -EINVAL;

}

 

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);

if (!i2c) {

dev_err(&pdev->dev, "no memory for staten");

return -ENOMEM;

}

 

strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); // 这里确定了sys/devices/platform/ 下设备的名称

i2c->adap.owner   = THIS_MODULE;

i2c->adap.algo    = &s3c24xx_i2c_algorithm;

i2c->adap.retries = 2;

i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;

i2c->tx_setup     = 50;

 

spin_lock_init(&i2c->lock);

init_waitqueue_head(&i2c->wait);

 

/* find the clock and enable it */

 

i2c->dev = &pdev->dev;

i2c->clk = clk_get(&pdev->dev, "i2c");

if (IS_ERR(i2c->clk)) {

dev_err(&pdev->dev, "cannot get clockn");

ret = -ENOENT;

goto err_noclk;

}

 

dev_dbg(&pdev->dev, "clock source %pn", i2c->clk);

 

clk_enable(i2c->clk);

 

/* map the registers */

 

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

if (res == NULL) {

dev_err(&pdev->dev, "cannot find IO resourcen");

ret = -ENOENT;

goto err_clk;

}

 

i2c->ioarea = request_mem_region(res->start, resource_size(res),

pdev->name);

 

if (i2c->ioarea == NULL) {

dev_err(&pdev->dev, "cannot request IOn");

ret = -ENXIO;

goto err_clk;

}

 

i2c->regs = ioremap(res->start, resource_size(res));

 

if (i2c->regs == NULL) {

dev_err(&pdev->dev, "cannot map IOn");

ret = -ENXIO;

goto err_ioarea;

}

 

dev_dbg(&pdev->dev, "registers %p (%p, %p)n",

i2c->regs, i2c->ioarea, res);

 

/* setup info block for the i2c core */

 

i2c->adap.algo_data = i2c;

i2c->adap.dev.parent = &pdev->dev;  /* 这里pdev->dev到底是什么? 这个pdev->dev对应到sysfs中应该就是 /sys/devices/platform/s3c2440-i2c/  */

 

/* initialise the i2c controller */

 

ret = s3c24xx_i2c_init(i2c);

if (ret != 0)

goto err_iomap;

 

/* find the IRQ for this unit (note, this relies on the init call to

* ensure no current IRQs pending

*/

 

i2c->irq = ret = platform_get_irq(pdev, 0);

if (ret <= 0) {

dev_err(&pdev->dev, "cannot find IRQn");

goto err_iomap;

}

 

ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,

  dev_name(&pdev->dev), i2c);

 

if (ret != 0) {

dev_err(&pdev->dev, "cannot claim IRQ %dn", i2c->irq);

goto err_iomap;

}

 

/* Note, previous versions of the driver used i2c_add_adapter()

* to add the bus at any number. We now pass the bus number via

* the platform data, so if unset it will now default to always

* being bus 0.

*/

 

i2c->adap.nr = pdata->bus_num;

 

ret = i2c_add_numbered_adapter(&i2c->adap);  // 在这个函数中进行设备的注册,调用 i2c_register_adapter, 确定 i2c->dev 的name, 调用device_register进行设备的注册。

if (ret < 0) {

dev_err(&pdev->dev, "failed to add bus to i2c coren");

goto err_cpufreq;

}

 

platform_set_drvdata(pdev, i2c);

 

dev_info(&pdev->dev, "%s: S3C I2C adaptern", dev_name(&i2c->adap.dev));

return 0;

 

 }


在linux中,将i2c也当做platformdevice。在archarmplat-s3cdev-i2c0.c文件中,有下面的描述:


static struct resource s3c_i2c_resource[] = {

[0] = {

.start = S3C_PA_IIC,

.end   = S3C_PA_IIC + SZ_4K - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_IIC,

.end   = IRQ_IIC,

.flags = IORESOURCE_IRQ,

},

};

 

struct platform_device s3c_device_i2c0 = {

.name   = "s3c2410-i2c",

#ifdef CONFIG_S3C_DEV_I2C1

.id   = 0,

#else

.id   = -1,

#endif

.num_resources   = ARRAY_SIZE(s3c_i2c_resource),

.resource   = s3c_i2c_resource,

};

 

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {

.flags = 0,

.slave_addr = 0x10,

.frequency = 100*1000,

.sda_delay = 100,

};

 

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)

{

struct s3c2410_platform_i2c *npd;

 

if (!pd)

pd = &default_i2c_data0;

 

npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);

if (!npd)

printk(KERN_ERR "%s: no memory for platform datan", __func__);

else if (!npd->cfg_gpio)

npd->cfg_gpio = s3c_i2c0_cfg_gpio;

 

s3c_device_i2c0.dev.platform_data = npd;

}

这样,我们就可以知道probe函数中,与Platformdevice相关的变量的值了。在s3c_i2c0_set_platdata中还设置了一个函数,这个函数是在Probe中用来设置管脚用的,这也是用来分离逻辑的方法。调用时,是这样调用的:


if (pdata->cfg_gpio)

pdata->cfg_gpio(to_platform_device(i2c->dev));


下面分析一下数据的传输流程。


在i2c中,将i2c设备也注册成为了一个dev,可以通过读写/dev/i2c0来操作设备。具体的实现是在i2c-dev.c中实现的。程序中这个i2c-client 代表了那个eeprom。


struct i2c_client {

unsigned short flags; /* div., see below */

unsigned short addr; /* chip address - NOTE: 7bit */

/* addresses are stored in the */

/* _LOWER_ 7 bits */

char name[I2C_NAME_SIZE];

struct i2c_adapter *adapter; /* the adapter we sit on */

struct i2c_driver *driver; /* and our access routines */

struct device dev; /* the device structure */

int irq; /* irq issued by device */

struct list_head detected;

};

#define to_i2c_client(d) container_of(d, struct i2c_client, dev)

 


在i2c-dev.c中,注册了设备i2c-dev。


static int __init i2c_dev_init(void)

{

int res;

 

printk(KERN_INFO "i2c /dev entries drivern");

 

res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);

if (res)

goto out;

 

i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");

if (IS_ERR(i2c_dev_class)) {

res = PTR_ERR(i2c_dev_class);

goto out_unreg_chrdev;

}

 

res = i2c_add_driver(&i2cdev_driver);

if (res)

goto out_unreg_class;

 

return 0;

}


在这里通过class_create函数,在/sys/class下面生成了目录/i2c-dev。另外,通过register_chrdev在/dev下面生成了i2c/ 路径。


这个是i2cdev_driver。这里的attach_adapter和detach_adapter是如何调用的?


static struct i2c_driver i2cdev_driver = {

.driver = {

.name = "dev_driver",

},

.attach_adapter = i2cdev_attach_adapter,

.detach_adapter = i2cdev_detach_adapter,

};

推荐阅读

史海拾趣

Ferranti Electric Inc公司的发展小趣事

作为一家具有社会责任感的企业,Ferranti Electric Inc公司一直致力于可持续发展。公司注重环保和节能,采用环保材料和生产工艺,减少对环境的影响。同时,公司也积极参与公益事业和社会活动,回馈社会。这些举措不仅体现了公司的社会责任感,也为公司的长远发展奠定了坚实的基础。

Furukawa Electric Co Ltd公司的发展小趣事

Ferranti Electric Inc公司非常重视知识产权的保护和积累。通过不断的技术研发和创新,公司积累了大量的知识产权,包括商标、专利等。这些知识产权的拥有不仅彰显了公司的技术实力和创新能力,也为公司的市场竞争提供了有力的保障。公司不断加大在研发和创新方面的投入,以保持其在电气行业的领先地位。

e2v technologies公司的发展小趣事

随着电子行业的快速发展,e2v不断拓展其产品线,以满足市场的多样化需求。除了传统的图像传感器产品外,公司还开发了示波器数据转换器、高性能图像处理器等,广泛应用于科学、医学、工业、汽车和电信等领域。这些产品的推出,不仅拓展了e2v的市场空间,也为其带来了可观的经济效益。

Global Components & Controls公司的发展小趣事
如果台灯亮度无法稳定或调节范围异常,可能是光敏电阻失效。需要检查光敏电阻是否老化或损坏,必要时进行更换。
EICHHOFF公司的发展小趣事

EICHHOFF公司成立于XX世纪XX年代,当时由创始人XXX在德国的一个小镇上开设了一个小型的电子元件加工厂。由于XXX对电子技术的热爱和对市场需求的敏锐洞察,他专注于生产高质量的连接器和其他电子组件。在初创阶段,EICHHOFF的产品主要供应给当地的电子制造商和维修店。随着口碑的逐渐传播和产品质量的不断提升,公司开始接到来自更广泛地区的订单。

为了应对不断增长的市场需求,EICHHOFF公司逐步扩大了生产规模,并引入了先进的生产设备和技术。同时,公司也注重人才培养和团队建设,吸引了一批有才华的工程师和技术人员加入。这些努力使得EICHHOFF在电子行业中逐渐崭露头角。

HM International公司的发展小趣事

EICHHOFF公司成立于XX世纪XX年代,当时由创始人XXX在德国的一个小镇上开设了一个小型的电子元件加工厂。由于XXX对电子技术的热爱和对市场需求的敏锐洞察,他专注于生产高质量的连接器和其他电子组件。在初创阶段,EICHHOFF的产品主要供应给当地的电子制造商和维修店。随着口碑的逐渐传播和产品质量的不断提升,公司开始接到来自更广泛地区的订单。

为了应对不断增长的市场需求,EICHHOFF公司逐步扩大了生产规模,并引入了先进的生产设备和技术。同时,公司也注重人才培养和团队建设,吸引了一批有才华的工程师和技术人员加入。这些努力使得EICHHOFF在电子行业中逐渐崭露头角。

问答坊 | AI 解惑

ChinaEDA--嵌入式Linux操作系统学习规划

嵌入式Linux操作系统学习规划 ARM+LINUX路线,主攻嵌入式Linux操作系统及其上应用软件开发目标: (1) 掌握主流嵌入式微处理器的结构与原理(初步定为arm9) (2) 必须掌握一个嵌入式操作系统 (初步定为uclinux或linux,版本待定) (3) ...…

查看全部问答>

机器人运动学初级篇(1)

本帖最后由 paulhyde 于 2014-9-15 09:36 编辑 里面有各种机器人的运动原理和方案  …

查看全部问答>

在VxWorks下如何打印文件

最近在做VxWorks下的打印功能,可是VxWorks不像Windows或Linux那样提供“所见即所得”的GDI函数接口, 所以对于在VxWorks下做可打印和可显示的GDI函数接口一头雾水,无从下手,最近看到了介绍打印机控制语言 PCL和PostScript,感觉里面介绍的东西 ...…

查看全部问答>

建文件

怎么才能在建立的项目中.c文件中添加一子文件如.h文件…

查看全部问答>

怎样知道菜单

比如,点击 文件 下的 打开  ,我想在点击文件之前,重新生成菜单,我该怎么做啊?谢谢…

查看全部问答>

NE555上面写着16M/38M......我可能想多了

刚接触555的时候,我知道555最高频率不会超过500K,后来听说有所提高,刚才百度的时候,看到一个说最高2M。   最近,我在整理我的元器件,无意发现,有几块NE555上面写着 16M或者38M。 但是百度了没反应。 想去 ALLDATESHEET,发现进不去 ...…

查看全部问答>

STM8!8位单片机的终结者吗?!

网上现在 STM8 火爆,价格便宜,据说128K  flash 的20元,只是现在还不好买,性价比不错,而且是5V的,从网上下载了 STM8 用户手册粗看了一下,感觉是我最偏爱的 6502(类拟飞思卡尔 6800) 内核的升级版,但是指令系统比现在的飞思 ...…

查看全部问答>

版主:STM32外部中断EXTI调试失败

使用万利开发板进行EXTI简单调试:设置PD3,PD4引脚作为中断输入端口,他们分别与板载按键KEY2,KEY3相连。设置好后主程序运行正常,可一旦按下KEY2或KEY3任何一个产生中断信号时,主程序死掉,中断也进不去!! 附EXTI设置源码: *********** ...…

查看全部问答>

verilog硬件语言

在学习夏宇闻老师的数字系统设计verilog硬件语言的过程中,学习流水线的的设计及理解有点摸透不清,,请教高人指点。。。。…

查看全部问答>