以MSM8953为例。

原文(有删改):https://blog.csdn.net/qq_29890089/article/details/108294710

项目场景

因为项目需要,需要在高通MSM8953平台的LK阶段使用I2C。本文只介绍在LK阶段配置使用I2C5的方法。

在调试SLM753某客户项目LCM时,客户使用LVDS的LCM,而msm8916只有一个mipi的接口,所以就是用到了mipi-2-lvds转换芯片:icn6202。这颗芯片需要使用I2C进行配置LVDS屏的时钟和分辨率等信息,以至于LVDS屏可以正常显示。

Kernel阶段i2c比较容易使用,只需在dts中配置一个i2c设备即可以使用对应的i2c接口进行数据传输,但是LK阶段的代码就显得蹩脚了,默认只配置了i2c0接口!其他的i2c都不能使用,因此需要进行有关的调试。

调试准备

文档: 80-nu767-1_k_bam_low-speed_peripherals_(blsp)_user_guide

查看文档,有I2C介绍如下:

I2c-3对应的:

  • 物理地址为0x78B7000
  • 中断IRQ:97
  • 钟信号 clk :clk_gcc_blsp1_qup3_i2c_apps_clk

I2c-8对应的:

  • 物理地址为0x7AF8000
  • 中断IRQ:302
  • 时钟信号 clk :clk_gcc_blsp2_qup4_i2c_apps_clk

查看产品配置表有:

I2C3:gpio10&gpio11;

i2c8:gpio10&gpio11;gpio98&gpio99;

I2C Description :
1、 arg: BLSP ID can be BLSP_ID_1 or BLSP_ID_2
2、 arg: QUP ID can be QUP_ID_0:QUP_ID_5
3、 arg: I2C CLK. should be 100KHZ, or 400KHz
4、 arg: Source clock, should be set @ 19.2MHz

步骤

为了符合规范,有些内容以对应的路径为准。

由于这里的步骤是为了满足“LVDS转MIPI”,因此添加的文件与mipi有关。

初始化I2C总线

创建platform/msm_shared/mipi_dsi_i2c.c文件

#define I2C_CLK_FREQ     100000
#define I2C_SRC_CLK_FREQ 19200000 int mipi_dsi_i2c_device_init(uint8_t blsp_id, uint8_t qup_id)
{
if(BLSP_ID_2 == blsp_id) {
// qup_blsp_i2c_init(BLSP_ID_1, QUP_ID_0, 100000, 19200000);
i2c8_dev = qup_blsp_i2c_init(blsp_id, qup_id,
I2C_CLK_FREQ, I2C_SRC_CLK_FREQ);
if(!i2c8_dev) {
dprintf(CRITICAL, "mipi_dsi_i2c_device_init() failed\n");
return ERR_NOT_VALID;
}
}
else
{
i2c_dev = qup_blsp_i2c_init(blsp_id, qup_id,
I2C_CLK_FREQ, I2C_SRC_CLK_FREQ);
if(!i2c_dev) {
dprintf(CRITICAL, "mipi_dsi_i2c_device_init() failed\n");
return ERR_NOT_VALID;
} }
return NO_ERROR;
}

对应的驱动platform/msm_shared/i2c_qup.c做如下修改,主要是添加:

  • i2c设备所需的头文件
  • 设备对象
  • 初始化时的特殊判断
// 新引入的头文件
#include <blsp_qup.h>
#include <platform.h> static struct qup_i2c_dev *dev_addr = NULL;
// 创建新的设备对象
static struct qup_i2c_dev *dev8_addr = NULL; 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 0
if (dev_addr != NULL) {
return dev_addr;
}
#else
// 针对i2c-8的特殊处理
if(BLSP_ID_2 == blsp_id)
dev = dev8_addr;
else
dev = dev_addr; if (dev != NULL)
return dev;
#endif 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. */
#if 0
dev_addr = dev;
#else
if(BLSP_ID_2 == blsp_id)
dev8_addr = dev;
else
dev_addr = dev;
#endif /* 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;
}

platform/msm_shared/i2c_qup.c添加新的I2C-8操作函数,包括读写函数。

int mipi_dsi_i2c8_read_bytes(uint8_t addr, uint8_t *reg, uint8_t *buf, uint8_t len)
{
if (!buf)
return ERR_INVALID_ARGS; if(!i2c8_dev)
return ERR_NOT_VALID; struct i2c_msg rd_buf[] = {
{addr, I2C_M_WR, 2, reg},
{addr, I2C_M_RD, len, buf}
}; int err = qup_i2c_xfer(i2c8_dev, rd_buf, 2);
if (err < 0) {
dprintf(CRITICAL, "Read reg %x failed\n", (int)reg[0]);
return err;
} return NO_ERROR;
} int mipi_dsi_i2c8_write_bytes(uint8_t addr, uint8_t *reg, uint32_t len)
{
if (!i2c8_dev)
return ERR_NOT_VALID; struct i2c_msg msg_buf[] = {
{addr, I2C_M_WR, len, reg},
}; int err = qup_i2c_xfer(i2c8_dev, msg_buf, 1);
if (err < 0) {
dprintf(CRITICAL, "Write reg %x failed\n", (int)reg[0]);
return err;
}
return NO_ERROR;
} int mipi_dsi_i2c8_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t len)
{
if (!buf)
return ERR_INVALID_ARGS; if(!i2c8_dev)
return ERR_NOT_VALID; struct i2c_msg rd_buf[] = {
{addr, I2C_M_WR, 1, &reg},
{addr, I2C_M_RD, len, buf}
}; int err = qup_i2c_xfer(i2c8_dev, rd_buf, 2);
if (err < 0) {
dprintf(CRITICAL, "Read reg %x failed\n", reg);
return err;
} return NO_ERROR;
} int mipi_dsi_i2c8_read_byte(uint8_t addr, uint8_t reg, uint8_t *buf)
{
if (!buf)
return ERR_INVALID_ARGS; return mipi_dsi_i2c8_read(addr, reg, buf, 1);
} int mipi_dsi_i2c8_write_byte(uint8_t addr, uint8_t reg, uint8_t val)
{
if (!i2c8_dev)
return ERR_NOT_VALID; unsigned char buf[2] = {reg, val};
struct i2c_msg msg_buf[] = {
{addr, I2C_M_WR, 2, buf},
}; int err = qup_i2c_xfer(i2c8_dev, msg_buf, 1);
if (err < 0) {
dprintf(CRITICAL, "Write reg %x failed\n", reg);
return err;
}
return NO_ERROR;
}

platform/msm_shared/include/i2c_qup.h新增下列函数声明

int mipi_dsi_i2c8_read_bytes(uint8_t addr, uint8_t *reg, uint8_t *buf, uint8_t len);
int mipi_dsi_i2c8_write_bytes(uint8_t addr, uint8_t *reg, uint32_t len);
int mipi_dsi_i2c8_read_byte(uint8_t addr, uint8_t reg, uint8_t *buf);
int mipi_dsi_i2c8_write_byte(uint8_t addr, uint8_t reg, uint8_t val);
int mipi_dsi_i2c8_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t len);

配置GPIO为I2C

// platform/msm8953/gpio.c
#define GPIO_BLSP1_ACTIVE_1 10
#define GPIO_BLSP1_ACTIVE_2 11 #define GPIO_BLSP2_ACTIVE_1 98
#define GPIO_BLSP2_ACTIVE_2 99 void gpio_config_blsp_i2c(uint8_t blsp_id, uint8_t qup_id)
{
if(blsp_id == BLSP_ID_1) {
switch (qup_id) {
case QUP_ID_2:
/* configure I2C SDA gpio */
gpio_tlmm_config(GPIO_BLSP1_ACTIVE_1, 2, GPIO_OUTPUT,
GPIO_NO_PULL, GPIO_6MA, GPIO_ENABLE); /* configure I2C SCL gpio */
gpio_tlmm_config(GPIO_BLSP1_ACTIVE_2, 2, GPIO_OUTPUT,
GPIO_NO_PULL, GPIO_6MA, GPIO_ENABLE); break;
default:
dprintf(CRITICAL, "Incorrect QUP id %d\n", qup_id);
ASSERT(0);
};
}
else if(blsp_id == BLSP_ID_2) {
switch (qup_id) {
case QUP_ID_3:
/* configure I2C SDA gpio */
gpio_tlmm_config(GPIO_BLSP2_ACTIVE_1, 1, GPIO_OUTPUT,
GPIO_PULL_UP, GPIO_6MA, GPIO_ENABLE); /* configure I2C SCL gpio */
gpio_tlmm_config(GPIO_BLSP2_ACTIVE_2, 1, GPIO_OUTPUT,
GPIO_PULL_UP, GPIO_6MA, GPIO_ENABLE);
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);
}
}

开启I2C对应的时钟

bootable/bootloader/lk/platform/msm8953/acpuclock.c中新增下列函数

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; if((blsp_id != BLSP_ID_1 && blsp_id != BLSP_ID_2)) {
dprintf(CRITICAL, "Incorrect BLSP-%d or QUP-%d configuration\n",
blsp_id, qup_id);
ASSERT(0);
}
if(blsp_id == BLSP_ID_1){ if (qup_id == QUP_ID_2) {
snprintf(clk_name, sizeof(clk_name), "blsp1_qup3_ahb_iface_clk");
}
else if (qup_id == QUP_ID_3) {
snprintf(clk_name, sizeof(clk_name), "blsp1_qup4_ahb_iface_clk");
}
} if(blsp_id == BLSP_ID_2){
if (qup_id == QUP_ID_3) {
snprintf(clk_name, sizeof(clk_name), "blsp2_qup4_ahb_iface_clk");
}
} ret = clk_get_set_enable(clk_name, 0 , 1);
if (ret) {
dprintf(CRITICAL, "Failed to enable %s clock\n", clk_name);
return;
} if(blsp_id == BLSP_ID_1){
if (qup_id == QUP_ID_2) {
snprintf(clk_name, sizeof(clk_name), "gcc_blsp1_qup3_i2c_apps_clk");
}
else if (qup_id == QUP_ID_3) {
snprintf(clk_name, sizeof(clk_name), "gcc_blsp1_qup4_i2c_apps_clk");
}
}
if(blsp_id == BLSP_ID_2){
if (qup_id == QUP_ID_3) {
snprintf(clk_name, sizeof(clk_name), "gcc_blsp2_qup4_i2c_apps_clk");
}
}
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;
}
}

platform/msm8953/msm8953-clock.c添加进时钟序列中

// 新增
static struct vote_clk gcc_blsp2_ahb_clk = {
.cbcr_reg = (uint32_t *) BLSP2_AHB_CBCR,
.vote_reg = (uint32_t *) APCS_CLOCK_BRANCH_ENA_VOTE,
.en_mask = BIT(20), .c = {
.dbg_name = "gcc_blsp2_ahb_clk",
.ops = &clk_ops_vote,
},
}; // 新增
static struct clk_freq_tbl ftbl_gcc_blsp1_qup2_i2c_apps_clk_src[] = {
F( 96000, cxo, 10, 1, 2),
F( 4800000, cxo, 4, 0, 0),
F( 9600000, cxo, 2, 0, 0),
F( 16000000, gpll0, 10, 1, 5),
F( 19200000, gpll0, 1, 0, 0),
F( 25000000, gpll0, 16, 1, 2),
F( 50000000, gpll0, 16, 0, 0),
F_END
}; // 新增
static struct rcg_clk gcc_blsp1_qup2_i2c_apps_clk_src = {
.cmd_reg = (uint32_t *) GCC_BLSP1_QUP2_CMD_RCGR,
.cfg_reg = (uint32_t *) GCC_BLSP1_QUP2_CFG_RCGR,
.set_rate = clock_lib2_rcg_set_rate_hid,
.freq_tbl = ftbl_gcc_blsp1_qup2_i2c_apps_clk_src,
.current_freq = &rcg_dummy_freq, .c = {
.dbg_name = "gcc_blsp1_qup2_i2c_apps_clk_src",
.ops = &clk_ops_rcg,
},
}; // 新增
static struct branch_clk gcc_blsp1_qup2_i2c_apps_clk = {
.cbcr_reg = (uint32_t *) GCC_BLSP1_QUP2_APPS_CBCR,
.parent = &gcc_blsp1_qup2_i2c_apps_clk_src.c, .c = {
.dbg_name = "gcc_blsp1_qup2_i2c_apps_clk",
.ops = &clk_ops_branch,
},
}; // 新增
static struct clk_freq_tbl ftbl_gcc_blsp1_qup3_i2c_apps_clk_src[] = {
F( 96000, cxo, 10, 1, 2),
F( 4800000, cxo, 4, 0, 0),
F( 9600000, cxo, 2, 0, 0),
F( 16000000, gpll0, 10, 1, 5),
F( 19200000, gpll0, 1, 0, 0),
F( 25000000, gpll0, 16, 1, 2),
F( 50000000, gpll0, 16, 0, 0),
F_END
}; #if 0
static struct clk_freq_tbl ftbl_gcc_blsp2_qup4_i2c_apps_clk_src[] = {
F( 96000, cxo, 10, 1, 2),
F( 4800000, cxo, 4, 0, 0),
F( 9600000, cxo, 2, 0, 0),
F( 16000000, gpll0, 10, 1, 5),
F( 19200000, gpll0, 1, 0, 0),
F( 25000000, gpll0, 16, 1, 2),
F( 50000000, gpll0, 16, 0, 0),
F_END
};
#endif // 新增
static struct rcg_clk gcc_blsp1_qup3_i2c_apps_clk_src = {
.cmd_reg = (uint32_t *) GCC_BLSP1_QUP3_CMD_RCGR,
.cfg_reg = (uint32_t *) GCC_BLSP1_QUP3_CFG_RCGR,
.set_rate = clock_lib2_rcg_set_rate_hid,
.freq_tbl = ftbl_gcc_blsp1_qup3_i2c_apps_clk_src,
.current_freq = &rcg_dummy_freq, .c = {
.dbg_name = "gcc_blsp1_qup3_i2c_apps_clk_src",
.ops = &clk_ops_rcg,
},
}; // 新增
static struct rcg_clk gcc_blsp2_qup4_i2c_apps_clk_src = {
.cmd_reg = (uint32_t *) GCC_BLSP2_QUP4_CMD_RCGR,
.cfg_reg = (uint32_t *) GCC_BLSP2_QUP4_CFG_RCGR,
.set_rate = clock_lib2_rcg_set_rate_hid,
// .freq_tbl = ftbl_gcc_blsp2_qup4_i2c_apps_clk_src,
.freq_tbl = ftbl_gcc_blsp1_qup3_i2c_apps_clk_src,
.current_freq = &rcg_dummy_freq, .c = {
.dbg_name = "gcc_blsp2_qup4_i2c_apps_clk_src",
.ops = &clk_ops_rcg,
},
}; // 新增
static struct branch_clk gcc_blsp1_qup3_i2c_apps_clk = {
.cbcr_reg = (uint32_t *) GCC_BLSP1_QUP3_APPS_CBCR,
.parent = &gcc_blsp1_qup3_i2c_apps_clk_src.c, .c = {
.dbg_name = "gcc_blsp1_qup3_i2c_apps_clk",
.ops = &clk_ops_branch,
},
}; // 新增
static struct branch_clk gcc_blsp2_qup4_i2c_apps_clk = {
.cbcr_reg = (uint32_t *) GCC_BLSP2_QUP4_APPS_CBCR,
.parent = &gcc_blsp2_qup4_i2c_apps_clk_src.c, .c = {
.dbg_name = "gcc_blsp2_qup4_i2c_apps_clk",
.ops = &clk_ops_branch,
},
}; // 新增
static struct clk_freq_tbl ftbl_mdss_esc1_1_clk[] = {
F_MM(19200000, cxo, 1, 0, 0),
F_END
}; // 新增
static struct rcg_clk dsi_esc1_clk_src = {
.cmd_reg = (uint32_t *) DSI_ESC1_CMD_RCGR,
.cfg_reg = (uint32_t *) DSI_ESC1_CFG_RCGR,
.set_rate = clock_lib2_rcg_set_rate_hid,
.freq_tbl = ftbl_mdss_esc1_1_clk, .c = {
.dbg_name = "dsi_esc1_clk_src",
.ops = &clk_ops_rcg,
},
}; // 新增
static struct branch_clk mdss_esc1_clk = {
.cbcr_reg = (uint32_t *) DSI_ESC1_CBCR,
.parent = &dsi_esc1_clk_src.c,
.has_sibling = 0, .c = {
.dbg_name = "mdss_esc1_clk",
.ops = &clk_ops_branch,
},
}; // 在这个时钟组中添加这么一段
static struct clk_lookup msm_clocks_8953[] =
{
... // 维持不变 /*add start by Yubel for blsp 20200730 */
/* BLSP CLOCKS FOR I2C-8*/
CLK_LOOKUP("blsp1_qup2_ahb_iface_clk", gcc_blsp1_ahb_clk.c),
CLK_LOOKUP("gcc_blsp1_qup2_i2c_apps_clk_src",
gcc_blsp1_qup2_i2c_apps_clk_src.c),
CLK_LOOKUP("gcc_blsp1_qup2_i2c_apps_clk",
gcc_blsp1_qup2_i2c_apps_clk.c), CLK_LOOKUP("blsp1_qup3_ahb_iface_clk", gcc_blsp1_ahb_clk.c),
CLK_LOOKUP("gcc_blsp1_qup3_i2c_apps_clk_src",
gcc_blsp1_qup3_i2c_apps_clk_src.c),
CLK_LOOKUP("gcc_blsp1_qup3_i2c_apps_clk",
gcc_blsp1_qup3_i2c_apps_clk.c), CLK_LOOKUP("blsp2_qup4_ahb_iface_clk", gcc_blsp2_ahb_clk.c),
CLK_LOOKUP("gcc_blsp2_qup4_i2c_apps_clk_src",
gcc_blsp2_qup4_i2c_apps_clk_src.c),
CLK_LOOKUP("gcc_blsp2_qup4_i2c_apps_clk",
gcc_blsp2_qup4_i2c_apps_clk.c), /*add end by Yubel 20200730 */
};

高通 LK阶段配置使用I2C-8的更多相关文章

  1. 高通lk屏幕向kernel传参

    LK把相关参数报存到cmdline上: 在Bootable\bootloader\lk\dev\gcdb\display\gcdb_display_param.c上gcdb_display_cmdli ...

  2. 高通spi 屏幕 -lk代码分析

    lk SPI驱动 1. 初始化时钟 在lk中,我们是从kmain开始执行下来的,而执行顺序则是先初始化时钟,也就是在platform_early_init函数中开始执行的: 在这里我们需要修改这个函数 ...

  3. 【转】高通平台android 环境配置编译及开发经验总结

    原文网址:http://blog.csdn.net/dongwuming/article/details/12784535 1.高通平台android开发总结 1.1 搭建高通平台环境开发环境 在高通 ...

  4. 高通APQ8074 spi 接口配置

    高通APQ8074 spi 接口配置 8074 平台含有两个BLSP(BAM Low-Speed Peripheral) , 每一个BLSP含有两个QUP, 每一个QUP可以被配置为I2C, SPI, ...

  5. 高通移植mipi LCD的过程LK代码

    lk部分:(实现LCD兼容) 1. 函数定位 aboot_init()来到target_display_init(): 这就是高通原生lk LCD 兼容的关键所在.至于你需要兼容多少LCD 就在whi ...

  6. 最新内核3.4)Linux 设备树加载I2C client adapter 的流程(内核3.4 高通)【转】

    转自:https://blog.csdn.net/lsn946803746/article/details/52515225 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转 ...

  7. 高通平台msm8909 LK 实现LCD 兼容

    前段时间小米出现红米note2 换屏门,现在我们公司也要上演了:有两个供应商提供不同IC 的LCD panel. 软件区分的办法是读取LCD IC 的ID 寄存器,下面解析高通平台LK中LCD兼容的过 ...

  8. Linux加载DTS设备节点的过程(以高通8974平台为例)

    DTS是Device Tree Source的缩写,用来描述设备的硬件细节.在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码, ...

  9. 高通开发笔记---Yangtze worknote

    点击打开链接 1. repo init -u git://review.sonyericsson.net/platform/manifest -b volatile-jb-mr1-yangtze 2. ...

  10. 高通 MSM8K bootloader : SBL1 .

    一. MSM8K Boot Flow 图1: 高通MSM8K平台bootloader启动流程基本类似,但具体各平台,比如MSM8974.MSM8916.MSM8994等,会有微小区别. 从上图,可以看 ...

随机推荐

  1. Halo博客搭建小记

    准备工作 阿里云服务器,操作系统为CentOS 7.9.2009 x86_64(Py3.7.9) 宝塔面板 Nginx 1.24.0(用于反向代理) 已备案的域名 ssl证书(https访问) 参考官 ...

  2. Swift实现判断目录下是否存在指定文件功能

    本文主要讲解以下这段名为 isDataJsonFilePathExists 的私有函数的 Swift 代码实现细节,该函数的作用是检查指定文件或文件夹是否存在,其返回值类型为 Bool 类型,如果存在 ...

  3. 超级详细的Oracle安装图文详解!手把手教会您从下载到安装!

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485532&idx=1 ...

  4. 密码学—仿射密码Python程序

    文章目录 仿射密码 加密算法 解密算法 仿射密码 古典密码,且属于单表加密. 加密算法 仿射密码公式 c = m×k + b mod 26 c是密文,m是明文,m作为26字母中的明文,因此计算出来的密 ...

  5. 数据库—SQL语言学习

    文章目录 SQL 数据类型 重要的关键字 定义数据库 数据库的文件 table创建与删除 表的定义 表的alter 表的删除 视图 定义视图 删除视图 更新视图 插入视图 视图总结 索引 SQL单表查 ...

  6. 移动通信网络中的 FDD/TDD 无线帧

    目录 文章目录 目录 前文列表 无线帧 FDD 与 TDD 的区别 FDD 无线帧 TDD 无线帧 前文列表 <移动通信网络中的资源类型> 无线帧 LTE 支持两种类型的无线帧:FDD(F ...

  7. RestTemplate 介绍和用法

    RestTemplate 简介 RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求.POST 请求.PUT ...

  8. 我对IdentityServer4的初步了解

    官网:https://identityserver4.readthedocs.io/en/latest/quickstarts/2_interactive_aspnetcore.html 官网例子:h ...

  9. mit 6.824 lab1 思路贴

    前言 为遵守 mit 的约定,这个帖子不贴太多具体的代码,主要聊聊自己在码代码时的一些想法和遇到的问题. 这个实验需要我们去实现一个 map-reduce 的功能.实质上,这个实验分为两个大的板块,m ...

  10. 安利一个好用的IDEA插件 object-helper-plugin

    更多精彩博文请关注:听到微笑的博客 一. 插件背景 object-helper 插件是一个日常开发工具集插件,提供丰富的功能,最开始是基于 GenerateO2O 插件开发而来,它提供了对象之间值拷贝 ...