(最新核心3.4)Linux 裝置樹載入I2C client adapter 的流程(核心3.4 高通)
阿新 • • 發佈:2018-12-22
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_t(typedef 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<