1. 程式人生 > >(最新核心3.4)Linux 裝置樹載入I2C client adapter 的流程(核心3.4 高通)

(最新核心3.4)Linux 裝置樹載入I2C client adapter 的流程(核心3.4 高通)

BLSP(BAM Low-Speed Peripheral) , 每一個BLSP含有兩個QUP, 每一個QUP可以被配置為I2C, SPI, UART, UIM介面, BLSP是高通對於低速介面的一種管理方式。

    [email protected] { /* BLSP-1 QUP-1 */
        cell-index = <1>;
        compatible = "qcom,i2c-qup";
        #address-cells = <1>;
        #size-cells = <0>;
        reg-names
= "qup_phys_addr"; reg = <0xf9923000 0x1000>; interrupt-names = "qup_err_intr"; interrupts = <0 95 0>; qcom,i2c-bus-freq = <100000>; qcom,i2c-src-freq = <19200000>; qcom,sda-gpio = <&msmgpio 2 0>; qcom,scl-gpio = <&msmgpio 3
0>; qcom,master-id = <86>; }; i2c_cdc: [email protected] { /* BLSP1 QUP5 */ cell-index = <5>; compatible = "qcom,i2c-qup"; #address-cells = <1>; #size-cells = <0>; reg-names
= "qup_phys_addr"; reg = <0xf9927000 0x1000>; interrupt-names = "qup_err_intr"; interrupts = <0 99 0>; qcom,i2c-bus-freq = <100000>; qcom,i2c-src-freq = <19200000>; qcom,master-id = <86>; }; i2c: [email protected]f9928000 { /* BLSP1 QUP6 */ cell-index = <6>; compatible = "qcom,i2c-qup"; #address-cells = <1>; #size-cells = <0>; reg-names = "qup_phys_addr"; reg = <0xf9928000 0x1000>; interrupt-names = "qup_err_intr"; interrupts = <0 100 0>; qcom,i2c-bus-freq = <100000>; qcom,i2c-src-freq = <19200000>; qcom,sda-gpio = <&msmgpio 16 0>; qcom,scl-gpio = <&msmgpio 17 0>; qcom,master-id = <86>; }; [email protected] { /* BLSP-1 QUP-3 */ cell-index = <2>; compatible = "qcom,i2c-qup"; #address-cells = <1>; #size-cells = <0>; reg-names = "qup_phys_addr"; reg = <0xf9924000 0x1000>; interrupt-names = "qup_err_intr"; interrupts = <0 96 0>; qcom,i2c-bus-freq = <100000>; qcom,i2c-src-freq = <19200000>; qcom,sda-gpio = <&msmgpio 8 0>; qcom,scl-gpio = <&msmgpio 9 0>; qcom,master-id = <86>; }; [email protected] { /* BLSP-1 QUP-3 */ cell-index = <0>; compatible = "qcom,i2c-qup"; #address-cells = <1>; #size-cells = <0>; reg-names = "qup_phys_addr"; reg = <0xf9925000 0x1000>; interrupt-names = "qup_err_intr"; interrupts = <0 97 0>; qcom,i2c-bus-freq = <100000>; qcom,i2c-src-freq = <19200000>; qcom,sda-gpio = <&msmgpio 10 0>; qcom,scl-gpio = <&msmgpio 11 0>; qcom,clk-ctl-xfer; qcom,master-id = <86>; };

以上是I2C的硬體介面描述。

 [email protected]{    //I2C裝置掛的兩種裝置 focaltech  goodix
        [email protected]38{
            compatible = "focaltech,5x06";
            reg = <0x38>;
            interrupt-parent = <&msmgpio>;
            interrupts = <1 0x2008>;
            vdd-supply = <&pm8110_l19>;
            vcc_i2c-supply = <&pm8110_l14>;
            focaltech,name = "ft5x06";
            focaltech,family-id = <0x54>;
            focaltech,reset-gpio = <&msmgpio 0 0x00>;
            focaltech,irq-gpio = <&msmgpio 1 0x00>;
            focaltech,display-coords = <0 0 480 854>;
            focaltech,button-map= <139 172 158>;
            focaltech,no-force-update;
            focaltech,i2c-pull-up;
            focaltech,group-id = <1>;
            focaltech,hard-reset-delay-ms = <20>;
            focaltech,soft-reset-delay-ms = <150>;
            focaltech,num-max-touches = <10>;
            focaltech,fw-name = "5436_Ref_Asus89118_V12_D01_20160712_app.i";
            focaltech,fw-delay-aa-ms = <50>;
            focaltech,fw-delay-55-ms = <30>;
            focaltech,fw-upgrade-id1 = <0x79>;
            focaltech,fw-upgrade-id2 = <0x03>;
            focaltech,fw-delay-readid-ms = <10>;
            focaltech,fw-delay-era-flsh-ms = <2000>;
        };
        [email protected]5d {
            compatible = "goodix,gt915l";    //用於.of_match_table = goodix_match_table,
            reg = <0x5d>;           //地址
            interrupt-parent = <&msmgpio>;  //中斷塊
            interrupts = <1 0x2008>;    //中斷地址
            vdd_ana-supply = <&pm8110_l19>; 
            vcc_i2c-supply = <&pm8110_l14>;
            goodix,rst-gpio = <&msmgpio 0 0x00>;    //復位引腳
            goodix,irq-gpio = <&msmgpio 1 0x00>;    //中斷引腳
            //goodix,panel-coords = <0 0 540 980>;
            //goodix,display-coords = <0 0 540 960>;
            //goodix,button-map= <139 172 158>;
            //goodix,product-id = "915L";
            //goodix,enable-power-off;
            goodix,cfg-group0 = [
                47 E0 01 56 03 05 34 01 01 05 
                28 08 50 32 03 05 00 00 FF 7F 
                00 11 05 17 18 23 14 8C 2E 0E 
                35 33 0F 0A 00 00 01 BA 33 1D 
                00 01 00 00 00 00 00 0A 10 00 
                2B 2E 55 94 C5 02 00 00 00 04 
                83 31 00 79 37 00 70 3E 00 68 
                46 00 61 50 00 61 18 38 60 00 
                F0 4A 3A EE EE 27 00 00 00 00 
                00 00 00 00 00 00 00 00 00 00 
                00 00 00 00 00 00 0F 19 00 00 
                4B 37 0E 10 12 14 16 18 1A 1C 
                02 04 06 08 0A 0C 00 00 00 00 
                00 00 00 00 00 00 00 00 00 00 
                00 00 2A 29 28 26 24 22 21 20 
                1F 1E 1D 1C 16 18 00 02 04 06 
                08 0A 0C 0F 10 12 13 14 00 00 
                00 00 00 00 00 00 00 00 00 00 
                00 00 00 00 4C 01];
            goodix,cfg-group2 = [
                48 D0 02 00 05 05 75 01 01 0F 24 
                0F 64 3C 03 05 00 00 00 02 00 00 
                00 16 19 1C 14 8C 0E 0E 24 00 31 
                0D 00 00 00 83 33 1D 00 41 00 00 
                3C 0A 14 08 0A 00 2B 1C 3C 94 D5 
                03 08 00 00 04 93 1E 00 82 23 00 
                74 29 00 69 2F 00 5F 37 00 5F 20 
                40 60 00 F0 40 30 55 50 27 00 00 
                00 00 00 00 00 00 00 00 00 00 00 
                00 00 00 00 00 00 00 14 19 00 00 
                50 50 02 04 06 08 0A 0C 0E 10 12 
                14 16 18 1A 1C 00 00 00 00 00 00 
                00 00 00 00 00 00 00 00 00 00 1D 
                1E 1F 20 21 22 24 26 28 29 2A 1C 
                18 16 14 13 12 10 0F 0C 0A 08 06 
                04 02 00 00 00 00 00 00 00 00 00 
                00 00 00 00 00 00 00 00 3C 01];
        };
    };

I2C bus 匯流排的構建。
在kernel/include/linux/init.h中

#define arch_initcall(fn) 
    #define __define_initcall(level,fn,id) \  
        static initcall_t __initcall_##fn##id __used \  
        __attribute__((__section__(".initcall" level ".init"))) = fn  

將i2c-bus的driver init函式放到.initcall3.init 程式碼段中。
之所以,i2c-bus的driver和i2c 裝置的驅動init函式使用的不同的函式放到.initcall程式碼段,主要原因是後面呼叫.initcall中的函式執行順序是按.initcall段的函式順序進行的(按照initcall的level從0到7依次存放的)。

    #define INIT_CALLS_LEVEL(level)                        \  
            VMLINUX_SYMBOL(__initcall##level##_start) = .;        \  
            *(.initcall##level##.init)                \  
            *(.initcall##level##s.init)      

    #define INIT_CALLS                          \  
            VMLINUX_SYMBOL(__initcall_start) = .;           \  
            *(.initcallearly.init)                  \  
            INIT_CALLS_LEVEL(0)                 \  
            INIT_CALLS_LEVEL(1)                 \  
            INIT_CALLS_LEVEL(2)                 \  
            INIT_CALLS_LEVEL(3)                 \  
            INIT_CALLS_LEVEL(4)                 \  
            INIT_CALLS_LEVEL(5)                 \  
            INIT_CALLS_LEVEL(rootfs)                \  
            INIT_CALLS_LEVEL(6)                 \  
            INIT_CALLS_LEVEL(7)                 \  
            VMLINUX_SYMBOL(__initcall_end) = .;  

如module_init()的驅動初始化是等級66

(kernel/include/linux/init.h)

#define module_init(x)        __initcall(x);

#define __initcall(fn)                               device_initcall(fn)
#define device_initcall(fn)                __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) /
        static initcall_t __initcall_##fn##id __used /
        __attribute__((__section__(".initcall" level ".init"))) = fn

initcall_ttypedef int (*initcall_t)(void))

這就保證了I2C匯流排的會提前的註冊好,在註冊掛載的驅動和裝置。

在linux  /init/main.c 中,
static void __init do_initcalls(void)
{
  initcall_t *call;

  call = &__initcall_start;
  do {
   (*call)();
   call++;
  } while (call < &__initcall_end);
  /* Make sure there is no pending stuff from the initcall sequence */
  flush_scheduled_tasks();
} 

通過do_initcalls就可以把__initcall section段的函式呼叫起來。
kernel/drivers/i2c/busses/i2c-qup.c

/* Copyright (c) 2009-2014, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
/*
 * QUP driver for Qualcomm MSM platforms
 *
 */

/* #define DEBUG */

#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/i2c/i2c-qup.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_i2c.h>
#include <linux/of_gpio.h>
#include <mach/board.h>
#include <mach/gpiomux.h>
#include <mach/msm_bus_board.h>

MODULE_LICENSE("GPL v2");
MODULE_VERSION("0.2");
MODULE_ALIAS("platform:i2c_qup");

/* QUP Registers */
enum {
    QUP_CONFIG              = 0x0,
    QUP_STATE               = 0x4,
    QUP_IO_MODE             = 0x8,
    QUP_SW_RESET            = 0xC,
    QUP_OPERATIONAL         = 0x18,
    QUP_ERROR_FLAGS         = 0x1C,
    QUP_ERROR_FLAGS_EN      = 0x20,
    QUP_MX_READ_CNT         = 0x208,
    QUP_MX_INPUT_CNT        = 0x200,
    QUP_MX_WR_CNT           = 0x100,
    QUP_OUT_DEBUG           = 0x108,
    QUP_OUT_FIFO_CNT        = 0x10C,
    QUP_OUT_FIFO_BASE       = 0x110,
    QUP_IN_READ_CUR         = 0x20C,
    QUP_IN_DEBUG            = 0x210,
    QUP_IN_FIFO_CNT         = 0x214,
    QUP_IN_FIFO_BASE        = 0x218,
    QUP_I2C_CLK_CTL         = 0x400,
    QUP_I2C_STATUS          = 0x404,
};

/* QUP States and reset values */
enum {
    QUP_RESET_STATE         = 0,
    QUP_RUN_STATE           = 1U,
    QUP_STATE_MASK          = 3U,
    QUP_PAUSE_STATE         = 3U,
    QUP_STATE_VALID         = 1U << 2,
    QUP_I2C_MAST_GEN        = 1U << 4,
    QUP_OPERATIONAL_RESET   = 0xFF0,
    QUP_I2C_STATUS_RESET    = 0xFFFFFC,
};

/* QUP OPERATIONAL FLAGS */
enum {
    QUP_OUT_SVC_FLAG        = 1U << 8,
    QUP_IN_SVC_FLAG         = 1U << 9,
    QUP_MX_INPUT_DONE       = 1U << 11,
};

/* QUP_CONFIG values and flags */
enum {
    I2C_MINI_CORE           = 2U << 8,
    I2C_N_VAL               = 0xF,
    I2C_CORE_CLK_ON_EN      = BIT(13),

};

/* Packing Unpacking words in FIFOs , and IO modes*/
enum {
    QUP_WR_BLK_MODE  = 1U << 10,
    QUP_RD_BLK_MODE  = 1U << 12,
    QUP_UNPACK_EN = 1U << 14,
    QUP_PACK_EN = 1U << 15,
};

/* QUP tags */
enum {
    QUP_OUT_NOP   = 0,
    QUP_OUT_START = 1U << 8,
    QUP_OUT_DATA  = 2U << 8,
    QUP_OUT_STOP  = 3U << 8,
    QUP_OUT_REC   = 4U << 8,
    QUP_IN_DATA   = 5U << 8,
    QUP_IN_STOP   = 6U << 8,
    QUP_IN_NACK   = 7U << 8,
};

/* Status, Error flags */
enum {
    I2C_STATUS_WR_BUFFER_FULL  = 1U << 0,
    I2C_STATUS_BUS_ACTIVE      = 1U << 8,
    I2C_STATUS_BUS_MASTER      = 1U << 9,
    I2C_STATUS_ERROR_MASK      = 0x38000FC,
    QUP_I2C_NACK_FLAG          = 1U << 3,
    QUP_IN_NOT_EMPTY           = 1U << 5,
    QUP_STATUS_ERROR_FLAGS     = 0x7C,
};

/* Master status clock states */
enum {
    I2C_CLK_RESET_BUSIDLE_STATE = 0,
    I2C_CLK_FORCED_LOW_STATE    = 5,
};

enum msm_i2c_state {
    MSM_I2C_PM_ACTIVE,
    MSM_I2C_PM_SUSPENDED,
    MSM_I2C_SYS_SUSPENDING,
    MSM_I2C_SYS_SUSPENDED,
};
#define QUP_MAX_CLK_STATE_RETRIES   300
#define DEFAULT_CLK_RATE        (19200000)
#define I2C_STATUS_CLK_STATE        13
#define QUP_OUT_FIFO_NOT_EMPTY      0x10
#define I2C_GPIOS_DT_CNT        (2)     /* sda and scl */

static char const * const i2c_rsrcs[] = {"i2c_clk", "i2c_sda"};

static struct gpiomux_setting recovery_config = {
    .func = GPIOMUX_FUNC_GPIO,
    .drv = GPIOMUX_DRV_8MA,
    .pull = GPIOMUX_PULL_NONE,
};

/**
 * qup_i2c_clk_path_vote: data to use bus scaling driver for clock path vote
 *
 * @client_hdl when zero, client is not registered with the bus scaling driver,
 *      and bus scaling functionality should not be used. When non zero, it
 *      is a bus scaling client id and may be used to vote for clock path.
 * @reg_err when true, registration error was detected and an error message was
 *      logged. i2c will attempt to re-register but will log error only once.
 *      once registration succeed, the flag is set to false.
 */
struct qup_i2c_clk_path_vote {
    u32                         client_hdl;
    struct msm_bus_scale_pdata *pdata;
    bool                        reg_err;
};

struct qup_i2c_dev {
    struct device                *dev;
    void __iomem                 *base;     /* virtual */
    void __iomem                 *gsbi;     /* virtual */
    int                          in_irq;
    int                          out_irq;
    int                          err_irq;
    int                          num_irqs;
    struct clk                   *clk;
    struct clk                   *pclk;
    struct i2c_adapter           adapter;

    struct i2c_msg               *msg;
    int                          pos;
    int                          cnt;
    int                          err;
    int                          mode;
    int                          clk_ctl;
    int                          one_bit_t;
    int                          out_fifo_sz;
    int                          in_fifo_sz;
    int                          out_blk_sz;
    int                          in_blk_sz;
    int                          wr_sz;
    struct msm_i2c_platform_data *pdata;
    enum msm_i2c_state           pwr_state;
    atomic_t             xfer_progress;
    struct mutex                 mlock;
    void                         *complete;
    int                          i2c_gpios[ARRAY_SIZE(i2c_rsrcs)];
    struct qup_i2c_clk_path_vote clk_path_vote;
};

#ifdef CONFIG_PM
static int i2c_qup_pm_resume_runtime(struct device *device);
#endif

#ifdef DEBUG
static void
qup_print_status(struct qup_i2c_dev *dev)
{
    uint32_t val;
    val = readl_relaxed(dev->base+QUP_CONFIG);
    dev_dbg(dev->dev, "Qup config is :0x%x\n", val);
    val = readl_relaxed(dev->base+QUP_STATE);
    dev_dbg(dev->dev, "Qup state is :0x%x\n", val);
    val = readl_relaxed(dev->base+QUP_IO_MODE);
    dev_dbg(dev->dev, "Qup mode is :0x%x\n", val);
}
#else
static inline void qup_print_status(struct qup_i2c_dev *dev)
{
}
#endif

static irqreturn_t
qup_i2c_interrupt(int irq, void *devid)
{
    struct qup_i2c_dev *dev = devid;
    uint32_t status = 0;
    uint32_t status1 = 0;
    uint32_t op_flgs = 0;
    int err = 0;

    if (atomic_read(&dev->xfer_progress) != 1) {
        dev_err(dev->dev, "irq:%d when PM suspended\n", irq);
        return IRQ_NONE;
    }

    status = readl_relaxed(dev->base + QUP_I2C_STATUS);
    status1 = readl_relaxed(dev->base + QUP_ERROR_FLAGS);
    op_flgs = readl_relaxed(dev->base + QUP_OPERATIONAL);

    if (!dev->msg || !dev->complete) {
        /* Clear Error interrupt if it's a level triggered interrupt*/
        if (dev->num_irqs == 1) {
            writel_relaxed(QUP_RESET_STATE, dev->base+QUP_STATE);
            /* Ensure that state is written before ISR exits */
            mb();
        }
        return IRQ_HANDLED;
    }

    if (status & I2C_STATUS_ERROR_MASK) {
        dev_err(dev->dev, "QUP: I2C status flags :0x%x, irq:%d\n",
            status, irq);
        err = status;
        /* Clear Error interrupt if it's a level triggered interrupt*/
        if (dev->num_irqs == 1) {
            writel_relaxed(QUP_RESET_STATE, dev->base+QUP_STATE);
            /* Ensure that state is written before ISR exits */
            mb();
        }
        goto intr_done;
    }

    if (status1 & 0x7F) {
        dev_err(dev->dev, "QUP: QUP status flags :0x%x\n", status1);
        err = -status1;
        /* Clear Error interrupt if it's a level triggered interrupt*/
        if (dev->num_irqs == 1) {
            writel_relaxed((status1 & QUP_STATUS_ERROR_FLAGS),
                dev->base + QUP_ERROR_FLAGS);
            /* Ensure that error flags are cleared before ISR
             * exits
             */
            mb();
        }
        goto intr_done;
    }

    if ((dev->num_irqs == 3) && (dev->msg->flags == I2C_M_RD)
        && (irq == dev->out_irq))
        return IRQ_HANDLED;
    if (op_flgs & QUP_OUT_SVC_FLAG) {
        writel_relaxed(QUP_OUT_SVC_FLAG, dev->base + QUP_OPERATIONAL);
        /* Ensure that service flag is acknowledged before ISR exits */
        mb();
    }
    if (dev->msg->flags == I2C_M_RD) {
        if ((op_flgs & QUP_MX_INPUT_DONE) ||
            (op_flgs & QUP_IN_SVC_FLAG)) {
            writel_relaxed(QUP_IN_SVC_FLAG, dev->base
                    + QUP_OPERATIONAL);
            /* Ensure that service flag is acknowledged before ISR
             * exits
             */
            mb();
        } else
            return IRQ_HANDLED;
    }

intr_done:
    dev_dbg(dev->dev, "QUP intr= %d, i2c status=0x%x, qup status = 0x%x\n",
            irq, status, status1);
    qup_print_status(dev);
    dev->err = err;
    complete(dev->complete);
    return IRQ_HANDLED;
}

static int
qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t req_state, bool only_valid)
{
    uint32_t retries = 0;

    dev_dbg(dev->dev, "Polling for state:0x%x, or valid-only:%d\n",
                req_state, only_valid);

    while (retries != 2000) {
        uint32_t status = readl_relaxed(dev->base + QUP_STATE);

        /*
         * If only valid bit needs to be checked, requested state is
         * 'don't care'
         */
        if (status & QUP_STATE_VALID) {
            if (only_valid)
                return 0;
            else if ((req_state & QUP_I2C_MAST_GEN) &&
                    (status & QUP_I2C_MAST_GEN))
                return 0;
            else if ((status & QUP_STATE_MASK) == req_state)
                return 0;
        }
        if (retries++ == 1000)
            udelay(100);
    }
    return -ETIMEDOUT;
}

static int
qup_update_state(struct qup_i2c_dev *dev, uint32_t state)
{
    if (qup_i2c_poll_state(dev, 0, true) != 0<