Qcom 平臺 LK 階段配置 I2C
qcom平臺LK 階段配置IIC
版本號:V 1.0
作者:Leo
目錄
本文著重介紹,如何在qcom 平臺的LK 階段配置和使用I2C。
硬體平臺:msm8909
軟體平臺:Android5.0、Android8.0
I2C裝置:ADV7533
參考部落格和文件:
80-np408-2x_c_msm8909_msm8209_msm8208_hardware_register_description_document_for_oems.pdf
80-nu767-1_h_linux_bam_low-speed_peripherals_configuration_and_debug_guide.pdf
https://blog.csdn.net/eliot_shao/article/details/53351759
1.確定硬體
檢視原理圖確定gpio
確定晶片I2C內部的qup地址、中斷號、通道等引數
根據文件:80-nu767-1_h_linux_bam_low-speed_peripherals_configuration_and_debug_guide.pdf
從以上資訊可以確定:
- 我們要使用的是 gpio10、gpio11對應的IIC
- 晶片內部引數是
- QUP ID: BLSP6
- QUP BASE Addr: 78BA000
- IRQ#: 100
- src clk:qup6_i2c_apps_clk
2.LK I2C 函式介面
根據qcom文件:
80-nu767-1_h_linux_bam_low-speed_peripherals_configuration_and_debug_guide.pdf
可知:qcom LK 階段的I2C 函式介面為:qup_blsp_i2c_init
函式定義:
struct qup_i2c_dev * qup_blsp_i2c_init(uint8_t blsp_id, uint8_t qup_id, uint32_t clk_freq, uint32_t src_clk_freq)
引數含義:
uint8_t blsp_id: (msm8909 上用 BLSP_ID_1)
enum {
BLSP_ID_1 = 1,
BLSP_ID_2,
} blsp_instance;
uint8_t qup_id:
enum {
QUP_ID_0 = 0,
QUP_ID_1,
QUP_ID_2,
QUP_ID_3,
QUP_ID_4,
QUP_ID_5,
} qup_instance;
(具體多少,以qup的base addr 為準。如使用IIC-6對應地址78ba000,那麼傳參QUP_ID_5)
uint32_t clk_freq:
IIC匯流排速率,預設 100K
uint32_t src_clk_freq:
時鐘源頻率,預設19.2MHz
老規矩,先分析下這個函式的實現流程。
struct qup_i2c_dev *qup_blsp_i2c_init(uint8_t blsp_id, uint8_t qup_id,
uint32_t clk_freq, uint32_t src_clk_freq)
{
struct qup_i2c_dev *dev;
if (dev_addr != NULL) {
return dev_addr;
}
dev = malloc(sizeof(struct qup_i2c_dev));
if (!dev) {
return NULL;
}
dev = memset(dev, 0, sizeof(struct qup_i2c_dev));
/* Platform uses BLSP */
dev->qup_irq = BLSP_QUP_IRQ(blsp_id, qup_id);
dev->qup_base = BLSP_QUP_BASE(blsp_id, qup_id);
/* This must be done for qup_i2c_interrupt to work. */
dev_addr = dev;
/* Initialize the GPIO for BLSP i2c */
gpio_config_blsp_i2c(blsp_id, qup_id);
clock_config_blsp_i2c(blsp_id, qup_id);
qup_i2c_sec_init(dev, clk_freq, src_clk_freq);
return dev;
}
從以上流程看,有5個需要關注的地方
1. dev->qup_irq = BLSP_QUP_IRQ(blsp_id, qup_id);
2. dev->qup_base = BLSP_QUP_BASE(blsp_id, qup_id);
3. gpio_config_blsp_i2c(blsp_id, qup_id);
4. clock_config_blsp_i2c(blsp_id, qup_id);
5. qup_i2c_sec_init(dev, clk_freq, src_clk_freq); // 略過,I2C的初始化過程
一個一個分析:
2.1 qup irq 中斷號賦值
dev->qup_irq = BLSP_QUP_IRQ(blsp_id, qup_id);
中斷號,使用 BLSP_QUP_IRQ 這個巨集來賦值,那麼找到它的定義:
bootable\bootloader\lk\platform\msm8909\include\platform\irqs.h
#define GIC_SPI_START 32
#define BLSP_QUP_IRQ(blsp_id, qup_id) (GIC_SPI_START + 95 + qup_id)
從章節1的qup 資訊可知,qup6的中斷號為100,那麼傳參qup_id 為5,中斷號為
GIC_SPI_START + 95 + 5, GIC_SPI_START 是一個偏移量,不用管它。
這裡大概可以推測,qup_id 應該傳參為5。
2.2 qup base addr 賦值
dev->qup_base = BLSP_QUP_BASE(blsp_id, qup_id);
qup 基地址的賦值,使用 BLSP_QUP_BASE來賦值,找到它的定義
bootable\bootloader\lk\platform\msm8909\include\platform\iomap.h
#define PERIPH_SS_BASE 0x07800000
bootable\bootloader\lk\platform\msm8909\include\platform\iomap.h
#define BLSP_QUP_BASE(blsp_id, qup_id) (PERIPH_SS_BASE + 0xB5000 + 0x1000 * qup_id)
從章節1的qup資訊圖得知,qup6的 base addr 為78ba000,那麼這裡qup_id 傳參應該為5
即:0x70800000 + 0x0B5000 + 0x1000 * 5 = 0x78bA000
到此為止,從qup irq的資訊和qup base addr的資訊確認,那麼I2C-6(gpio10、gpio11)對應的qup 傳參應該是 QUP_ID_5。
2.3 blsp6 gpio 初始化
/* Initialize the GPIO for BLSP i2c */
gpio_config_blsp_i2c(blsp_id, qup_id);
看gpio_config_blsp_i2c函式定義.
void gpio_config_blsp_i2c(uint8_t blsp_id, uint8_t qup_id)
{
#if DSI2DPI_TC358762
uint32_t hw_id = board_hardware_id();
uint32_t hw_subtype = board_hardware_subtype();
#endif
if(blsp_id == BLSP_ID_1) {
switch (qup_id) {
case QUP_ID_1:
/* configure I2C SDA gpio */
gpio_tlmm_config(6, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(7, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
break;
case QUP_ID_2:
/* configure I2C SDA gpio */
gpio_tlmm_config(111, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(112, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
break;
case QUP_ID_3:
/* configure I2C SDA gpio */
gpio_tlmm_config(29, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(30, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
break;
case QUP_ID_4:
#if DSI2DPI_TC358762
if ((HW_PLATFORM_SUBTYPE_LR3001 == hw_subtype)
&& (HW_PLATFORM_MTP == hw_id)) {
/* configure I2C SDA gpio */
gpio_tlmm_config(18, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_2MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(19, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_2MA, GPIO_DISABLE);
} else {
#endif
/* configure I2C SDA gpio */
gpio_tlmm_config(14, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(15, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
#if DSI2DPI_TC358762
}
#endif
break;
case QUP_ID_5:
/* configure I2C SDA gpio */
gpio_tlmm_config(18, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(19, 3, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* Begin: add by leo for add i2c-6(gpio_10 & gpio_11) */
#if 1
/* configure I2C SDA gpio */
gpio_tlmm_config(10, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(11, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
dprintf(CRITICAL, "leo i2c-%d (gpio10 & gpio11)\n", qup_id + 1);
#endif
/* End: add by leo for add i2c-6(gpio_10 & gpio_11) */
break;
default:
dprintf(CRITICAL, "Incorrect QUP id %d\n",qup_id);
ASSERT(0);
};
} else {
dprintf(CRITICAL, "Incorrect BLSP id %d\n",blsp_id);
ASSERT(0);
}
}
從以上呼叫流程可以看出,整個流程就是根據BLSP_ID 和QUP_ID 去初始化不同的gpio。
所以在這裡,需要判斷在 case QUP_ID_5 中是否已經包含我們需要的gpio管腳(gpio10、gpio11)的初配置。
程式碼修改:
/* Begin: add by leo for add i2c-6(gpio_10 & gpio_11) */
#if 1
/* configure I2C SDA gpio */
gpio_tlmm_config(10, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(11, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
dprintf(CRITICAL, "leo i2c-%d (gpio10 & gpio11)\n", qup_id + 1);
#endif
/* End: add by leo for add i2c-6(gpio_10 & gpio_11) */
顯然沒有,那麼就需要在case QUP_ID_5:新增 gpio10、gpio11的初始化。
注意這裡:這裡配置為功能2,是複用為i2c功能
依據:
80-np408-2x_c_msm8909_msm8209_msm8208_hardware_register_description_document_for_oems.pdf
page-2624 介紹:
配置為功能 2 是 IIC功能。
2.4 blsp6 時鐘源的配置
clock_config_blsp_i2c(blsp_id, qup_id);
看函式定義:
void clock_config_blsp_i2c(uint8_t blsp_id, uint8_t qup_id)
{
uint8_t ret = 0;
char clk_name[64];
struct clk *qup_clk;
qup_id = qup_id + 1;
if((blsp_id != BLSP_ID_1)) {
dprintf(CRITICAL, "Incorrect BLSP-%d configuration\n", blsp_id);
ASSERT(0);
}
snprintf(clk_name, sizeof(clk_name), "blsp1_qup%u_ahb_iface_clk", qup_id);
dprintf(CRITICAL, "leo i2c-%d clk_name=%s \n", qup_id, clk_name);
ret = clk_get_set_enable(clk_name, 0 , 1);
if (ret) {
dprintf(CRITICAL, "Failed to enable %s clock\n", clk_name);
return;
}
snprintf(clk_name, sizeof(clk_name), "gcc_blsp1_qup%u_i2c_apps_clk", qup_id);
dprintf(CRITICAL, "leo i2c-%d clk_name=%s \n", qup_id, clk_name);
qup_clk = clk_get(clk_name);
if (!qup_clk) {
dprintf(CRITICAL, "Failed to get %s\n", clk_name);
return;
}
ret = clk_enable(qup_clk);
if (ret) {
dprintf(CRITICAL, "Failed to enable %s\n", clk_name);
return;
}
}
首先,qup_id = qup_id + 1; 這裡,對qup進行了自加1,那麼下面對應的時鐘源引數就變成了blsp6。
且能看出,需要的是 blsp1_qup6_ahb_iface_clk 和 gcc_blsp1_qup6_i2c_apps_clk,這兩個時鐘源。
snprintf(clk_name, sizeof(clk_name), "blsp1_qup%u_ahb_iface_clk", qup_id);
dprintf(CRITICAL, "leo i2c-%d clk_name=%s \n", qup_id, clk_name);
ret = clk_get_set_enable(clk_name, 0 , 1);
if (ret) {
dprintf(CRITICAL, "Failed to enable %s clock\n", clk_name);
return;
}
snprintf(clk_name, sizeof(clk_name), "gcc_blsp1_qup%u_i2c_apps_clk", qup_id);
dprintf(CRITICAL, "leo i2c-%d clk_name=%s \n", qup_id, clk_name);
qup_clk = clk_get(clk_name);
可以看clk_get_set_enable 和 clk_get 的定義
clk_get_set_enable:
bootable\bootloader\lk\platform\msm_shared\clock.c
函式定義略
clk_get:
bootable\bootloader\lk\platform\msm_shared\clock.c
struct clk *clk_get (const char * cid)
{
unsigned i;
struct clk_lookup *cl= msm_clk_list.clist;
unsigned num = msm_clk_list.num;
if(!cl || !num)
{
dprintf (CRITICAL, "Alert!! clock list not defined!\n");
return NULL;
}
for(i=0; i < num; i++, cl++)
{
if(!strcmp(cl->con_id, cid))
{
return cl->clk;
}
}
dprintf(CRITICAL, "Alert!! Requested clock \"%s\" is not supported!\n", cid);
return NULL;
}
分析:都是從 bootable\bootloader\lk\platform\msm_shared\clock.c 中的static struct clk_list msm_clk_list; 全域性clk list 中獲取的資訊。
找到在哪初始化的:
路徑:
bootable\bootloader\lk\platform\msm_shared\clock.c
void clk_init(struct clk_lookup *clist, unsigned num)
{
if(clist && num)
{
msm_clk_list.clist = (struct clk_lookup *)clist;
msm_clk_list.num = num;
}
}
找到在哪呼叫的:
路徑:
bootable\bootloader\lk\platform\msm8909\msm8909-clock.c
void platform_clock_init(void)
{
clk_init(msm_clocks_msm8909, ARRAY_SIZE(msm_clocks_msm8909));
}
其中msm_clocks_msm8909 這個clk 結構體陣列,就是我們要修改的目標 。
此處需對應 clock_config_blsp_i2c 函式中的分析。
最終我們需要的有三個時鐘源配置,根據實際程式碼去修改。
bootable\bootloader\lk\platform\msm8909\msm8909-clock.c
msm_clocks_msm8909 陣列中
#else
CLK_LOOKUP("blsp1_qup6_ahb_iface_clk", gcc_blsp1_ahb_clk.c),
CLK_LOOKUP("gcc_blsp1_qup6_i2c_apps_clk_src", gcc_blsp1_qup6_i2c_apps_clk_src.c),
CLK_LOOKUP("gcc_blsp1_qup6_i2c_apps_clk", gcc_blsp1_qup6_i2c_apps_clk.c),
#endif
那麼總結下,所有的配置修改。
1. 呼叫I2C 初始化函式
struct qup_i2c_dev *
qup_blsp_i2c_init(uint8_t blsp_id, uint8_t qup_id, uint32_t clk_freq, uint32_t src_clk_freq)
2. 檢查gpio配置
檢視I2C對應的gpio配置是否包含,否則去新增
/* Begin: add by leo for add i2c-6(gpio_10 & gpio_11) */
#if 1
/* configure I2C SDA gpio */
gpio_tlmm_config(10, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
/* configure I2C SCL gpio */
gpio_tlmm_config(11, 2, GPIO_OUTPUT, GPIO_NO_PULL,
GPIO_8MA, GPIO_DISABLE);
dprintf(CRITICAL, "leo i2c-%d (gpio10 & gpio11)\n", qup_id + 1);
#endif
/* End: add by leo for add i2c-6(gpio_10 & gpio_11) */
3. 檢查I2C src clk 配置,沒有則新增
bootable\bootloader\lk\platform\msm8909\msm8909-clock.c
msm_clocks_msm8909 陣列中
#else
CLK_LOOKUP("blsp1_qup6_ahb_iface_clk", gcc_blsp1_ahb_clk.c),
CLK_LOOKUP("gcc_blsp1_qup6_i2c_apps_clk_src", gcc_blsp1_qup6_i2c_apps_clk_src.c),
CLK_LOOKUP("gcc_blsp1_qup6_i2c_apps_clk", gcc_blsp1_qup6_i2c_apps_clk.c),
#endif
4. I2C 讀函式demo。 (暫存器地址和資料都以8bit為例)
static uint8_t adv7533_i2c_read(uint8_t slave_addr, uint8_t reg)
{
int ret;
int err_return = -1;
uint8_t value;
dprintf(SPEW, "%s: Leo enter \n", __func__);
uint8_t tx_data[] = {
reg & 0xff,
};
uint8_t rx_data[1];
struct i2c_msg msgs[] = {
{
.addr = slave_addr,
.flags = I2C_M_WR,
.buf = tx_data,
.len = ADV_ARRAY_SIZE(tx_data),
},
{
.addr = slave_addr,
.flags = I2C_M_RD,
.buf = rx_data,
.len = ADV_ARRAY_SIZE(rx_data),
}
};
ret = qup_i2c_xfer(i2c6_dev, msgs, ADV_ARRAY_SIZE(msgs));
if (ret < 0) {
dprintf(CRITICAL, "%s: reg 0x%04x error %d\n", __func__, reg, ret);
return ret;
}
if (ret < ADV_ARRAY_SIZE(msgs)) {
dprintf(CRITICAL, "%s: reg 0x%04x msgs %d\n", __func__, reg, ret);
return err_return;
}
value = rx_data[0];
dprintf(CRITICAL, "%s: reg 0x%x 0x%x\n", __func__, reg, value);
return value;
}
5. I2C 寫函式 demo
static int adv7533_i2c_write(uint8_t slave_addr, uint8_t reg, uint8_t value)
{
int ret = 0;
uint8_t tx_data[2] = {reg, value};
/* Send commands via I2C */
struct i2c_msg msgs[1] = {
{
.addr = slave_addr,
.flags = I2C_M_WR, // 0
.len = 2,
.buf = tx_data,
},
};
ret = qup_i2c_xfer(i2c6_dev, msgs, 1);
if (ret < 0) {
dprintf(CRITICAL, "%s failed ret=%d\n", __func__, ret);
return ret;
}
mdelay(2);
return 0;
}