LINUX的IIC驅動從這開始(四)
轉載地址:http://blog.csdn.net/xie0812/article/details/24291153
首先這篇文章主要介紹IIC匯流排的實現過程,怎麼說了,本人也是一個小菜鳥,可能有許多錯誤在裡面,如有大神發現,請指出來,多謝多謝!
注意:平臺還是和前面的一樣,所以分析三星的iic匯流排實現,當然這部分,可能不需要咱們驅動工程師實現,但本人認為好好研究這部分內容有助於提高水平,也能更好的理解linux的裝置模型,不過今天僅僅討論三星iic匯流排驅動具體是怎麼操作s5pv210上的iic模組的,至於涉及到的platform匯流排還有裝置模型會在接下來的文章中討論,希望能給像我一樣的菜鳥一點幫助!
1、首先說一下主要的討論內容,主要是通過解釋下面幾個問題為指引展開的
(1)怎麼把s5pv210上的iic模組註冊到linux中的?
(2)iic模組是怎麼獲得時鐘的,以及怎麼樣才能調節時鐘的快慢?
(3)iic的匯流排驅動是怎麼註冊到iic-core當中的?
(4)具體怎麼把一個字元通過片上iic模組傳送出去?
好吧,咱們就按問題來吧。三星的iic匯流排驅動的位置在:linux-3.0.8/drivers/i2c/busses/i2c-s3c2410.c就是這個檔案了,整個程式也就是1000行吧。那先從init和exit看起吧,這是看驅動的出發點嘛,為方便起見,我就直接附上程式碼了
- staticint __init i2c_adap_s3c_init(
- {
- return platform_driver_register(&s3c24xx_i2c_driver);
- }
- subsys_initcall(i2c_adap_s3c_init);
- staticvoid __exit i2c_adap_s3c_exit(void)
- {
- platform_driver_unregister(&s3c24xx_i2c_driver);
- }
- module_exit(i2c_adap_s3c_exit);
- static
- ...
- <span style="color:#ff0000;">&s3c_device_i2c0,
- &s3c_device_i2c1,
- &s3c_device_i2c2,</span>}
static void __init mini210_machine_init(void)
{
......
platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));
......
}
上面的這個函式就是把剛才那個指標陣列中定義的裝置資訊註冊到platform總線上了。至於當給platform總線上註冊一個驅動後核心如何進行操作,這裡就不討論了,等那天再寫一篇吧。當註冊到platform總線上的驅動的名字和已經註冊到platform上的裝置的名字匹配後,就會呼叫probe函式,其實許多事都是在probe函式做的,我下面直接把程式碼附上,直接在程式碼裡做解釋吧。
- staticint 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; //通過這裡獲得了,iic模組的一些附加資訊,包括匯流排裝置號、iic的時鐘頻率等
- if (!pdata) {
- dev_err(&pdev->dev, "no platform data\n");
- return -EINVAL;
- }
- i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);
- if (!i2c) {
- dev_err(&pdev->dev, "no memory for state\n");
- return -ENOMEM;
- }
- //下面的這幾句很關鍵,你應該還記得iic中的adapter這個結構體吧,它就代表一個iic介面卡
- strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));//給匯流排取名字
- i2c->adap.owner = THIS_MODULE;
- i2c->adap.algo = <span style="color:#ff0000;">&s3c24xx_i2c_algorithm</span>;//這個是最重要的,是iic介面卡對應的通訊方法,也就是具體怎麼把資料通過iic模組傳輸出去的方法,這也是我們的主要問題之一,所以會在後面好好討論一下
- i2c->adap.retries = 2;
- i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
- i2c->tx_setup = 50;//這個在通訊方法裡面可以看到,就是每次傳送資料後的要延時的ns數
- spin_lock_init(&i2c->lock);
- init_waitqueue_head(&i2c->wait);
- /* find the clock and enable it */
- <span style="white-space:pre"> </span>
- i2c->dev = &pdev->dev;
- i2c->clk = clk_get(&pdev->dev, "i2c");//獲取iic介面卡的時鐘,至於具體通過怎麼用名字匹配獲取等,這個這裡就不說了
- if (IS_ERR(i2c->clk)) {
- dev_err(&pdev->dev, "cannot get clock\n");
- ret = -ENOENT;
- goto err_noclk;
- }
- dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
- clk_enable(i2c->clk);//這裡就把iic裝置的時鐘給使能了,僅僅是使能,通訊的頻率不是這裡調節的啊,具體怎麼調節通訊的頻率高低我會詳細介紹
- /* map the registers */
- <span style="white-space:pre"> </span>//下面就是通過呼叫相應的函式獲取我們在mach-mini210.c檔案裡註冊到platform總線上的iic介面卡的資訊了,下面是獲取記憶體資源
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (res == NULL) {
- dev_err(&pdev->dev, "cannot find IO resource\n");
- ret = -ENOENT;
- goto err_clk;
- }
- <span style="white-space:pre"> </span>//這裡根據獲取到的地址和大小,在記憶體中申請這樣一塊大小的記憶體區
- i2c->ioarea = request_mem_region(res->start, resource_size(res),
- pdev->name);
- if (i2c->ioarea == NULL) {
- dev_err(&pdev->dev, "cannot request IO\n");
- ret = -ENXIO;
- goto err_clk;
- }
- <span style="white-space:pre"> </span>//這裡就是用申請到的記憶體做實際的映射了
- i2c->regs = ioremap(res->start, resource_size(res));
- if (i2c->regs == NULL) {
- dev_err(&pdev->dev, "cannot map IO\n");
- 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;
- /* initialise the i2c controller */
- <span style="white-space:pre"> </span>//這個函式裡,主要就是通過配置iic介面卡的幾個暫存器,來完成實際的介面卡的初始化,上面的過程主要是獲得介面卡的資源,比如說地址了,暫存器基地址什麼的
- 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
- */
- <span style="white-space:pre"> </span>//下面這句話就是獲取iic介面卡的中斷資源,其實iic介面卡實際傳輸資料時用中斷的方式完成的,所以作用是可想而知了
- i2c->irq = ret = platform_get_irq(pdev, 0);
- if (ret <= 0) {
- dev_err(&pdev->dev, "cannot find IRQ\n");
- goto err_iomap;
- }
- <span style="white-space:pre"> </span>//這就是把中斷註冊到核心中的函數了,注意這裡的s3c24xx_i2c_irq這個中斷服務函式,其實在這裡真真完成資料傳輸
- 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 %d\n", i2c->irq);
- goto err_iomap;
- }
- ret = s3c24xx_i2c_register_cpufreq(i2c);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
- goto err_irq;
- }
- /* 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;
- <span style="white-space:pre"> </span>//下面這個函式式iic-core裡面的函式,這個函式的作用就是把上面獲取的iic介面卡的註冊為iic匯流排裝置,具體的實現可以看看iic-core
- ret = i2c_add_numbered_adapter(&i2c->adap);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to add bus to i2c core\n");
- goto err_cpufreq;
- }
- platform_set_drvdata(pdev, i2c);
- dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
- clk_disable(i2c->clk);
- return 0;
- err_cpufreq:
- s3c24xx_i2c_deregister_cpufreq(i2c);
- err_irq:
- free_irq(i2c->irq, i2c);
- err_iomap:
- iounmap(i2c->regs);
- err_ioarea:
- release_resource(i2c->ioarea);
- kfree(i2c->ioarea);
- err_clk:
- clk_disable(i2c->clk);
- clk_put(i2c->clk);
- err_noclk:
- kfree(i2c);
- return ret;
- }
- staticconststruct i2c_algorithm s3c24xx_i2c_algorithm = {
- .master_xfer = s3c24xx_i2c_xfer, //這個函式是具體的通訊方法
- .functionality = s3c24xx_i2c_func, //這個函式描述的iic介面卡的功能特性
- };
- staticint s3c24xx_i2c_xfer(struct i2c_adapter *adap,
- struct i2c_msg *msgs, int num)
- {
- struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
- int retry;
- int ret;
- clk_enable(i2c->clk); //使能時鐘嘛
- for (retry = 0; retry < adap->retries; retry++) { //這個迴圈來完成實際的資料傳輸,迴圈的次數可以自定義了,這是為了保證資料傳輸的可靠性嘛
- ret = s3c24xx_i2c_doxfer(i2c, msgs, num);//還記得iic裡面重要的那個幾個資料結構嘛,其中一個就是msg吧,在linux中,把要通過iic傳輸的具體資料會封裝成msg結構的一個包,它裡面包含的要傳輸資料的長度,裝置的地址,要傳輸的資料。看出來吧,還沒到到真真傳輸資料了,核心會呼叫這個函式
- if (ret != -EAGAIN) { //你看,只要以傳輸成功,馬上就返回了
- clk_disable(i2c->clk);
- return ret;
- }
- dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);
- udelay(100);
- }
- clk_disable(i2c->clk);
- return -EREMOTEIO;
- }
- staticint s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
- struct i2c_msg *msgs, int num)
- {
- unsigned long timeout;
- int ret;
- if (i2c->suspended)
- return -EIO;
- ret = s3c24xx_i2c_set_master(i2c); //這個函式就是看iic介面卡是否在忙,如果在忙就等待,直到把得到這個iic介面卡的使用權
- if (ret != 0) {
- dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
- ret = -EAGAIN;
- goto out;
- }
- spin_lock_irq(&i2c->lock);
- i2c->msg = msgs;
- i2c->msg_num = num;
- i2c->msg_ptr = 0;
- i2c->msg_idx = 0;
- i2c->state = STATE_START; //上面這幾句就是把msg結構體賦值給在前面全域性定義的iic結構體,因為還要包含其他資訊嘛
- s3c24xx_i2c_enable_irq(i2c);//終於使能了中斷,看來的確快到實際傳輸的函數了
- s3c24xx_i2c_message_start(i2c, msgs);//看這個函式的名字就知道,應該開始傳輸了
- spin_unlock_irq(&i2c->lock);
- timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
- ret = i2c->msg_idx;
- /* having these next two as dev_err() makes life very
- * noisy when doing an i2cdetect */
- if (timeout == 0)
- dev_dbg(i2c->dev, "timeout\n");
- elseif (ret != num)
- dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);
- /* ensure the stop has been through the bus */
- udelay(10);
- out:
- return ret;
- }
- staticvoid s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
- struct i2c_msg *msg)
- {
- unsigned int addr = (msg->addr & 0x7f) << 1;
- unsigned long stat;
- unsigned long iiccon;
- stat = 0;
- stat |= S3C2410_IICSTAT_TXRXEN;
- if (msg->flags & I2C_M_RD) { //看到了吧,這就是msg結構體裡面的flag起的作用,判斷是否是接收資料,也就是讀資料了
- stat |= S3C2410_IICSTAT_MASTER_RX;
- addr |= 1;
- } else
- stat |= S3C2410_IICSTAT_MASTER_TX;//這當然就是傳送資料了
- if (msg->flags & I2C_M_REV_DIR_ADDR)
- addr ^= 1;
- /* todo - check for wether ack wanted or not */
- s3c24xx_i2c_enable_ack(i2c);
- iiccon = readl(i2c->regs + S3C2410_IICCON);
- writel(stat, i2c->regs + S3C2410_IICSTAT);
- dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
- writeb(addr, i2c->regs + S3C2410_IICDS);
- /* delay here to ensure the data byte has gotten onto the bus
- * before the transaction is started */
- ndelay(i2c->tx_setup);
- dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);
- writel(iiccon, i2c->regs + S3C2410_IICCON);
- stat |= S3C2410_IICSTAT_START;
- writel(stat, i2c->regs + S3C2410_IICSTAT); //上面這些通過readl和writel來讀取和設定了一些iic介面卡的暫存器,最後邊的這句話,是最關鍵的,它設定iic介面卡為開始狀態,這樣就能觸發中斷,來完成實際資料的傳輸,用中斷傳輸的好處我就不說了,想來大家都會知道啊。
- }