1. 程式人生 > >Zynq平臺下linux的I2C驅動(RTC+EEPROM)

Zynq平臺下linux的I2C驅動(RTC+EEPROM)

現在ARM下對SoC開發板的硬體描述都是採用devicetree檔案,使用linux自帶的dtc程式將dts編譯成dtb之後,由u-boot將dtb匯入給linux核心,linux核心讀取dtb,然後註冊裝置的resource,linux核心使用of_系列函式API讀取硬體資源。具體的說明可以看下

http://blog.csdn.net/21cnbao/article/details/8457546

.dts檔案根據具體的硬體配置好後,編譯生成.dtb檔案。

然後需要在menuconfig核心配置中為硬體選擇驅動程式,只有硬體驅動程式和dts中的硬體名字匹配時,才能觸發驅動的probe函式

rtc-8564和pcf8563的驅動是相容的,均為pcf8563驅動。

 注:以下的分析基於3.12.0linux核心。個人分析難免存在紕漏,懇請大家指正。

一、I2C的linux主要涉及4個結構體:i2c_adapter,i2c_algorithm,i2c_client,i2c_driver

struct i2c_adapter {
	struct module *owner;
	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	*/
	struct rt_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 mutex userspace_clients_lock;
	struct list_head userspace_clients;

	struct i2c_bus_recovery_info *bus_recovery_info;
};

i2c匯流排控制器資料依附於algo_data,比如xi2cps,s3c24xx_i2c。


struct device dev;成員表明i2c_adapter是一個硬體,對應SoC上的I2C控制器。

而i2c_algorithm則是這個I2C控制器的底層驅動程式。

同理:

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;
};

struct i2c_client代表一個掛載到i2c總線上的i2c從裝置,該裝置所需要的資料結構,其中包括

  • 該i2c從裝置所依附的i2c主裝置 struct i2c_adapter *adapter
  • 該i2c從裝置的驅動程式struct i2c_driver *driver
  • 作為i2c從裝置所通用的成員變數,比如addr, name等
  • 該i2c從裝置驅動所特有的資料,依附於dev->driver_data下,在i2c_driver中的probe函式中設定這個結構體成員。比如eeprom的eeprom_data。
  • 所有i2c從裝置組成的雙向連結串列:detected


struct device dev表明struct i2c_client代表的是一個硬體,比如eeprom晶片,或則rtc晶片,通過i2c匯流排連線到i2c_adapter硬體上。

而i2c_driver則是這個i2c_client晶片硬體的驅動程式。

我們一般會對每個I2C字元裝置定義一個私有資訊結構體,而i2c_client一般被包含在這個私有資訊結構體中。看過LDR3原始碼的hacker應該比較清楚。

i2c_client依附於i2c_adapter,也就是I2C裝置和I2C匯流排控制器的對應關係,一個i2c_adapter可以掛接多個i2c_client,i2c_adapter的struct list_head userspace_clients;結構成員就是所有client的連結串列。

linux的最新版本基本上支援目前所有的I2C介面卡硬體和I2C從裝置,但是對於工程師來說,可能要面臨各種情況:為i2c_adapter和i2c_client編寫驅動程式。

二、I2C核心

    I2C核心是原始碼位於drivers/i2c/i2c-core.c,它並不依賴於硬體平臺的介面函式,是I2C匯流排驅動和裝置驅動的紐帶。

增加/刪除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adapter)      //呼叫i2c_register_adapter()

int i2c_del_adapter(struct i2c_adapter *adapter)

增加/刪除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

int i2c_add_driver(struct i2c_driver *driver)         //呼叫i2c_register_driver

void i2c_del_driver(struct i2c_driver *driver)

增加/刪除i2c_client

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)

void i2c_unregister_device(struct i2c_client *client)

      注:在2.6.30版本之前使用的是i2c_attach_client()和i2c_detach_client()函式。之後attach被merge到了i2c_new_device中,而detach直接被unregister取代。實際上這兩個函式內部都是呼叫了device_register()和device_unregister()

I2C傳輸、傳送接收

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

int i2c_master_send(struct i2c_client *client,const char *buf ,int count)

int i2c_master_recv(struct i2c_client *client, char *buf ,int count)

     i2c_transfer()函式用於進行I2C 介面卡和I2C 裝置之間的一組訊息互動,i2c_master_send()函式和i2c_master_recv()函式內部會呼叫i2c_transfer()函式分別完成一條寫訊息和一條讀訊息。

      i2c_transfer()本身不能和硬體完成訊息互動,它尋找i2c_adapter對應的i2c_algorithm,要實現資料傳送就要實現i2c_algorithm的master_xfer(),這個函式與具體的硬體有關,大部分時間由廠商完成。

      i2c_transfer()通過呼叫__i2c_transfer()完成I2C通訊:

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	unsigned long orig_jiffies;
	int ret, try;

	/* Retry automatically on arbitration loss */
	orig_jiffies = jiffies;
	for (ret = 0, try = 0; try <= adap->retries; try++) {
		ret = adap->algo->master_xfer(adap, msgs, num);
		if (ret != -EAGAIN)
			break;
		if (time_after(jiffies, orig_jiffies + adap->timeout))
			break;
	}

	return ret;
}


可見retries為重傳嘗試次數,timeout為超時時間。

三、Linux I2C匯流排驅動

1、I2C介面卡的載入和卸除

載入:申請硬體資源,比如IO地址,中斷號,呼叫i2c_add_adapter載入介面卡

i2c_add_adapter中會呼叫i2c_register_adapter函式

static int i2c_register_adapter(struct i2c_adapter *adap)
{
   ... ...

  device_register(&adap->dev);     //完成I2C主裝置adapter的註冊,即註冊object和傳送uevent等

  i2c_scan_static_board_info(adap);       //註冊i2c_client

   ... ...
}

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_new_device呼叫device_register註冊i2c從裝置。

那麼,這個I2C從裝置組成的雙向迴圈連結串列,是什麼時候通過什麼方式建立起來的呢?

以 /arch/arm/mach-pxa/saar.c 為例

static void __init saar_init(void)

{

   ... ...

saar_init_i2c();

  ........

}

static void __init saar_init_i2c(void)
{
 pxa_set_i2c_info(NULL);
 i2c_register_board_info(0, ARRAY_AND_SIZE(saar_i2c_info));
}

static struct i2c_board_info saar_i2c_info[] = {
	[0] = {
		.type		= "da9034",
		.addr		= 0x34,
		.platform_data	= &saar_da9034_info,
		.irq		= PXA_GPIO_TO_IRQ(mfp_to_gpio(MFP_PIN_GPIO83)),
	},
};

 /* drivers/i2c/i2c-boardinfo.c */

 int __init i2c_register_board_info(int busnum, structi2c_board_info const *info, unsigned len)
{

   ... ...

  struct i2c_devinfo *devinfo;
  devinfo->board_info = *info;
  list_add_tail(&devinfo->list, &__i2c_board_list);    //將I2C從裝置加入該連結串列中
   ... ...
}

 所以,在系統初始化的過程中,我們可以通過 i2c_register_board_info,將所需要的I2C從裝置加入一個名為__i2c_board_list雙向迴圈連結串列,系統在成功載入I2C主裝置adapt後,就會對這張連結串列裡所有I2C從裝置逐一地完成 i2c_client的註冊。

也就是說,i2c_client和i2c_adapter都是由i2c_core來維護的。

在xilinx-linux中,i2c從裝置是通過dts檔案傳遞給核心的,核心通過zynq_init_machine函式註冊所有的i2c從裝置,i2c_client.

      在linux的裝置和驅動管理體系中,所有的非熱插拔裝置預設是在 init_machine函式成員中加入相應維護裝置的雙向連結串列中,包括platform_device和其他的裝置。當一個特定的裝置驅動通過driver_register加入對應的匯流排下時,回去遍歷對應匯流排下的裝置雙向連結串列,當驅動和裝置匹配時,會觸發驅動的probe函式。

DT_MACHINE_START(XILINX_EP107, "Xilinx Zynq Platform")
	.smp		= smp_ops(zynq_smp_ops),
	.map_io		= zynq_map_io,
	.init_irq	= zynq_irq_init,
	.init_machine	= zynq_init_machine,
	.init_late	= zynq_init_late,
	.init_time	= zynq_timer_init,
	.dt_compat	= zynq_dt_match,
	.reserve	= zynq_memory_init,
	.restart	= zynq_system_reset,
MACHINE_END


可以參考mach-zynq的電路板初始化程式碼

卸除:釋放硬體資源,呼叫i2c_del_adapter解除安裝i2c介面卡

void i2c_del_adapter(struct i2c_adapter *adap)

{

..........

	list_for_each_entry_safe(client, next, &adap->userspace_clients,
				 detected) {
		dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
			client->addr);
		list_del(&client->detected);
		i2c_unregister_device(client);
	}

解除安裝所有的從i2c裝置

..............

device_unregister(&adap->dev);

解除安裝i2c介面卡
..............

}

2、編寫I2C的匯流排通訊方法algorithm

	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);


主要實現上面的兩個函式。

  大部分時間,我們需要定義一個XXX_i2c結構體,比如drivers/i2c/busses/i2c-s3c2410.c中的struct s3c24xx_i2c。

XXX_i2c結構體中包含struct i2c_msg  *msg;struct i2c_adapter adap;void __iomem  *regs;等

struct i2c_msg  *msg接收使用者層的資料發到i2c匯流排,或從i2c匯流排讀取資料到使用者層。

在介面卡的probe函式中:

struct xi2cps *id;

platform_set_drvdata(pdev, id); 

id->adap.dev.of_node = pdev->dev.of_node;
id->adap.algo = (struct i2c_algorithm *) &xi2cps_algo;
id->adap.timeout = 0x1F;/* Default timeout value */
id->adap.retries = 3;/* Default retry value. */
id->adap.algo_data = id;
id->adap.dev.parent = &pdev->dev;

四、linux i2c從裝置驅動

        硬體方面,I2C主裝置已經整合在主晶片內,軟體方面,linux也為我們提供了相應的驅動程式,位於drivers/i2c/bus下。那麼接下來I2C從裝置驅動就變得容易得多。既然系統載入I2C主裝置驅動時已經註冊了i2c_adapter和i2c_client,那麼I2C從裝置主要完成三大任務:

  • 系統初始化時新增以i2c_board_info為結構的I2C從裝置的資訊(針對不使用dts描述硬體資訊的電路板)
  • 在I2C從裝置驅動程式裡使用i2c_adapter裡所提供的演算法,即實現I2C通訊。
  • 將I2C從裝置的特有資料結構掛在到i2c_client.dev->driver_data下。

以/driver/misc/eeprom/eeprom.c為例:

static struct i2c_driver eeprom_driver = {
	.driver = {
		.name	= "eeprom",
	},
	.probe		= eeprom_probe,
	.remove		= eeprom_remove,
	.id_table	= eeprom_id,

	.class		= I2C_CLASS_DDC | I2C_CLASS_SPD,
	.detect		= eeprom_detect,
	.address_list	= normal_i2c,
};

       i2c_driver 中的driver.name 不一定要和i2c_client一致,因為這只是他們配備的依據之一。id_table 是i2c_device_id結構體的一個物件,裡面定義了i2c驅動對應裝置的i2c地址。struct i2c_device_id裡面的字串與 I2C_BOARD_INFO裡面的匹配後,xxx_led_probe也會呼叫,這是裝置和驅動匹配的依據之二。

使用Device Tree後,驅動需要與.dts中描述的裝置結點進行匹配,從而引發驅動的probe()函式執行。對於platform_driver而言,需要新增一個OF匹配表

static const struct i2c_device_id pcf8563_id[] = {
	{ "pcf8563", 0 },
	{ "rtc8564", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, pcf8563_id);
#ifdef CONFIG_OF
static const struct of_device_id pcf8563_of_match[] = {
	{ .compatible = "nxp,pcf8563" },
	{}
};
MODULE_DEVICE_TABLE(of, pcf8563_of_match);
#endif
static struct i2c_driver pcf8563_driver = {
	.driver		= {
		.name	= "rtc-pcf8563",
		.owner	= THIS_MODULE,
		.of_match_table = of_match_ptr(pcf8563_of_match),
	},
	.probe		= pcf8563_probe,
	.id_table	= pcf8563_id,
};
/* Each client has this additional data */


不過這邊有一點需要提醒的是,I2C和SPI外設驅動和Device Tree中裝置結點的compatible 屬性還有一種弱式匹配方法,就是別名匹配。compatible 屬性的組織形式為<manufacturer>,<model>,別名其實就是去掉compatible 屬性中逗號前的manufacturer字首。關於這一點,可檢視drivers/spi/spi.c的原始碼,函式spi_match_device()暴露了更多的細節,如果別名出現在裝置spi_driver的id_table裡面,或者別名與spi_driver的name欄位相同,SPI裝置和驅動都可以匹配上:

struct eeprom_data {
	struct mutex update_lock;
	u8 valid;			/* bitfield, bit!=0 if slice is valid */
	unsigned long last_updated[8];	/* In jiffies, 8 slices */
	u8 data[EEPROM_SIZE];		/* Register values */
	enum eeprom_nature nature;
};

static int eeprom_probe(struct i2c_client *client,
   const struct i2c_device_id *id)
{

 ... ...

struct eeprom_data *data;

......

i2c_set_clientdata(client, data);//將裝置的資料結構掛到i2c_client.dev->p->driver_data下
.............

name[0] = i2c_smbus_read_byte_data(client, 0x80);//i2c-core提供的介面,利用i2c_adapter的演算法實現I2C通訊


}

以及http://blog.csdn.net/zclongembedded/article/details/8207722

也就是說,i2c_adapter是一個i2c匯流排控制器,i2c_add_driver會把i2c_driver掛到i2c總線上,並搜尋總線上所有和它匹配的i2c_client,成功的話i2c_driver的probe函式就會被呼叫,搜尋到的i2c_client會作為引數傳遞給probe函式。因為一個i2c_driver可能被多個i2c_client使用,因此就瞭解i2c_set_clientdata(client, data);呼叫的必要性了。就是說多個clients可以用一個driver,但是各自有自己的私有資料。

注:module_i2c_driver是一個針對i2c的巨集定義, 

定義位於/include/linux/i2c.h/

/**
 * module_i2c_driver() - Helper macro for registering a I2C driver
 * @__i2c_driver: i2c_driver struct
 *
 * Helper macro for I2C drivers which do not do anything special in module
 * init/exit. This eliminates a lot of boilerplate. Each module may only
 * use this macro once, and calling it replaces module_init() and module_exit()
 */
#define module_i2c_driver(__i2c_driver) \
	module_driver(__i2c_driver, i2c_add_driver, \
			i2c_del_driver)

使用module_i2c_driver(xxx_i2c_driver)

可以取代

 -static int __init xxx_i2c_init(void)
-{
-       return i2c_add_driver(&xxx_i2c_driver);
-}
-
-static void __exit xxx_i2c_exit(void)
-{
-       i2c_del_driver(&xxx_i2c_driver);
-}
-
-
-module_init(xxx_i2c_init);
-module_exit(xxx_i2c_exit); 
 

相關推薦

Zynq臺下linux的I2C驅動RTC+EEPROM

現在ARM下對SoC開發板的硬體描述都是採用devicetree檔案,使用linux自帶的dtc程式將dts編譯成dtb之後,由u-boot將dtb匯入給linux核心,linux核心讀取dtb,然後註冊裝置的resource,linux核心使用of_系列函式API讀取硬體

IntelliJ IDEA臺下JNI程式設計—本地C程式碼建立Java物件及引用

本文學習如何在C程式碼中建立Java物件和物件陣列,前面我們學習了C程式碼中訪問Java物件的屬性和方法,其實在建立物件時本質上也就是呼叫建構函式,因此本文知識學習起來也很輕鬆。有了前面學習陣列建立的方法後,C程式碼建立物件陣列同樣很容易,下面開始學習吧~

MTK臺下Battery驅動分析及充電流程

轉自:http://blog.csdn.net/baidu_34021173/article/details/51105223主要涉及程式碼:Kernel:kernel-3.10\drivers\power\mediatek\kernel-3.10\drivers\misc\

ubuntu 14.04 安裝惠普印表機驅動測試成功

根據網上的許多教程進行了嘗試,但是都失敗了。今天終於摸索成功!我的情況是在ubuntu14.04的機器上安裝惠普印表機驅動,印表機不是直接與電腦有USB連線,而是在一個路由器下。並且沒能成功通過系統設定裡的方法新增,因此有了這篇部落格。 1. 安裝hplip,選擇一個最新版

0.96寸OLED驅動基於STM32f103

最近入手了一塊0.96寸的oled,一直在用給的例程,想自己從底層寫一下驅動,瞭解oled的初始化流程和控制 注:本文只涉及oled的初始化,如果需要顯示GUI或者圖片,漢字,需要使用庫或者自己更進一步折騰。 =========================================

selenium之配置瀏覽器驅動phantomJS, Chrome

首先請確保已經安裝了selenium 安裝命令:pip install selenium -U 配置瀏覽器驅動: IE:iedriver.exe Firefox:geckodriver.exe chrome:chromedriver.exe phantomjs:phant

springcloud系列—Stream—第8章-3: Spring Cloud Stream 訊息驅動消費組

使用消費組實現訊息消費的負載均衡 通常在生產環境,我們的每個服務都不會以單節點的方式執行在生產環境,當同一個服務啟動多個例項的時候,這些例項都會繫結到同一個訊息通道的目標主題(Topic)上。 預設情況下,當生產者發出一條訊息到繫結通道上,這條訊息會產生多個副本被每個消費者例項接收和處理,但

vue的原始碼學習之五——5.資料驅動Virtual DOM

1. 介紹       版本:2.5.17。        我們使用vue-vli建立基於Runtime+Compiler的vue腳手架。    &nbs

Linux驅動開發----塊裝置驅動記憶體模擬Tiny6410

寫了好久的字元裝置驅動,是時候看下塊裝置驅動程式設計方法了,塊裝置驅動和字元裝置不同,字元裝置是直接和虛擬檔案系統進行互動,而塊裝置驅動則是通過塊緩衝/排程層間接和虛擬檔案系統互動;塊裝置驅動資料訪問都是以塊為單位;多個塊I/O需要組成一個請求佇列,這個功能是塊緩衝/排程層

linux裝置驅動GPIO子系統

一、gpio子系統的核心原始碼主要函式 原始碼:Gpiolib.c (arch\arm\mach-s5pv210) static __init int s5pv210_gpiolib_init(void) { struct s3c_gpio_chip *chip =

十一、編寫按鍵混雜設備驅動輪詢

turn nbsp print gis tdi [] 運行 mage dynamic 1. 確定硬件連接   主要電路連接如下:   從電路圖中可以發現開發板上6個按鍵連接到了GPN0~5,當按鍵彈起時IO狀態應為高電平,當按鍵按下時IO口狀態為低電平。 2. 確定寄存器

kettle 5.3.0 找不到mysql驅動JDBC jar的解決辦法

使用kettle 5.3.0建立和mysql的連線時,提示找不到jar包,把mysql的jdbc jar包放入data-integration\lib或者data-integration\libswt\win64下,再重新執行Spoon.bat即可OK,不重啟的話不生效,切

從S5PV210學習最基礎的定時器RTC

注:下文都以S5PV210為背景。       本文只學習RTC的讀取和設定還有鬧鐘功能。       RTC,一個較為特殊的定時器,其他定時器都是定的時間段,而RTC定的是時間點。 一.RTC介紹      (1)real time clock,真實時間,就是所謂的xx

驅動之路四------adc驅動input裝置

開發板:smdk6410 開發環境:Linux 突然想起一點,寫這些驅動,核心需要配成支援搶佔才行。 前面的部落格已經將其它的基本知識都解釋了,這裡也就不過多的闡述了,咱就直接寫程式碼吧 這次寫的是adc驅動,將其做為輸入裝置進行使用, 先寫標頭檔案,s3c_adc.h

安裝MySQL ODBC connector5.3.10驅動未成功

安裝MySQL ODBC connector5.3.10驅動 環境準備一 需要安裝Microsoft Visual C++ 2013 Redistributable Package 後,才能安裝成功。 下載地址 環境準備二 1.下載

ARMv7 PMU(Performance Monitor Unit) 驅動cache 命中率

[ARM CPU cache命中率(proc驅動支援)] SOC專案需要對系統性能進行評價,因此需要對CPU cache命中率進行統計,幸好ARM CPU夠強大,通過在kernel proc下新增相關驅動,成功解決,具體如下: 新增proc下的cache命中率統計模組

3LinuxI2C驅動--解析EEPROM的讀寫

本節介紹eeprom的讀寫時序,參考的是AT24C01A的datasheet。 1. 概述 AT24C01A的儲存大小是1K,頁大小是8個位元組。 2. 裝置地址 7位地址,前四位是1010,後三位由晶片引腳決定,由原理圖可知後三位是00

Nginx配置CI框架問題Linux臺下Centos系統

項目 末尾 規則 -c 官方文檔 nbsp fas src 路由 CI框架:官方文檔 http://codeigniter.org.cn/user_guide/index.html CI框架的數據流程圖如下: 其中:index.php作為入口文件,在安裝好CI框架後,i

在idea2018和vs2017臺下JNI編程調用C++算法2-調用嵌套dll實踐

call 簡單的 所有 實踐 路徑 因此 imp 接口 nic 背景 我之所以采用JNI是由於我要實現一個java系統,java系統需調用C++核心程序。而目前的需求是不想破壞C++核心程序的獨立性。因此想把JNI調用的接口寫在C++核心程序之外。這就需要 首先將C++核

.net臺下C#socket通信

copy 成功 int 原因 獲取數據 ++ etl any repl 本文主要講述: 1、正常通信中握手建立 2、一對多的通信 3、發送接收數據格式轉換 4、資源釋放 5、開啟並保持服務監聽 1、握手建立正常的通信通道   項目需要通信的雙方(假設是一個上位機、一個下